From ff59eb9415d3df520df98e14ef66d9a3d9804c5b Mon Sep 17 00:00:00 2001
From: Runt <qingingrunt2010@qq.com>
Date: Sun, 18 May 2025 17:50:11 +0000
Subject: [PATCH] 封装dialog,popup,loading弹框, 优化权限申请 优化文件选择

---
 libmvi/src/main/java/com/runt/open/mvi/base/model/BaseViewModel.kt       |   48 +++++
 libmvi/src/main/java/com/runt/open/mvi/retrofit/utils/RetrofitUtils.java |   13 +
 libmvi/src/main/java/com/runt/open/mvi/views/PublicViews.kt              |  149 ++++++++++++++++++
 /dev/null                                                                |    8 -
 libmvi/src/main/java/com/runt/open/mvi/CrashHandler.java                 |    2 
 libmvi/src/main/java/com/runt/open/mvi/data/MessageState.kt              |   21 ++
 libmvi/src/main/java/com/runt/open/mvi/OpenApplication.kt                |    2 
 libmvi/build.gradle.kts                                                  |    2 
 libmvi/src/main/java/com/runt/open/mvi/base/LayoutView.kt                |   19 ++
 libmvi/src/main/java/com/runt/open/mvi/data/PopupMessage.kt              |    8 +
 libmvi/src/main/java/com/runt/open/mvi/data/LoadingState.kt              |   11 +
 libmvi/src/main/java/com/runt/open/mvi/base/BaseActivity.kt              |  156 +++++++++++++++++++
 12 files changed, 423 insertions(+), 16 deletions(-)

