profile
viewpoint
Chris Banes chrisbanes @google working on @android London, UK https://chris.banes.me

chrisbanes/PhotoView 16903

Implementation of ImageView for Android that supports zooming, by various touch gestures.

chrisbanes/cheesesquare 7783

Demos the new Android Design library.

android/android-ktx 7556

A set of Kotlin extensions for Android app development.

chrisbanes/tivi 2855

Tivi is a work-in-progress TV show tracking Android app, which connects to Trakt.tv. It is still in its early stages of development and currently only contains two pieces of UI. It is under heavy development.

chrisbanes/philm 2129

Movie collection and information app for Android.

chrisbanes/gradle-mvn-push 968

Helper to upload Gradle Android Artifacts to Maven repositories

push eventchrisbanes/tivi

Chris Banes

commit sha eb5235f0da9c5ee688b046e791f4206798df2658

Update doc samples to use Spacer

view details

push time in 4 days

push eventchrisbanes/tivi

Chris Banes

commit sha fea9c09fc127315ea5460cd0e29bcc88e6c60a06

Use placeAbsolute in InsetsSizeModifier

view details

Chris Banes

commit sha fd1e29f5dbf90cf51855955a22c5c4dc10e4d0aa

Add system gesture inset support

view details

push time in 4 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.WindowInsetsCompat+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.preferredHeight+import androidx.ui.layout.preferredWidth+import androidx.ui.unit.Density+import androidx.ui.unit.Dp+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ *+ * TODO add other inset types (IME, visibility, etc)+ */+class DisplayInsets {

Yep, eventually I'll add IME insets. I'll add the gesture insets now.

chrisbanes

comment created time in 4 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import android.view.View+import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.ui.core.Constraints+import androidx.ui.core.IntrinsicMeasurable+import androidx.ui.core.IntrinsicMeasureScope+import androidx.ui.core.LayoutDirection+import androidx.ui.core.LayoutModifier+import androidx.ui.core.Measurable+import androidx.ui.core.MeasureScope+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.core.offset+import androidx.ui.layout.height+import androidx.ui.layout.width+import kotlin.math.min++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars = Insets()+}++@Stable+class Insets {+    var left by mutableStateOf(0)+        internal set+    var top by mutableStateOf(0)+        internal set+    var right by mutableStateOf(0)+        internal set+    var bottom by mutableStateOf(0)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set+}++val InsetsAmbient = staticAmbientOf<DisplayInsets>()++@Composable+fun ProvideDisplayInsets(content: @Composable () -> Unit) {+    val view = ViewAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->+            displayInsets.systemBars.updateFrom(windowInsets.systemWindowInsets)++            // Return the unconsumed insets+            windowInsets+        }++        // Add an OnAttachStateChangeListener to request an inset pass each time we're attached+        // to the window+        val attachListener = object : View.OnAttachStateChangeListener {+            override fun onViewAttachedToWindow(v: View) {+                v.requestApplyInsets()+            }++            override fun onViewDetachedFromWindow(v: View) = Unit+        }+        view.addOnAttachStateChangeListener(attachListener)++        if (view.isAttachedToWindow) {+            // If the view is already attached, we can request an inset pass now+            view.requestApplyInsets()+        }++        onDispose {+            view.removeOnAttachStateChangeListener(attachListener)+        }+    }++    Providers(InsetsAmbient provides displayInsets) {+        content()+    }+}++/**+ * Selectively apply additional space which matches the width/height of any system bars present+ * on the respective edges of the screen.+ *+ * @param enabled Whether to apply padding using the system bar dimensions on the respective edges.+ * Defaults to `true`.+ */+fun Modifier.systemBarsPadding(enabled: Boolean = true) = composed {+    insetsPadding(+        insets = InsetsAmbient.current.systemBars,+        left = enabled,+        top = enabled,+        right = enabled,+        bottom = enabled+    )+}++/**+ * Apply additional space which matches the height of the status height along the top edge+ * of the content.+ */+fun Modifier.statusBarPadding() = composed {+    insetsPadding(insets = InsetsAmbient.current.systemBars, top = true)+}++/**+ * Apply additional space which matches the height of the navigation bar height+ * along the [bottom] edge of the content, and additional space which matches the width of+ * the navigation bar on the respective [left] and [right] edges.+ *+ * @param bottom Whether to apply padding to the bottom edge, which matches the navigation bar+ * height (if present) at the bottom edge of the screen. Defaults to `true`.+ * @param left Whether to apply padding to the left edge, which matches the navigation bar width+ * (if present) on the left edge of the screen. Defaults to `true`.+ * @param right Whether to apply padding to the right edge, which matches the navigation bar width+ * (if present) on the right edge of the screen. Defaults to `true`.+ */+fun Modifier.navigationBarPadding(+    bottom: Boolean = true,+    left: Boolean = true,+    right: Boolean = true+) = composed {+    insetsPadding(+        insets = InsetsAmbient.current.systemBars,+        left = left,+        right = right,+        bottom = bottom+    )+}++/**+ * Updates our mutable state backed [Insets] from an Android system insets.+ */+private fun Insets.updateFrom(insets: androidx.core.graphics.Insets) {+    left = insets.left+    top = insets.top+    right = insets.right+    bottom = insets.bottom+}++/**+ * Declare the height of the content to match the height of the status bar exactly.+ *+ * This is very handy when used with `Spacer` to push content below the status bar:+ * ```+ * Column {+ *     Spacer(Modifier.statusBarHeight())+ *+ *     // Content to be drawn below status bar (y-axis)+ * }+ * ```+ *+ * It's also useful when used with `Box` to draw a scrim which matches the status bar:+ * ```+ * Box(+ *     Modifier.statusBarHeight()+ *         .fillMaxWidth()+ *         .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f)+ * )+ * ```+ *+ * Internally this uses [Modifier.height] so has the same characteristics with regards to incoming+ * layout constraints.+ */+fun Modifier.statusBarHeight() = composed {+    // TODO: Move to Android 11 WindowInsets APIs when they land in AndroidX.+    // It currently assumes that status bar == top which is probably fine, but doesn't work+    // in multi-window, etc.+    InsetsSizeModifier(insets = InsetsAmbient.current.systemBars, heightSide = VerticalSide.Top)+}++/**+ * Declare the preferred height of the content to match the height of the navigation bar when present at the bottom of the screen.+ *+ * This is very handy when used with `Spacer` to push content below the navigation bar:+ * ```+ * Column {+ *     // Content to be drawn above status bar (y-axis)+ *     Spacer(Modifier.navigationBarHeight())+ * }+ * ```+ *+ * It's also useful when used with `Box` to draw a scrim which matches the navigation bar:+ * ```+ * Box(+ *     Modifier.navigationBarHeight()+ *         .fillMaxWidth()+ *         .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f)+ * )+ * ```+ *+ * Internally this uses [Modifier.height] so has the same characteristics with regards to incoming+ * layout constraints.+ */+fun Modifier.navigationBarHeight() = composed {+    // TODO: Move to Android 11 WindowInsets APIs when they land in AndroidX.+    // It currently assumes that nav bar == bottom, which is wrong in landscape.+    // It also doesn't handle the IME correctly.+    InsetsSizeModifier(insets = InsetsAmbient.current.systemBars, heightSide = VerticalSide.Bottom)+}++enum class HorizontalSide { Left, Right }+enum class VerticalSide { Top, Bottom }++/**+ * Declare the preferred width of the content to match the width of the navigation bar,+ * on the given [side].+ *+ * This is very handy when used with `Spacer` to push content inside from any vertical+ * navigation bars (typically when the device is in landscape):+ * ```+ * Row {+ *     Spacer(Modifier.navigationBarWidth(HorizontalSide.Left))+ *+ *     // Content to be inside the navigation bars (x-axis)+ *+ *     Spacer(Modifier.navigationBarWidth(HorizontalSide.Right))+ * }+ * ```+ *+ * It's also useful when used with `Box` to draw a scrim which matches the navigation bar:+ * ```+ * Box(+ *     Modifier.navigationBarWidth(HorizontalSide.Left)+ *         .fillMaxHeight()+ *         .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f)+ * )+ * ```+ *+ * Internally this uses [Modifier.width] so has the same characteristics with regards to incoming+ * layout constraints.+ *+ * @param side The navigation bar side to use as the source for the width.+ */+fun Modifier.navigationBarWidth(side: HorizontalSide) = composed {+    // TODO: Move to Android 11 WindowInsets APIs when they land in AndroidX.+    // It currently assumes that nav bar == left/right+    InsetsSizeModifier(insets = InsetsAmbient.current.systemBars, widthSide = side)+}++/**+ * Allows conditional setting of [insets] on each dimension.+ */+private inline fun Modifier.insetsPadding(+    insets: Insets,+    left: Boolean = false,+    top: Boolean = false,+    right: Boolean = false,+    bottom: Boolean = false+) = this + InsetsPaddingModifier(insets, left, top, right, bottom)++private data class InsetsPaddingModifier(+    private val insets: Insets,+    private val applyLeft: Boolean = false,+    private val applyTop: Boolean = false,+    private val applyRight: Boolean = false,+    private val applyBottom: Boolean = false+) : LayoutModifier {+    override fun MeasureScope.measure(+        measurable: Measurable,+        constraints: Constraints,+        layoutDirection: LayoutDirection+    ): MeasureScope.MeasureResult {+        val left = if (applyLeft) insets.left else 0+        val top = if (applyTop) insets.top else 0+        val right = if (applyRight) insets.right else 0+        val bottom = if (applyBottom) insets.bottom else 0+        val horizontal = left + right+        val vertical = top + bottom++        val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))++        val width = (placeable.width + horizontal)+            .coerceIn(constraints.minWidth, constraints.maxWidth)+        val height = (placeable.height + vertical)+            .coerceIn(constraints.minHeight, constraints.maxHeight)+        return layout(width, height) {+            placeable.placeAbsolute(left, top)+        }+    }+}++private data class InsetsSizeModifier(+    private val insets: Insets,+    private val widthSide: HorizontalSide? = null,+    private val heightSide: VerticalSide? = null+) : LayoutModifier {+    private val targetConstraints+        get() = Constraints(+            minWidth = when (widthSide) {+                HorizontalSide.Left -> insets.left+                HorizontalSide.Right -> insets.right+                null -> 0+            },+            minHeight = when (heightSide) {+                VerticalSide.Top -> insets.top+                VerticalSide.Bottom -> insets.bottom+                null -> 0+            },+            maxWidth = when (widthSide) {+                HorizontalSide.Left -> insets.left+                HorizontalSide.Right -> insets.right+                null -> Constraints.Infinity+            },+            maxHeight = when (heightSide) {+                VerticalSide.Top -> insets.top+                VerticalSide.Bottom -> insets.bottom+                null -> Constraints.Infinity+            }+        )++    override fun MeasureScope.measure(+        measurable: Measurable,+        constraints: Constraints,+        layoutDirection: LayoutDirection+    ): MeasureScope.MeasureResult {+        val wrappedConstraints = targetConstraints.let { targetConstraints ->+            val resolvedMinWidth = if (widthSide != null) {+                targetConstraints.minWidth+            } else {+                min(constraints.minWidth, targetConstraints.maxWidth)+            }+            val resolvedMaxWidth = if (widthSide != null) {+                targetConstraints.maxWidth+            } else {+                min(constraints.maxWidth, targetConstraints.minWidth)+            }+            val resolvedMinHeight = if (heightSide != null) {+                targetConstraints.minHeight+            } else {+                min(constraints.minHeight, targetConstraints.maxHeight)+            }+            val resolvedMaxHeight = if (heightSide != null) {+                targetConstraints.maxHeight+            } else {+                min(constraints.maxHeight, targetConstraints.minHeight)+            }+            Constraints(resolvedMinWidth, resolvedMaxWidth, resolvedMinHeight, resolvedMaxHeight)+        }+        val placeable = measurable.measure(wrappedConstraints)+        return layout(placeable.width, placeable.height) {+            placeable.place(0, 0)

Good catch, fixed.

chrisbanes

comment created time in 4 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import android.view.View+import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.ui.core.Constraints+import androidx.ui.core.IntrinsicMeasurable+import androidx.ui.core.IntrinsicMeasureScope+import androidx.ui.core.LayoutDirection+import androidx.ui.core.LayoutModifier+import androidx.ui.core.Measurable+import androidx.ui.core.MeasureScope+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.core.offset+import androidx.ui.layout.height+import androidx.ui.layout.width+import kotlin.math.min++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars = Insets()+}++@Stable+class Insets {+    var left by mutableStateOf(0)+        internal set+    var top by mutableStateOf(0)+        internal set+    var right by mutableStateOf(0)+        internal set+    var bottom by mutableStateOf(0)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set+}++val InsetsAmbient = staticAmbientOf<DisplayInsets>()++@Composable+fun ProvideDisplayInsets(content: @Composable () -> Unit) {+    val view = ViewAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->+            displayInsets.systemBars.updateFrom(windowInsets.systemWindowInsets)++            // Return the unconsumed insets+            windowInsets+        }++        // Add an OnAttachStateChangeListener to request an inset pass each time we're attached+        // to the window+        val attachListener = object : View.OnAttachStateChangeListener {+            override fun onViewAttachedToWindow(v: View) {+                v.requestApplyInsets()+            }++            override fun onViewDetachedFromWindow(v: View) = Unit+        }+        view.addOnAttachStateChangeListener(attachListener)++        if (view.isAttachedToWindow) {+            // If the view is already attached, we can request an inset pass now+            view.requestApplyInsets()+        }++        onDispose {+            view.removeOnAttachStateChangeListener(attachListener)

I had this originally, but I'm not sure it's right because technically the dev could have set their own listener.

Ideally this would live in ComposeView itself (or whatever it is called), and then it forwards as appropriate from onApplyWindowInsets.

chrisbanes

comment created time in 4 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import android.view.View+import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.ui.core.Constraints+import androidx.ui.core.IntrinsicMeasurable+import androidx.ui.core.IntrinsicMeasureScope+import androidx.ui.core.LayoutDirection+import androidx.ui.core.LayoutModifier+import androidx.ui.core.Measurable+import androidx.ui.core.MeasureScope+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.core.offset+import androidx.ui.layout.height+import androidx.ui.layout.width+import kotlin.math.min++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars = Insets()+}++@Stable+class Insets {+    var left by mutableStateOf(0)+        internal set+    var top by mutableStateOf(0)+        internal set+    var right by mutableStateOf(0)+        internal set+    var bottom by mutableStateOf(0)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set+}++val InsetsAmbient = staticAmbientOf<DisplayInsets>()++@Composable+fun ProvideDisplayInsets(content: @Composable () -> Unit) {+    val view = ViewAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->+            displayInsets.systemBars.updateFrom(windowInsets.systemWindowInsets)++            // Return the unconsumed insets+            windowInsets+        }++        // Add an OnAttachStateChangeListener to request an inset pass each time we're attached+        // to the window+        val attachListener = object : View.OnAttachStateChangeListener {+            override fun onViewAttachedToWindow(v: View) {+                v.requestApplyInsets()+            }++            override fun onViewDetachedFromWindow(v: View) = Unit+        }+        view.addOnAttachStateChangeListener(attachListener)++        if (view.isAttachedToWindow) {+            // If the view is already attached, we can request an inset pass now+            view.requestApplyInsets()+        }++        onDispose {+            view.removeOnAttachStateChangeListener(attachListener)+        }+    }++    Providers(InsetsAmbient provides displayInsets) {+        content()+    }+}++/**+ * Selectively apply additional space which matches the width/height of any system bars present+ * on the respective edges of the screen.+ *+ * @param enabled Whether to apply padding using the system bar dimensions on the respective edges.+ * Defaults to `true`.+ */+fun Modifier.systemBarsPadding(enabled: Boolean = true) = composed {+    insetsPadding(+        insets = InsetsAmbient.current.systemBars,+        left = enabled,+        top = enabled,+        right = enabled,+        bottom = enabled+    )+}++/**+ * Apply additional space which matches the height of the status height along the top edge+ * of the content.+ */+fun Modifier.statusBarPadding() = composed {+    insetsPadding(insets = InsetsAmbient.current.systemBars, top = true)+}++/**+ * Apply additional space which matches the height of the navigation bar height+ * along the [bottom] edge of the content, and additional space which matches the width of+ * the navigation bar on the respective [left] and [right] edges.+ *+ * @param bottom Whether to apply padding to the bottom edge, which matches the navigation bar+ * height (if present) at the bottom edge of the screen. Defaults to `true`.+ * @param left Whether to apply padding to the left edge, which matches the navigation bar width+ * (if present) on the left edge of the screen. Defaults to `true`.+ * @param right Whether to apply padding to the right edge, which matches the navigation bar width+ * (if present) on the right edge of the screen. Defaults to `true`.+ */+fun Modifier.navigationBarPadding(+    bottom: Boolean = true,+    left: Boolean = true,+    right: Boolean = true+) = composed {+    insetsPadding(+        insets = InsetsAmbient.current.systemBars,+        left = left,+        right = right,+        bottom = bottom+    )+}++/**+ * Updates our mutable state backed [Insets] from an Android system insets.+ */+private fun Insets.updateFrom(insets: androidx.core.graphics.Insets) {+    left = insets.left+    top = insets.top+    right = insets.right+    bottom = insets.bottom+}++/**+ * Declare the height of the content to match the height of the status bar exactly.+ *+ * This is very handy when used with `Spacer` to push content below the status bar:+ * ```+ * Column {+ *     Spacer(Modifier.statusBarHeight())+ *+ *     // Content to be drawn below status bar (y-axis)+ * }+ * ```+ *+ * It's also useful when used with `Box` to draw a scrim which matches the status bar:+ * ```+ * Box(+ *     Modifier.statusBarHeight()+ *         .fillMaxWidth()+ *         .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f)+ * )+ * ```+ *+ * Internally this uses [Modifier.height] so has the same characteristics with regards to incoming+ * layout constraints.+ */+fun Modifier.statusBarHeight() = composed {+    // TODO: Move to Android 11 WindowInsets APIs when they land in AndroidX.+    // It currently assumes that status bar == top which is probably fine, but doesn't work+    // in multi-window, etc.+    InsetsSizeModifier(insets = InsetsAmbient.current.systemBars, heightSide = VerticalSide.Top)+}++/**+ * Declare the preferred height of the content to match the height of the navigation bar when present at the bottom of the screen.+ *+ * This is very handy when used with `Spacer` to push content below the navigation bar:+ * ```+ * Column {+ *     // Content to be drawn above status bar (y-axis)+ *     Spacer(Modifier.navigationBarHeight())+ * }+ * ```+ *+ * It's also useful when used with `Box` to draw a scrim which matches the navigation bar:+ * ```+ * Box(+ *     Modifier.navigationBarHeight()+ *         .fillMaxWidth()+ *         .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f)+ * )+ * ```+ *+ * Internally this uses [Modifier.height] so has the same characteristics with regards to incoming+ * layout constraints.+ */+fun Modifier.navigationBarHeight() = composed {+    // TODO: Move to Android 11 WindowInsets APIs when they land in AndroidX.+    // It currently assumes that nav bar == bottom, which is wrong in landscape.+    // It also doesn't handle the IME correctly.+    InsetsSizeModifier(insets = InsetsAmbient.current.systemBars, heightSide = VerticalSide.Bottom)+}++enum class HorizontalSide { Left, Right }+enum class VerticalSide { Top, Bottom }++/**+ * Declare the preferred width of the content to match the width of the navigation bar,+ * on the given [side].+ *+ * This is very handy when used with `Spacer` to push content inside from any vertical+ * navigation bars (typically when the device is in landscape):+ * ```+ * Row {+ *     Spacer(Modifier.navigationBarWidth(HorizontalSide.Left))+ *+ *     // Content to be inside the navigation bars (x-axis)+ *+ *     Spacer(Modifier.navigationBarWidth(HorizontalSide.Right))+ * }+ * ```+ *+ * It's also useful when used with `Box` to draw a scrim which matches the navigation bar:+ * ```+ * Box(+ *     Modifier.navigationBarWidth(HorizontalSide.Left)+ *         .fillMaxHeight()+ *         .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f)+ * )+ * ```+ *+ * Internally this uses [Modifier.width] so has the same characteristics with regards to incoming+ * layout constraints.+ *+ * @param side The navigation bar side to use as the source for the width.+ */+fun Modifier.navigationBarWidth(side: HorizontalSide) = composed {+    // TODO: Move to Android 11 WindowInsets APIs when they land in AndroidX.+    // It currently assumes that nav bar == left/right+    InsetsSizeModifier(insets = InsetsAmbient.current.systemBars, widthSide = side)+}++/**+ * Allows conditional setting of [insets] on each dimension.+ */+private inline fun Modifier.insetsPadding(+    insets: Insets,+    left: Boolean = false,+    top: Boolean = false,+    right: Boolean = false,+    bottom: Boolean = false+) = this + InsetsPaddingModifier(insets, left, top, right, bottom)++private data class InsetsPaddingModifier(+    private val insets: Insets,+    private val applyLeft: Boolean = false,+    private val applyTop: Boolean = false,+    private val applyRight: Boolean = false,+    private val applyBottom: Boolean = false+) : LayoutModifier {+    override fun MeasureScope.measure(+        measurable: Measurable,+        constraints: Constraints,+        layoutDirection: LayoutDirection+    ): MeasureScope.MeasureResult {+        val left = if (applyLeft) insets.left else 0+        val top = if (applyTop) insets.top else 0+        val right = if (applyRight) insets.right else 0+        val bottom = if (applyBottom) insets.bottom else 0+        val horizontal = left + right+        val vertical = top + bottom++        val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))++        val width = (placeable.width + horizontal)+            .coerceIn(constraints.minWidth, constraints.maxWidth)+        val height = (placeable.height + vertical)+            .coerceIn(constraints.minHeight, constraints.maxHeight)+        return layout(width, height) {+            placeable.placeAbsolute(left, top)

Insets are always absolute, so no need.

chrisbanes

comment created time in 4 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.doOnAttach+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.height+import androidx.ui.layout.width+import androidx.ui.unit.Density+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars by lazy(LazyThreadSafetyMode.NONE) { Insets() }+}++@Stable+class Insets {+    var left by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var top by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var right by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var bottom by mutableStateOf(0.dp, StructurallyEqual)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set++    override fun equals(other: Any?): Boolean {+        if (this === other) return true+        if (javaClass != other?.javaClass) return false+        other as Insets+        if (left != other.left) return false+        if (top != other.top) return false+        if (right != other.right) return false+        if (bottom != other.bottom) return false+        if (visible != other.visible) return false+        return true+    }++    override fun hashCode(): Int {+        var result = left.hashCode()+        result = 31 * result + top.hashCode()+        result = 31 * result + right.hashCode()+        result = 31 * result + bottom.hashCode()+        result = 31 * result + visible.hashCode()+        return result+    }+}++val InsetsAmbient = staticAmbientOf<DisplayInsets>()++@Composable+fun ProvideInsets(content: @Composable () -> Unit) {+    val view = ViewAmbient.current+    val density = DensityAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->+            displayInsets.systemBars.updateFrom(windowInsets.systemWindowInsets, density)++            // Return the unconsumed insets+            windowInsets+        }+        view.doOnAttach { it.requestApplyInsets() }++        onDispose {+            ViewCompat.setOnApplyWindowInsetsListener(view, null)+        }+    }++    Providers(InsetsAmbient provides displayInsets) {+        content()+    }+}++/**+ * Selectively apply additional space which matches the width/height of any system bars present+ * on the respective edges of the screen.+ *+ * @param enabled Whether to apply padding using the system bar dimensions on the respective edges.+ * Defaults to `true`.+ */+fun Modifier.systemBarsPadding(enabled: Boolean = true) = composed {+    insetsPadding(+        insets = InsetsAmbient.current.systemBars,+        left = enabled,+        top = enabled,+        right = enabled,+        bottom = enabled+    )+}++/**+ * Apply additional space which matches the height of the status height along the top edge+ * of the content.+ */+fun Modifier.statusBarPadding() = composed {+    insetsPadding(insets = InsetsAmbient.current.systemBars, top = true)+}++/**+ * Apply additional space which matches the height of the navigation bar height+ * along the [bottom] edge of the content, and additional space which matches the width of+ * the navigation bar on the respective [left] and [right] edges.+ *+ * @param bottom Whether to apply padding to the bottom edge, which matches the navigation bar+ * height (if present) at the bottom edge of the screen. Defaults to `true`.+ * @param left Whether to apply padding to the left edge, which matches the navigation bar width+ * (if present) on the left edge of the screen. Defaults to `true`.+ * @param right Whether to apply padding to the right edge, which matches the navigation bar width+ * (if present) on the right edge of the screen. Defaults to `true`.+ */+fun Modifier.navigationBarPadding(+    bottom: Boolean = true,+    left: Boolean = true,+    right: Boolean = true+) = composed {+    insetsPadding(+        insets = InsetsAmbient.current.systemBars,+        left = left,+        right = right,+        bottom = bottom+    )+}++/**+ * Allows conditional setting of [insets] on each dimension.+ */+private inline fun Modifier.insetsPadding(+    insets: Insets,+    left: Boolean = false,+    top: Boolean = false,+    right: Boolean = false,+    bottom: Boolean = false+) = absolutePadding(+    left = if (left) insets.left else 0.dp,+    top = if (top) insets.top else 0.dp,+    right = if (right) insets.right else 0.dp,+    bottom = if (bottom) insets.bottom else 0.dp+)++/**+ * Updates our mutable state backed [Insets] from an Android system insets.+ */+private fun Insets.updateFrom(+    insets: androidx.core.graphics.Insets,+    density: Density+) = with(density) {+    left = insets.left.toDp()+    top = insets.top.toDp()+    right = insets.right.toDp()+    bottom = insets.bottom.toDp()+}++/**+ * Declare the height of the content to match the height of the status bar exactly.+ *+ * This is very handy when used with `Spacer` to push content below the status bar:+ * ```+ * Column {+ *     Spacer(Modifier.statusBarHeight())+ *+ *     // Content to be drawn below status bar (y-axis)+ * }+ * ```+ *+ * It's also useful when used with `Box` to draw a scrim which matches the status bar:+ * ```+ * Box(+ *     Modifier.statusBarHeight()+ *         .fillMaxWidth()+ *         .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f)+ * )+ * ```+ *+ * Internally this uses [Modifier.height] so has the same characteristics with regards to incoming+ * layout constraints.+ */+fun Modifier.statusBarHeight() = composed {+    // TODO: Move to Android 11 WindowInsets APIs when they land in AndroidX.+    // It currently assumes that status bar == top which is probably fine, but doesn't work+    // in multi-window, etc.+    val insets = InsetsAmbient.current+    Modifier.height(insets.systemBars.top)+}++/**+ * Declare the preferred height of the content to match the height of the navigation bar when present at the bottom of the screen.+ *+ * This is very handy when used with `Spacer` to push content below the navigation bar:+ * ```+ * Column {+ *     // Content to be drawn above status bar (y-axis)+ *     Spacer(Modifier.navigationBarHeight())+ * }+ * ```+ *+ * It's also useful when used with `Box` to draw a scrim which matches the navigation bar:+ * ```+ * Box(+ *     Modifier.navigationBarHeight()+ *         .fillMaxWidth()+ *         .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f)+ * )+ * ```+ *+ * Internally this uses [Modifier.height] so has the same characteristics with regards to incoming+ * layout constraints.+ */+fun Modifier.navigationBarHeight() = composed {+    // TODO: Move to Android 11 WindowInsets APIs when they land in AndroidX.+    // It currently assumes that nav bar == bottom, which is wrong in landscape.+    // It also doesn't handle the IME correctly.+    val insets = InsetsAmbient.current+    Modifier.height(insets.systemBars.bottom)+}++enum class HorizontalSide { Left, Right }++/**+ * Declare the preferred width of the content to match the width of the navigation bar,+ * on the given [side].+ *+ * This is very handy when used with `Spacer` to push content inside from any vertical+ * navigation bars (typically when the device is in landscape):+ * ```+ * Row {+ *     Spacer(Modifier.navigationBarWidth(HorizontalSide.Left))+ *+ *     // Content to be inside the navigation bars (x-axis)+ *+ *     Spacer(Modifier.navigationBarWidth(HorizontalSide.Right))+ * }+ * ```+ *+ * It's also useful when used with `Box` to draw a scrim which matches the navigation bar:+ * ```+ * Box(+ *     Modifier.navigationBarWidth(HorizontalSide.Left)+ *         .fillMaxHeight()+ *         .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f)+ * )+ * ```+ *+ * Internally this uses [Modifier.width] so has the same characteristics with regards to incoming+ * layout constraints.+ *+ * @param side The navigation bar side to use as the source for the width.+ */+fun Modifier.navigationBarWidth(side: HorizontalSide) = composed {+    // TODO: Move to Android 11 WindowInsets APIs when they land in AndroidX.+    // It currently assumes that nav bar == left/right+    val insets = InsetsAmbient.current+    when (side) {+        HorizontalSide.Left -> Modifier.width(insets.systemBars.left)

Done.

chrisbanes

comment created time in 4 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.doOnAttach+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.height+import androidx.ui.layout.width+import androidx.ui.unit.Density+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars by lazy(LazyThreadSafetyMode.NONE) { Insets() }+}++@Stable+class Insets {+    var left by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var top by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var right by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var bottom by mutableStateOf(0.dp, StructurallyEqual)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set++    override fun equals(other: Any?): Boolean {+        if (this === other) return true+        if (javaClass != other?.javaClass) return false+        other as Insets+        if (left != other.left) return false+        if (top != other.top) return false+        if (right != other.right) return false+        if (bottom != other.bottom) return false+        if (visible != other.visible) return false+        return true+    }++    override fun hashCode(): Int {+        var result = left.hashCode()+        result = 31 * result + top.hashCode()+        result = 31 * result + right.hashCode()+        result = 31 * result + bottom.hashCode()+        result = 31 * result + visible.hashCode()+        return result+    }+}++val InsetsAmbient = staticAmbientOf<DisplayInsets>()++@Composable+fun ProvideInsets(content: @Composable () -> Unit) {+    val view = ViewAmbient.current+    val density = DensityAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->+            displayInsets.systemBars.updateFrom(windowInsets.systemWindowInsets, density)++            // Return the unconsumed insets+            windowInsets+        }+        view.doOnAttach { it.requestApplyInsets() }++        onDispose {+            ViewCompat.setOnApplyWindowInsetsListener(view, null)+        }+    }++    Providers(InsetsAmbient provides displayInsets) {+        content()+    }+}++/**+ * Selectively apply additional space which matches the width/height of any system bars present+ * on the respective edges of the screen.+ *+ * @param enabled Whether to apply padding using the system bar dimensions on the respective edges.+ * Defaults to `true`.+ */+fun Modifier.systemBarsPadding(enabled: Boolean = true) = composed {+    insetsPadding(+        insets = InsetsAmbient.current.systemBars,+        left = enabled,+        top = enabled,+        right = enabled,+        bottom = enabled+    )+}++/**+ * Apply additional space which matches the height of the status height along the top edge+ * of the content.+ */+fun Modifier.statusBarPadding() = composed {+    insetsPadding(insets = InsetsAmbient.current.systemBars, top = true)+}++/**+ * Apply additional space which matches the height of the navigation bar height+ * along the [bottom] edge of the content, and additional space which matches the width of+ * the navigation bar on the respective [left] and [right] edges.+ *+ * @param bottom Whether to apply padding to the bottom edge, which matches the navigation bar+ * height (if present) at the bottom edge of the screen. Defaults to `true`.+ * @param left Whether to apply padding to the left edge, which matches the navigation bar width+ * (if present) on the left edge of the screen. Defaults to `true`.+ * @param right Whether to apply padding to the right edge, which matches the navigation bar width+ * (if present) on the right edge of the screen. Defaults to `true`.+ */+fun Modifier.navigationBarPadding(+    bottom: Boolean = true,+    left: Boolean = true,+    right: Boolean = true+) = composed {+    insetsPadding(+        insets = InsetsAmbient.current.systemBars,+        left = left,+        right = right,+        bottom = bottom+    )+}++/**+ * Allows conditional setting of [insets] on each dimension.+ */+private inline fun Modifier.insetsPadding(+    insets: Insets,+    left: Boolean = false,+    top: Boolean = false,+    right: Boolean = false,+    bottom: Boolean = false+) = absolutePadding(

Done in the latest commit 👍

chrisbanes

comment created time in 4 days

delete branch chrisbanes/tivi

delete branch : cb/crash-nullable

delete time in 4 days

push eventchrisbanes/tivi

Chris Banes

commit sha f66508125b2cb3cec671df8c5559fead2e98e6ed

Fix crash due to wrong nullability Users can be null in the database, therefore the flows should be of a nullable type.

view details

Chris Banes

commit sha b4a7a308992f70f9aba1bc05c950b23de1f83d36

Merge pull request #659 from chrisbanes/cb/crash-nullable Fix crash due to wrong nullability

view details

push time in 4 days

push eventchrisbanes/tivi

Chris Banes

commit sha b2830191e1538ae3c61d9c3ed0df760b6f350abb

Remove lazy and unnecessary equals/hashCode

view details

Chris Banes

commit sha 4f3d46bcda43026e0b339809e87b0cd8ee6345b0

Use an explicit OnAttachStateChangeListener Rather than using the extfun doOnAttach, we now use an explicit listener to be notified on every attach/detach.

view details

Chris Banes

commit sha 5e22185b55bbf6ae8efbca9dccc1824ec81336f8

Rename ProvideInsets to ProvideDisplayInsets

view details

Chris Banes

commit sha 6049b9fa07c8bdb2e486bddab72df8a12c4f1bfc

Stop converting to Dp

view details

Chris Banes

commit sha 054d492c6880c2562c7ad7ca41d5e5e57a8f87b3

Move Insets back to using Int values This requires new custom modifiers for both padding and size, which has two benefits: 1) We're no longer doing the unnecessary conversion of Int to Dp, and then back to Int in measure/layout. 2) We can pass the state backed Insets instance straight to the modifier, meaning that any state changes are read directly in layout rather than triggering a full recomposition.

