Runt
2025-12-26 442e37c2ae14b2fab73afd4f3ef928cc47f1af30
libmvi/src/main/java/com/runt/open/mvi/views/PublicViews.kt
@@ -1,5 +1,8 @@
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
@@ -8,13 +11,17 @@
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.ArrowBack
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
@@ -23,17 +30,28 @@
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
@@ -44,25 +62,41 @@
 * @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)
            }
        }
    }
@@ -72,40 +106,33 @@
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.onCancelRequest.invoke()
                            },
@@ -119,8 +146,9 @@
                        }
                        Spacer(modifier = Modifier.weight(1f))
                        Button(onClick = {
                            if(message.confirmDissmiss){
                            if(message.confirmDismiss){
                                message.setDismiss.invoke()
                                message.onDismissRequest.invoke()
                            }
                            message.onConfirmRequest.invoke()
                        }) {
@@ -133,6 +161,80 @@
    }
}
@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 = if(message.maxLines < 1) Int.MAX_VALUE else 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){
@@ -140,10 +242,7 @@
            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)
@@ -164,18 +263,13 @@
@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.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))
    }