wip
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2026 MagicalBits
|
||||
*
|
||||
* 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 xyz.magicalbits.smsremote.components
|
||||
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.util.lerp
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* A layout that shows an icon and a text element used as the content for a FAB that extends with
|
||||
* an animation.
|
||||
*/
|
||||
@Composable
|
||||
fun AnimatingFabContent(
|
||||
icon: @Composable () -> Unit,
|
||||
text: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
extended: Boolean = true,
|
||||
) {
|
||||
val currentState = if (extended) ExpandableFabStates.Extended else ExpandableFabStates.Collapsed
|
||||
val transition = updateTransition(currentState, "fab_transition")
|
||||
|
||||
val textOpacity by transition.animateFloat(
|
||||
transitionSpec = {
|
||||
if (targetState == ExpandableFabStates.Collapsed) {
|
||||
tween(
|
||||
easing = LinearEasing,
|
||||
durationMillis = (transitionDuration / 12f * 5).roundToInt(), // 5 / 12 frames
|
||||
)
|
||||
} else {
|
||||
tween(
|
||||
easing = LinearEasing,
|
||||
delayMillis = (transitionDuration / 3f).roundToInt(), // 4 / 12 frames
|
||||
durationMillis = (transitionDuration / 12f * 5).roundToInt(), // 5 / 12 frames
|
||||
)
|
||||
}
|
||||
},
|
||||
label = "fab_text_opacity",
|
||||
) { state ->
|
||||
if (state == ExpandableFabStates.Collapsed) {
|
||||
0f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
}
|
||||
val fabWidthFactor by transition.animateFloat(
|
||||
transitionSpec = {
|
||||
if (targetState == ExpandableFabStates.Collapsed) {
|
||||
tween(
|
||||
easing = FastOutSlowInEasing,
|
||||
durationMillis = transitionDuration,
|
||||
)
|
||||
} else {
|
||||
tween(
|
||||
easing = FastOutSlowInEasing,
|
||||
durationMillis = transitionDuration,
|
||||
)
|
||||
}
|
||||
},
|
||||
label = "fab_width_factor",
|
||||
) { state ->
|
||||
if (state == ExpandableFabStates.Collapsed) {
|
||||
0f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
}
|
||||
// Deferring reads using lambdas instead of Floats here can improve performance,
|
||||
// preventing recompositions.
|
||||
IconAndTextRow(
|
||||
icon,
|
||||
text,
|
||||
{ textOpacity },
|
||||
{ fabWidthFactor },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IconAndTextRow(
|
||||
icon: @Composable () -> Unit,
|
||||
text: @Composable () -> Unit,
|
||||
opacityProgress: () -> Float, // Lambdas instead of Floats, to defer read
|
||||
widthProgress: () -> Float,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
Layout(
|
||||
modifier = modifier,
|
||||
content = {
|
||||
icon()
|
||||
Box(modifier = Modifier.graphicsLayer { alpha = opacityProgress() }) {
|
||||
text()
|
||||
}
|
||||
},
|
||||
) { measurables, constraints ->
|
||||
|
||||
val iconPlaceable = measurables[0].measure(constraints)
|
||||
val textPlaceable = measurables[1].measure(constraints)
|
||||
|
||||
val height = constraints.maxHeight
|
||||
|
||||
// FAB has an aspect ratio of 1 so the initial width is the height
|
||||
val initialWidth = height.toFloat()
|
||||
|
||||
// Use it to get the padding
|
||||
val iconPadding = (initialWidth - iconPlaceable.width) / 2f
|
||||
|
||||
// The full width will be : padding + icon + padding + text + padding
|
||||
val expandedWidth = iconPlaceable.width + textPlaceable.width + iconPadding * 3
|
||||
|
||||
// Apply the animation factor to go from initialWidth to fullWidth
|
||||
val width = lerp(initialWidth, expandedWidth, widthProgress())
|
||||
|
||||
layout(width.roundToInt(), height) {
|
||||
iconPlaceable.place(
|
||||
iconPadding.roundToInt(),
|
||||
constraints.maxHeight / 2 - iconPlaceable.height / 2,
|
||||
)
|
||||
textPlaceable.place(
|
||||
(iconPlaceable.width + iconPadding * 2).roundToInt(),
|
||||
constraints.maxHeight / 2 - textPlaceable.height / 2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class ExpandableFabStates { Collapsed, Extended }
|
||||
|
||||
private const val transitionDuration = 200
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2026 MagicalBits
|
||||
*
|
||||
* 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 xyz.magicalbits.smsremote.components
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.FirstBaseline
|
||||
import androidx.compose.ui.layout.LastBaseline
|
||||
import androidx.compose.ui.layout.LayoutModifier
|
||||
import androidx.compose.ui.layout.Measurable
|
||||
import androidx.compose.ui.layout.MeasureResult
|
||||
import androidx.compose.ui.layout.MeasureScope
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
/**
|
||||
* Applied to a Text, it sets the distance between the top and the first baseline. It
|
||||
* also makes the bottom of the element coincide with the last baseline of the text.
|
||||
*
|
||||
* _______________
|
||||
* | | ↑
|
||||
* | | | heightFromBaseline
|
||||
* |Hello, World!| ↓
|
||||
* ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||
*
|
||||
* This modifier can be used to distribute multiple text elements using a certain distance between
|
||||
* baselines.
|
||||
*/
|
||||
data class BaselineHeightModifier(
|
||||
val heightFromBaseline: Dp,
|
||||
) : LayoutModifier {
|
||||
override fun MeasureScope.measure(
|
||||
measurable: Measurable,
|
||||
constraints: Constraints,
|
||||
): MeasureResult {
|
||||
val textPlaceable = measurable.measure(constraints)
|
||||
val firstBaseline = textPlaceable[FirstBaseline]
|
||||
val lastBaseline = textPlaceable[LastBaseline]
|
||||
|
||||
val height = heightFromBaseline.roundToPx() + lastBaseline - firstBaseline
|
||||
return layout(constraints.maxWidth, height) {
|
||||
val topY = heightFromBaseline.roundToPx() - firstBaseline
|
||||
textPlaceable.place(0, topY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Modifier.baselineHeight(heightFromBaseline: Dp): Modifier = this.then(BaselineHeightModifier(heightFromBaseline))
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2026 MagicalBits
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package xyz.magicalbits.smsremote.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import xyz.magicalbits.smsremote.R
|
||||
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun JetchatAppBar(
|
||||
modifier: Modifier = Modifier,
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
onNavIconPressed: () -> Unit = { },
|
||||
title: @Composable () -> Unit,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
CenterAlignedTopAppBar(
|
||||
modifier = modifier,
|
||||
actions = actions,
|
||||
title = title,
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
JetchatIcon(
|
||||
contentDescription = stringResource(id = R.string.navigation_drawer_open),
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.clickable(onClick = onNavIconPressed)
|
||||
.padding(16.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview
|
||||
@Composable
|
||||
fun JetchatAppBarPreview() {
|
||||
JetchatTheme {
|
||||
JetchatAppBar(title = { Text("Preview!") })
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview
|
||||
@Composable
|
||||
fun JetchatAppBarPreviewDark() {
|
||||
JetchatTheme(isDarkTheme = true) {
|
||||
JetchatAppBar(title = { Text("Preview!") })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright 2026 MagicalBits
|
||||
*
|
||||
* 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 xyz.magicalbits.smsremote.components
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.windowInsetsTopHeight
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment.Companion.CenterStart
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import xyz.magicalbits.smsremote.R
|
||||
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
||||
import xyz.magicalbits.smsremote.widget.WidgetReceiver
|
||||
|
||||
@Composable
|
||||
fun JetchatDrawerContent(onChatClicked: (String) -> Unit, selectedMenu: String = "iPhone XYZ") {
|
||||
// Use windowInsetsTopHeight() to add a spacer which pushes the drawer content
|
||||
// below the status bar (y-axis)
|
||||
Column {
|
||||
Spacer(Modifier.windowInsetsTopHeight(WindowInsets.statusBars))
|
||||
DrawerHeader()
|
||||
DividerItem()
|
||||
DrawerItemHeader("Devices")
|
||||
DeviceItem("Samsung A14", selectedMenu == "Samsung A14") {
|
||||
onChatClicked("Samsung A14")
|
||||
}
|
||||
DeviceItem("iPhone XYZ", selectedMenu == "iPhone XYZ") {
|
||||
onChatClicked("iPhone XYZ")
|
||||
}
|
||||
// DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
|
||||
if (widgetAddingIsSupported(LocalContext.current)) {
|
||||
DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
|
||||
DrawerItemHeader("Settings")
|
||||
WidgetDiscoverability()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DrawerHeader() {
|
||||
Row(modifier = Modifier.padding(16.dp), verticalAlignment = CenterVertically) {
|
||||
JetchatIcon(
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.jetchat_logo),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DrawerItemHeader(text: String) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.heightIn(min = 52.dp)
|
||||
.padding(horizontal = 28.dp),
|
||||
contentAlignment = CenterStart,
|
||||
) {
|
||||
Text(
|
||||
text,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeviceItem(text: String, selected: Boolean, onChatClicked: () -> Unit) {
|
||||
val background = if (selected) {
|
||||
Modifier.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(56.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp)
|
||||
.clip(CircleShape)
|
||||
.then(background)
|
||||
.clickable(onClick = onChatClicked),
|
||||
verticalAlignment = CenterVertically,
|
||||
) {
|
||||
val iconTint = if (selected) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_jetchat),
|
||||
tint = iconTint,
|
||||
modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 16.dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (selected) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
},
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfileItem(text: String, @DrawableRes profilePic: Int?, selected: Boolean = false, onProfileClicked: () -> Unit) {
|
||||
val background = if (selected) {
|
||||
Modifier.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(56.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp)
|
||||
.clip(CircleShape)
|
||||
.then(background)
|
||||
.clickable(onClick = onProfileClicked),
|
||||
verticalAlignment = CenterVertically,
|
||||
) {
|
||||
val paddingSizeModifier = Modifier
|
||||
.padding(start = 16.dp, top = 16.dp, bottom = 16.dp)
|
||||
.size(24.dp)
|
||||
if (profilePic != null) {
|
||||
Image(
|
||||
painter = painterResource(id = profilePic),
|
||||
modifier = paddingSizeModifier.then(Modifier.clip(CircleShape)),
|
||||
contentScale = ContentScale.Crop,
|
||||
contentDescription = null,
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = paddingSizeModifier)
|
||||
}
|
||||
Text(
|
||||
text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DividerItem(modifier: Modifier = Modifier) {
|
||||
HorizontalDivider(
|
||||
modifier = modifier,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun DrawerPreview() {
|
||||
JetchatTheme {
|
||||
Surface {
|
||||
Column {
|
||||
JetchatDrawerContent({})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun DrawerPreviewDark() {
|
||||
JetchatTheme(isDarkTheme = true) {
|
||||
Surface {
|
||||
Column {
|
||||
JetchatDrawerContent({})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
private fun WidgetDiscoverability() {
|
||||
val context = LocalContext.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(56.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = {
|
||||
addWidgetToHomeScreen(context)
|
||||
}),
|
||||
verticalAlignment = CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
stringResource(id = R.string.add_widget_to_home_page),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun addWidgetToHomeScreen(context: Context) {
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val myProvider = ComponentName(context, WidgetReceiver::class.java)
|
||||
if (widgetAddingIsSupported(context)) {
|
||||
appWidgetManager.requestPinAppWidget(myProvider, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
|
||||
private fun widgetAddingIsSupported(context: Context): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||
AppWidgetManager.getInstance(context).isRequestPinAppWidgetSupported
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2026 MagicalBits
|
||||
*
|
||||
* 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 xyz.magicalbits.smsremote.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import xyz.magicalbits.smsremote.R
|
||||
|
||||
@Composable
|
||||
fun JetchatIcon(contentDescription: String?, modifier: Modifier = Modifier) {
|
||||
val semantics = if (contentDescription != null) {
|
||||
Modifier.semantics {
|
||||
this.contentDescription = contentDescription
|
||||
this.role = Role.Image
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Box(modifier = modifier.then(semantics)) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_jetchat_back),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primaryContainer,
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_jetchat_front),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2026 MagicalBits
|
||||
*
|
||||
* 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 xyz.magicalbits.smsremote.components
|
||||
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.DrawerValue.Closed
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalDrawerSheet
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
||||
|
||||
@Composable
|
||||
fun JetchatDrawer(
|
||||
drawerState: DrawerState = rememberDrawerState(initialValue = Closed),
|
||||
selectedMenu: String,
|
||||
onChatClicked: (String) -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
JetchatTheme {
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
ModalDrawerSheet(
|
||||
drawerState = drawerState,
|
||||
drawerContainerColor = MaterialTheme.colorScheme.background,
|
||||
drawerContentColor = MaterialTheme.colorScheme.onBackground,
|
||||
) {
|
||||
JetchatDrawerContent(
|
||||
onChatClicked = onChatClicked,
|
||||
selectedMenu = selectedMenu,
|
||||
)
|
||||
}
|
||||
},
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user