I am using Jetpack Compose for developing one app which contains multiple screens and we can navigate to them using Navigation Drawer.
How can I put my navigation drawer below TopAppBar ( or Toolbar). Below is the gif for the issue:
Below is the composable function for my screen:
fun Home(navController: NavHostController) {
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope()
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
buildAnnotatedString {
append("Hello World")
pushStyle(SpanStyle(fontWeight = FontWeight.Medium, fontSize = 12.sp))
append("\nhelloworld#gmail.com")
pushStyle(SpanStyle(fontWeight = FontWeight.Light, fontSize = 10.sp))
}
)
},
navigationIcon = {
IconButton(onClick = {
coroutineScope.launch {
scaffoldState.drawerState.open()
}
}) {
Icon(
imageVector = Icons.Outlined.Menu,
contentDescription = null
)
}
}
)
},
drawerContent = {},
scaffoldState = scaffoldState,
drawerContentColor = MaterialTheme.colors.onBackground,
content = {
HomeScreenContent(scaffoldState, navController)
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text(text = "Compose") },
onClick = { navController.navigate("CreateMsg") },
modifier = Modifier.padding(0.dp),
icon = {
Icon(
imageVector = Icons.Outlined.Create,
contentDescription = null,
tint = Color.White,
)
},
shape = CircleShape,
backgroundColor = MaterialTheme.colors.primary
)
}
)
}
If you want it under your TopAppbar put TopAppbar and Scaffold inside a column as
Column(modifier=Modifier.fillMaxSize()){
TopAppbar()
Scaffold()
}
If you want your NavigationDrawer below TopAppbar put them inside a Box with
Box(modifier=Modifier.fillMaxSize()){
Scaffold()
TopAppbar()
}
And give your HomeContent topPadding as big as your TopAAppbar's height.
First one
#Composable
private fun MyComposable() {
val scaffoldState = rememberScaffoldState()
Column(modifier = Modifier.fillMaxSize()) {
val coroutineScope = rememberCoroutineScope()
TopAppBar(
modifier = Modifier.fillMaxWidth(),
title = {
Text(
buildAnnotatedString {
append("Hello World")
pushStyle(SpanStyle(fontWeight = FontWeight.Medium, fontSize = 12.sp))
append("\nhelloworld#gmail.com")
pushStyle(SpanStyle(fontWeight = FontWeight.Light, fontSize = 10.sp))
}
)
},
navigationIcon = {
IconButton(onClick = {
coroutineScope.launch {
scaffoldState.drawerState.open()
}
}) {
Icon(
imageVector = Icons.Outlined.Menu,
contentDescription = null
)
}
}
)
Scaffold(
modifier = Modifier
.fillMaxSize(),
drawerContent = {},
scaffoldState = scaffoldState,
drawerContentColor = MaterialTheme.colors.onBackground,
content = {
HomeScreenContent(scaffoldState)
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text(text = "Compose") },
onClick = { },
modifier = Modifier.padding(0.dp),
icon = {
Icon(
imageVector = Icons.Outlined.Create,
contentDescription = null,
tint = Color.White,
)
},
shape = CircleShape,
backgroundColor = MaterialTheme.colors.primary
)
}
)
}
}
#Composable
fun HomeScreenContent(scaffoldState: ScaffoldState) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Red)
) {
Text("HOME CONTENT", fontSize = 30.sp, color = Color.White)
}
}
Second one
#Composable
private fun MyComposable() {
val scaffoldState = rememberScaffoldState()
Box(modifier = Modifier.fillMaxSize()) {
val coroutineScope = rememberCoroutineScope()
Scaffold(
modifier = Modifier
.fillMaxSize(),
drawerContent = {},
scaffoldState = scaffoldState,
drawerContentColor = MaterialTheme.colors.onBackground,
content = {
HomeScreenContent(scaffoldState, 56.dp)
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text(text = "Compose") },
onClick = { },
modifier = Modifier.padding(0.dp),
icon = {
Icon(
imageVector = Icons.Outlined.Create,
contentDescription = null,
tint = Color.White,
)
},
shape = CircleShape,
backgroundColor = MaterialTheme.colors.primary
)
}
)
TopAppBar(
modifier = Modifier.fillMaxWidth(),
title = {
Text(
buildAnnotatedString {
append("Hello World")
pushStyle(SpanStyle(fontWeight = FontWeight.Medium, fontSize = 12.sp))
append("\nhelloworld#gmail.com")
pushStyle(SpanStyle(fontWeight = FontWeight.Light, fontSize = 10.sp))
}
)
},
navigationIcon = {
IconButton(onClick = {
coroutineScope.launch {
scaffoldState.drawerState.open()
}
}) {
Icon(
imageVector = Icons.Outlined.Menu,
contentDescription = null
)
}
}
)
}
}
#Composable
fun HomeScreenContent(scaffoldState: ScaffoldState, topPadding:Dp) {
Column(
modifier = Modifier
.padding(top= topPadding)
.fillMaxSize()
.background(Color.Red)
) {
Text("HOME CONTENT", fontSize = 30.sp, color = Color.White)
}
}
56.dp is default size for TopAppBar on my device. If you have custom height you need to get it via Modifier.onSizeChanged{} Modifier that set to TopAppBar.
Related
After data change in Cloud Firestore database, such as new item, or item's read value the Jetpack Compose UI won't recompose, even though i provided list as mutableState.
Getting the items from Firebase (viewModel)
private val _booksFromFB = mutableStateOf(listOf<BookFB>())
val booksFromFB = _booksFromFB
init {
getBooksFromFB()
}
fun getBooksFromFB() {
viewModelScope.launch {
_isLoading.value = true
_booksFromFB.value = repository.getBooksFromFB().data!!
if (_booksFromFB.value.isNotEmpty())
_isLoading.value = false
}
}
Getting the items from Firebase (repository)
override suspend fun getBooksFromFB(): Resource<List<BookFB>> {
return try {
Resource.Loading(true)
val response = queryBook.get().await().documents.map { documentSnapshot ->
documentSnapshot.toObject(BookFB::class.java)!!
}
if (response.isNotEmpty())
Resource.Loading(false)
Resource.Success(response)
} catch (exception: FirebaseFirestoreException) {
Resource.Error(exception.message.toString())
}
}
Getting the list and use it in UI (in LazyColumn)
val userBooks = viewModel.booksFromFB.value.filter {
it.userId == currentUser?.uid.toString()
}
Further usage of the userBooks (LazyColum included)
CurrentlyReadingSection(
context = context,
navController = navController,
userBooks = userBooks.filter { it.read == false },
commonViewModel = commonViewModel
)
CurrentlyReadingSection
LazyRow() {
items(userBooks) { book ->
BookRow(
modifier = Modifier.padding(4.dp),
onItemClicked = { }
) {
val isExpanded = remember {
mutableStateOf(false)
}
val isRead = rememberSaveable {
mutableStateOf(false)
}
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.mBackgroundSec),
contentAlignment = BottomCenter
) {
Row {
AnimatedVisibility(visible = isExpanded.value) {
Box(
modifier = Modifier
.fillMaxSize()
.clickable {
isExpanded.value = !isExpanded.value
},
contentAlignment = Center
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
MyButton(
modifier = Modifier
.fillMaxWidth(0.7f)
.fillMaxHeight(0.2f)
.clip(RoundedCornerShape(12.dp)),
text = "READ",
fontSize = 12,
contentPadding = 8
) {
isRead.value = !isRead.value
isExpanded.value = !isExpanded.value
viewModel.updateBook(context = context, book = book, isRead = isRead.value)
}
Spacer(modifier = Modifier.height(4.dp))
MyButton(
modifier = Modifier
.fillMaxWidth(0.7f)
.fillMaxHeight(0.24f)
.clip(RoundedCornerShape(12.dp)),
text = "RATE",
fontSize = 12,
contentPadding = 8
) {
commonViewModel.currentBook.value = book
navController.navigate(Screen.Rate.route)
}
}
}
}
AsyncImage(
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(12.dp))
.clickable { isExpanded.value = !isExpanded.value },
model = ImageRequest.Builder(context)
.data(
if (isValid(book.image))
book.image
else
com.example.read.R.drawable.imagenotfound
)
.crossfade(true)
.build(),
contentScale = ContentScale.FillBounds,
contentDescription = "Book image"
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.1f)
.background(if (book.read!!) AppColors.mGreen else AppColors.mRed)
)
}
}
}
}
BookRow
#Composable
fun BookRow(
modifier: Modifier = Modifier,
book: BookFB = BookFB(),
category: MyCategory = MyCategory("", ""),
shapeDp: Int = 12,
heightSize: Int = 170,
widthSize: Int = 120,
isForYou: Boolean = false,
isYourCollection: Boolean = false,
onItemClicked: () -> Unit = {},
content: #Composable() () -> Unit,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = if (isYourCollection) Modifier.widthIn(max = widthSize.dp) else Modifier.fillMaxWidth()
) {
Surface(
shape = RoundedCornerShape(shapeDp.dp),
elevation = 4.dp,
modifier = modifier.border(2.dp, color = Color.Black, shape = RoundedCornerShape(shapeDp.dp))
) {
Box(
modifier = Modifier
.clickable {
onItemClicked()
}
.height(heightSize.dp)
.width(widthSize.dp)
.background(AppColors.mBackgroundSec))
{
content()
}
}
if (isYourCollection) {
Text(
modifier = Modifier.padding(top = 4.dp),
text = book.title.toString(),
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic,
fontSize = 14.sp,
color = AppColors.mTextWhite,
textAlign = TextAlign.Center,
maxLines = 3,
overflow = TextOverflow.Clip
)
}
if (isForYou) {
Text(
modifier = Modifier.padding(top = 4.dp),
text = category.name,
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic,
fontSize = 18.sp,
color = AppColors.mTextWhite,
textAlign = TextAlign.Center,
maxLines = 3,
overflow = TextOverflow.Clip
)
}
}
}
Progress Bar in Home Screen
if (viewModel.isLoading.value) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(color = AppColors.mMain)
}
} else {
content..
}
It's not recognizing the parameter and invocations related to the drawer
#Composable
fun PantallaPrincipal(userViewModel: UserViewModel) {
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState(rememberDrawerState(initialValue = DrawerValue.Closed))
val scope= rememberCoroutineScope()
Scaffold(scaffoldState = scaffoldState,
topBar= { TopBar(scope,scaffoldState=scaffoldState)},
bottomBar= { BottomBar(navController=navController)},
drawerContent = { Drawer(scope=scope,scaffoldState=scaffoldState,navController=navController)},
floatingActionButton = {FloatingBottom(navController=navController)}
){
Navigation(navController = navController, userViewModel = userViewModel)
}
}
#Composable
fun DrawerItem (item: navigationDrawer, selected:Boolean, onclick:(navigationDrawer)->Unit) {
val itemBackground=if(selected) Color.Gray else Color.Transparent
Row(modifier = Modifier
.fillMaxWidth()
.height(45.dp)
.background(itemBackground)
.padding(10.dp)
.clickable { onclick(item) },
verticalAlignment = Alignment.CenterVertically
) {
Image(
imageVector = item.icono,
contentDescription = item.texto,
colorFilter = ColorFilter.tint(Color.Black),
modifier = Modifier
.height(35.dp)
.width(35.dp)
)
Spacer(modifier = Modifier.width(7.dp))
Text(text = item.texto,
fontSize = 18.sp,
color = Color.Black
)
}
}
#Composable
fun Drawer(scope: CoroutineScope, scaffoldState: ScaffoldState, navController: NavHostController) {
val listItems = listOf<navigationDrawer>(navigationDrawer.Inicio, navigationDrawer.Favoritos, navigationDrawer.Perfil)
val rutaActual = navController.currentBackStackEntry?.destination?.route
Column(modifier = Modifier
.background(Color.White)
){
Image(imageVector = Icons.Default.Face,
contentDescription = "logo",
modifier = Modifier
.background(Color.White)
.height(100.dp)
.fillMaxWidth()
.padding(10.dp)
)
Spacer(modifier = Modifier
.fillMaxWidth()
.height(5.dp)
)
listItems.forEach{
DrawerItem(item = it,
selected = (rutaActual==it.ruta),
onclick = {navController.navigate(it.ruta)
scope.launch {scaffoldState.drawerState.close()}
})
}
}
}
Important: You must use scaffold as ModalNavigationDrawer content.
Material 3 Example - Navigation Drawer with scaffold in compose
val scope = rememberCoroutineScope()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val items = listOf(Icons.Default.Close, Icons.Default.Clear, Icons.Default.Call)
val selectedItem = remember { mutableStateOf(items[0]) }
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Spacer(Modifier.height(12.dp))
items.forEach { item ->
NavigationDrawerItem(
icon = { Icon(item, contentDescription = null) },
label = { Text(item.name) },
selected = item == selectedItem.value,
onClick = {
scope.launch { drawerState.close() }
selectedItem.value = item
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
},
content = {
Scaffold(
topBar = { TopBar(preesOnDrawerIcon = { scope.launch { drawerState.open() } }) },
bottomBar = {},
snackbarHost = {},
content = {},
)
}
)
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun TopBar(pressOnDrawer: () -> Unit){
...
}
In Material 3 it's called ModalNavigationDrawer. You can use it using a ModalDrawerSheet and ModalDrawerItems like in this example taken from the docs:
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Spacer(Modifier.height(12.dp))
items.forEach { item ->
NavigationDrawerItem(
icon = { Icon(item, contentDescription = null) },
label = { Text(item.name) },
selected = item == selectedItem.value,
onClick = {
scope.launch { drawerState.close() }
selectedItem.value = item
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
},
val languages = listOf(
"en" to "English",
"vi" to "VietNam",
)
#Composable
fun LoginScreen(
navController: NavController?,
viewModel: LoginViewModel = hiltViewModel()
) {
val currentLanguageIndex = viewModel.language.observeAsState().value ?: 0
SetLanguage(currentLanguageIndex)
Scaffold() {
MainUI(navController = navController, viewModel = viewModel,index= currentLanguageIndex)
}
}
#Composable
fun SetLanguage(languageIndex: Int) {
val locale = Locale(if (languageIndex == 0) "en" else "vi")
val configuration = LocalConfiguration.current
configuration.setLocale(locale)
val resources = LocalContext.current.resources
resources.updateConfiguration(configuration, resources.displayMetrics)
}
#OptIn(ExperimentalComposeUiApi::class)
//#Preview(device = Devices.AUTOMOTIVE_1024p)
#Composable
fun MainUI(navController: NavController?, viewModel: LoginViewModel,index : Int) {
val keyboardController = LocalSoftwareKeyboardController.current
val image = painterResource(id = R.drawable.ic_login_img)
var emailValue by remember { mutableStateOf("") }
var passwordValue by remember { mutableStateOf("") }
val passwordVisibility = remember { mutableStateOf(false) }
val focusRequesterEmail = remember { FocusRequester() }
val focusRequesterPassword = remember { FocusRequester() }
val scrollState = rememberScrollState()
val checkState = remember {
mutableStateOf(true)
}
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = Color.White),
horizontalAlignment = Alignment.End
) {
// DropdownDemo(viewModel = viewModel,index = index)
val scope = rememberCoroutineScope()
var expanded by remember { mutableStateOf(false) }
var selectedIndex by remember { mutableStateOf(index) }
Box(
modifier = Modifier
.width(150.dp)
.padding(top = 16.dp, end = 8.dp)
.wrapContentSize(Alignment.TopEnd)
) {
DropDownLabel(languages[selectedIndex].second, onClick = {
expanded = true
})
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.width(150.dp)
) {
languages.forEachIndexed { index, s ->
DropdownMenuItem(
onClick = {
selectedIndex = index
expanded = false
scope.launch {
viewModel.saveLocale(index)
}
},
) {
DropDownLabel(s.second, onClick = {
selectedIndex = index
expanded = false
scope.launch {
viewModel.saveLocale(index)
}
})
}
}
}
}
Spacer(modifier = Modifier.height(20.dp))
Column(
modifier = Modifier
.fillMaxSize()
.scrollable(state = scrollState, orientation = Orientation.Vertical),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.height(70.dp)
.width(140.dp),
contentAlignment = Alignment.TopCenter
) {
Image(painter = image, contentDescription = null)
}
Spacer(modifier = Modifier.height(19.dp))
Column(
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.hello),
style = MaterialTheme.typography.h4,
)
Spacer(modifier = Modifier.height(10.dp))
Text(
text = stringResource(R.string.enter_credentials_msg),
style = MaterialTheme.typography.body2,
fontSize = 14.sp
)
Spacer(modifier = Modifier.padding(10.dp))
Column(
modifier = Modifier
.fillMaxWidth(0.6f)
.padding(
start = 16.dp,
end = 16.dp
),
horizontalAlignment = Alignment.CenterHorizontally
) {
OutlinedTextField(
value = emailValue,
onValueChange = { emailValue = it },
label = { Text(text = stringResource(id = R.string.email),
style = MaterialTheme.typography.body2.copy(color = Gray1)) },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
),
placeholder = { Text(text = stringResource(id = R.string.email),
style = MaterialTheme.typography.body2.copy(color = Gray1)) },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester = focusRequesterEmail),
colors = TextFieldDefaults.outlinedTextFieldColors(
unfocusedBorderColor = Gray2
),
keyboardActions = KeyboardActions(
onNext = {
CoroutineScope(Default).launch {
keyboardController?.hide()
delay(400)
focusRequesterPassword.requestFocus()
}
}
)
)
Spacer(modifier = Modifier.padding(5.dp))
OutlinedTextField(
value = passwordValue,
colors = TextFieldDefaults.outlinedTextFieldColors(
unfocusedBorderColor = Gray2
),
onValueChange = { passwordValue = it },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
trailingIcon = {
IconButton(onClick = {
passwordVisibility.value = !passwordVisibility.value
}) {
Icon(
painter = painterResource(id = if (passwordVisibility.value) R.drawable.ic_baseline_visibility_24 else R.drawable.ic_baseline_visibility_off_24),
contentDescription = "Visibility button",
)
}
},
label = { Text(stringResource(id = R.string.password),
style = MaterialTheme.typography.body2.copy(color = Gray1)) },
placeholder = { Text(text = stringResource(id = R.string.password),
style = MaterialTheme.typography.body2.copy(color = Gray1)) },
singleLine = true,
visualTransformation = if (passwordVisibility.value) VisualTransformation.None
else PasswordVisualTransformation(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester = focusRequesterPassword),
keyboardActions = KeyboardActions(
onDone = { keyboardController?.hide() }
),
)
Spacer(modifier = Modifier.padding(25.dp))
Row(
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier.width(intrinsicSize = IntrinsicSize.Max),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = checkState.value, onCheckedChange = {
checkState.value = it
})
Text(
stringResource(id = R.string.remember_me),
maxLines = 1,
style = MaterialTheme.typography.body2.copy(color = Gray1)
)
}
Box(
contentAlignment = Alignment.CenterEnd,
modifier = Modifier.fillMaxWidth()
) {
OutlinedButton(
onClick = {
// TODO
},
border = BorderStroke(0.5.dp, BlueLight),
shape = RoundedCornerShape(8.dp)
) {
Text(
text = stringResource(id = R.string.sign_in),
fontSize = 20.sp,
style = MaterialTheme.typography.button.copy(color = BlueLight)
)
}
}
}
}
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 8.dp),
contentAlignment = Alignment.BottomCenter
) {
Text(
text = stringResource(id = R.string.skytech_software_solutions_pvt_ltd),
style = MaterialTheme.typography.caption.copy(color = Gray1),
)
}
}
}
}
#Composable
fun DropdownDemo(viewModel: LoginViewModel,index: Int) {
val scope = rememberCoroutineScope()
var expanded by remember { mutableStateOf(false) }
var selectedIndex by remember { mutableStateOf(index) }
Box(
modifier = Modifier
.width(150.dp)
.padding(top = 16.dp, end = 8.dp)
.wrapContentSize(Alignment.TopEnd)
) {
DropDownLabel(languages[selectedIndex].second, onClick = {
expanded = true
})
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.width(150.dp)
) {
languages.forEachIndexed { index, s ->
DropdownMenuItem(
onClick = {
selectedIndex = index
expanded = false
scope.launch {
viewModel.saveLocale(index)
}
},
) {
DropDownLabel(s.second, onClick = {
selectedIndex = index
expanded = false
scope.launch {
viewModel.saveLocale(index)
}
})
}
}
}
}
}
#Composable
fun DropDownLabel(labelName: String, onClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
) {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_language_24),
contentDescription = "language"
)
Spacer(modifier = Modifier.width(10.dp))
Text(
labelName, modifier = Modifier
.fillMaxWidth()
)
}
}
when we change the language UI does not update some of text like sign in button and Edit text. but normal text are changing. how do we over come this situation. I have used mutable live data for the viewModel to UI. when we change language and kill the application and restart, then update all the labels.
I have made datePicker composeable, when i call that composable in on click of Icon , it show
#Composable invocation can happen only within the context of composable
I simply want to open Date picker while on clicking and on ok press My Text is updated .
My DatePicker
#Composable
fun DatePicker(onDateSelected: (LocalDate) -> Unit, onDismissRequest: () -> Unit) {
val selDate = remember { mutableStateOf(LocalDate.now()) }
Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) {
Column(
modifier = Modifier
.wrapContentSize()
.background(
color = MaterialTheme.colors.surface,
shape = RoundedCornerShape(size = 16.dp)
)
) {
Column(
Modifier
.defaultMinSize(minHeight = 72.dp)
.fillMaxWidth()
.background(
color = MaterialTheme.colors.primary,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
)
.padding(16.dp)
) {
Text(
text = "Select date".uppercase(),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onPrimary
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = selDate.value.format(DateTimeFormatter.ofPattern("MMM d, YYYY")),
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.onPrimary
)
Spacer(modifier = Modifier.size(16.dp))
}
CustomCalendarView(onDateSelected = {
selDate.value = it
})
Spacer(modifier = Modifier.size(8.dp))
Row(
modifier = Modifier
.align(Alignment.End)
.padding(bottom = 16.dp, end = 16.dp)
) {
TextButton(
onClick = onDismissRequest
) {
//TODO - hardcode string
Text(
text = "Cancel",
style = MaterialTheme.typography.button,
color = MaterialTheme.colors.onPrimary
)
}
TextButton(
onClick = {
onDateSelected(selDate.value)
onDismissRequest()
}
) {
//TODO - hardcode string
Text(
text = "OK",
style = MaterialTheme.typography.button,
color = MaterialTheme.colors.onPrimary
)
}
}
}
}
}
#Composable
fun CustomCalendarView(onDateSelected: (LocalDate) -> Unit) {
AndroidView(
modifier = Modifier.wrapContentSize(),
factory = { context ->
CalendarView(ContextThemeWrapper(context, R.style.CalenderViewCustom))
},
update = { view ->
view.setOnDateChangeListener { _, year, month, dayOfMonth ->
onDateSelected(
LocalDate
.now()
.withMonth(month + 1)
.withYear(year)
.withDayOfMonth(dayOfMonth)
)
}
}
)
}
ViewModel Class :
#HiltViewModel
class AddWeightViewModel #Inject() constructor(private val repository: WeightRepository)
:
ViewModel() {
val weightState = mutableStateOf("")
val dateState = mutableStateOf("")
fun onWeightChange(weight: String) {
weightState.value = weight
}
fun onDateChange(date : String) {
dateState.value = date
}
fun addWeight() = viewModelScope.launch {
val weight = Weight( 0, weightState.value , dateState.value )
repository.addWeight(weight)
}
}
MyWeightScreen :
#ExperimentalComposeUiApi
#Composable
fun AddWeightScreen(navController: NavController, viewModel: AddWeightViewModel) {
Column(modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colors.primary)
.padding(bottom = 56.dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Cancel", fontSize = 16.sp, modifier = Modifier
.padding(16.dp)
.align(alignment = Alignment.Start)
.clickable {
navController.navigate(Screens.MyWeight.route)
}, color = background)
WeightData(viewModel)
Button(
onClick = {
viewModel.addWeight()
navController.navigate(Screens.MyWeight.route)
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = background,
contentColor = textDark)
) {
Text(text = "Done", fontSize = 18.sp)
}
}
}
#ExperimentalComposeUiApi
#Composable
fun WeightData(viewModel: AddWeightViewModel) {
val selDate = LocalDate.now()
val date = viewModel.dateState.value
val keyboardController = LocalSoftwareKeyboardController.current
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(22.dp),
modifier = Modifier
.drawBehind {
drawCircle(
color = textColor,
radius = 450f,
style = Stroke(width = 1.dp.toPx())
)
}
) {
Text(text = "Enter Weight", color = Color.White.copy(alpha = .8f))
TextField(
value = viewModel.weightState.value,
onValueChange = { viewModel.onWeightChange(it) },
modifier = Modifier.background(color = MaterialTheme.colors.primary),
keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number,imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
singleLine = true)
Row(verticalAlignment = Alignment.CenterVertically , horizontalArrangement = Arrangement.Center ) {
Icon(imageVector = Icons.Default.DateRange,
contentDescription = null,
modifier = Modifier.clickable { DatePicker(onDateSelected = ) {
}})
Spacer(modifier = Modifier.width(20.dp))
DatePicker(onDateSelected = {selDate}) {
viewModel.onDateChange(date)
}
}
}
This is what i did .
This is my Modal drawer when open
Code to implement it
#ExperimentalMaterialApi
#Composable
private fun TutorialContent() {
ModalDrawerComponent()
}
#ExperimentalMaterialApi
#Composable
private fun ModalDrawerComponent() {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
val openDrawer: () -> Unit = { coroutineScope.launch { drawerState.open() } }
val closeDrawer: () -> Unit = { coroutineScope.launch { drawerState.close() } }
var selectedIndex by remember { mutableStateOf(0) }
ModalDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerContentHeader()
Divider()
ModelDrawerContentBody(
selectedIndex,
onSelected = {
selectedIndex = it
},
closeDrawer = closeDrawer
)
},
content = {
Column(modifier = Modifier.fillMaxSize()) {
ModalDrawerTopAppBar(openDrawer)
ModalContent(openDrawer)
}
}
)
}
#Composable
fun ModalDrawerTopAppBar(openDrawer: () -> Unit) {
TopAppBar(
title = {
Text("ModalDrawer")
},
navigationIcon = {
IconButton(onClick = openDrawer) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null
)
}
},
actions = {}
)
}
#Composable
fun ModalDrawerContentHeader() {
Column(
modifier = Modifier
.fillMaxWidth()
.height(180.dp)
.padding(20.dp)
) {
Image(
modifier = Modifier
.size(60.dp)
.clip(CircleShape),
painter = painterResource(id = R.drawable.avatar_1_raster),
contentDescription = null
)
Spacer(modifier = Modifier.weight(1f))
Text(text = "Android", fontWeight = FontWeight.Bold, fontSize = 22.sp)
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(text = "android#android.com")
Spacer(modifier = Modifier.weight(1f))
Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = null)
}
}
}
}
#Composable
fun ModelDrawerContentBody(
selectedIndex: Int,
onSelected: (Int) -> Unit,
closeDrawer: () -> Unit
) {
Column(modifier = Modifier.fillMaxWidth()) {
modalDrawerList.forEachIndexed { index, pair ->
val label = pair.first
val imageVector = pair.second
DrawerButton(
icon = imageVector,
label = label,
isSelected = selectedIndex == index,
action = {
onSelected(index)
}
)
}
}
}
#ExperimentalMaterialApi
#Composable
fun ModalContent(openDrawer: () -> Unit) {
LazyColumn {
items(userList) { item: String ->
ListItem(
modifier = Modifier.clickable {
openDrawer()
},
icon = {
Image(
modifier = Modifier
.size(40.dp)
.clip(CircleShape),
painter = painterResource(id = R.drawable.avatar_1_raster),
contentDescription = null
)
},
secondaryText = {
Text(text = "Secondary text")
}
) {
Text(text = item, fontSize = 18.sp)
}
}
}
}
val modalDrawerList = listOf(
Pair("My Files", Icons.Filled.Folder),
Pair("Shared with Me", Icons.Filled.People),
Pair("Starred", Icons.Filled.Star),
Pair("Recent", Icons.Filled.AccessTime),
Pair("Offline", Icons.Filled.OfflineShare),
Pair("Uploads", Icons.Filled.Upload),
Pair("Backups", Icons.Filled.CloudUpload),
)
What i actually desire is to have transparent status over the drawer as in the image below