Runt
2025-12-12 53f2a4266f3e42296bdcc0f0c8365b2cd4a152b1
输入框
2 files added
4 files modified
274 ■■■■ changed files
libmvi/src/main/java/com/runt/open/mvi/base/LayoutView.kt 9 ●●●● patch | view | raw | blame | history
libmvi/src/main/java/com/runt/open/mvi/base/model/BaseViewModel.kt 45 ●●●● patch | view | raw | blame | history
libmvi/src/main/java/com/runt/open/mvi/data/InputMessageState.kt 31 ●●●●● patch | view | raw | blame | history
libmvi/src/main/java/com/runt/open/mvi/data/Message.kt 21 ●●●●● patch | view | raw | blame | history
libmvi/src/main/java/com/runt/open/mvi/data/MessageState.kt 29 ●●●● patch | view | raw | blame | history
libmvi/src/main/java/com/runt/open/mvi/views/PublicViews.kt 139 ●●●● patch | view | raw | blame | history
libmvi/src/main/java/com/runt/open/mvi/base/LayoutView.kt
@@ -4,6 +4,9 @@
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import com.runt.open.mvi.base.model.BaseViewModel
import com.runt.open.mvi.data.InputMessageState
import com.runt.open.mvi.data.MessageState
import com.runt.open.mvi.views.InputDialog
import com.runt.open.mvi.views.LoadingDialog
import com.runt.open.mvi.views.MessageDialog
import com.runt.open.mvi.views.PopupWindow
@@ -25,7 +28,11 @@
        layout()
        // 显示 loading 弹窗
        LoadingDialog(isLoading)
        MessageDialog(messageState)
        if(messageState is MessageState){
            MessageDialog(message = messageState as MessageState)
        }else if(messageState is InputMessageState){
            InputDialog(message = messageState as InputMessageState)
        }
        PopupWindow(popupState)
    }
libmvi/src/main/java/com/runt/open/mvi/base/model/BaseViewModel.kt
@@ -3,14 +3,16 @@
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.util.Log
import androidx.compose.ui.text.input.KeyboardType
import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import com.google.gson.Gson
import com.runt.open.mvi.base.BaseActivity
import com.runt.open.mvi.base.LayoutView
import com.runt.open.mvi.data.InputMessageState
import com.runt.open.mvi.data.LoadingState
import com.runt.open.mvi.data.Message
import com.runt.open.mvi.data.MessageState
import com.runt.open.mvi.data.PopupMessage
import com.runt.open.mvi.retrofit.AndroidScheduler
@@ -27,7 +29,6 @@
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
@@ -47,9 +48,9 @@
    val isLoading = _isLoading.asStateFlow()
    val messageSetDismiss = {
        _messageState.value = _messageState.value.copy(isVisible = false);
        _messageState.value = MessageState(isVisible = false)
    }
    private val _messageState = MutableStateFlow(MessageState(isVisible = false, setDismiss = { }))
    private val _messageState = MutableStateFlow(Message(isVisible = false, setDismiss = { }))
    val messageState = _messageState.asStateFlow()
    private val _popupState = MutableStateFlow(PopupMessage(isVisible = false,"",""))