view details

push time in 4 days

PR opened chrisbanes/tivi

Fix crash due to wrong nullability

Users can be null in the database, therefore the user flows should be of a nullable type.

+30 -34

0 comment

8 changed files

pr created time in 5 days

create barnchchrisbanes/tivi

branch : cb/crash-nullable

created branch time in 5 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.doOnAttach+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.height+import androidx.ui.layout.width+import androidx.ui.unit.Density+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars by lazy(LazyThreadSafetyMode.NONE) { Insets() }+}++@Stable+class Insets {+    var left by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var top by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var right by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var bottom by mutableStateOf(0.dp, StructurallyEqual)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set++    override fun equals(other: Any?): Boolean {+        if (this === other) return true+        if (javaClass != other?.javaClass) return false+        other as Insets+        if (left != other.left) return false+        if (top != other.top) return false+        if (right != other.right) return false+        if (bottom != other.bottom) return false+        if (visible != other.visible) return false+        return true+    }++    override fun hashCode(): Int {+        var result = left.hashCode()+        result = 31 * result + top.hashCode()+        result = 31 * result + right.hashCode()+        result = 31 * result + bottom.hashCode()+        result = 31 * result + visible.hashCode()+        return result+    }+}++val InsetsAmbient = staticAmbientOf<DisplayInsets>()++@Composable+fun ProvideInsets(content: @Composable () -> Unit) {+    val view = ViewAmbient.current+    val density = DensityAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->+            displayInsets.systemBars.updateFrom(windowInsets.systemWindowInsets, density)++            // Return the unconsumed insets+            windowInsets+        }+        view.doOnAttach { it.requestApplyInsets() }++        onDispose {+            ViewCompat.setOnApplyWindowInsetsListener(view, null)+        }+    }++    Providers(InsetsAmbient provides displayInsets) {

I tried, but I'm not sure this is going to work as-is. A view can only have one OnApplyWindowInsetsListener set, so if the producer function (say called displayInsets()) is called in multiple places, only one them will actually work.

I could shove a multiplexing OnApplyWindowInsetsListener or a InsetsController to an Ambient, and then have each displayInsets() subscribe to it. WDYT?

chrisbanes

comment created time in 5 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.doOnAttach+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.height+import androidx.ui.layout.width+import androidx.ui.unit.Density+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars by lazy(LazyThreadSafetyMode.NONE) { Insets() }+}++@Stable+class Insets {+    var left by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var top by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var right by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var bottom by mutableStateOf(0.dp, StructurallyEqual)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set++    override fun equals(other: Any?): Boolean {+        if (this === other) return true+        if (javaClass != other?.javaClass) return false+        other as Insets+        if (left != other.left) return false+        if (top != other.top) return false+        if (right != other.right) return false+        if (bottom != other.bottom) return false+        if (visible != other.visible) return false+        return true+    }++    override fun hashCode(): Int {+        var result = left.hashCode()+        result = 31 * result + top.hashCode()+        result = 31 * result + right.hashCode()+        result = 31 * result + bottom.hashCode()+        result = 31 * result + visible.hashCode()+        return result+    }+}++val InsetsAmbient = staticAmbientOf<DisplayInsets>()++@Composable+fun ProvideInsets(content: @Composable () -> Unit) {+    val view = ViewAmbient.current+    val density = DensityAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->+            displayInsets.systemBars.updateFrom(windowInsets.systemWindowInsets, density)++            // Return the unconsumed insets+            windowInsets+        }+        view.doOnAttach { it.requestApplyInsets() }

Done.

chrisbanes

comment created time in 5 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.doOnAttach+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.height+import androidx.ui.layout.width+import androidx.ui.unit.Density+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars by lazy(LazyThreadSafetyMode.NONE) { Insets() }+}++@Stable+class Insets {+    var left by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var top by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var right by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var bottom by mutableStateOf(0.dp, StructurallyEqual)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set++    override fun equals(other: Any?): Boolean {+        if (this === other) return true+        if (javaClass != other?.javaClass) return false+        other as Insets+        if (left != other.left) return false+        if (top != other.top) return false+        if (right != other.right) return false+        if (bottom != other.bottom) return false+        if (visible != other.visible) return false+        return true+    }++    override fun hashCode(): Int {+        var result = left.hashCode()+        result = 31 * result + top.hashCode()+        result = 31 * result + right.hashCode()+        result = 31 * result + bottom.hashCode()+        result = 31 * result + visible.hashCode()+        return result+    }+}++val InsetsAmbient = staticAmbientOf<DisplayInsets>()++@Composable+fun ProvideInsets(content: @Composable () -> Unit) {+    val view = ViewAmbient.current+    val density = DensityAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->+            displayInsets.systemBars.updateFrom(windowInsets.systemWindowInsets, density)++            // Return the unconsumed insets+            windowInsets+        }+        view.doOnAttach { it.requestApplyInsets() }++        onDispose {+            ViewCompat.setOnApplyWindowInsetsListener(view, null)

Removed this call, since it doesn't do much anyway.

chrisbanes

comment created time in 5 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.doOnAttach+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.height+import androidx.ui.layout.width+import androidx.ui.unit.Density+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars by lazy(LazyThreadSafetyMode.NONE) { Insets() }

No particular reason, removed.

chrisbanes

comment created time in 5 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.doOnAttach+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.height+import androidx.ui.layout.width+import androidx.ui.unit.Density+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars by lazy(LazyThreadSafetyMode.NONE) { Insets() }+}++@Stable+class Insets {+    var left by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var top by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var right by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var bottom by mutableStateOf(0.dp, StructurallyEqual)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set++    override fun equals(other: Any?): Boolean {

Nah, I can remove them

chrisbanes

comment created time in 7 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.+ */++@file:Suppress("NOTHING_TO_INLINE")++package app.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.Stable+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.doOnAttach+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.height+import androidx.ui.layout.width+import androidx.ui.unit.Density+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ */+@Stable+class DisplayInsets {+    val systemBars by lazy(LazyThreadSafetyMode.NONE) { Insets() }+}++@Stable+class Insets {+    var left by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var top by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var right by mutableStateOf(0.dp, StructurallyEqual)+        internal set+    var bottom by mutableStateOf(0.dp, StructurallyEqual)+        internal set++    /**+     * TODO: doesn't currently work+     */+    var visible by mutableStateOf(true)+        internal set++    override fun equals(other: Any?): Boolean {+        if (this === other) return true+        if (javaClass != other?.javaClass) return false+        other as Insets+        if (left != other.left) return false+        if (top != other.top) return false+        if (right != other.right) return false+        if (bottom != other.bottom) return false+        if (visible != other.visible) return false+        return true+    }++    override fun hashCode(): Int {+        var result = left.hashCode()+        result = 31 * result + top.hashCode()+        result = 31 * result + right.hashCode()+        result = 31 * result + bottom.hashCode()+        result = 31 * result + visible.hashCode()+        return result+    }+}++val InsetsAmbient = staticAmbientOf<DisplayInsets>()++@Composable+fun ProvideInsets(content: @Composable () -> Unit) {+    val view = ViewAmbient.current+    val density = DensityAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->+            displayInsets.systemBars.updateFrom(windowInsets.systemWindowInsets, density)++            // Return the unconsumed insets+            windowInsets+        }+        view.doOnAttach { it.requestApplyInsets() }++        onDispose {+            ViewCompat.setOnApplyWindowInsetsListener(view, null)

There's no getter :sadface:

chrisbanes

comment created time in 7 days

push eventchrisbanes/tivi

Chris Banes

commit sha 2d65492372fe4bb7ae8e402f8469107d4c1f35f1

Re-work the Insets modifiers

view details

push time in 7 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.WindowInsetsCompat+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.preferredHeight+import androidx.ui.layout.preferredWidth+import androidx.ui.unit.Density+import androidx.ui.unit.Dp+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ *+ * TODO add other inset types (IME, visibility, etc)+ */+class DisplayInsets {+    var systemBars by mutableStateOf(Insets.Zero, areEquivalent = StructurallyEqual)+        internal set+}++data class Insets(+    val left: Dp = 0.dp,+    val top: Dp = 0.dp,+    val right: Dp = 0.dp,+    val bottom: Dp = 0.dp+) {+    companion object {+        val Zero = Insets()+    }+}++val InsetsAmbient = staticAmbientOf { DisplayInsets() }++@Composable+fun ProvideInsets(children: @Composable () -> Unit) {

Done 👍

chrisbanes

comment created time in 7 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.WindowInsetsCompat+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.preferredHeight+import androidx.ui.layout.preferredWidth+import androidx.ui.unit.Density+import androidx.ui.unit.Dp+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ *+ * TODO add other inset types (IME, visibility, etc)+ */+class DisplayInsets {

Good idea, done.

chrisbanes

comment created time in 7 days

Pull request review commentchrisbanes/tivi

Trying out new Insets implementation

+/*+ * Copyright 2020 Google LLC+ *+ * 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.tivi.common.compose++import androidx.compose.Composable+import androidx.compose.Providers+import androidx.compose.StructurallyEqual+import androidx.compose.getValue+import androidx.compose.mutableStateOf+import androidx.compose.onCommit+import androidx.compose.remember+import androidx.compose.setValue+import androidx.compose.staticAmbientOf+import androidx.core.view.ViewCompat+import androidx.core.view.WindowInsetsCompat+import androidx.ui.core.DensityAmbient+import androidx.ui.core.Modifier+import androidx.ui.core.ViewAmbient+import androidx.ui.core.composed+import androidx.ui.layout.absolutePadding+import androidx.ui.layout.preferredHeight+import androidx.ui.layout.preferredWidth+import androidx.ui.unit.Density+import androidx.ui.unit.Dp+import androidx.ui.unit.dp++/**+ * Main holder of our inset values.+ *+ * TODO add other inset types (IME, visibility, etc)+ */+class DisplayInsets {+    var systemBars by mutableStateOf(Insets.Zero, areEquivalent = StructurallyEqual)+        internal set+}++data class Insets(+    val left: Dp = 0.dp,+    val top: Dp = 0.dp,+    val right: Dp = 0.dp,+    val bottom: Dp = 0.dp+) {+    companion object {+        val Zero = Insets()+    }+}++val InsetsAmbient = staticAmbientOf { DisplayInsets() }++@Composable+fun ProvideInsets(children: @Composable () -> Unit) {+    val view = ViewAmbient.current+    val density = DensityAmbient.current++    val displayInsets = remember { DisplayInsets() }++    onCommit(view.id) {+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets ->+            displayInsets.updateFrom(insets, density)+            // Return the unconsumed insets+            insets+        }

Yep, added 👍

chrisbanes

comment created time in 7 days

push eventchrisbanes/tivi

Chris Banes

commit sha d124e2f07d208529c4d0a22ac509c6ed28f4d5d1

Re-work the Insets modifiers

view details

push time in 7 days

push eventchrisbanes/tivi

Chris Banes

commit sha a6ad7df6646b2ac1be70ec5f777c44a6f3d02734

Trying out new Insets implementation

view details

push time in 8 days

push eventchrisbanes/tivi

Chris Banes

commit sha c068233c785a4026597a363cabe9513b8c687901

Migrate to Hilt (#640) This PR moves Tivi away from Dagger-Android to Hilt. Most of the changes are moving modules around or adding @InstallIn. Since Tivi uses some Kotlin only modules (i.e. not Android modules), this migration required either @Module(includes = ...) them from an Android module, or moving the module. This PR did require Tivi to migrate away from MvRx. Unfortunately MvRx is very opinionated about how its ViewModels are created, which does not work with the new Hilt AndroidX integration. Luckily Tivi is only using a small subset of what MvRx provides, so I've reimplemented it using Coroutines in ReduxViewModel. This was going to be required soon anyway, to be able to use Jetpack Compose's upcoming ViewModel support. For me, where Hilt really shines is for testing. No more brittle and hacky Dagger test components just to provide fake dependencies, it's now just a case of @UninstallModules for the prod dependencies. See here for more info. The automatic support for Workers was 👌 too.

view details

Chris Banes

commit sha 8eeeb913335bf3e1c9ace663459fd24b9589daa3

Migrate to AGP 4.2.0-alpha01

view details

Chris Banes

commit sha f86638e05ecd2241c7237d1e8bf700fceed64bc0

Revert "Migrate to AGP 4.2.0-alpha01" 8eeeb913335bf3e1c9ace663459fd24b9589daa3

view details

Chris Banes

commit sha dda946d2aca1b0f27137221ab3a9da3562f68fe1

Update to Compose SNAPSHOT #6602655

view details

Chris Banes

commit sha 449407ccf39fce0905871d7e659845a9ecf7ea74

Migrate to LazyRowItems

view details

Chris Banes

commit sha 348c98c1a70d7dba3c762d77d151d1d3535fc0cf

Fix Modifier.onSizeChanged

view details

Chris Banes

commit sha de0ca116ca0a4beecea20ea1703b8bd8861d7529

Provide initial state through constructor parameter Means we can start inject initial state again

view details

Chris Banes

commit sha a8d185777cbab4cd7c6c7d1b7bbf936373d258e3

Inject initial state into show details

view details

Chris Banes

commit sha af7a2e57f31bf20a7398ebe9b9af15d2cb782b99

Update episode details to inject initial state

view details

Chris Banes

commit sha 66a19a9e890545e525caaa8bc6299e141eda6a8d

Tidy up RedexViewModel

view details

Chris Banes

commit sha 14fcda17b23df47631956636e929efa48daba7e9

Start scoping operations to the VMs

view details

Chris Banes

commit sha 876b76f9f8b5d05cab093e56ba8f68f94853580b

Ensure interactors are invoked correctly

view details

Chris Banes

commit sha 8e2312c66b6eac63f9b586e35ed2b6a300ebbb2b

Remove the ProcessLifetime scope

view details

Chris Banes

commit sha 3cb9d111bd8194e00c2d994fc47de6ad473d7ca8

Merge pull request #644 from chrisbanes/cb/scopes Start scoping operations back to the ViewModel again

view details

Chris Banes

commit sha 1d7056b21f0752fc4638228fe9d02fec392a9524

Fix popular shows crashing when opened

view details

Chris Banes

commit sha 2ee667a6631f7b5bd1f6073bc26f70dc4c7f46df

Remove Proguard kotlin-reflect config

view details

Chris Banes

commit sha 3d97004e94c991db265f37d7153efba6b58484b0

Fix remaining interactors I missed some in the migration of where the dispatcher is set

view details

Chris Banes

commit sha 9e88f438a813175fd335526b08f87842cab0d98b

Stop observing oberservers on io dispatcher Room already observes the database on a background thread so there's no point jumping onto the io dispatcher just to jump back off it.

view details

Chris Banes

commit sha 883fb4f7d83339f4a28785b92fc8d293ec5a2249

Use yield() and ensureActive() Ensures that the long running interactors will cancel cooperatively.

view details

Chris Banes

commit sha 7a38cc7078c0a14decdc2647f441f5126ca839ef

Remove explicit Store scope

view details

push time in 8 days

push eventchrisbanes/tivi

Chris Banes

commit sha 9e426322d22b48ec329391157fc6ad975963ac10

Remove ReduxViewModel.execute() and Async classes We don't use them as intended and they're an unnecessary object allocation

view details

Chris Banes

commit sha a60c09701c2951fdc0cb418d5f917a5bc406e0d5

Start tracking compositions through logging

view details

Chris Banes

commit sha 86ab0b2286663a41a581ab07b63bbb09bef77c70

Stop composition loops Turns out that using `ambientOf` for our insets ambient triggered a composition on every acccess. Using `staticAmbientOf` fixes this loop behavior. Thanks to @objcode for the debug code.

view details

Chris Banes

commit sha 6548266997c9ef12e641239ee1dca6c1a29f570c

Track the root cause of the composition looping Thanks to @adamp for catching this. We were not remembering any LiveData transformations around insets, which meant that the stream was being created on every call. Fixed by remembering the transformed LiveData, and then observing that. Meaning we can go back to `ambientOf`.

view details

Chris Banes

commit sha 9c24afa524218af3d7e52bcaca0476ae7c58f844

Merge pull request #658 from chrisbanes/cb/composition Fix show details looping composition

view details

push time in 8 days

delete branch chrisbanes/tivi

delete branch : cb/composition

delete time in 8 days

PR merged chrisbanes/tivi

Fix show details looping composition cla: yes
  • Fixed by remembering any LiveData transformations around insets. Previously we were creating it on every call 🤦
  • Split out ShowDetails.kt into more composable functions
  • Tidied up the ShowDetailsViewState
  • Removed the unnecessary execute() (copied from MvRx) and now rely on a simple collectAndSetState
+289 -394

0 comment

20 changed files

chrisbanes

pr closed time in 8 days

push eventchrisbanes/tivi

Chris Banes

commit sha 6548266997c9ef12e641239ee1dca6c1a29f570c

Track the root cause of the composition looping Thanks to @adamp for catching this. We were not remembering any LiveData transformations around insets, which meant that the stream was being created on every call. Fixed by remembering the transformed LiveData, and then observing that. Meaning we can go back to `ambientOf`.

view details

push time in 8 days

CommitCommentEvent

push eventchrisbanes/accompanist

Chris Banes

commit sha 20bef63c2310b19790043537071f3dd94ec3ba44

Bump next dev version

view details

push time in 8 days

created tagchrisbanes/accompanist

tagv0.1.6

A collection of extension libraries for Jetpack Compose

created time in 8 days

push eventchrisbanes/accompanist

Chris Banes

commit sha 4f67364cdc13b059cd9303b132552935774bab69

Update to Compose/UI dev14 (#34) * Setup snapshot branch * Upgrade to AndroidX SNAPSHOT 6572081 * Update to Compose SNAPSHOT 6574163 (#22) * Update to ui SNAPSHOT 6574163 * Fix tests * Fix sample crashing by using AppCompat alpha * Update to Compose snapshot #6581424 (#27) * Try out Compose/UI Snapshot 6589751 * Update to SNAPSHOT #6602655 * Fix isNotMaterialTheme test not running * Update to UI/Compose dev14

view details

Chris Banes

commit sha 0ffcd49b7b30462380cade9fa8b461603ebfbfe9

Tag v0.1.6

view details

Chris Banes

commit sha 20bef63c2310b19790043537071f3dd94ec3ba44

Bump next dev version

view details

Chris Banes

commit sha 48566dfcc05188955ae14ddfbce002d05dc3f468

Merge branch 'main' into snapshot

view details

push time in 8 days

push eventchrisbanes/accompanist

Chris Banes

commit sha 0ffcd49b7b30462380cade9fa8b461603ebfbfe9

Tag v0.1.6

view details

push time in 8 days

PR opened chrisbanes/tivi

Fix show details looping composition
  • Fixed by moving InsetsAmbient to be a staticAmbientOf
  • Split out ShowDetails.kt into more composable functions
  • Tidied up the ShowDetailsViewState
  • Removed the unnecessary execute() (copied from MvRx) and now rely on a simple collectAndSetState
+286 -397

0 comment

20 changed files

pr created time in 8 days

create barnchchrisbanes/tivi

branch : cb/composition

created branch time in 8 days

MemberEvent

push eventchrisbanes/accompanist

Chris Banes

commit sha 4f67364cdc13b059cd9303b132552935774bab69

Update to Compose/UI dev14 (#34) * Setup snapshot branch * Upgrade to AndroidX SNAPSHOT 6572081 * Update to Compose SNAPSHOT 6574163 (#22) * Update to ui SNAPSHOT 6574163 * Fix tests * Fix sample crashing by using AppCompat alpha * Update to Compose snapshot #6581424 (#27) * Try out Compose/UI Snapshot 6589751 * Update to SNAPSHOT #6602655 * Fix isNotMaterialTheme test not running * Update to UI/Compose dev14

view details

push time in 9 days

delete branch chrisbanes/accompanist

delete branch : cb/dev14

delete time in 9 days

PR merged chrisbanes/accompanist

Update to Compose/UI dev14 cla: yes
+131 -87

0 comment

17 changed files

chrisbanes

pr closed time in 9 days

MemberEvent

PR opened chrisbanes/accompanist

Update to Compose/UI dev14
+131 -87

0 comment

17 changed files

pr created time in 9 days

create barnchchrisbanes/accompanist

branch : cb/dev14

created branch time in 9 days

issue commentchrisbanes/tivi

How do I navigate to a fragment which accept arguments?

This isn't a development forum for asking questions.

sudansh

comment created time in 9 days

issue closedchrisbanes/tivi

How do I navigate to a fragment which accept arguments?

Since the navigation files are in app module, feature modules won't have access to the Args file. Is there any workaround for that?

example: if I have a fragment which accepts email and password like so

<fragment
        android:id="@+id/navigation_sample"
        android:name="SampleFragment">
        <argument
            android:name="email"
            app:argType="string" />
        <argument
            android:name="password"
            app:argType="string" />
    </fragment>

I would have a SampleFragmentArgs generated which could be used to navigate findnavController.navigate(R.id.navigation_sample, SampleFragmentArgs(email, password).toBundle)

But I can use this only from app module not in a feature module

closed time in 9 days

sudansh

push eventchrisbanes/tivi

Chris Banes

commit sha a715cf7270651944cfea27eda55756faebb51a5c

Tidy up show details

view details

Chris Banes

commit sha c6724ce874196a0fe8616313ee0482436f486f6e

Try using ConstraintLayout in show details

view details

Chris Banes

commit sha a3bef7fda4e16c904d0f27bbf92b865fcc2fa4ec

More ConstraintLayout

view details

Chris Banes

commit sha 0a564e671e74f2f93efa869a9b1ea3d3fe801d89

Add parallax to show details backdrop

view details

Chris Banes

commit sha d0e77ecc8d1bfb193e839741ec2a1d7e55aac09d

Merge pull request #656 from chrisbanes/cb/show-details-comp Some small improvements around show details composition

view details

push time in 10 days

delete branch chrisbanes/tivi

delete branch : cb/show-details-comp

delete time in 10 days

push eventchrisbanes/tivi

Chris Banes

commit sha d3d11aa8268c2951e8c22136ec465ba4741d050b

Extract settings to new module

view details

Chris Banes

commit sha 19290d38b802736e0aa0b05ca94bd15a8d20fa20

Collapse to single Activity

view details

Chris Banes

commit sha 41167b8b227b46229e4770bae3da3e9098f5a647

Move account to navigation graph Also fixed logging out not removing the user data profile info

view details

Chris Banes

commit sha c918b2560bbfc5e8078dc754b7ed0dd6fabab8a0

Use Insetter's new AUTO consumption setting

view details

Chris Banes

commit sha 441ecf54e0f8a37ca7b69de5478eb701609ed338

Move back to using asFlow() for collections

view details

Chris Banes

commit sha e83c8f5a7cb52f37c4606f7d28da1e81da9badf1

Fix followed and watched lists not refreshing

view details

Chris Banes

commit sha 779e8fc9b3f914a8c9bfdbb6723531353946bdd1

Remove AppNavigator.openAccount()

view details

Chris Banes

commit sha 863e5015098207fd27a052d4b6e920d87a502fc1

Use explicit ConstraintLayout dep

view details

Chris Banes

commit sha 327ce93bdb5876abb4ceb67c0a9527bd0e805498

Force Store stream() to flow on background thread

view details

Chris Banes

commit sha 2e8602fe2edf65f8b03f43de0196d1da1e6e9d19

Update to Store 4.0.0-alpha06

view details

Chris Banes

commit sha 6db8a9f6b1683a5ec84bc0216488ccbeb39be92a

Merge pull request #653 from chrisbanes/cb/store-dispatcher Force Store stream() to flow on background thread

view details

Chris Banes

commit sha d492c28fd641084d44accf03419aa9cf5dc65eb1

Merge pull request #654 from chrisbanes/cb/store-alpha06 Update to Store v4.0.0-alpha06

view details

Chris Banes

commit sha e63b76330173099991c19d19e0d68fb761048b57

Upgrade to AGP 4.2.0-alpha02 f86638e05ecd2241c7237d1e8bf700fceed64bc0

view details

Chris Banes

commit sha df4f44e2b8856346fe2d5a27f22b085eaf48e1c6

Merge pull request #655 from chrisbanes/cb/4.2.0-alpha02 Upgrade to AGP 4.2.0-alpha02

view details

Chris Banes

commit sha a715cf7270651944cfea27eda55756faebb51a5c

Tidy up show details

view details

Chris Banes

commit sha c6724ce874196a0fe8616313ee0482436f486f6e

Try using ConstraintLayout in show details

view details

Chris Banes

commit sha a3bef7fda4e16c904d0f27bbf92b865fcc2fa4ec

More ConstraintLayout

view details

Chris Banes

commit sha 0a564e671e74f2f93efa869a9b1ea3d3fe801d89

Add parallax to show details backdrop

view details

push time in 10 days

push eventchrisbanes/accompanist

Chris Banes

commit sha fd40e9c54db9bdfcb398cd36d87685eee4a5799b

Move away from using `WithConstraints` (#30) * Move away from using WithConstraints WithConstraints is not suitable for how we're using it, so we're moving to Modifier.onSizeChanged(). This required some changes to how CoilImage is implemented. While here we now use the new launchInComposition() function. * Utilize actor pattern * Stop returning to composition to callback * Add some doc for RequestActor * Fix Coil request being launch on every composition Due to using the wrong key parameter to the outer `remember`. Also fixed the state which holds the current callback. * Update to Coil v0.11.0 * Address feedback

view details

Chris Banes

commit sha f5e2f66ec18ae1a7702ed14ad018679815aef115

Merge branch 'main' into snapshot

view details

push time in 10 days

Pull request review commentchrisbanes/accompanist

Move away from using `WithConstraints`

 fun CoilImage(     loading: @Composable (() -> Unit)? = null,     onRequestCompleted: (RequestResult) -> Unit = emptySuccessLambda ) {-    WithConstraints(modifier) {-        val requestWidth = constraints.requestWidth.value-        val requestHeight = constraints.requestHeight.value+    var result by state<RequestResult?> { null } -        // Execute the request using executeAsComposable(), which guards the actual execution-        // so that the request is only run if the request changes.-        val result = when {-            request.sizeResolver != null -> {-                // If the request has a sizeResolver set, we just execute the request as-is-                request.executeAsComposable()-            }-            requestWidth > 0 && requestHeight > 0 -> {-                // If we have a non-zero size, we can modify the request to include the size-                request.newBuilder()-                    .size(requestWidth, requestHeight)-                    .build()-                    .executeAsComposable()-            }-            else -> {-                // Otherwise we have a zero size, so no point executing a request-                null+    // This may look a little weird, but allows the launchInComposition callback to always+    // invoke the last provided [onRequestCompleted].+    //+    // If a composition happens *after* launchInComposition has launched, the given+    // [onRequestCompleted] might have changed. If the actor lambda below directly referenced+    // [onRequestCompleted] it would have captured access to the initial onRequestCompleted+    // value, not the latest.+    //+    // This `callback` state enables the actor lambda to only capture the remembered state+    // reference, which we can update on each composition.+    val callback = state { onRequestCompleted }

👍

chrisbanes

comment created time in 10 days

delete branch chrisbanes/accompanist

delete branch : cb/subcomp

delete time in 10 days

push eventchrisbanes/accompanist

Chris Banes

commit sha fd40e9c54db9bdfcb398cd36d87685eee4a5799b

Move away from using `WithConstraints` (#30) * Move away from using WithConstraints WithConstraints is not suitable for how we're using it, so we're moving to Modifier.onSizeChanged(). This required some changes to how CoilImage is implemented. While here we now use the new launchInComposition() function. * Utilize actor pattern * Stop returning to composition to callback * Add some doc for RequestActor * Fix Coil request being launch on every composition Due to using the wrong key parameter to the outer `remember`. Also fixed the state which holds the current callback. * Update to Coil v0.11.0 * Address feedback

view details

push time in 10 days

PR merged chrisbanes/accompanist

Move away from using `WithConstraints` cla: yes

WithConstraints is not suitable for how we're using it, so we're moving to Modifier.onSizeChanged() (which is based on Modifier.onPositioned). This required some changes to how CoilImage is implemented. While here, I also migrated us to the new launchInComposition() to launch our Coil request.

+227 -102

0 comment

4 changed files

chrisbanes

pr closed time in 10 days

push eventchrisbanes/tivi

Chris Banes

commit sha e63b76330173099991c19d19e0d68fb761048b57

Upgrade to AGP 4.2.0-alpha02 f86638e05ecd2241c7237d1e8bf700fceed64bc0

view details

Chris Banes

commit sha df4f44e2b8856346fe2d5a27f22b085eaf48e1c6

Merge pull request #655 from chrisbanes/cb/4.2.0-alpha02 Upgrade to AGP 4.2.0-alpha02

view details

push time in 10 days

delete branch chrisbanes/tivi

delete branch : cb/4.2.0-alpha02

delete time in 10 days

PR merged chrisbanes/tivi

Upgrade to AGP 4.2.0-alpha02 cla: yes
+14 -17

0 comment

4 changed files

chrisbanes

pr closed time in 10 days

PR opened chrisbanes/tivi

Upgrade to AGP 4.2.0-alpha02
+14 -17

0 comment

4 changed files

pr created time in 10 days

push eventchrisbanes/tivi

Chris Banes

commit sha 1d7056b21f0752fc4638228fe9d02fec392a9524

Fix popular shows crashing when opened

view details

Chris Banes

commit sha 2ee667a6631f7b5bd1f6073bc26f70dc4c7f46df

Remove Proguard kotlin-reflect config

view details

Chris Banes

commit sha 3d97004e94c991db265f37d7153efba6b58484b0

Fix remaining interactors I missed some in the migration of where the dispatcher is set

view details

Chris Banes

commit sha 9e88f438a813175fd335526b08f87842cab0d98b

Stop observing oberservers on io dispatcher Room already observes the database on a background thread so there's no point jumping onto the io dispatcher just to jump back off it.

view details

Chris Banes

commit sha 883fb4f7d83339f4a28785b92fc8d293ec5a2249

Use yield() and ensureActive() Ensures that the long running interactors will cancel cooperatively.

view details

Chris Banes

commit sha 7a38cc7078c0a14decdc2647f441f5126ca839ef

Remove explicit Store scope

view details

Chris Banes

commit sha 21982038de9199dd669f40cd85a27599c117ab1d

Update our use of Coroutines - Updated to Coroutines 1.3.7 - Replaced all usage of ConflatedBroadcastChannel with StateFlow - Started consuming `action` Channels via consumeAsFlow() to ensure the channels get closed. - Started checking for closed channels before `offer()`ing - Tidied up some of the internals of ReduxViewModel since we can now rely on `StateFlow`

view details

Chris Banes

commit sha 9475312285808077a3453309893917900968cf44

Stop fetching show data from TMDb We drop most information on the floor and it's a waste of network bandwidth. The only thing we use from it is the network logo path, which we can do in a better later.

view details

Chris Banes

commit sha 8974f9cf1eaafb313d126d05b73a58f49a733b37

Stop requesting all images from TMDb We only use the single poster + backdrop so there's little point currently requesting them all.

view details

Chris Banes

commit sha 974eb77cb946e9158ff9cacc5f087f8610be753f

Lazily fetch findHighestRatedPoster() Currently we call findHighestRatedPoster() a lot, which does quite a lot of work. This commit does 2 things: 1) Simplify findHighestRatedPoster() and findHighestRatedBackdrop() when there is <= 1 images. 2) Start lazily caching the result in the entity classes.

view details

Chris Banes

commit sha bcefed46f232c33a273d9230d8b8d2a53edba076

Fix related shows not loading correctly

view details

Chris Banes

commit sha d3d11aa8268c2951e8c22136ec465ba4741d050b

Extract settings to new module

view details

Chris Banes

commit sha 19290d38b802736e0aa0b05ca94bd15a8d20fa20

Collapse to single Activity

view details

Chris Banes

commit sha 41167b8b227b46229e4770bae3da3e9098f5a647

Move account to navigation graph Also fixed logging out not removing the user data profile info

view details

Chris Banes

commit sha c918b2560bbfc5e8078dc754b7ed0dd6fabab8a0

Use Insetter's new AUTO consumption setting

view details

Chris Banes

commit sha 441ecf54e0f8a37ca7b69de5478eb701609ed338

Move back to using asFlow() for collections

view details

Chris Banes

commit sha e83c8f5a7cb52f37c4606f7d28da1e81da9badf1

Fix followed and watched lists not refreshing

view details

Chris Banes

commit sha 779e8fc9b3f914a8c9bfdbb6723531353946bdd1

Remove AppNavigator.openAccount()

view details

Chris Banes

commit sha 863e5015098207fd27a052d4b6e920d87a502fc1

Use explicit ConstraintLayout dep

view details

Chris Banes

commit sha 327ce93bdb5876abb4ceb67c0a9527bd0e805498

Force Store stream() to flow on background thread

view details

push time in 10 days

delete branch chrisbanes/tivi

delete branch : cb/store-alpha06

delete time in 10 days

push eventchrisbanes/tivi

Chris Banes

commit sha 2e8602fe2edf65f8b03f43de0196d1da1e6e9d19

Update to Store 4.0.0-alpha06

view details

Chris Banes

commit sha d492c28fd641084d44accf03419aa9cf5dc65eb1

Merge pull request #654 from chrisbanes/cb/store-alpha06 Update to Store v4.0.0-alpha06

view details

push time in 10 days

PR merged chrisbanes/tivi

Update to Store v4.0.0-alpha06 cla: yes
+131 -67

0 comment

11 changed files

chrisbanes

pr closed time in 10 days

delete branch chrisbanes/tivi

delete branch : cb/store-dispatcher

delete time in 10 days

push eventchrisbanes/tivi

Chris Banes

commit sha 327ce93bdb5876abb4ceb67c0a9527bd0e805498

Force Store stream() to flow on background thread

view details

Chris Banes

commit sha 6db8a9f6b1683a5ec84bc0216488ccbeb39be92a

Merge pull request #653 from chrisbanes/cb/store-dispatcher Force Store stream() to flow on background thread

view details

push time in 10 days

PR merged chrisbanes/tivi

Force Store stream() to flow on background thread cla: yes
+10 -2

0 comment

2 changed files

chrisbanes

pr closed time in 10 days

push eventchrisbanes/tivi

Chris Banes

commit sha 2e8602fe2edf65f8b03f43de0196d1da1e6e9d19

Update to Store 4.0.0-alpha06

view details

push time in 10 days

issue commentdropbox/Store

[BUG] Fetcher and SourceOfTruth must now have the same type signatures

Looking at this again, it seems to be a type inference issue when using a Unit key. I can workaround it by explicitly setting the type.

return StoreBuilder.from(
    fetcher = nonFlowFetcher { _: Unit ->
        /* Need to explicit declare the key type here */
    },
    sourceOfTruth = SourceOfTruth.from(
        writer = { _: Unit, response ->
            /* Need to explicit declare the key's type here */
        }
    )
).build()

Closing this! Thanks.

chrisbanes

comment created time in 10 days

issue closeddropbox/Store

[BUG] Fetcher and SourceOfTruth must now have the same type signatures

I'm just updating to alpha06, and hit a bit of a roadblock....

The old API allowed this:

return StoreBuilder.fromNonFlow<Param, Pair<Result, ExtraInfo> { p: Param ->
    return result to extraInfoWhichPersisterNeeds
}.persister<Param, Result>(
    writer = { (result, extraInfo) ->
        // persist, using the extra info
    }
}

But now the SourceOfTruth and Fetcher need to have the same type signatures, so there's no way to pass along that extra info. Feels a little restrictive to me.

closed time in 10 days

chrisbanes

PR opened chrisbanes/tivi

Update to Store v4.0.0-alpha06
+130 -66

0 comment

11 changed files

pr created time in 10 days

issue closedchrisbanes/tivi

Re-enable Crashlytics

It's currently incompatible with AGP 4.1.0-alpha04. Disabled in #579

closed time in 10 days

chrisbanes

PR opened chrisbanes/tivi

Force Store stream() to flow on background thread
+10 -2

0 comment

2 changed files

pr created time in 10 days

create barnchchrisbanes/tivi

branch : cb/store-dispatcher

created branch time in 10 days

create barnchchrisbanes/tivi

branch : cb/store-alpha06

created branch time in 10 days

issue openeddropbox/Store

[BUG] Fetcher and SourceOfTruth must now have the same type signatures

I'm just updating to alpha06, and hit a bit of a roadblock....

The old API allowed this:

return StoreBuilder.fromNonFlow<Param, Pair<Result, ExtraInfo> { p: Param ->
    return result to extraInfoWhichPersisterNeeds
}.persister<Param, Result>(
    writer = { (result, extraInfo) ->
        // persist, using the extra info
    }
}

But now the SourceOfTruth and Fetcher need to have the same type signatures, so there's no way to pass along that extra info. Feels a little restrictive to me.

created time in 10 days

issue openeddropbox/Store

[BUG] ANR due to ConcurrentHashMap lock in `RealCache`

See the following thread stack. I'm using a pretty vanilla setup for Room + Store and streaming in ViewModels.

This might be a guidance issue, where we tell people to stream(...).flowOn(Dispatcher.Default)

"main" prio=5 tid=1 Runnable
  | group="main" sCount=1 dsCount=0 flags=9 obj=0x717a02f8 self=0xb4000070b9eec380
  | sysTid=27737 nice=0 cgrp=default sched=0/0 handle=0x71e08094f8
  | state=R schedstat=( 45250255527 4245629451 61014 ) utm=4253 stm=271 core=4 HZ=100
  | stack=0x7ff1d62000-0x7ff1d64000 stackSize=8192KB
  | held mutexes= "mutator lock"(shared held)
  at java.util.concurrent.ConcurrentHashMap.replaceNode (ConcurrentHashMap.java:1116)
  at java.util.concurrent.ConcurrentHashMap.remove (ConcurrentHashMap.java:1107)
  at com.dropbox.android.external.cache4.RealCache.evictEntries (RealCache.java:228)
  at com.dropbox.android.external.cache4.RealCache.put (RealCache.java:151)
  at com.dropbox.android.external.store4.impl.RealStore$stream$2.invokeSuspend (RealStore.java:116)
  at com.dropbox.android.external.store4.impl.RealStore$stream$2.invoke (RealStore.java)
  at kotlinx.coroutines.flow.FlowKt__TransformKt$onEach$$inlined$unsafeTransform$1$2.emit (FlowKt__TransformKt.java:134)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:15)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:1)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:73)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:55)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:15)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:1)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:73)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:55)
  at com.dropbox.android.external.store4.impl.RealStore$diskNetworkCombined$$inlined$transform$1$1.emit (RealStore.java:152)
  at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt (FlowKt__ChannelsKt.java:58)
  at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend (FlowKt__ChannelsKt.java)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (BaseContinuationImpl.java:33)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.java:56)
  at kotlinx.coroutines.EventLoop.processUnconfinedEvent (EventLoop.java:69)
  at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core (CancellableContinuationImpl.java:184)
  at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined (DispatchedTaskKt.java:184)
  at kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTaskKt.java:108)
  at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.java:308)
  at kotlinx.coroutines.CancellableContinuationImpl.completeResume (CancellableContinuationImpl.java:395)
  at kotlinx.coroutines.channels.AbstractChannel$ReceiveElement.completeResumeReceive (AbstractChannel.java:872)
  at kotlinx.coroutines.channels.ArrayChannel.offerInternal (ArrayChannel.java:83)
  at kotlinx.coroutines.channels.AbstractSendChannel.send (AbstractSendChannel.java:133)
  at kotlinx.coroutines.channels.ChannelCoroutine.send$suspendImpl (ChannelCoroutine.java:1)
  at kotlinx.coroutines.channels.ChannelCoroutine.send (ChannelCoroutine.java:1)
  at kotlinx.coroutines.flow.internal.SendingCollector.emit (SendingCollector.java:19)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:15)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:1)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:73)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:55)
  at com.dropbox.android.external.store4.impl.SourceOfTruthWithBarrier$reader$1$invokeSuspend$$inlined$flatMapLatest$1$lambda$1$1.emit (SourceOfTruthWithBarrier.java:153)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:15)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:1)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:73)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:55)
  at androidx.room.CoroutinesRoom$Companion$createFlow$1$1$1.invokeSuspend (CoroutinesRoom.java:82)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (BaseContinuationImpl.java:33)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.java:56)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:223)
  at android.app.ActivityThread.main (ActivityThread.java:7656)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:592)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:947)