diff --git a/libmvi/build.gradle.kts b/libmvi/build.gradle.kts
index 7c42701..a50342d 100644
--- a/libmvi/build.gradle.kts
+++ b/libmvi/build.gradle.kts
@@ -44,6 +44,8 @@
 
 dependencies {
 
+    api(libs.androidx.material3)
+    api(platform(libs.androidx.compose.bom))
     api(libs.androidx.activity.compose)
     api(libs.androidx.core.ktx)
     api(libs.androidx.lifecycle.runtime.ktx)
diff --git a/libmvi/src/main/java/com/runt/open/mvi/CrashHandler.java b/libmvi/src/main/java/com/runt/open/mvi/CrashHandler.java
index fee8251..4152db7 100644
--- a/libmvi/src/main/java/com/runt/open/mvi/CrashHandler.java
+++ b/libmvi/src/main/java/com/runt/open/mvi/CrashHandler.java
@@ -153,7 +153,7 @@
      * @return  返回文件名称,便于将文件传送到服务器
      */
     private String saveCatchInfoFile(Throwable ex) {
-        //ex.printStackTrace();
+        ex.printStackTrace();
         //Log.i(TAG, "saveCatchInfo2File Throwable:"+ex);
 
         StringBuffer sb = new StringBuffer();
diff --git a/libmvi/src/main/java/com/runt/open/mvi/OpenApplication.kt b/libmvi/src/main/java/com/runt/open/mvi/OpenApplication.kt
index 4bc247a..596fdcb 100644
--- a/libmvi/src/main/java/com/runt/open/mvi/OpenApplication.kt
+++ b/libmvi/src/main/java/com/runt/open/mvi/OpenApplication.kt
@@ -11,7 +11,7 @@
  * @purpose
  * @date 6/2/24
  */
-class OpenApplication :Application(){
+open class OpenApplication :Application(){
 
     val TAG = "MyApplication"
     var activities : MutableList<Activity> = ArrayList()
diff --git a/libmvi/src/main/java/com/runt/open/mvi/base/BaseActivity.kt b/libmvi/src/main/java/com/runt/open/mvi/base/BaseActivity.kt
index 20db9c3..91dae29 100644
--- a/libmvi/src/main/java/com/runt/open/mvi/base/BaseActivity.kt
+++ b/libmvi/src/main/java/com/runt/open/mvi/base/BaseActivity.kt
@@ -1,17 +1,24 @@
 package com.runt.open.mvi.base
 
+import android.Manifest
 import android.app.ActivityManager
+import android.content.ContentResolver
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
+import android.net.Uri
 import android.os.Build
 import android.os.Bundle
 import android.os.Environment
+import android.provider.MediaStore
+import android.provider.Settings
+import android.text.TextUtils
 import android.util.Log
 import android.view.MotionEvent
 import android.view.View
 import android.view.WindowManager
 import android.view.inputmethod.InputMethodManager
+import android.webkit.MimeTypeMap
 import android.widget.EditText
 import android.widget.Toast
 import androidx.activity.ComponentActivity
@@ -53,6 +60,11 @@
 
     val PARAMS_TITLE = "title"
 
+    private var requestFileResult:Observer<List<String>>? = null;
+
+    private var fileLauncher : ActivityResultLauncher<Intent>? = null //选择文件
+    private var filePermissionLauncher : ActivityResultLauncher<Intent>? = null //文件权限
+    private val fileUriList = mutableListOf<Uri>()
 
     override fun onCreate(savedInstanceState : Bundle?) {
         super.onCreate(savedInstanceState) // get genericity "B"
@@ -77,7 +89,7 @@
         init();
         //setContentView(mLayout)
         setContent {
-            mLayout!!.layout()
+            mLayout!!.layoutFrame()
         }
         mContext = this
         TAG = this.javaClass.simpleName
@@ -85,18 +97,20 @@
         mViewModel!!.onCreate(this  as BaseActivity<LayoutView<BaseViewModel> , BaseViewModel>)
         loadData() //加载数据
     }
+
     abstract fun init()
 
     abstract fun initViews()
 
     abstract fun loadData()
 
-    fun registerPermissionResult(){
+    protected fun registerPermissionResult(){
         permissionsLauncher = this.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
             if (result == null || result.size === 0) {
                 permissionObserver !!.onChanged("")
                 return@registerForActivityResult
             }
+            mViewModel!!.hidePopupWindow()
             Log.d(TAG , "result:" + result + " size:" + result.size)
             var allGranted = true
             for (key in result.keys) {
@@ -112,6 +126,115 @@
                 permissionObserver !!.onChanged(com.google.gson.Gson().toJson(result.keys).replace("[" , "").replace("]" , "").replace("\"" , ""))
             }
         }
+    }
+
+    protected fun registerFileResult(){
+
+        filePermissionLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+            Log.i(TAG,"filePermission ${result}")
+            onRequestFileResult();
+        }
+        //选择文件
+        fileLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult() , object : ActivityResultCallback<ActivityResult?> {
+            override fun onActivityResult(result : ActivityResult?) {
+                Log.i(TAG,"fileLauncher ${result!!.data?.data}")
+                Log.i(TAG,"fileLauncher clipData= ${result!!.data?.clipData}")
+                fileUriList.clear();
+                result!!.data?.data?.let{
+                    fileUriList.add(it);
+                }
+                result!!.data?.clipData?.let{
+                    val count = it.itemCount
+                    for (i in 0 until count) {
+                        fileUriList.add(it.getItemAt(i).uri)
+                    }
+                }
+                if(fileUriList.size == 0){
+                    requestFileResult?.onChanged(ArrayList())
+                }else{
+                    onRequestFileResult();
+                }
+            }
+        })
+    }
+
+    private fun onRequestFileResult(){
+
+        val cR : ContentResolver = getContentResolver()
+        val mime = MimeTypeMap.getSingleton()
+        var filePath = mViewModel!!.getFilePathFromUri(fileUriList.get(0)!!);
+        //文件不存在或读写权限受限
+        if(filePath == null){
+            //android 11需要申请权限 //是否有所有问读写权限
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && ! Environment.isExternalStorageManager()) {
+                //跳转到打开权限页面
+                var intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
+
+                intent.setData(Uri.parse("package:" + mContext!!.getPackageName()));
+                filePermissionLauncher!!.launch(intent)
+                return;
+            }
+            requestFileResult?.onChanged(ArrayList())
+        }else{
+            var filePathes = ArrayList<String>()
+            for (i in 0 until fileUriList.size) {
+                var filePath = mViewModel!!.getFilePathFromUri(fileUriList.get(i)!!);
+                filePathes.add(filePath!!)
+            }
+            requestFileResult?.onChanged(filePathes)
+        }
+    }
+
+    fun requestFile(fileType :String,maxCount:Int = 1,fileResult:Observer<List<String>>){
+        requestFileResult = fileResult;
+        var permissions  = "";
+        //android 13 权限申请细化类型
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            permissions = fileType ;
+            //android 14 选择文件授权
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                permissions+=","+ Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED;
+            }
+        }else{
+            //读取权限
+            permissions = Manifest.permission.READ_EXTERNAL_STORAGE ;
+        }
+        //申请权限
+        requestPermissions(permissions , object :Observer<String>{
+            override fun onChanged(value : String) {
+                if(!value.isEmpty()){
+                    var intent = Intent()
+                    // intent.action = Intent.ACTION_PICK
+                    // intent.action = Intent.ACTION_GET_CONTENT
+
+                    //intent.setType("image/*")
+                    when(fileType){
+                        Manifest.permission.READ_MEDIA_VIDEO ->{
+                            intent.action = MediaStore.ACTION_PICK_IMAGES
+                            intent.setType("video/*") // 设置文件类型,可以更具体如"application/pdf"
+                        }
+                        Manifest.permission.READ_MEDIA_AUDIO ->{
+                            intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+                            intent.setType("audio/*") // 设置文件类型,可以更具体如"application/pdf"
+                        }
+                        Manifest.permission.READ_MEDIA_IMAGES ->{
+                            intent.action = MediaStore.ACTION_PICK_IMAGES
+                            intent.setType("image/*") // 设置文件类型,可以更具体如"application/pdf"
+                        }
+                        else -> {}
+                    }
+                    // 设置可多选
+                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, maxCount > 1);
+                    if(maxCount > 1){
+                        //设置最大数量
+                        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
+                    }
+                    fileLauncher!!.launch(intent)
+                }else{
+                    requestFileResult?.onChanged(ArrayList())
+                }
+            }
+        })
     }
 
     fun registerForActivityResult(callback : ActivityResultCallback<ActivityResult>):ActivityResultLauncher<Intent>{
@@ -271,7 +394,33 @@
                 allGranted = false
                 if (showPop) {
                     // TODO:
-                    //showPermissionPopu(permission)
+                    var title = ""
+                    var message = "";
+                    if(permissions.contains(Manifest.permission.READ_EXTERNAL_STORAGE)){
+                        title = "申请文件读取权限";
+                        message = "通过文件读取权限后,可无障碍使用图片、音乐、视频并添加到直播的音视频流中"
+                    }else if(permissions.contains(Manifest.permission.CAMERA)){
+                        title = "申请相机权限";
+                        message = "通过相机权限后,可将相机画面添加到直播画面流中"
+                    }else if(permissions.contains(Manifest.permission.RECORD_AUDIO)){
+                        title = "申请话筒权限";
+                        message = "通过话筒权限后,可将话筒声音添加到直播声音流中"
+                    }else if(permissions.contains(Manifest.permission.READ_MEDIA_IMAGES)){
+                        title = "申请图片和视频文件权限";
+                        message = "通过图片和视频文件权限后,可将图片和视频画面添加到直播画面流中"
+                    }else if(permissions.contains(Manifest.permission.READ_MEDIA_VIDEO)){
+                        title = "申请图片和视频文件权限";
+                        message = "通过图片和视频文件权限后,可将图片和视频画面添加到直播画面流中"
+                    }else if(permissions.contains(Manifest.permission.READ_MEDIA_AUDIO)){
+                        title = "申请音频文件权限";
+                        message = "通过音频文件权限后,可将音频文件的声音添加到直播声音流中"
+                    }/*else if(permissions.contains(Manifest.permission.POST_NOTIFICATIONS)){
+                        title = "申请通知栏权限";
+                        message = "通过通知栏权限后,可开启后台服务,保证相机、话筒、截屏、扬声器等功能不被系统终止"
+                    }*/
+                    if(!TextUtils.isEmpty(title)){
+                        mViewModel!!.showPopupWindow(title = title, message = message)
+                    }
                 }
                 break
             }