@@ -91,22 +92,46 @@
    fun hideLoading() {
        _isLoading.value = _isLoading.value.copy(isVisible = false);
    }
    fun showDialog(
        title : String = "" , message : String = "" , confirmText : String = "确定" , cancelText : String = "" ,
    fun showInputDialog(
        title : String = "" , message : String = "" , hint : String = "",regex : String = "",
        confirmText:String = "确定", cancelText:String = "取消",
        maxLines:Int = 1, minLength:Int = 0, maxLength:Int = 0,
        inputType : KeyboardType = KeyboardType.Text ,
        touchOutside : Boolean = true , //空白和系统返回 是否关闭
        showClose : Boolean = false , //显示关闭图标(默认不显示)
        confirmDissmiss : Boolean = true , //点击确定是否关闭
        cancelDissmiss : Boolean = true , //点击取消是否关闭
        confirmDismiss : Boolean = true , //点击确定是否关闭
        cancelDismiss : Boolean = true , //点击取消是否关闭
        onDismissRequest : () -> Unit = {} ,
        onCancelRequest : () -> Unit = {} ,
        onConfirmRequest : (String) -> Unit = {} ,
    ){
        _messageState.value = InputMessageState(title = title, message = message, hint = hint, regex = regex,
            maxLines = maxLines, minLength = minLength, maxLength = maxLength, inputType = inputType,
            touchOutside = touchOutside, showClose = showClose, confirmText = confirmText,
            cancelText = cancelText, confirmDismiss = confirmDismiss, cancelDismiss = cancelDismiss, onCancelRequest = onCancelRequest, onDismissRequest = onDismissRequest,
            onConfirmRequest = onConfirmRequest, setDismiss = messageSetDismiss);
    }
    fun showDialog(
        title : String = "" , message : String = "" ,
        confirmText : String = "确定" , cancelText : String = "" ,
        touchOutside : Boolean = true , //空白和系统返回 是否关闭
        showClose : Boolean = false , //显示关闭图标(默认不显示)
        confirmDismiss : Boolean = true , //点击确定是否关闭
        cancelDismiss : Boolean = true , //点击取消是否关闭
        onDismissRequest : () -> Unit = {} ,
        onCancelRequest : () -> Unit = {} ,
        onConfirmRequest : () -> Unit = {} ,
    ){
        _messageState.value = MessageState(title = title, message = message, touchOutside = touchOutside, showClose = showClose, confirmText = confirmText,
            cancelText = cancelText, confirmDissmiss = confirmDissmiss, cancelDissmiss = cancelDissmiss, onCancelRequest = onCancelRequest, onDismissRequest = onDismissRequest,
            cancelText = cancelText, confirmDismiss = confirmDismiss, cancelDismiss = cancelDismiss, onCancelRequest = onCancelRequest, onDismissRequest = onDismissRequest,
            onConfirmRequest = onConfirmRequest, setDismiss = messageSetDismiss);
    }
    fun dismissDialog(){
        messageSetDismiss.invoke();
    }
    fun showPopupWindow(title : String,message : String){
        _popupState.value = PopupMessage(isVisible = true,title,message)
    }
libmvi/src/main/java/com/runt/open/mvi/data/InputMessageState.kt
New file
@@ -0,0 +1,31 @@
package com.runt.open.mvi.data
import androidx.compose.ui.text.input.KeyboardType
/**
 * @author Runt(qingingrunt2010@qq.com)
 * @purpose
 * @date 12/13/25
 */
class InputMessageState(
    isVisible: Boolean = true,
    title:String = "",
    val hint:String = "请输入文本",
    message: String = "加载中...",
    confirmText:String = "确定",
    cancelText:String = "取消",
    val maxLines:Int = 1,
    val minLength:Int = 0,
    val maxLength:Int = 0,
    val inputType : KeyboardType  = KeyboardType.Text ,
    val regex:String = "",//限制字符
    touchOutside:Boolean = true,//空白和系统返回 是否关闭
    showClose:Boolean = false,//显示关闭图标(默认不显示)
    confirmDismiss:Boolean = true,//点击确定是否关闭
    cancelDismiss:Boolean = true,//点击取消是否关闭
    onDismissRequest : () -> Unit = {},
    onCancelRequest : () -> Unit = {},
    val onConfirmRequest : (String) -> Unit = {},
    setDismiss:() -> Unit = {}
):Message(isVisible,title,message,confirmText,cancelText,touchOutside,showClose,
    confirmDismiss,cancelDismiss,onDismissRequest,onCancelRequest,setDismiss)
libmvi/src/main/java/com/runt/open/mvi/data/Message.kt
New file
@@ -0,0 +1,21 @@
package com.runt.open.mvi.data
/**
 * @author Runt(qingingrunt2010@qq.com)
 * @purpose
 * @date 12/13/25
 */
open class Message (
    val isVisible: Boolean = true,
    val title:String = "",
    val message: String = "加载中...",
    val confirmText:String = "确定",
    val cancelText:String = "",
    val touchOutside:Boolean = true,//空白和系统返回 是否关闭
    val showClose:Boolean = false,//显示关闭图标(默认不显示)
    val confirmDismiss:Boolean = true,//点击确定是否关闭
    val cancelDismiss:Boolean = true,//点击取消是否关闭
    val onDismissRequest : () -> Unit = {},
    val onCancelRequest : () -> Unit = {},
    var setDismiss:() -> Unit = {}
)
libmvi/src/main/java/com/runt/open/mvi/data/MessageState.kt
@@ -5,18 +5,19 @@
 * @purpose
 * @date 5/18/25
 */
data class MessageState(
    val isVisible: Boolean = true,
    val title:String = "",
    val message: String = "加载中...",
    val confirmText:String = "确定",
    val cancelText:String = "",
    val touchOutside:Boolean = true,//空白和系统返回 是否关闭
    val showClose:Boolean = false,//显示关闭图标(默认不显示)
    val confirmDissmiss:Boolean = true,//点击确定是否关闭
    val cancelDissmiss:Boolean = true,//点击取消是否关闭
    val onDismissRequest : () -> Unit = {},
    val onCancelRequest : () -> Unit = {},
class MessageState(
    isVisible: Boolean = true,
    title:String = "",
    message: String = "加载中...",
    confirmText:String = "确定",
    cancelText:String = "",
    touchOutside:Boolean = true,//空白和系统返回 是否关闭
    showClose:Boolean = false,//显示关闭图标(默认不显示)
    confirmDismiss:Boolean = true,//点击确定是否关闭
    cancelDismiss:Boolean = true,//点击取消是否关闭
    onDismissRequest : () -> Unit = {},
    onCancelRequest : () -> Unit = {},
    val onConfirmRequest : () -> Unit = {},
    var setDismiss:() -> Unit = {}
)
    setDismiss:() -> Unit = {}
):Message(isVisible,title,message,confirmText,cancelText,touchOutside,showClose,
    confirmDismiss,cancelDismiss,onDismissRequest,onCancelRequest,setDismiss)
libmvi/src/main/java/com/runt/open/mvi/views/PublicViews.kt
@@ -1,6 +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
@@ -9,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
@@ -24,12 +30,19 @@
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
@@ -37,6 +50,8 @@
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
@@ -57,10 +72,7 @@
) {
    Layout(content, modifier) { measurables, parent ->
        // 给子树宽高用"无限"上限测量
        val loose = Constraints(
            minWidth = 0, minHeight = 0,
            maxWidth = Int.MAX_VALUE, maxHeight = Int.MAX_VALUE
        )
        val loose = Constraints(minWidth = 0, minHeight = 0, maxWidth = Int.MAX_VALUE, maxHeight = Int.MAX_VALUE)
        val placeables = measurables.map { it.measure(loose) }
        // 自己仍按父约束的大小布局(不撑爆父)
@@ -78,20 +90,13 @@
    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)
            }
        }
    }
@@ -109,33 +114,23 @@
            }
        }) {
            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()
                                }
@@ -151,7 +146,7 @@
                        }
                        Spacer(modifier = Modifier.weight(1f))
                        Button(onClick = {
                            if(message.confirmDissmiss){
                            if(message.confirmDismiss){
                                message.setDismiss.invoke()
                                message.onDismissRequest.invoke()
                            }
@@ -166,6 +161,80 @@
    }
}
@Composable
fun InputDialog(message : InputMessageState) {
    if(!message.isVisible){
        return
    }
    var text by remember {
        mutableStateOf(if(TextUtils.isEmpty(message.message)) message.hint else 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){
@@ -173,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)
@@ -197,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))
    }