yayan 1 年間 前
コミット
77de4d3216

+ 117 - 0
app/src/main/java/io/nexilis/alpha/ui/components/PopUpMenu.kt

@@ -0,0 +1,117 @@
+//package io.nexilis.alpha.ui.components
+//
+//import androidx.compose.animation.core.MutableTransitionState
+//import androidx.compose.foundation.layout.Box
+//import androidx.compose.runtime.Composable
+//import androidx.compose.runtime.remember
+//import androidx.compose.ui.Modifier
+//import androidx.compose.ui.platform.LocalDensity
+//import androidx.compose.ui.unit.Density
+//import androidx.compose.ui.unit.DpOffset
+//import androidx.compose.ui.unit.IntOffset
+//import androidx.compose.ui.unit.IntRect
+//import androidx.compose.ui.unit.IntSize
+//import androidx.compose.ui.unit.LayoutDirection
+//import androidx.compose.ui.unit.dp
+//import androidx.compose.ui.util.fastFirstOrNull
+//import androidx.compose.ui.util.fastMap
+//import androidx.compose.ui.window.Popup
+//import androidx.compose.ui.window.PopupPositionProvider
+//import androidx.compose.ui.window.PopupProperties
+//
+//@Composable
+//fun PopupMenu(
+//    expanded: Boolean,
+//    onDismissRequest: () -> Unit,
+//    modifier: Modifier = Modifier,
+//    offset: DpOffset = DpOffset(0.dp, 0.dp),
+//    properties: PopupProperties = PopupProperties(focusable = true),
+//) {
+//    val expandedState = remember { MutableTransitionState(false) }
+//    expandedState.targetState = expanded
+//
+//    if (expandedState.currentState || expandedState.targetState) {
+//        val density = LocalDensity.current
+//        val popupPositionProvider = remember(offset, density) {
+//            PositionProvider(
+//                offset,
+//                density
+//            )
+//        }
+//
+//        Popup(
+//            popupPositionProvider = popupPositionProvider,
+//            onDismissRequest = onDismissRequest,
+//            properties = properties
+//        ) {
+//            Box(modifier = modifier) {
+//
+//            }
+//        }
+//    }
+//}
+//
+//data class PositionProvider(
+//    val contentOffset: DpOffset,
+//    val density: Density
+//) : PopupPositionProvider {
+//
+//
+//    init {
+//        // Horizontal position
+//        val contentOffsetX = with(density) { contentOffset.x.roundToPx() }
+//        // Vertical position
+//        val contentOffsetY = with(density) { contentOffset.y.roundToPx() }
+//    }
+//
+//    override fun calculatePosition(
+//        anchorBounds: IntRect,
+//        windowSize: IntSize,
+//        layoutDirection: LayoutDirection,
+//        popupContentSize: IntSize
+//    ): IntOffset {
+//        val xCandidates = listOf(
+//            startToAnchorStart,
+//            endToAnchorEnd,
+//            if (anchorBounds.center.x < windowSize.width / 2) {
+//                leftToWindowLeft
+//            } else {
+//                rightToWindowRight
+//            }
+//        ).fastMap {
+//            it.position(
+//                anchorBounds = anchorBounds,
+//                windowSize = windowSize,
+//                menuWidth = popupContentSize.width,
+//                layoutDirection = layoutDirection
+//            )
+//        }
+//        val x = xCandidates.fastFirstOrNull {
+//            it >= 0 && it + popupContentSize.width <= windowSize.width
+//        } ?: xCandidates.last()
+//
+//        val yCandidates = listOf(
+//            topToAnchorBottom,
+//            bottomToAnchorTop,
+//            centerToAnchorTop,
+//            if (anchorBounds.center.y < windowSize.height / 2) {
+//                topToWindowTop
+//            } else {
+//                bottomToWindowBottom
+//            }
+//        ).fastMap {
+//            it.position(
+//                anchorBounds = anchorBounds,
+//                windowSize = windowSize,
+//                menuHeight = popupContentSize.height
+//            )
+//        }
+//        val y = yCandidates.fastFirstOrNull {
+//            it >= verticalMargin &&
+//                    it + popupContentSize.height <= windowSize.height - verticalMargin
+//        } ?: yCandidates.last()
+//
+//        val menuOffset = IntOffset(x, y)
+//        return menuOffset
+//    }
+//}

+ 137 - 38
app/src/main/java/io/nexilis/alpha/ui/main/Chat.kt