@@ -281,6 +430,7 @@
 
     var permissionsLauncher : ActivityResultLauncher<Array<String>>? = null //权限
     var permissionObserver : Observer<String>? = null //权限和文件请求回调
+
     /**
      * 统一权限申请
      */
diff --git a/libmvi/src/main/java/com/runt/open/mvi/base/LayoutView.kt b/libmvi/src/main/java/com/runt/open/mvi/base/LayoutView.kt
index 38aa0d7..5a863fc 100644
--- a/libmvi/src/main/java/com/runt/open/mvi/base/LayoutView.kt
+++ b/libmvi/src/main/java/com/runt/open/mvi/base/LayoutView.kt
@@ -1,7 +1,12 @@
 package com.runt.open.mvi.base
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import com.runt.open.mvi.base.model.BaseViewModel
+import com.runt.open.mvi.views.LoadingDialog
+import com.runt.open.mvi.views.MessageDialog
+import com.runt.open.mvi.views.PopupWindow
 
 /**
  * @author Runt(qingingrunt2010@qq.com)
@@ -13,5 +18,17 @@
     val TAG : String = javaClass.simpleName
 
     @Composable
-    abstract fun layout();
+    fun layoutFrame(){
+        val isLoading by mViewModel.isLoading.collectAsState()
+        val messageState by mViewModel.messageState.collectAsState()
+        val popupState by mViewModel.popupState.collectAsState()
+        layout()
+        // 显示 loading 弹窗
+        LoadingDialog(isLoading)
+        MessageDialog(messageState)
+        PopupWindow(popupState)
+    }
+
+    @Composable
+    protected abstract fun layout();
 }
\ No newline at end of file
diff --git a/libmvi/src/main/java/com/runt/open/mvi/base/model/BaseViewModel.kt b/libmvi/src/main/java/com/runt/open/mvi/base/model/BaseViewModel.kt
index 8ecb0d2..12fedd1 100644
--- a/libmvi/src/main/java/com/runt/open/mvi/base/model/BaseViewModel.kt
+++ b/libmvi/src/main/java/com/runt/open/mvi/base/model/BaseViewModel.kt
@@ -7,14 +7,18 @@
 import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.core.content.FileProvider
-import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import com.runt.open.mvi.base.BaseActivity
 import com.runt.open.mvi.base.LayoutView
+import com.runt.open.mvi.data.LoadingState
+import com.runt.open.mvi.data.MessageState
+import com.runt.open.mvi.data.PopupMessage
 import com.runt.open.mvi.retrofit.AndroidScheduler
 import io.reactivex.Observable
 import io.reactivex.Observer
 import io.reactivex.schedulers.Schedulers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import okhttp3.Callback
 import okhttp3.OkHttpClient
 import okhttp3.Request
@@ -35,7 +39,18 @@
     var TAG = "";
 
     protected var mActivity : BaseActivity<LayoutView<BaseViewModel> , BaseViewModel>? = null
-    var verifyResult = MutableLiveData<Int>()
+
+    private val _isLoading = MutableStateFlow(LoadingState())
+    val isLoading = _isLoading.asStateFlow()
+
+    val messageSetDismiss = {
+        _messageState.value = _messageState.value.copy(isVisible = false);
+    }
+    private val _messageState = MutableStateFlow(MessageState(isVisible = false, setDismiss = { }))
+    val messageState = _messageState.asStateFlow()
+
+    private val _popupState = MutableStateFlow(PopupMessage(isVisible = false,"",""))
+    val popupState = _popupState.asStateFlow()
 
     open fun onCreate(activity : BaseActivity<LayoutView<BaseViewModel> , BaseViewModel>) {
         mActivity = activity
@@ -46,6 +61,35 @@
         return mActivity!!;
     }
 
+    fun showLoading(message : String = "加载中...") {
+        _isLoading.value = LoadingState(isVisible = true,message = message);
+    }
+
+    fun hideLoading() {
+        _isLoading.value = _isLoading.value.copy(isVisible = false);
+    }
+
+    fun showDialog( title:String = "", message: String = "", confirmText:String = "确定", cancelText:String = "",
+        touchOutside:Boolean = true,//空白和系统返回 是否关闭
+        showClose:Boolean = false,//显示关闭图标(默认不显示)
+        confirmDissmiss:Boolean = true,//点击确定是否关闭
+        cancelDissmiss:Boolean = true,//点击取消是否关闭
+        onDismissRequest : () -> Unit = {},
+        onConfirmRequest : () -> Unit = {}, ){
+
+        _messageState.value = MessageState(title = title, message = message, touchOutside = touchOutside, showClose = showClose, confirmText = confirmText,
+            cancelText = cancelText, confirmDissmiss = confirmDissmiss, cancelDissmiss = cancelDissmiss, onDismissRequest = onDismissRequest,
+            onConfirmRequest = onConfirmRequest, setDismiss = messageSetDismiss);
+    }
+
+    fun showPopupWindow(title : String,message : String){
+        _popupState.value = PopupMessage(isVisible = true,title,message)
+    }
+
+    fun hidePopupWindow(){
+        _popupState.value = _popupState.value.copy(isVisible = false);
+    }
+
     /**
      * 获取用户信息
      */
