Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Jetchat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ project from Android Studio following the steps
This sample showcases:

* UI state management
* Integration with Architecture Components: Navigation, Fragments, LiveData, ViewModel
* Integration with Architecture Components: Navigation, Fragments, ViewModel
* Back button handling
* Text Input and focus management
* Multiple types of animations and transitions
Expand Down Expand Up @@ -81,6 +81,8 @@ Tracked in https://issuetracker.google.com/164859446

2. There are only two profiles, clicking on anybody except "me" will show the same data.

3. The app crashes if the text field is focused and the user navigates back.
https://issuetracker.google.com/165034731

## License
```
Expand Down
3 changes: 2 additions & 1 deletion Jetchat/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ android {

buildFeatures {
compose true
viewBinding true

// Disable unused AGP features
buildConfig false
Expand Down Expand Up @@ -98,7 +99,7 @@ dependencies {
implementation Libs.AndroidX.Compose.materialIconsExtended
implementation Libs.AndroidX.Compose.tooling
implementation Libs.AndroidX.Compose.runtime
implementation Libs.AndroidX.Compose.runtimeLivedata
implementation Libs.AndroidX.Compose.viewBinding

androidTestImplementation Libs.junit
androidTestImplementation Libs.AndroidX.Test.core
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2020 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
*
* https://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.compose.jetchat

import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

/**
* Used to communicate between screens.
*/
class MainViewModel : ViewModel() {

private val _drawerShouldBeOpened = MutableStateFlow(false)
val drawerShouldBeOpened: StateFlow<Boolean> = _drawerShouldBeOpened

fun openDrawer() {
_drawerShouldBeOpened.value = true
}
fun resetOpenDrawer() {
_drawerShouldBeOpened.value = false
}
}
104 changes: 53 additions & 51 deletions Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,72 +17,74 @@
package com.example.compose.jetchat

import android.os.Bundle
import android.view.Menu
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Providers
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.viewinterop.AndroidViewBinding
import androidx.core.os.bundleOf
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.navigation.NavigationView
import com.example.compose.jetchat.components.JetchatScaffold
import com.example.compose.jetchat.conversation.BackPressedDispatcherAmbient
import com.example.compose.jetchat.conversation.backPressHandler
import com.example.compose.jetchat.databinding.ContentMainBinding

/**
* Main activity for the app. Shows a drawer and a toolbar rendered with traditional Views, for now.
* Main activity for the app.
*/
class NavActivity : AppCompatActivity() {

private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var drawerLayout: DrawerLayout
// Used for navigation events between fragments.
private val viewModel: MainViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setContent {
Providers(BackPressedDispatcherAmbient provides this) {
val scaffoldState = rememberScaffoldState()

drawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_home,
R.id.nav_profile
),
drawerLayout
)
navView.setupWithNavController(navController)
}
val openDrawerEvent = viewModel.drawerShouldBeOpened.collectAsState()
if (openDrawerEvent.value) {
// Open drawer and reset state in VM.
scaffoldState.drawerState.open {
viewModel.resetOpenDrawer()
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
return true
}
// Intercepts back navigation when the drawer is open
backPressHandler(
enabled = scaffoldState.drawerState.isOpen,
onBackPressed = { scaffoldState.drawerState.close() },
highPriority = true
)

override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}

/**
* Back closes drawer if open.
*/
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
JetchatScaffold(
scaffoldState,
onChatClicked = {
findNavController(R.id.nav_host_fragment)
.popBackStack(R.id.nav_home, true)
scaffoldState.drawerState.close()
},
onProfileClicked = {
val bundle = bundleOf("userId" to it)
findNavController(R.id.nav_host_fragment).navigate(
R.id.nav_profile,
bundle
)
scaffoldState.drawerState.close()
}
) {
// Inflate the XML layout using View Binding:
AndroidViewBinding(ContentMainBinding::inflate)
}
}
}
}

/**
* Opens the drawer if present.
*
* ]TODO: Replace with compose Scaffold.
*/
fun openDrawer() {
val drawer = findViewById<DrawerLayout>(R.id.drawer_layout)
drawer?.openDrawer(GravityCompat.START)
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright 2020 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
*
* https://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.compose.jetchat.components

import androidx.annotation.DrawableRes
import androidx.compose.foundation.AmbientContentColor
import androidx.compose.foundation.Image
import androidx.compose.foundation.Text
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.preferredHeight
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.preferredWidth
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.AmbientEmphasisLevels
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideEmphasis
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import androidx.ui.tooling.preview.Preview
import com.example.compose.jetchat.R
import com.example.compose.jetchat.data.colleagueProfile
import com.example.compose.jetchat.data.meProfile
import com.example.compose.jetchat.theme.JetchatTheme

@Composable
fun ColumnScope.JetchatDrawer(onProfileClicked: (String) -> Unit, onChatClicked: (String) -> Unit) {
DrawerHeader()
Divider()
DrawerItemHeader("Chats")
ChatItem("composers", true) { onChatClicked("composers") }
ChatItem("droidcon-nyc", false) { onChatClicked("droidcon-nyc") }
DrawerItemHeader("Recent Profiles")
ProfileItem("Ali Conors (you)", meProfile.photo) { onProfileClicked(meProfile.userId) }
ProfileItem("Taylor Brooks", colleagueProfile.photo) { onProfileClicked(colleagueProfile.userId) }
}

@Composable
private fun DrawerHeader() {
Row(modifier = Modifier.padding(16.dp), verticalAlignment = CenterVertically) {
Image(
vectorResource(id = R.drawable.ic_jetchat),
modifier = Modifier.preferredSize(24.dp)
)
Image(
vectorResource(id = R.drawable.jetchat_logo),
modifier = Modifier.padding(start = 8.dp)
)
}
}
@Composable
private fun DrawerItemHeader(text: String) {
ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) {
Text(text, style = MaterialTheme.typography.caption, modifier = Modifier.padding(16.dp))
}
}

@Composable
private fun ChatItem(text: String, selected: Boolean, onChatClicked: () -> Unit) {
val background = if (selected) {
Modifier.background(MaterialTheme.colors.primary.copy(alpha = 0.08f))
} else {
Modifier
}
Row(
modifier = Modifier
.preferredHeight(48.dp)
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp)
.then(background)
.clip(MaterialTheme.shapes.medium)
.clickable(onClick = onChatClicked),
verticalAlignment = CenterVertically
) {
val mediumEmphasisOnSurface = AmbientEmphasisLevels.current.medium
.applyEmphasis(MaterialTheme.colors.onSurface)
Icon(
vectorResource(id = R.drawable.ic_jetchat),
tint = if (selected) MaterialTheme.colors.primary else mediumEmphasisOnSurface,
modifier = Modifier.padding(8.dp)
)
ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) {
Text(
text,
style = MaterialTheme.typography.body2,
color = if (selected) MaterialTheme.colors.primary else AmbientContentColor.current,
modifier = Modifier.padding(8.dp)
)
}
}
}

@Composable
private fun ProfileItem(text: String, @DrawableRes profilePic: Int?, onProfileClicked: () -> Unit) {
Row(
modifier = Modifier
.preferredHeight(48.dp)
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp)
.clip(MaterialTheme.shapes.medium)
.clickable(onClick = onProfileClicked),
verticalAlignment = CenterVertically
) {
ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) {
val widthPaddingModifier = Modifier.preferredWidth(24.dp).padding(8.dp)
if (profilePic != null) {
Image(
imageResource(id = profilePic),
modifier = widthPaddingModifier.then(Modifier.clip(CircleShape)),
contentScale = ContentScale.Crop
)
} else {
Spacer(modifier = widthPaddingModifier)
}
Text(text, style = MaterialTheme.typography.body2, modifier = Modifier.padding(8.dp))
}
}
}

@Composable
@Preview
fun DrawerPreview() {
JetchatTheme {
Surface {
Column {
JetchatDrawer({}, {})
}
}
}
}
@Composable
@Preview
fun DrawerPreviewDark() {
JetchatTheme(isDarkTheme = true) {
Surface {
Column {
JetchatDrawer({}, {})
}
}
}
}
Loading