@@ -3,6 +3,7 @@ package io.nexilis.alpha.ui.main
 import android.text.format.DateUtils
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.combinedClickable
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
@@ -15,27 +16,37 @@ import androidx.compose.material.icons.automirrored.filled.Send
 import androidx.compose.material.icons.filled.AccessTime
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.AttachFile
-import androidx.compose.material.icons.filled.AudioFile
 import androidx.compose.material.icons.filled.Camera
 import androidx.compose.material.icons.filled.Done
 import androidx.compose.material.icons.filled.DoneAll
+import androidx.compose.material.icons.filled.Headset
 import androidx.compose.material.icons.filled.Image
+import androidx.compose.material.icons.filled.LocationOn
+import androidx.compose.material.icons.filled.Person
 import androidx.compose.material3.*
 import androidx.compose.runtime.*
 import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.KeyboardCapitalization
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Popup
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.navigation.NavHostController
 import io.nexilis.alpha.ui.viewmodel.MainViewModel
+import io.nexilis.service.core.DarkLimeGreen
+import io.nexilis.service.core.Orange
+import io.nexilis.service.core.Purple
 import io.nexilis.service.data.entities.Message
 import io.nexilis.service.data.viewmodels.BuddyViewModel
 import io.nexilis.service.data.viewmodels.MessageStatusViewModel