Smartphone:

  • Device: various
  • OS: various
  • Store Version: 4.0.0-alpha05 (trying alpha06 now)

created time in 10 days

Pull request review commentchrisbanes/accompanist

Move away from using `WithConstraints`

 fun CoilImage(     loading: @Composable (() -> Unit)? = null,     onRequestCompleted: (RequestResult) -> Unit = emptySuccessLambda ) {-    WithConstraints(modifier) {-        val requestWidth = constraints.requestWidth.value-        val requestHeight = constraints.requestHeight.value+    var result by state<RequestResult?> { null } -        // Execute the request using executeAsComposable(), which guards the actual execution-        // so that the request is only run if the request changes.-        val result = when {-            request.sizeResolver != null -> {-                // If the request has a sizeResolver set, we just execute the request as-is-                request.executeAsComposable()-            }-            requestWidth > 0 && requestHeight > 0 -> {-                // If we have a non-zero size, we can modify the request to include the size-                request.newBuilder()-                    .size(requestWidth, requestHeight)-                    .build()-                    .executeAsComposable()-            }-            else -> {-                // Otherwise we have a zero size, so no point executing a request-                null+    // This may look a little weird, but allows the launchInComposition callback to always+    // invoke the last provided [onRequestCompleted].+    //+    // If a composition happens *after* launchInComposition has launched, the given+    // [onRequestCompleted] might have changed. If the actor lambda below directly referenced+    // [onRequestCompleted] it would have captured access to the initial onRequestCompleted+    // value, not the latest.+    //+    // This `callback` state enables the actor lambda to only capture the remembered state+    // reference, which we can update on each composition.+    val callback = state { onRequestCompleted }

@adamp is OK to use state here? I was thinking of using remember { AtomicReference() } (or some other mutable ref holder) to avoid the unnecessary re-composition.

chrisbanes

comment created time in 11 days

push eventchrisbanes/accompanist

Chris Banes

commit sha ee940127e705b2afce083f9388b60e5f73e5dcc3

Address feedback

view details

push time in 11 days

push eventchrisbanes/accompanist

Chris Banes

commit sha dcaac9668298de9fe838221cae043399f3541ceb

Update to AGP 4.2.0-alpha01

view details

Chris Banes

commit sha b811a6fa0c9bba8c7bd1b1a6b79542015077038b

Coil test resources to androidTest/ We were accidentally shipping them in the library /facepalm.

view details

Nick Rout

commit sha c54e39fc38b5edaeb8bb2ab4f069016a616d1fcf

Improve MDC theme assertion (#33) * Update AGP to 4.2.0-alpha02 * Improve MDC theme assertion

view details

Chris Banes

commit sha 82066e3c01392a1042f0498c5bc1007ca9e84f8c

Move away from using WithConstraints WithConstraints is not suitable for how we're using it, so we're moving to Modifier.onSizeChanged(). This required some changes to how CoilImage is implemented. While here we now use the new launchInComposition() function.

view details

Chris Banes

commit sha 765a02694b7e5b747a8ee0a1f56641577a4a5c61

Utilize actor pattern

view details

Chris Banes

commit sha cdcb46f8b2e05973d7164e551e97c08ff8731b04

Stop returning to composition to callback

view details

Chris Banes

commit sha b31d6ad962c7c382b0b1090f19f16aca7a5c0355

Add some doc for RequestActor

view details

Chris Banes

commit sha d3ec560956a945a815032ec5fe5756a1a4e88cc4

Fix Coil request being launch on every composition Due to using the wrong key parameter to the outer `remember`. Also fixed the state which holds the current callback.

view details

Chris Banes

commit sha 0fda7d83439f7513579036afd8a90bd4a6ec317f

Update to Coil v0.11.0

view details

push time in 11 days

push eventchrisbanes/accompanist

Chris Banes

commit sha e47b294269ee6ff323d928cc692fc3ca9acbd2ec

Fix isNotMaterialTheme test not running

view details

push time in 11 days

push eventchrisbanes/accompanist

Nick Rout

commit sha c54e39fc38b5edaeb8bb2ab4f069016a616d1fcf

Improve MDC theme assertion (#33) * Update AGP to 4.2.0-alpha02 * Improve MDC theme assertion

view details

Chris Banes

commit sha 3509f8e54c5cb21f9e2f164942bbc602c22bd0fe

Merge branch 'main' into snapshot

view details

push time in 11 days

push eventchrisbanes/tivi

Chris Banes

commit sha d3d11aa8268c2951e8c22136ec465ba4741d050b

Extract settings to new module

view details

Chris Banes

commit sha 19290d38b802736e0aa0b05ca94bd15a8d20fa20

Collapse to single Activity

view details

Chris Banes

commit sha 41167b8b227b46229e4770bae3da3e9098f5a647

Move account to navigation graph Also fixed logging out not removing the user data profile info

view details

Chris Banes

commit sha c918b2560bbfc5e8078dc754b7ed0dd6fabab8a0

Use Insetter's new AUTO consumption setting

view details

Chris Banes

commit sha 441ecf54e0f8a37ca7b69de5478eb701609ed338

Move back to using asFlow() for collections

view details

Chris Banes

commit sha e83c8f5a7cb52f37c4606f7d28da1e81da9badf1

Fix followed and watched lists not refreshing

view details

Chris Banes

commit sha 779e8fc9b3f914a8c9bfdbb6723531353946bdd1

Remove AppNavigator.openAccount()

view details

Chris Banes

commit sha 863e5015098207fd27a052d4b6e920d87a502fc1

Use explicit ConstraintLayout dep

view details

push time in 11 days

delete branch chrisbanes/tivi

delete branch : cb/tidy-up

delete time in 11 days

PR merged chrisbanes/tivi

Various small tidy-ups cla: yes
  • Moved settings to a new module
  • Moved to a single activity (from 2)
  • Upgraded to Insetter v0.3.0
  • Fixed followed and watched shows not refreshing
+424 -419

0 comment

52 changed files

chrisbanes

pr closed time in 11 days

push eventchrisbanes/tivi

Chris Banes

commit sha 1d7056b21f0752fc4638228fe9d02fec392a9524

Fix popular shows crashing when opened

view details

Chris Banes

commit sha 2ee667a6631f7b5bd1f6073bc26f70dc4c7f46df

Remove Proguard kotlin-reflect config

view details

Chris Banes

commit sha 3d97004e94c991db265f37d7153efba6b58484b0

Fix remaining interactors I missed some in the migration of where the dispatcher is set

view details

Chris Banes

commit sha 9e88f438a813175fd335526b08f87842cab0d98b

Stop observing oberservers on io dispatcher Room already observes the database on a background thread so there's no point jumping onto the io dispatcher just to jump back off it.

view details

Chris Banes

commit sha 883fb4f7d83339f4a28785b92fc8d293ec5a2249

Use yield() and ensureActive() Ensures that the long running interactors will cancel cooperatively.

view details

Chris Banes

commit sha 7a38cc7078c0a14decdc2647f441f5126ca839ef

Remove explicit Store scope

view details

Chris Banes

commit sha 21982038de9199dd669f40cd85a27599c117ab1d

Update our use of Coroutines - Updated to Coroutines 1.3.7 - Replaced all usage of ConflatedBroadcastChannel with StateFlow - Started consuming `action` Channels via consumeAsFlow() to ensure the channels get closed. - Started checking for closed channels before `offer()`ing - Tidied up some of the internals of ReduxViewModel since we can now rely on `StateFlow`

view details

Chris Banes

commit sha 9475312285808077a3453309893917900968cf44

Stop fetching show data from TMDb We drop most information on the floor and it's a waste of network bandwidth. The only thing we use from it is the network logo path, which we can do in a better later.

view details

Chris Banes

commit sha 8974f9cf1eaafb313d126d05b73a58f49a733b37

Stop requesting all images from TMDb We only use the single poster + backdrop so there's little point currently requesting them all.

view details

Chris Banes

commit sha 974eb77cb946e9158ff9cacc5f087f8610be753f

Lazily fetch findHighestRatedPoster() Currently we call findHighestRatedPoster() a lot, which does quite a lot of work. This commit does 2 things: 1) Simplify findHighestRatedPoster() and findHighestRatedBackdrop() when there is <= 1 images. 2) Start lazily caching the result in the entity classes.

view details

Chris Banes

commit sha bcefed46f232c33a273d9230d8b8d2a53edba076

Fix related shows not loading correctly

view details

Chris Banes

commit sha 6d233408bb9e463c6ab10daf374b122f9482b352

Tidy up show details

view details

Chris Banes

commit sha a5ac9ba5f4f2fdd24db411f15db4090e9835cae9

Try using ConstraintLayout in show details

view details

push time in 11 days

push eventchrisbanes/tivi

Chris Banes

commit sha 0c7614e13ebd090e1a85df39cb08b5324d699442

Use explicit ConstraintLayout dep

view details

push time in 11 days

PR opened chrisbanes/tivi

Various small tidy-ups
  • Moved settings to a new module
  • Moved to a single activity (from 2)
  • Upgraded to Insetter v0.3.0
  • Fixed followed and watched shows not refreshing
+424 -419

0 comment

52 changed files

pr created time in 11 days

push eventchrisbanes/tivi

Chris Banes

commit sha 8a264f610b3ca508b76d12cdac5f7539873971a0

Move account to navigation graph Also fixed logging out not removing the user data profile info

view details

Chris Banes

commit sha 33dc33bbf15cbaf04abeec3ffd461affed236d27

Use Insetter's new AUTO consumption setting

view details

Chris Banes

commit sha 5d73532a82140b723964a7f4b021f0add4e49b34

Move back to using asFlow() for collections

view details

Chris Banes

commit sha 2f4b340be7a1d41c94ce0af7e888ec313d87addb

Fix followed and watched lists not refreshing

view details

Chris Banes

commit sha 86f3b3c4fea6b2f7b4c485b4255e4f44e25a6b07

Remove AppNavigator.openAccount()

view details

push time in 11 days

push eventchrisbanes/accompanist

Nick Rout

commit sha c54e39fc38b5edaeb8bb2ab4f069016a616d1fcf

Improve MDC theme assertion (#33) * Update AGP to 4.2.0-alpha02 * Improve MDC theme assertion

view details

push time in 11 days

PR merged chrisbanes/accompanist

Improve MDC theme assertion cla: yes

This improves the check that requires the context is using a Theme.MaterialComponents.* theme. MDC introduced a particular attr for this: isMaterialTheme.

I considered using ThemeEnforcement#obtainStyledAttributes (which makes use of this attr) but it's quite verbose and is currently restricted to the library group.

A basic test for this was added.

Note: AGP was also updated to 4.2.0-alpha02.

+41 -2

2 comments

7 changed files

ricknout

pr closed time in 11 days

pull request commentchrisbanes/accompanist

Improve MDC theme assertion

Weird, just tweaked the protected branch setup. Looks like it's working now!

ricknout

comment created time in 11 days

push eventchrisbanes/insetter

Chris Banes

commit sha 518592f7b549a4da23624f2bb2b88ecb077d7a50

Tweak build workflow triggers

view details

push time in 11 days

push eventchrisbanes/insetter

Chris Banes

commit sha 98fc0c36571f6d783663107f73f8596570a4fcdb

Add new consumption options Added a new option, which allows selective consumption based on the other parameters to Insetter.Builder.

view details

push time in 11 days

delete branch chrisbanes/insetter

delete branch : cb/selective-consumption

delete time in 11 days

PR merged chrisbanes/insetter

Add new consumption options cla: yes

Added a new option, which allows selective consumption based on the other parameters to Insetter.Builder.

+85 -24

0 comment

1 changed file

chrisbanes

pr closed time in 11 days

PR opened chrisbanes/insetter

Add new consumption options

Added a new option, which allows selective consumption based on the other parameters to Insetter.Builder.

+85 -24

0 comment

1 changed file

pr created time in 11 days

create barnchchrisbanes/insetter

branch : cb/selective-consumption

created branch time in 11 days

create barnchchrisbanes/tivi

branch : cb/tidy-up

created branch time in 11 days

push eventchrisbanes/tivi

Chris Banes

commit sha bcefed46f232c33a273d9230d8b8d2a53edba076

Fix related shows not loading correctly

view details

push time in 12 days

more