2 files added
4 files modified
| | |
| | | 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 |
| | |
| | | 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) |
| | | } |
| | | |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | 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,"","")) |
| | |
| | | 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) |
| | | } |
| New file |
| | |
| | | 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) |
| New file |
| | |
| | | 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 = {} |
| | | ) |
| | |
| | | * @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) |
| | |
| | | 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.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.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.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 |
| | |
| | | ) { |
| | | 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) } |
| | | |
| | | // 自己仍按父约束的大小布局(不撑爆父) |
| | |
| | | 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) |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | }) { |
| | | 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() |
| | | } |
| | |
| | | } |
| | | Spacer(modifier = Modifier.weight(1f)) |
| | | Button(onClick = { |
| | | if(message.confirmDissmiss){ |
| | | if(message.confirmDismiss){ |
| | | message.setDismiss.invoke() |
| | | message.onDismissRequest.invoke() |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | @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){ |
| | |
| | | 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.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)) |
| | | } |