@@ -44,6 +55,7 @@ import io.nexilis.service.data.viewmodels.MessageViewModel
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 fun Chat(navController: NavHostController, pin: String, mainViewModel: MainViewModel) {
+    val focusRequester = remember { FocusRequester() }
     var textInput by rememberSaveable(stateSaver = TextFieldValue.Saver) {
         mutableStateOf(TextFieldValue("", TextRange(0, 7)))
     }
@@ -62,6 +74,9 @@ fun Chat(navController: NavHostController, pin: String, mainViewModel: MainViewM
     Column(
         modifier = Modifier
             .fillMaxSize()
+            .onSizeChanged {
+
+            }
             .background(color = MaterialTheme.colorScheme.surfaceContainer)
             .padding(start = 8.dp, end = 8.dp, bottom = 8.dp)
     ) {
@@ -184,6 +199,7 @@ fun Chat(navController: NavHostController, pin: String, mainViewModel: MainViewM
                     shape = CircleShape
                     clip = true
                 }
+                .focusRequester(focusRequester)
                 .imePadding(),
             value = textInput,
             onValueChange = { textInput = it },
@@ -192,7 +208,18 @@ fun Chat(navController: NavHostController, pin: String, mainViewModel: MainViewM
                 Text(text = "Typing here...", color = MaterialTheme.colorScheme.onSurface)
             },
             leadingIcon = {
-                Attachments()
+                Attachments(
+                    modifier = Modifier
+                        .height(300.dp)
+                        .fillMaxWidth()
+                        .padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
+                        .graphicsLayer {
+                            shape = RoundedCornerShape(16.dp)
+                            clip = true
+                        }
+                        .background(Color(0xEFFFFFFF)),
+                    focusRequester = focusRequester
+                )
             },
             trailingIcon = {
                 Row {
@@ -245,12 +272,17 @@ fun Chat(navController: NavHostController, pin: String, mainViewModel: MainViewM
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
-fun Attachments() {
+fun Attachments(modifier: Modifier, focusRequester: FocusRequester) {
+    val keyboardController = LocalSoftwareKeyboardController.current
+    val positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider()
     var isOpenMenu by remember {
         mutableStateOf(false)
     }
     IconButton(onClick = {
+        focusRequester.requestFocus()
+        keyboardController?.hide()
         isOpenMenu = !isOpenMenu
     }
     ) {
@@ -260,40 +292,107 @@ fun Attachments() {
             tint = MaterialTheme.colorScheme.primary
         )
     }
-    DropdownMenu(
-        expanded = isOpenMenu,
-        onDismissRequest = { isOpenMenu = false }) {
-        DropdownMenuItem(
-            text = { Text(text = "Camera") },
-            onClick = { },
-            leadingIcon = {
-                Icon(imageVector = Icons.Default.Camera, contentDescription = "")
-            },
-            colors = MenuDefaults.itemColors(leadingIconColor = Color.Red)
-        )
-        DropdownMenuItem(
-            text = { Text(text = "Gallery") },
-            onClick = { },
-            leadingIcon = {
-                Icon(imageVector = Icons.Default.Image, contentDescription = "")
-            },
-            colors = MenuDefaults.itemColors(leadingIconColor = Color(0xFF800080))
-        )
-        DropdownMenuItem(
-            text = { Text(text = "Documents") },
-            onClick = { },
-            leadingIcon = {
-                Icon(imageVector = Icons.Default.AttachFile, contentDescription = "")
-            },
-            colors = MenuDefaults.itemColors(leadingIconColor = Color.Blue)
-        )
-        DropdownMenuItem(
-            text = { Text(text = "Audio") },
-            onClick = { },
-            leadingIcon = {
-                Icon(imageVector = Icons.Default.AudioFile, contentDescription = "")
-            },
-            colors = MenuDefaults.itemColors(leadingIconColor = Color(0xFFFFA500))
-        )
+    if (isOpenMenu) {
+        Popup(popupPositionProvider = positionProvider, onDismissRequest = { isOpenMenu = false }) {
+            Box(modifier = modifier) {
+                Row(Modifier.align(Alignment.Center)) {
+                    Column {
+                        AttachmentButton(
+                            color = MaterialTheme.colorScheme.primary,
+                            onClick = {},
+                            text = {
+                                Text(text = "File", style = MaterialTheme.typography.bodySmall)
+                            }) {
+                            Icon(
+                                imageVector = Icons.Default.AttachFile,
+                                contentDescription = "",
+                                tint = Color.White
+                            )
+                        }
+                        Spacer(modifier = Modifier.size(32.dp))
+                        AttachmentButton(color = Color.Orange, onClick = {}, text = {
+                            Text(text = "Audio", style = MaterialTheme.typography.bodySmall)
+                        }) {
+                            Icon(
+                                imageVector = Icons.Default.Headset,
+                                contentDescription = "",
+                                tint = Color.White
+                            )
+                        }
+                    }
+                    Spacer(modifier = Modifier.size(16.dp))
+                    Column {
+                        AttachmentButton(color = Color.Red, onClick = {}, text = {
+                            Text(text = "Camera", style = MaterialTheme.typography.bodySmall)
+                        }) {
+                            Icon(
+                                imageVector = Icons.Default.Camera,
+                                contentDescription = "",
+                                tint = Color.White
+                            )
+                        }
+                        Spacer(modifier = Modifier.size(32.dp))
+                        AttachmentButton(color = Color.DarkLimeGreen, onClick = {}, text = {
+                            Text(text = "Location", style = MaterialTheme.typography.bodySmall)
+                        }) {
+                            Icon(
+                                imageVector = Icons.Default.LocationOn,
+                                contentDescription = "",
+                                tint = Color.White
+                            )
+                        }
+                    }
+                    Spacer(modifier = Modifier.size(16.dp))
+                    Column {
+                        AttachmentButton(color = Color.Purple, onClick = {}, text = {
+                            Text(text = "Gallery", style = MaterialTheme.typography.bodySmall)
+                        }) {
+                            Icon(
+                                imageVector = Icons.Default.Image,
+                                contentDescription = "",
+                                tint = Color.White
+                            )
+                        }
+                        Spacer(modifier = Modifier.size(32.dp))
+                        AttachmentButton(color = Color.Blue, onClick = {}, text = {
+                            Text(text = "Contact", style = MaterialTheme.typography.bodySmall)
+                        }) {
+                            Icon(
+                                imageVector = Icons.Default.Person,
+                                contentDescription = "",
+                                tint = Color.White
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+fun AttachmentButton(
+    color: Color = MaterialTheme.colorScheme.primary,
+    onClick: () -> Unit,
+    text: @Composable () -> Unit,
+    content: @Composable () -> Unit
+) {
+    Column(horizontalAlignment = Alignment.CenterHorizontally) {
+        Box(
+            modifier = Modifier
+                .then(Modifier.size(72.dp))
+                .graphicsLayer {
+                    shape = CircleShape
+                    clip = true
+                }
+                .clickable(onClick = onClick)
+                .padding(0.dp)
+                .background(color),
+            contentAlignment = Alignment.Center
+        ) {
+            content()
+        }
+        Spacer(modifier = Modifier.size(6.dp))
+        text()
     }
 }

+ 9 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/Extension.kt

@@ -2,6 +2,7 @@ package io.nexilis.service.core
 
 import android.content.Context
 import android.content.SharedPreferences
+import androidx.compose.ui.graphics.Color
 import io.nexilis.service.namePreference
 import java.net.URLDecoder
 import java.net.URLEncoder
@@ -54,6 +55,14 @@ fun Context.getSharedPreferences(): SharedPreferences {
     )
 }
 
+val Color.Companion.Purple: Color
+    get() = Color(0xFF800080)
+
+val Color.Companion.Orange: Color
+    get() = Color(0xFFFFA500)
+val Color.Companion.DarkLimeGreen: Color
+    get() = Color(0xFF007600)
+
 class Secret {
     private val iBB = 48 // 0