| | |
| | | package com.runt.open.mvi.views |
| | | |
| | | import android.text.TextUtils |
| | | import android.util.Log |
| | | import android.widget.Toast |
| | | import androidx.compose.foundation.BorderStroke |
| | | import androidx.compose.foundation.background |
| | | import androidx.compose.foundation.layout.Arrangement |
| | |
| | | import androidx.compose.foundation.layout.Spacer |
| | | 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.width |
| | | import androidx.compose.foundation.layout.wrapContentHeight |
| | | import androidx.compose.foundation.layout.wrapContentSize |
| | | import androidx.compose.foundation.shape.RoundedCornerShape |
| | | import androidx.compose.foundation.text.KeyboardOptions |
| | | import androidx.compose.material.icons.Icons |
| | | import androidx.compose.material.icons.filled.Close |
| | | import androidx.compose.material.icons.filled.ArrowBack |
| | | import androidx.compose.material3.AlertDialog |
| | | import androidx.compose.material3.Button |
| | | import androidx.compose.material3.ButtonDefaults |
| | | import androidx.compose.material3.Card |
| | | import androidx.compose.material3.CircularProgressIndicator |
| | | import androidx.compose.material3.Icon |
| | | import androidx.compose.material3.IconButton |
| | | import androidx.compose.material3.Surface |
| | | import androidx.compose.material3.Text |
| | | import androidx.compose.material3.TextButton |
| | | import androidx.compose.material3.TextField |
| | | import androidx.compose.runtime.Composable |
| | | import androidx.compose.runtime.getValue |
| | | import androidx.compose.runtime.mutableStateOf |
| | | import androidx.compose.runtime.remember |
| | | import androidx.compose.runtime.setValue |
| | | import androidx.compose.ui.Alignment |
| | | import androidx.compose.ui.Modifier |
| | | import androidx.compose.ui.draw.clip |
| | | import androidx.compose.ui.graphics.Color |
| | | import androidx.compose.ui.layout.Layout |
| | | import androidx.compose.ui.text.TextStyle |
| | | import androidx.compose.ui.text.style.TextAlign |
| | | import androidx.compose.ui.tooling.preview.Preview |
| | | import androidx.compose.ui.unit.Constraints |
| | | import androidx.compose.ui.unit.dp |
| | | import androidx.compose.ui.unit.sp |
| | | import androidx.compose.ui.window.Dialog |
| | | import androidx.compose.ui.window.Popup |
| | | import com.runt.open.mvi.OpenApplication |
| | | import com.runt.open.mvi.data.InputMessageState |
| | | import com.runt.open.mvi.data.LoadingState |
| | | import com.runt.open.mvi.data.MessageState |
| | | import com.runt.open.mvi.data.PopupMessage |
| | |
| | | * @date 5/18/25 |
| | | */ |
| | | |
| | | /** |
| | | * 超出父容器 |
| | | */ |
| | | @Composable |
| | | fun UnboundedBox( |
| | | modifier: Modifier = Modifier, |
| | | content: @Composable () -> Unit |
| | | ) { |
| | | Layout(content, modifier) { measurables, parent -> |
| | | // 给子树宽高用"无限"上限测量 |
| | | val loose = Constraints(minWidth = 0, minHeight = 0, maxWidth = Int.MAX_VALUE, maxHeight = Int.MAX_VALUE) |
| | | val placeables = measurables.map { it.measure(loose) } |
| | | |
| | | // 自己仍按父约束的大小布局(不撑爆父) |
| | | val w = parent.maxWidth |
| | | val h = parent.maxHeight |
| | | layout(w, h) { |
| | | // 放在(0,0)。如需往外探出,可用负偏移:placeRelative(-50, 0) |
| | | placeables.forEach { it.placeRelative(0, 0) } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Composable |
| | | fun LoadingDialog( loadingState : LoadingState) { |
| | | if (loadingState.isVisible) { |
| | | Dialog(onDismissRequest = {}) { |
| | | Column( |
| | | modifier = Modifier |
| | | .size(140.dp) |
| | | .clip(RoundedCornerShape(16.dp)) |
| | | .background(Color.White), |
| | | modifier = Modifier.size(140.dp).clip(RoundedCornerShape(16.dp)).background(Color.White), |
| | | horizontalAlignment = Alignment.CenterHorizontally, |
| | | verticalArrangement = Arrangement.Center |
| | | ) { |
| | | CircularProgressIndicator() |
| | | Spacer(modifier = Modifier.height(12.dp)) |
| | | Text( |
| | | text = loadingState.message, |
| | | fontSize = 14.sp, |
| | | color = Color.Black |
| | | ) |
| | | Text(text = loadingState.message, fontSize = 14.sp, color = Color.Black) |
| | | } |
| | | } |
| | | } |
| | |
| | | fun MessageDialog(message : MessageState){ |
| | | if(message.isVisible){ |
| | | Dialog(onDismissRequest = { |
| | | if(message.cancelDissmiss){ |
| | | //系统响应 |
| | | Log.i("PublicViews" , "MessageDialog: onDismiss") |
| | | if(message.touchOutside){ |
| | | message.setDismiss.invoke() |
| | | message.onDismissRequest.invoke() |
| | | } |
| | | message.onDismissRequest.invoke() |
| | | }) { |
| | | Card( |
| | | modifier = Modifier |
| | | .fillMaxWidth() |
| | | .wrapContentHeight() |
| | | .padding(16.dp), |
| | | modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(16.dp), |
| | | shape = RoundedCornerShape(16.dp), |
| | | ) { |
| | | Column(modifier = Modifier.wrapContentHeight()){ |
| | | Text(text = message.title, fontSize = 16.sp, |
| | | modifier = Modifier.fillMaxWidth() |
| | | .padding(top = 20.dp , start = 20.dp) |
| | | .wrapContentSize(Alignment.Center), |
| | | modifier = Modifier.fillMaxWidth().padding(top = 20.dp , start = 20.dp).wrapContentSize(Alignment.Center), |
| | | textAlign = TextAlign.Center) |
| | | Text( |
| | | text = message.message, |
| | | modifier = Modifier |
| | | .fillMaxWidth() |
| | | .wrapContentSize() |
| | | .padding(top = 30.dp , bottom = 20.dp, start = 14.dp, end = 15.dp) |
| | | .wrapContentSize(Alignment.Center), |
| | | modifier = Modifier.fillMaxWidth().wrapContentSize().padding(top = 30.dp , bottom = 20.dp , start = 14.dp , end = 15.dp).wrapContentSize(Alignment.Center), |
| | | textAlign = TextAlign.Center, |
| | | ) |
| | | Row (modifier = Modifier.padding(start = 15.dp, end = 15.dp, bottom = 20.dp)){ |
| | | if(!message.cancelText.equals("")){ |
| | | Spacer(modifier = Modifier.weight(1f)) |
| | | Button(onClick = { |
| | | if(message.cancelDissmiss){ |
| | | if(message.cancelDismiss){ |
| | | message.setDismiss.invoke() |
| | | message.onDismissRequest.invoke() |
| | | } |
| | | message.onDismissRequest.invoke() |
| | | }) { |
| | | message.onCancelRequest.invoke() |
| | | }, |
| | | colors = ButtonDefaults.buttonColors( |
| | | containerColor = Color.Gray, // 背景色 |
| | | contentColor = Color.White, // 文字/图标颜色 |
| | | disabledContainerColor = Color.Gray // 禁用时背景色 |
| | | ),) { |
| | | Text(text = message.cancelText) |
| | | } |
| | | } |
| | | Spacer(modifier = Modifier.weight(1f)) |
| | | Button(onClick = { |
| | | if(message.confirmDissmiss){ |
| | | if(message.confirmDismiss){ |
| | | message.setDismiss.invoke() |
| | | message.onDismissRequest.invoke() |
| | | } |
| | | message.onConfirmRequest.invoke() |
| | | }) { |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | @Composable |
| | | fun InputDialog(message : InputMessageState) { |
| | | if(!message.isVisible){ |
| | | return |
| | | } |
| | | var text by remember { |
| | | mutableStateOf(message.message) |
| | | } |
| | | var modifier : Modifier; |
| | | var keyboardType = message.inputType; |
| | | if (message.maxLines == 1) { |
| | | modifier = Modifier.width(260.dp).height(50.dp).padding(0.dp) |
| | | } else { |
| | | modifier = Modifier.width(260.dp).padding(0.dp).heightIn(min = 50.dp, max = 200.dp) |
| | | } |
| | | AlertDialog(onDismissRequest = { |
| | | if(message.touchOutside){ |
| | | message.setDismiss.invoke() |
| | | message.onDismissRequest.invoke() |
| | | } |
| | | } , confirmButton = { |
| | | TextButton(onClick = { |
| | | if (message.minLength > 0 || message.maxLength > 0 || !TextUtils.isEmpty(message.regex)) { |
| | | var flag = true; |
| | | if(text.length < message.minLength){ |
| | | flag = false; |
| | | } |
| | | if(text.length > message.maxLength && message.maxLength > 0){ |
| | | flag = false; |
| | | } |
| | | if (!message.regex.isEmpty() && !message.regex.toRegex().matches(text)) { |
| | | flag = false; |
| | | } |
| | | if(flag){ |
| | | if(message.confirmDismiss){ |
| | | message.setDismiss.invoke() |
| | | message.onDismissRequest.invoke() |
| | | } |
| | | message.onConfirmRequest(text) |
| | | } else { |
| | | Toast.makeText(OpenApplication.getApplication() , message.hint , Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } else { |
| | | if(message.confirmDismiss){ |
| | | message.setDismiss.invoke() |
| | | message.onDismissRequest.invoke() |
| | | } |
| | | message.onConfirmRequest(text) |
| | | } |
| | | }) { |
| | | Text(text = message.confirmText) |
| | | } |
| | | } , dismissButton = { |
| | | TextButton(onClick = { |
| | | if(message.cancelDismiss){ |
| | | message.setDismiss.invoke() |
| | | message.onDismissRequest.invoke() |
| | | } |
| | | message.onCancelRequest.invoke() |
| | | }) { |
| | | Text(text = message.cancelText) |
| | | } |
| | | } , title = { |
| | | Text(text = message.title , fontSize = 16.sp) |
| | | } , text = { |
| | | TextField(value = text , onValueChange = { //val pattern = """(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]""".toRegex() |
| | | text = it; |
| | | } , singleLine = message.maxLines == 1 , maxLines = message.maxLines, modifier = modifier , keyboardOptions = KeyboardOptions(keyboardType = keyboardType) , placeholder = { |
| | | Text(text = message.hint , fontSize = 14.sp) |
| | | } , textStyle = TextStyle(fontSize = 14.sp , color = Color.Black)) |
| | | }) |
| | | } |
| | | |
| | | @Composable |
| | | fun PopupWindow(popupMessage : PopupMessage){ |
| | | if(popupMessage.isVisible){ |
| | |
| | | alignment = Alignment.TopCenter, |
| | | ) { |
| | | Surface( |
| | | modifier = Modifier.fillMaxWidth() |
| | | .wrapContentHeight().padding(10.dp), |
| | | modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(10.dp), |
| | | shadowElevation = 2.dp, |
| | | border = BorderStroke(1.dp, Color.Gray) , |
| | | shape = RoundedCornerShape(8.dp) |
| | |
| | | |
| | | @Composable |
| | | fun TitleBarView(title:String,onBackClick:()->Unit){ |
| | | Row(modifier = Modifier |
| | | .wrapContentSize(Alignment.Center) |
| | | .height(50.dp), verticalAlignment = Alignment.CenterVertically) { |
| | | Row(modifier = Modifier.wrapContentSize(Alignment.Center).height(50.dp), verticalAlignment = Alignment.CenterVertically) { |
| | | Spacer(modifier = Modifier.size(15.dp)) |
| | | IconButton(onClick = onBackClick, modifier = Modifier.size(30.dp,30.dp)) { |
| | | Icon(Icons.Default.Close, contentDescription = null, tint = Color.Black) |
| | | Icon(Icons.Default.ArrowBack, contentDescription = null, tint = Color.Black) |
| | | } |
| | | |
| | | Text(text = "${title}", modifier = Modifier |
| | | .weight(1f) |
| | | .fillMaxWidth() |
| | | .wrapContentSize(Alignment.Center)) |
| | | Text(text = "${title}", modifier = Modifier.weight(1f).fillMaxWidth().wrapContentSize(Alignment.Center)) |
| | | Spacer(modifier = Modifier.size(30.dp)) |
| | | Spacer(modifier = Modifier.size(15.dp)) |
| | | } |
| | | } |
| | | @Preview |
| | | @Composable |
| | | fun previewLayout(){ |
| | | MessageDialog(message = MessageState(cancelText = "lll")) |
| | | } |