Skip to content

Commit

Permalink
Add keyboard and mouse support to CanonicalLayouts/feed-compose
Browse files Browse the repository at this point in the history
  • Loading branch information
chikoski committed Oct 28, 2022
1 parent 39f319a commit 5a3bcae
Show file tree
Hide file tree
Showing 21 changed files with 741 additions and 143 deletions.
1 change: 1 addition & 0 deletions CanonicalLayouts/feed-compose/.gitignore
Expand Up @@ -7,6 +7,7 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea/deploymentTargetDropDown.xml
.DS_Store
/build
/captures
Expand Down
23 changes: 13 additions & 10 deletions CanonicalLayouts/feed-compose/app/build.gradle
Expand Up @@ -17,15 +17,16 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-parcelize'
}

android {
compileSdk 32
compileSdk 33

defaultConfig {
applicationId "com.example.feed_compose"
minSdk 21
targetSdk 32
targetSdk 33
versionCode 1
versionName "1.0"

Expand All @@ -52,26 +53,28 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerExtensionVersion '1.3.0'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
namespace 'com.example.feedcompose'
}

dependencies {

implementation 'androidx.core:core-ktx:1.8.0'
def compose_version = '1.3.0'
def material3_version = '1.0.0'
implementation 'androidx.core:core-ktx:1.9.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation 'androidx.compose.material3:material3:1.0.0-alpha15'
implementation 'androidx.compose.material3:material3-window-size-class:1.0.0-alpha15'
implementation "androidx.compose.material3:material3:$material3_version"
implementation "androidx.compose.material3:material3-window-size-class:$material3_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.5.1'
implementation 'androidx.navigation:navigation-compose:2.5.1'
implementation 'io.coil-kt:coil-compose:2.1.0'
implementation 'androidx.activity:activity-compose:1.6.1'
implementation 'androidx.navigation:navigation-compose:2.5.3'
implementation 'io.coil-kt:coil-compose:2.2.2'

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
Expand Down
Expand Up @@ -16,8 +16,7 @@
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.feedcompose">
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

Expand Down
Expand Up @@ -29,6 +29,9 @@ import com.example.feedcompose.ui.FeedSampleApp
import com.example.feedcompose.ui.theme.FeedComposeTheme

class MainActivity : ComponentActivity() {

private val hasHardwareKey = false

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down
@@ -0,0 +1,50 @@
/*
* Copyright 2022 The Android Open Source Project
*
* 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 com.example.feedcompose.ui.components

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.offset
import androidx.compose.material3.DropdownMenu
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp

@Composable
internal fun ContextMenu(
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(0.dp, 0.dp),
content: @Composable ColumnScope.() -> Unit = {}
) {
var isExpanded by remember { mutableStateOf(true) }
if (isExpanded) {
Box(modifier = Modifier.offset(x = offset.x, y = offset.y)) {
DropdownMenu(
expanded = isExpanded,
onDismissRequest = { isExpanded = false },
modifier = modifier
) {
content()
}
}
}
}
@@ -0,0 +1,127 @@
/*
* Copyright 2022 The Android Open Source Project
*
* 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 com.example.feedcompose.ui.components

import androidx.compose.foundation.Indication
import androidx.compose.foundation.IndicationInstance
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.Dp

class OutlinedFocusIndication(
private val shape: Shape,
private val outlineWidth: Dp,
private val outlineColor: Color
) : Indication {

@Composable
override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
val isEnabledState = interactionSource.collectIsFocusedAsState()

return remember(interactionSource) {
OutlineIndicationInstance(
shape = shape,
outlineWidth = outlineWidth,
outlineColor = outlineColor,
isEnabledState = isEnabledState
)
}
}
}

private class OutlineIndicationInstance(
private val shape: Shape,
private val outlineWidth: Dp,
private val outlineColor: Color,
isEnabledState: State<Boolean>
) : IndicationInstance {
private val isEnabled by isEnabledState

override fun ContentDrawScope.drawIndication() {
drawContent()
if (isEnabled) {
drawOutline(
outline = shape.createOutline(
size = size,
layoutDirection = layoutDirection,
density = this
),
brush = SolidColor(outlineColor),
style = Stroke(width = outlineWidth.toPx())
)
}
}
}

class HighlightIndication(
private val highlightColor: Color = Color.White,
private val alpha: Float = 0.2f,
private val isEnabled: (isFocused: Boolean, isHovered: Boolean) -> Boolean = { isFocused, isHovered ->
isFocused || isHovered
}
) : Indication {

@Composable
override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
val isFocusedState = interactionSource.collectIsFocusedAsState()
val isHoveredState = interactionSource.collectIsHoveredAsState()
return remember(interactionSource) {
HighlightIndicationInstance(
isFocusedState = isFocusedState,
isHoveredState = isHoveredState,
isEnabled = isEnabled,
highlightColor = highlightColor,
alpha = alpha
)
}
}
}

private class HighlightIndicationInstance(
val highlightColor: Color = Color.White,
val alpha: Float = 0.2f,
isFocusedState: State<Boolean>,
isHoveredState: State<Boolean>,
val isEnabled: (isFocused: Boolean, isHovered: Boolean) -> Boolean = { isFocused, isHovered ->
isFocused || isHovered
}
) : IndicationInstance {
private val isFocused by isFocusedState
private val isHovered by isHoveredState

override fun ContentDrawScope.drawIndication() {
drawContent()
if (isEnabled(isFocused, isHovered)) {
drawRect(
size = size,
color = highlightColor,
alpha = alpha
)
}
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2022 The Android Open Source Project
*
* 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 com.example.feedcompose.ui.components

import android.view.MotionEvent
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInteropFilter

@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.rightClickable(onRightClick: (x: Float, y: Float) -> Unit): Modifier =
this.pointerInteropFilter {
if (MotionEvent.BUTTON_SECONDARY == it.buttonState) {
onRightClick(it.x, it.y)
true
} else {
false
}
}

0 comments on commit 5a3bcae

Please sign in to comment.