|
@@ -1,13 +1,26 @@
|
|
|
|
+import android.Manifest
|
|
|
|
+import android.content.Context
|
|
|
|
+import android.net.Uri
|
|
|
|
+import android.os.Bundle
|
|
|
|
+import android.os.Parcelable
|
|
|
|
+import android.provider.OpenableColumns
|
|
|
|
+import android.widget.Toast
|
|
|
|
+import androidx.activity.compose.rememberLauncherForActivityResult
|
|
|
|
+import androidx.activity.result.contract.ActivityResultContracts
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.Spacer
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
+import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
+import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.layout.size
|
|
import androidx.compose.foundation.layout.size
|
|
import androidx.compose.foundation.shape.CircleShape
|
|
import androidx.compose.foundation.shape.CircleShape
|
|
|
|
+import androidx.compose.foundation.text.KeyboardOptions
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.Icons
|
|
|
|
+import androidx.compose.material.icons.automirrored.filled.Send
|
|
import androidx.compose.material.icons.filled.Add
|
|
import androidx.compose.material.icons.filled.Add
|
|
import androidx.compose.material.icons.filled.AttachFile
|
|
import androidx.compose.material.icons.filled.AttachFile
|
|
import androidx.compose.material.icons.filled.Camera
|
|
import androidx.compose.material.icons.filled.Camera
|
|
@@ -20,27 +33,51 @@ import androidx.compose.material3.Icon
|
|
import androidx.compose.material3.IconButton
|
|
import androidx.compose.material3.IconButton
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.material3.Text
|
|
|
|
+import androidx.compose.material3.TextField
|
|
|
|
+import androidx.compose.material3.TextFieldDefaults
|
|
import androidx.compose.material3.TooltipDefaults
|
|
import androidx.compose.material3.TooltipDefaults
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.runtime.remember
|
|
|
|
+import androidx.compose.runtime.saveable.rememberSaveable
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.graphicsLayer
|
|
import androidx.compose.ui.graphics.graphicsLayer
|
|
|
|
+import androidx.compose.ui.layout.ContentScale
|
|
|
|
+import androidx.compose.ui.platform.LocalContext
|
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
|
|
+import androidx.compose.ui.res.painterResource
|
|
|
|
+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.unit.dp
|
|
import androidx.compose.ui.window.Popup
|
|
import androidx.compose.ui.window.Popup
|
|
|
|
+import androidx.core.net.toUri
|
|
|
|
+import androidx.navigation.NavHostController
|
|
|
|
+import androidx.navigation.NavType
|
|
|
|
+import coil.compose.AsyncImage
|
|
|
|
+import coil.request.ImageRequest
|
|
|
|
+import io.nexilis.alpha.R
|
|
import io.nexilis.service.core.BlueSky
|
|
import io.nexilis.service.core.BlueSky
|
|
import io.nexilis.service.core.DarkLimeGreen
|
|
import io.nexilis.service.core.DarkLimeGreen
|
|
import io.nexilis.service.core.Orange
|
|
import io.nexilis.service.core.Orange
|
|
import io.nexilis.service.core.Purple
|
|
import io.nexilis.service.core.Purple
|
|
|
|
+import kotlinx.parcelize.Parcelize
|
|
|
|
+import kotlinx.serialization.KSerializer
|
|
|
|
+import kotlinx.serialization.Serializable
|
|
|
|
+import kotlinx.serialization.descriptors.PrimitiveKind
|
|
|
|
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
|
|
|
+import kotlinx.serialization.descriptors.SerialDescriptor
|
|
|
|
+import kotlinx.serialization.encoding.Decoder
|
|
|
|
+import kotlinx.serialization.encoding.Encoder
|
|
|
|
+import kotlinx.serialization.json.Json
|
|
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
@Composable
|
|
@Composable
|
|
-fun Attachments(modifier: Modifier) {
|
|
|
|
|
|
+fun Attachments(modifier: Modifier, onAttachment: (List<Uri>) -> Unit) {
|
|
val keyboardController = LocalSoftwareKeyboardController.current
|
|
val keyboardController = LocalSoftwareKeyboardController.current
|
|
val positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider()
|
|
val positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider()
|
|
var isOpenMenu by remember {
|
|
var isOpenMenu by remember {
|
|
@@ -62,9 +99,36 @@ fun Attachments(modifier: Modifier) {
|
|
Box(modifier = modifier) {
|
|
Box(modifier = modifier) {
|
|
Row(Modifier.align(Alignment.Center)) {
|
|
Row(Modifier.align(Alignment.Center)) {
|
|
Column {
|
|
Column {
|
|
|
|
+ val context = LocalContext.current
|
|
|
|
+ val launcher =
|
|
|
|
+ rememberLauncherForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) {
|
|
|
|
+ onAttachment(it)
|
|
|
|
+ isOpenMenu = false
|
|
|
|
+ }
|
|
|
|
+ val permissionLauncher = rememberLauncherForActivityResult(
|
|
|
|
+ ActivityResultContracts.RequestPermission()
|
|
|
|
+ ) {
|
|
|
|
+ if (it) {
|
|
|
|
+ Toast.makeText(context, "Permission Granted", Toast.LENGTH_SHORT)
|
|
|
|
+ .show()
|
|
|
|
+ launcher.launch(
|
|
|
|
+ arrayOf(
|
|
|
|
+ "doc/*",
|
|
|
|
+ "docx/*",
|
|
|
|
+ "application/*",
|
|
|
|
+ "image/*"
|
|
|
|
+ )
|
|
|
|
+ )
|
|
|
|
+ } else {
|
|
|
|
+ Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT)
|
|
|
|
+ .show()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
AttachmentButton(
|
|
AttachmentButton(
|
|
color = MaterialTheme.colorScheme.primary,
|
|
color = MaterialTheme.colorScheme.primary,
|
|
- onClick = {},
|
|
|
|
|
|
+ onClick = {
|
|
|
|
+ permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
|
|
+ },
|
|
text = {
|
|
text = {
|
|
Text(text = "File", style = MaterialTheme.typography.bodySmall)
|
|
Text(text = "File", style = MaterialTheme.typography.bodySmall)
|
|
}) {
|
|
}) {
|
|
@@ -160,4 +224,131 @@ fun AttachmentButton(
|
|
Spacer(modifier = Modifier.size(6.dp))
|
|
Spacer(modifier = Modifier.size(6.dp))
|
|
text()
|
|
text()
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+fun Context.getAttachmentItem(uri: Uri): AttachmentItem? {
|
|
|
|
+ val item = contentResolver.query(uri, null, null, null, null)
|
|
|
|
+ ?.use { cursor ->
|
|
|
|
+ val nameIndex =
|
|
|
|
+ cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
|
|
|
+ val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
|
|
|
|
+ cursor.moveToFirst()
|
|
|
|
+ AttachmentItem(
|
|
|
|
+ cursor.getString(nameIndex),
|
|
|
|
+ cursor.getLong(sizeIndex),
|
|
|
|
+ uri
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ return item
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+object UriSerializable : KSerializer<Uri> {
|
|
|
|
+ override val descriptor: SerialDescriptor
|
|
|
|
+ get() = PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING)
|
|
|
|
+
|
|
|
|
+ override fun deserialize(decoder: Decoder): Uri {
|
|
|
|
+ return decoder.decodeString().toUri()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ override fun serialize(encoder: Encoder, value: Uri) {
|
|
|
|
+ encoder.encodeString(value.toString())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@Serializable
|
|
|
|
+@Parcelize
|
|
|
|
+data class AttachmentItem(
|
|
|
|
+ val name: String,
|
|
|
|
+ val size: Long,
|
|
|
|
+ @Serializable(with = UriSerializable::class)
|
|
|
|
+ val uri: Uri
|
|
|
|
+) : Parcelable
|
|
|
|
+
|
|
|
|
+@Serializable
|
|
|
|
+@Parcelize
|
|
|
|
+data class AttachmentItemList(
|
|
|
|
+ val list: List<AttachmentItem?>
|
|
|
|
+) : Parcelable
|
|
|
|
+
|
|
|
|
+val AttachmentItemListType = object : NavType<AttachmentItemList>(isNullableAllowed = false) {
|
|
|
|
+ override fun put(bundle: Bundle, key: String, value: AttachmentItemList) {
|
|
|
|
+ bundle.putParcelable(key, value)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ override fun get(bundle: Bundle, key: String): AttachmentItemList? {
|
|
|
|
+ return bundle.getParcelable<AttachmentItemList>(key)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ override fun parseValue(value: String): AttachmentItemList {
|
|
|
|
+ return Json.decodeFromString<AttachmentItemList>(value)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@Composable
|
|
|
|
+fun AttachmentCaption(navController: NavHostController, attachments: AttachmentItemList) {
|
|
|
|
+ var textInput by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
|
|
|
+ mutableStateOf(TextFieldValue("", TextRange(0, 7)))
|
|
|
|
+ }
|
|
|
|
+ val context = LocalContext.current
|
|
|
|
+ Column(modifier = Modifier.fillMaxSize()) {
|
|
|
|
+ attachments.list[0]?.let {
|
|
|
|
+ val mimeType = context.contentResolver.getType(it.uri)
|
|
|
|
+ if ((mimeType != null) && mimeType.startsWith("image")) {
|
|
|
|
+ AsyncImage(
|
|
|
|
+ model = ImageRequest.Builder(LocalContext.current)
|
|
|
|
+ .data(it.uri)
|
|
|
|
+ .addHeader("Cookie", "PHPSESSID=123;MOBILE=123")
|
|
|
|
+ .crossfade(true)
|
|
|
|
+ .build(),
|
|
|
|
+ placeholder = painterResource(R.drawable.ic_placeholder),
|
|
|
|
+ contentDescription = "",
|
|
|
|
+ contentScale = ContentScale.Fit,
|
|
|
|
+ modifier = Modifier
|
|
|
|
+ .weight(1f)
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ TextField(
|
|
|
|
+ modifier = Modifier
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
+ .graphicsLayer {
|
|
|
|
+ shape = CircleShape
|
|
|
|
+ clip = true
|
|
|
|
+ },
|
|
|
|
+ value = textInput,
|
|
|
|
+ onValueChange = { textInput = it },
|
|
|
|
+ label = null,
|
|
|
|
+ placeholder = {
|
|
|
|
+ Text(text = "Add a caption...", color = MaterialTheme.colorScheme.onSurface)
|
|
|
|
+ },
|
|
|
|
+ trailingIcon = {
|
|
|
|
+ Row {
|
|
|
|
+ IconButton(onClick = {
|
|
|
|
+ if (textInput.text.trim().isEmpty()) {
|
|
|
|
+ return@IconButton
|
|
|
|
+ }
|
|
|
|
+ textInput = TextFieldValue("")
|
|
|
|
+ }) {
|
|
|
|
+ Icon(
|
|
|
|
+ imageVector = Icons.AutoMirrored.Filled.Send,
|
|
|
|
+ contentDescription = "",
|
|
|
|
+ tint = MaterialTheme.colorScheme.primary
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ colors = TextFieldDefaults.colors(
|
|
|
|
+ focusedContainerColor = MaterialTheme.colorScheme.surface,
|
|
|
|
+ unfocusedContainerColor = MaterialTheme.colorScheme.surface,
|
|
|
|
+ disabledContainerColor = MaterialTheme.colorScheme.surface,
|
|
|
|
+ focusedIndicatorColor = Color.Transparent,
|
|
|
|
+ unfocusedIndicatorColor = Color.Transparent,
|
|
|
|
+ disabledIndicatorColor = Color.Transparent,
|
|
|
|
+ errorIndicatorColor = Color.Transparent,
|
|
|
|
+ ),
|
|
|
|
+ keyboardOptions = KeyboardOptions(KeyboardCapitalization.Sentences),
|
|
|
|
+ maxLines = 3
|
|
|
|
+ )
|
|
|
|
+ }
|
|
}
|
|
}
|