diff --git a/libmvi/src/main/java/com/runt/open/mvi/data/LoadingState.kt b/libmvi/src/main/java/com/runt/open/mvi/data/LoadingState.kt
new file mode 100644
index 0000000..0e022d2
--- /dev/null
+++ b/libmvi/src/main/java/com/runt/open/mvi/data/LoadingState.kt
@@ -0,0 +1,11 @@
+package com.runt.open.mvi.data
+
+/**
+ * @author Runt(qingingrunt2010@qq.com)
+ * @purpose
+ * @date 5/18/25
+ */
+data class LoadingState(
+    val isVisible: Boolean = false,
+    val message: String = "加载中...",
+)
diff --git a/libmvi/src/main/java/com/runt/open/mvi/data/MessageState.kt b/libmvi/src/main/java/com/runt/open/mvi/data/MessageState.kt
new file mode 100644
index 0000000..3ff6b7e
--- /dev/null
+++ b/libmvi/src/main/java/com/runt/open/mvi/data/MessageState.kt
@@ -0,0 +1,21 @@
+package com.runt.open.mvi.data
+
+/**
+ * @author Runt(qingingrunt2010@qq.com)
+ * @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 onConfirmRequest : () -> Unit = {},
+    var setDismiss:() -> Unit
+)
\ No newline at end of file
diff --git a/libmvi/src/main/java/com/runt/open/mvi/data/PopupMessage.kt b/libmvi/src/main/java/com/runt/open/mvi/data/PopupMessage.kt
new file mode 100644
index 0000000..58767b5
--- /dev/null
+++ b/libmvi/src/main/java/com/runt/open/mvi/data/PopupMessage.kt
@@ -0,0 +1,8 @@
+package com.runt.open.mvi.data
+
+/**
+ * @author Runt(qingingrunt2010@qq.com)
+ * @purpose
+ * @date 5/19/25
+ */
+data class PopupMessage(var isVisible:Boolean,val title:String,val message:String)
\ No newline at end of file
diff --git a/libmvi/src/main/java/com/runt/open/mvi/interfaces/UIIntent.kt b/libmvi/src/main/java/com/runt/open/mvi/interfaces/UIIntent.kt
deleted file mode 100644
index d6dc2b0..0000000
--- a/libmvi/src/main/java/com/runt/open/mvi/interfaces/UIIntent.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.runt.open.mvi.interfaces
-
-/**
- * @author Runt(qingingrunt2010@qq.com)
- * @purpose
- * @date 6/2/24
- */
-interface UIIntent {}
\ No newline at end of file
diff --git a/libmvi/src/main/java/com/runt/open/mvi/interfaces/UIState.kt b/libmvi/src/main/java/com/runt/open/mvi/interfaces/UIState.kt
deleted file mode 100644
index 99dd6bf..0000000
--- a/libmvi/src/main/java/com/runt/open/mvi/interfaces/UIState.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.runt.open.mvi.interfaces
-
-/**
- * @author Runt(qingingrunt2010@qq.com)
- * @purpose
- * @date 6/2/24
- */
-interface UIState {}
\ No newline at end of file
diff --git a/libmvi/src/main/java/com/runt/open/mvi/retrofit/utils/RetrofitUtils.java b/libmvi/src/main/java/com/runt/open/mvi/retrofit/utils/RetrofitUtils.java
index 39695c3..b5b0988 100644
--- a/libmvi/src/main/java/com/runt/open/mvi/retrofit/utils/RetrofitUtils.java
+++ b/libmvi/src/main/java/com/runt/open/mvi/retrofit/utils/RetrofitUtils.java
@@ -38,6 +38,15 @@
         return instance;
     }
 
+    private Retrofit getRetrofit(OkHttpClient client,Retrofit.Builder builder,String url){
+        return builder
+                //设置OKHttpClient
+                .client(client)
+                //设置baseUrl,注意,baseUrl必须后缀"/"
+                .baseUrl(url)
+                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+                .build();
+    }
     /**
      * log输出,gson驼峰转换
      * @return
@@ -53,6 +62,10 @@
         return retrofit.create(clas);
     }
 
+    public <T> T getTempRetrofit(Class<T> clas,String url) {
+        return getRetrofit(getOkHttpClient(new OkHttpClient.Builder().addInterceptor(new HttpLoggingInterceptor(BuildConfig.DEBUG))),
+                new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(true)),url).create(clas);
+    }
     /**
      * log输出,gson不转换驼峰
      * @return
diff --git a/libmvi/src/main/java/com/runt/open/mvi/views/PublicViews.kt b/libmvi/src/main/java/com/runt/open/mvi/views/PublicViews.kt
new file mode 100644
index 0000000..3dd7708
--- /dev/null
+++ b/libmvi/src/main/java/com/runt/open/mvi/views/PublicViews.kt
@@ -0,0 +1,149 @@
+package com.runt.open.mvi.views
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+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.text.style.TextAlign
+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.data.LoadingState
+import com.runt.open.mvi.data.MessageState
+import com.runt.open.mvi.data.PopupMessage
+
+/**
+ * @author Runt(qingingrunt2010@qq.com)
+ * @purpose
+ * @date 5/18/25
+ */
+
+@Composable
+fun LoadingDialog( loadingState : LoadingState) {
+    if (loadingState.isVisible) {
+        Dialog(onDismissRequest = {}) {
+            Column(
+                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
+                )
+            }
+        }
+    }
+}
+
+@Composable
+fun MessageDialog(message : MessageState){
+    if(message.isVisible){
+        Dialog(onDismissRequest = {
+            if(message.cancelDissmiss){
+                message.setDismiss.invoke()
+            }
+            message.onDismissRequest
+        }) {
+            Card(
+                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),
+                        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),
+                        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){
+                                    message.setDismiss.invoke()
+                                }
+                                message.onDismissRequest
+                            }) {
+                                Text(text = message.cancelText)
+                            }
+                        }
+                        Spacer(modifier = Modifier.weight(1f))
+                        Button(onClick = {
+                            if(message.confirmDissmiss){
+                                message.setDismiss.invoke()
+                            }
+                            message.onConfirmRequest
+                        }) {
+                            Text(text = message.confirmText)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+fun PopupWindow(popupMessage : PopupMessage){
+    if(popupMessage.isVisible){
+        Popup(
+            alignment = Alignment.TopCenter,
+        ) {
+            Surface(
+                modifier = Modifier.fillMaxWidth()
+                    .wrapContentHeight().padding(10.dp),
+                shadowElevation = 2.dp,
+                border = BorderStroke(1.dp, Color.Gray) ,
+                shape = RoundedCornerShape(8.dp)
+            ) {
+                Column(
+                    modifier = Modifier.padding(16.dp),
+                    horizontalAlignment = Alignment.CenterHorizontally
+                ) {
+                    Text(popupMessage.title)
+                    Spacer(modifier = Modifier.height(16.dp))
+                    Text(popupMessage.message)
+                    Spacer(modifier = Modifier.height(15.dp))
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

--
Gitblit v1.9.1