app/src/main/AndroidManifest.xml
@@ -1,22 +1,14 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <!--允许访问网络,必选权限--> <uses-permission android:name="android.permission.INTERNET" /> <!--允许获取粗略位置,若用GPS实现定位小蓝点功能则必选--> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!--允许获取精准位置--> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!--允许获取网络状态,用于网络定位,若无gps但仍需实现定位小蓝点功能则此权限必选--> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!--允许获取wifi网络信息,用于网络定位,若无gps但仍需实现定位小蓝点功能则此权限必选--> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!--允许获取wifi状态改变,用于网络定位,若无gps但仍需实现定位小蓝点功能则此权限必选--> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!--允许写设备缓存,用于问题排查--> <!-- 允许访问网络,必选权限 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- 允许获取粗略位置,若用GPS实现定位小蓝点功能则必选 --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 允许获取精准位置 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 允许获取网络状态,用于网络定位,若无gps但仍需实现定位小蓝点功能则此权限必选 --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 允许获取wifi网络信息,用于网络定位,若无gps但仍需实现定位小蓝点功能则此权限必选 --> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 允许获取wifi状态改变,用于网络定位,若无gps但仍需实现定位小蓝点功能则此权限必选 --> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!-- 允许写设备缓存,用于问题排查 --> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-feature @@ -24,48 +16,44 @@ android:required="false" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <!--Android 13版本适配,细化存储权限--> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <!-- Android 13版本适配,细化存储权限 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!--允许通知权限--> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- 普通震动 --> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- 允许通知权限 --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- 普通震动 --> <uses-permission android:name="android.permission.VIBRATE" /> <application android:name=".MyApp" android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:extractNativeLibs="true" android:requestLegacyExternalStorage="true" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/img_logo" android:label="${APP_NAME}" android:networkSecurityConfig="@xml/network_security_config" android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/img_logo" android:supportsRtl="true" android:theme="@style/Theme.Yunwei"> <activity android:name=".ui.work.site.SiteErrorUploadActivity" android:exported="false" /> <activity android:name=".ui.user.login.LoginActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".ui.MainActivity" android:launchMode="singleTask" /> <activity android:name=".ui.user.login.FindPswActivity" android:launchMode="singleTop" /> @@ -87,7 +75,6 @@ <activity android:name=".ui.user.mine.protocol.ProtocolActivity" android:launchMode="singleTop" /> <activity android:name=".ui.work.notice.NoticeActivity" android:launchMode="singleTop" /> @@ -139,42 +126,34 @@ <activity android:name=".ui.work.site.SiteNavActivity" android:launchMode="singleTop" /> <activity android:name="com.xw.yunwei.ui.task.normal.NormalTaskDetailActivity" android:name=".ui.task.normal.NormalTaskDetailActivity" android:launchMode="singleTop" /> <activity android:name="com.xw.yunwei.ui.task.selectCar.SelectCarActivity" android:name=".ui.task.selectCar.SelectCarActivity" android:launchMode="singleTop" /> <activity android:name="com.xw.yunwei.ui.task.create.CreateTaskActivity" android:name=".ui.task.create.CreateTaskActivity" android:launchMode="singleTop" /> <activity android:name="com.xw.yunwei.ui.task.accident.detail.AccidentDetailActivity" android:name=".ui.task.accident.detail.AccidentDetailActivity" android:launchMode="singleTop" /> <activity android:name="com.xw.yunwei.ui.task.accident.handle.AccidentProcessActivity" android:name=".ui.task.accident.handle.AccidentProcessActivity" android:launchMode="singleTop" /> <activity android:name="com.xw.yunwei.ui.task.taskSub.TaskSubActivity" android:name=".ui.task.taskSub.TaskSubActivity" android:launchMode="singleTop" /> <activity android:name="com.xw.yunwei.ui.task.taskDetail.TaskDetailActivity" android:launchMode="singleTop" /> <!-- 配置高德key --> android:name=".ui.task.taskDetail.TaskDetailActivity" android:launchMode="singleTop" /> <!-- 配置高德key --> <meta-data android:name="com.amap.api.v2.apikey" android:value="dc26e21a7c2a494443eddbe52a331f19" /> <!-- android:value="c237d9248cc928ec73f078507122ed7d" />--> android:value="dc26e21a7c2a494443eddbe52a331f19" /> <!-- android:value="c237d9248cc928ec73f078507122ed7d" /> --> <!-- 配置高德定位服务 --> <service android:name="com.amap.api.location.APSService" /> <!--推送消息接收监听器--> <receiver android:name=".push.PushMsgReceiver" <service android:name="com.amap.api.location.APSService" /> <!-- 推送消息接收监听器 --> <receiver android:name=".push.PushMsgReceiver" android:exported="false"> <intent-filter> <action android:name="com.alibaba.push2.action.NOTIFICATION_OPENED" /> @@ -187,7 +166,6 @@ </intent-filter> </receiver> <provider android:name="androidx.core.content.FileProvider" android:authorities="com.xw.yunwei.fileprovider" @@ -198,7 +176,6 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application> </manifest> app/src/main/java/com/xw/yunwei/model/AgreeInfo.kt
New file @@ -0,0 +1,10 @@ package com.xw.yunwei.model //协议信息 data class AgreeInfo( val id: String, val content: String, val type: Int, val webType: Int = 1, val version: String ) app/src/main/java/com/xw/yunwei/net/UserApi.kt
@@ -2,6 +2,7 @@ import com.xw.common.net.interceptor.HttpResponse import com.xw.common.net.interceptor.PageResponse import com.xw.yunwei.model.AgreeInfo import com.xw.yunwei.model.AppPermissionModel import com.xw.yunwei.model.AppVersionModel import com.xw.yunwei.model.ClockInStatusModel @@ -12,8 +13,6 @@ import okhttp3.RequestBody import retrofit2.Call import retrofit2.http.Body import retrofit2.http.Field import retrofit2.http.FormUrlEncoded import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Path @@ -85,4 +84,7 @@ @GET("operation/maintenance-app/api/v1/getAppPermission") fun getAppPermission(): Call<HttpResponse<List<AppPermissionModel>>> /*获取协议配置 */ @GET("operation/maintenance-app/base/v1/loginAgreement/getLoginAgreementList") fun getAgreeConfig(): Call<HttpResponse<List<AgreeInfo>>> } app/src/main/java/com/xw/yunwei/net/UserRepository.kt
@@ -36,6 +36,9 @@ suspend fun clockOut(map: Map<String, Any?>) = api.clockOut(getBody(map)).interceptAwait() /*获取协议配置 */ suspend fun getAgreeConfig() = api.getAgreeConfig().interceptAwait() fun getUserInfo() = flow { emit(api.getUserInfo().interceptAwait()) } app/src/main/java/com/xw/yunwei/ui/user/login/LoginActivity.kt
@@ -1,21 +1,15 @@ package com.xw.yunwei.ui.user.login import android.annotation.SuppressLint import android.graphics.Color import android.text.TextPaint import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.view.View import android.webkit.WebSettings import android.webkit.WebView import android.widget.Button import androidx.core.os.bundleOf import androidx.databinding.DataBindingUtil import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.customview.getCustomView import androidx.lifecycle.Observer import com.blankj.utilcode.util.SpanUtils import com.google.android.material.tabs.TabLayout import com.xw.common.CommonDialog import com.xw.common.Constants import com.xw.common.R import com.xw.common.base.BaseActivity @@ -25,7 +19,7 @@ import com.xw.yunwei.BR import com.xw.yunwei.MyApp import com.xw.yunwei.databinding.ActivityLoginBinding import com.xw.yunwei.databinding.DialogProtocolBinding import com.xw.yunwei.model.AgreeInfo import com.xw.yunwei.ui.MainActivity import com.xw.yunwei.ui.work.car.detail.StandardDescriptionActivity @@ -46,67 +40,39 @@ binding.tabLayout.addTab(tab1, true) binding.tabLayout.addTab(tab2, false) val protocol = SpanUtils().append("我已阅读并同意") .setForegroundColor(getResColor(R.color.gray_9)) .append("《小鹏Robotaxi自动驾驶出行运维隐私政策》").setBold() .setForegroundColor(getResColor(R.color.blue_5)) .setClickSpan(object : ClickableSpan() { override fun onClick(widget: View) { // showProtocolDialog(1) startActivity( StandardDescriptionActivity::class.java, bundleOf("type" to 8, "webType" to 1) ) } override fun updateDrawState(ds: TextPaint) {} }) .append("《第三方共享信息清单》").setBold() .setForegroundColor(getResColor(R.color.blue_5)) .setClickSpan(object : ClickableSpan() { override fun onClick(widget: View) { // showProtocolDialog(2) startActivity( StandardDescriptionActivity::class.java, bundleOf("type" to 9, "webType" to 1) ) } override fun updateDrawState(ds: TextPaint) {} }) .append("《个人信息收集清单》").setBold() .setForegroundColor(getResColor(R.color.blue_5)) .setClickSpan(object : ClickableSpan() { override fun onClick(widget: View) { startActivity( StandardDescriptionActivity::class.java, bundleOf("type" to 10, "webType" to 1) ) } override fun updateDrawState(ds: TextPaint) {} }) .append("《权限调用》").setBold() .setForegroundColor(getResColor(R.color.blue_5)) .setClickSpan(object : ClickableSpan() { override fun onClick(widget: View) { startActivity( StandardDescriptionActivity::class.java, bundleOf("type" to 11, "webType" to 1) ) } override fun updateDrawState(ds: TextPaint) {} }) .create() binding.protocol.text = protocol binding.protocol.highlightColor = Color.TRANSPARENT binding.protocol.movementMethod = LinkMovementMethod.getInstance() viewModel.getAgreeConfig() } } //获取协议文案 private fun getAgreeText( prefix: String, prefixTextColor: Int = R.color.gray_9, list: List<AgreeInfo>, agreeBold: Boolean = true, agreeTextColor: Int = R.color.blue_5 ): CharSequence { val spanUtils = SpanUtils().append(prefix).setForegroundColor(getResColor(prefixTextColor)) list.forEach { spanUtils.append(it.content) if (agreeBold) { spanUtils.setBold() } spanUtils.setForegroundColor(getResColor(agreeTextColor)) .setClickSpan(object : ClickableSpan() { override fun onClick(widget: View) { startActivity( StandardDescriptionActivity::class.java, bundleOf("type" to it.type, "webType" to it.webType) ) } override fun updateDrawState(ds: TextPaint) {} }) } return spanUtils.create() } override fun setListener() { @@ -125,208 +91,43 @@ override fun onTabUnselected(tab: TabLayout.Tab?) {} override fun onTabReselected(tab: TabLayout.Tab?) {} }) binding.login.setOnClickListener { if (viewModel.protocolStatus.value != true) { CommonDialog.showNoticeDialog( context = this, title = "服务协议及隐私保护", content = getAgreeText( prefix = "为了更好地保障您的合法权益,请您阅读并同意以下协议", prefixTextColor = R.color.gray_9, list = viewModel.agreeList.value ?: listOf(), agreeBold = false, agreeTextColor = R.color.gray_9 ), leftOperationText = "不同意", rightOperationText = "同意", rightOperationBlock = { viewModel.protocolStatus.value = true viewModel.login() return@showNoticeDialog true }) return@setOnClickListener } viewModel.login() } viewModel.agreeList.observe(this, Observer<List<AgreeInfo>> { binding.protocol.text = getAgreeText(prefix = "我已阅读并同意", list = it) binding.protocol.highlightColor = Color.TRANSPARENT binding.protocol.movementMethod = LinkMovementMethod.getInstance() }) } override fun setObservable() { super.setObservable() viewModel.protocolStatus.observe(this){ viewModel.protocolStatus.observe(this) { MMKVManager.getInstance().saveBoolean(KeyPreferences.NEED_READ, !it) } viewModel.phone.observe(this){ viewModel.phone.observe(this) { viewModel.canGetCode.value = it.length == 11 } } // 标记两个协议是否都已读到底部 private var userProtocolRead = false private var privacyProtocolRead = false private var currentTab = 1 // 当前选中标签 fun showProtocolDialog(type: Int) { MaterialDialog(this).cornerRadius(15f).cancelable(true).show { customView( com.xw.yunwei.R.layout.dialog_protocol, noVerticalPadding = true, dialogWrapContent = true ) DataBindingUtil.bind<DialogProtocolBinding>(getCustomView())?.let { bind -> // 初始禁用确认按钮 bind.btnRight.isEnabled = false // bind.btnRight.alpha = 0.5f bind.btnRight.setTextColor(getResColor(R.color.gray_7)) val tab1 = bind.protocolLayout.tabLayout.newTab().apply { text = "用户协议" } val tab2 = bind.protocolLayout.tabLayout.newTab().apply { text = "隐私条款" } bind.protocolLayout.tabLayout.addTab(tab1, type == 1) bind.protocolLayout.tabLayout.addTab(tab2, type == 2) bind.btnLeft.setOnClickListener { dismiss() } bind.btnRight.setOnClickListener { viewModel.protocolStatus.value = true dismiss() } initWeb(bind.protocolLayout.web,bind.btnRight) val getProtocol: (Int) -> Unit = { type -> currentTab = type viewModel.getProtocol(type) { setContent(bind.protocolLayout.web, it, bind.btnRight) } } getProtocol(type) bind.protocolLayout.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab?) { tab?.let { when (it.text) { "用户协议" -> getProtocol(1) "隐私条款" -> getProtocol(2) else -> {} } } } override fun onTabUnselected(tab: TabLayout.Tab?) {} override fun onTabReselected(tab: TabLayout.Tab?) {} }) } } } @SuppressLint("SetJavaScriptEnabled") private fun initWeb(webView: WebView, confirmBtn: Button) { //声明WebSettings子类 val webSettings = webView.settings webSettings.domStorageEnabled = true //设置适应Html5 //如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript webSettings.javaScriptEnabled = true //允许js调用 webSettings.allowFileAccess = true //在File域下,能够执行任意的JavaScript代码,同源策略跨域访问能够对私有目录文件进行访问等 webSettings.allowContentAccess = true //是否允许在WebView中访问内容URL(Content Url),默认允许 //缩放操作 webSettings.setSupportZoom(true) //支持缩放,默认为true。是下面那个的前提。 webSettings.builtInZoomControls = true //设置内置的缩放控件。若为false,则该WebView不可缩放 webSettings.displayZoomControls = false //隐藏原生的缩放控件 //其他细节操作 webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK //关闭webview中缓存 webSettings.allowFileAccess = true //设置可以访问文件 webSettings.javaScriptCanOpenWindowsAutomatically = true //支持通过JS打开新窗口 webSettings.loadsImagesAutomatically = true //支持自动加载图片 webSettings.defaultTextEncodingName = "utf-8" //设置编码格式 // 关键:添加滚动监听 webView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> // 只有向下滑动时才检测 if (scrollY > oldScrollY) { checkScrollBottom(webView, confirmBtn) } } } @SuppressLint("SetJavaScriptEnabled") private fun setContent(webView: WebView, content: String?, confirmBtn: Button) { content?.let { webView.loadDataWithBaseURL(null, it, "text/html", "utf-8", null) // confirmBtn.text = if(currentTab == 1) "请阅读协议" else "请阅读条款" // 禁用按钮直到滚动到底 confirmBtn.isEnabled = false // confirmBtn.alpha = 0.5f confirmBtn.setTextColor(getResColor(R.color.gray_7)) // 页面加载完成后延迟检查(确保内容渲染完成) webView.postDelayed({ checkScrollBottom(webView, confirmBtn) }, 500) } } /** * 检测是否滚动到底部 */ private fun checkScrollBottom(webView: WebView, confirmBtn: Button) { webView.evaluateJavascript( """ (function() { var scrollHeight = Math.max( document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight ); var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; var clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0; // 计算距离底部距离,允许20px误差 var distanceToBottom = scrollHeight - scrollTop - clientHeight; return distanceToBottom <= 20; })(); """.trimIndent() ) { result -> val isBottom = result?.trim() == "true" if (isBottom) { // 标记当前协议已读 when (currentTab) { 1 -> userProtocolRead = true 2 -> privacyProtocolRead = true } updateButtonState(confirmBtn) } } } /** * 更新按钮状态:两个协议都必须阅读完才能点击 */ private fun updateButtonState(confirmBtn: Button) { val allRead = userProtocolRead && privacyProtocolRead if (allRead && !confirmBtn.isEnabled) { confirmBtn.isEnabled = true // confirmBtn.alpha = 1.0f confirmBtn.setTextColor(getResColor(R.color.blue_5)) confirmBtn.text = "同意并继续" // 可选:启用动画 confirmBtn.animate() .scaleX(1.05f) .scaleY(1.05f) .setDuration(150) .withEndAction { confirmBtn.animate() .scaleX(1.0f) .scaleY(1.0f) .setDuration(150) .start() } .start() } else if (!allRead) { confirmBtn.isEnabled = false // confirmBtn.alpha = 0.5f confirmBtn.setTextColor(getResColor(R.color.gray_7)) // 提示还需阅读哪个协议 // val hint = if(currentTab == 1){ // when { // !userProtocolRead -> "请阅读协议" // !privacyProtocolRead -> "请阅读条款" // else -> "请阅读协议" // } // }else{ // when { // !privacyProtocolRead -> "请阅读条款" // !userProtocolRead -> "请阅读协议" // else -> "请阅读协议" // } // } // confirmBtn.text = hint } } } app/src/main/java/com/xw/yunwei/ui/user/login/LoginVM.kt
@@ -10,13 +10,10 @@ import com.xw.common.utils.MMKVManager import com.xw.common.utils.ToastUtilCommon import com.xw.yunwei.R import com.xw.yunwei.model.AgreeInfo import com.xw.yunwei.net.UserRepository import com.xw.yunwei.net.WorkRepository import com.xw.yunwei.ui.MainActivity import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.koin.core.component.inject import kotlin.getValue class LoginVM(application: Application) : BaseViewModel(application) { val userRepository: UserRepository by inject() @@ -35,6 +32,8 @@ val protocolStatus = MutableLiveData(false) //协议列表 val agreeList: MutableLiveData<List<AgreeInfo>> = MutableLiveData(mutableListOf<AgreeInfo>()) val onClick = View.OnClickListener { when (it.id) { R.id.protocolStatus -> protocolStatus.value = !protocolStatus.value!! @@ -48,7 +47,7 @@ } } private fun login() { public fun login() { if (protocolStatus.value != true) { ToastUtilCommon.showWarn("请先阅读并同意用户协议和隐私政策") return @@ -69,7 +68,11 @@ return } launchRequest(tips = "登录中...") { val map = mapOf("phoneNumber" to phone.value, "verificationCode" to code.value, "deviceType" to 1) val map = mapOf( "phoneNumber" to phone.value, "verificationCode" to code.value, "deviceType" to 1 ) val data = userRepository.loginByCode(map) data?.let { Constants.token = it.token?.access_token ?: "" @@ -92,13 +95,15 @@ launchRequestFlow( tips = "登录中...", flowBlock = { val map = mapOf("phoneNumber" to phone.value, "password" to psw.value, "deviceType" to 1) val map = mapOf("phoneNumber" to phone.value, "password" to psw.value, "deviceType" to 1) userRepository.loginByPassword(map) }, onCollect = { data -> data?.let { Constants.token = it.token?.access_token ?: "" MMKVManager.getInstance().saveModel(KeyPreferences.USER, it.info?.xpOperationsUser) MMKVManager.getInstance() .saveModel(KeyPreferences.USER, it.info?.xpOperationsUser) startActivity(MainActivity::class.java) finish() } @@ -174,10 +179,11 @@ } } fun getProtocol(type: Int, block: (String) -> Unit) { launchRequest(showLoading = false) { val data = userRepository.getProtocol(type) runOnMainThread { block(data?.content ?: "") } /*获取协议配置 */ fun getAgreeConfig() { launchRequest { val data = userRepository.getAgreeConfig() runOnMainThread { agreeList.postValue(data ?: mutableListOf()) } } } app/src/main/java/com/xw/yunwei/ui/work/site/SiteErrorUploadActivity.kt
New file @@ -0,0 +1,26 @@ package com.xw.yunwei.ui.work.site import android.os.Bundle import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import com.xw.yunwei.R class SiteErrorUploadActivity : AppCompatActivity() { companion object{ fun getParams(params:List<Pair<String,Any>>): Bundle{ } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContentView(R.layout.activity_site_error_upload) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } } } app/src/main/java/com/xw/yunwei/ui/work/site/SiteNavActivity.kt
@@ -155,6 +155,11 @@ adapter.selectItem = it } } binding.btnErrorUpload.setOnClickListener { viewModel.siteItem.value?.let { } } } private fun setBehavior() { app/src/main/res/layout/activity_login.xml
@@ -154,7 +154,6 @@ android:layout_marginHorizontal="@dimen/dp_32" android:layout_marginTop="@dimen/dp_24" android:backgroundTint="@color/blue_5" android:onClick="@{viewModel.onClick}" android:text="登录" android:textSize="@dimen/sp_16" android:textStyle="bold" app/src/main/res/layout/activity_site_error_upload.xml
New file @@ -0,0 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.work.site.SiteErrorUploadActivity"> </androidx.constraintlayout.widget.ConstraintLayout> app/src/main/res/layout/activity_site_nav.xml
@@ -21,24 +21,24 @@ android:layout_height="match_parent" tools:background="@mipmap/img_test_map" /> <!-- <androidx.appcompat.widget.AppCompatTextView--> <!-- showAnim='@{!"".equals(viewModel.selectSiteName)}'--> <!-- android:layout_width="wrap_content"--> <!-- android:layout_height="wrap_content"--> <!-- android:layout_marginBottom="@dimen/dp_80"--> <!-- android:background="@drawable/shape_while_bg_100"--> <!-- android:elevation="@dimen/dp_2"--> <!-- android:paddingHorizontal="@dimen/dp_20"--> <!-- android:paddingVertical="@dimen/dp_6"--> <!-- android:text="@{viewModel.selectSiteName}"--> <!-- android:textColor="@color/gray_9"--> <!-- android:textSize="@dimen/sp_12"--> <!-- android:textStyle="bold"--> <!-- app:layout_constraintBottom_toBottomOf="parent"--> <!-- app:layout_constraintLeft_toLeftOf="parent"--> <!-- app:layout_constraintRight_toRightOf="parent"--> <!-- app:layout_constraintTop_toTopOf="parent"--> <!-- tools:text="点位名称" />--> <!-- <androidx.appcompat.widget.AppCompatTextView--> <!-- showAnim='@{!"".equals(viewModel.selectSiteName)}'--> <!-- android:layout_width="wrap_content"--> <!-- android:layout_height="wrap_content"--> <!-- android:layout_marginBottom="@dimen/dp_80"--> <!-- android:background="@drawable/shape_while_bg_100"--> <!-- android:elevation="@dimen/dp_2"--> <!-- android:paddingHorizontal="@dimen/dp_20"--> <!-- android:paddingVertical="@dimen/dp_6"--> <!-- android:text="@{viewModel.selectSiteName}"--> <!-- android:textColor="@color/gray_9"--> <!-- android:textSize="@dimen/sp_12"--> <!-- android:textStyle="bold"--> <!-- app:layout_constraintBottom_toBottomOf="parent"--> <!-- app:layout_constraintLeft_toLeftOf="parent"--> <!-- app:layout_constraintRight_toRightOf="parent"--> <!-- app:layout_constraintTop_toTopOf="parent"--> <!-- tools:text="点位名称" />--> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/topLayout" @@ -132,9 +132,9 @@ app:behavior_fitToContents="false" app:behavior_halfExpandedRatio="0.58" app:behavior_peekHeight="@dimen/dp_92" tools:behavior_peekHeight="@dimen/dp_500" app:behavior_skipCollapsed="false" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" tools:behavior_peekHeight="@dimen/dp_500"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/location" @@ -207,19 +207,19 @@ <androidx.recyclerview.widget.RecyclerView android:id="@+id/siteRv" showAnim="@{viewModel.siteItem==null}" tools:visibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false" /> android:nestedScrollingEnabled="false" tools:visibility="gone" /> <LinearLayout android:id="@+id/siteDetail" showAnim="@{viewModel.siteItem!=null}" android:layout_width="match_parent" tools:visibility="visible" android:layout_height="wrap_content" android:orientation="vertical" android:visibility="gone"> android:visibility="gone" tools:visibility="visible"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" @@ -292,11 +292,11 @@ android:id="@+id/arrow1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/dp_20" app:layout_constraintTop_toBottomOf="@+id/address" android:src="@drawable/svg_arrow_r" android:layout_marginTop="@dimen/dp_8" app:layout_constraintRight_toRightOf="parent" /> android:layout_marginEnd="@dimen/dp_20" android:src="@drawable/svg_arrow_r" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/address" /> <LinearLayout android:id="@+id/nav" @@ -363,6 +363,18 @@ android:textColor="@color/gray_7" android:textSize="@dimen/sp_14" /> <com.google.android.material.button.MaterialButton android:id="@+id/btn_error_upload" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_20" android:layout_marginTop="@dimen/dp_50" android:backgroundTint="@color/blue_5" android:text="现场情况上报" android:textSize="@dimen/sp_16" android:textStyle="bold" app:cornerRadius="@dimen/dp_4" /> </LinearLayout> </RelativeLayout> common/src/main/java/com/xw/common/CommonDialog.kt
@@ -1,13 +1,15 @@ package com.xw.common import android.content.Context import android.graphics.Color import android.os.Handler import android.os.Looper import android.text.method.LinkMovementMethod import android.util.TypedValue import android.view.Gravity import android.view.ViewGroup import android.view.WindowManager import android.widget.Button import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.databinding.DataBindingUtil import com.afollestad.materialdialogs.LayoutMode @@ -91,4 +93,88 @@ } } /** * 通用提示弹窗 * @param cancelable 是否能够点击物理返回按钮取消 * @param cancelOnTouchOutside 是否能够点击空白区域取消 * @param title 标题 * @param titleTextSize 标题字号大小 单位SP * @param titleTextColor 标题字体颜色资源id * @param content 内容 * @param contentTextSize 内容字号大小 单位SP * @param contentTextColor 内容字体颜色资源id * @param leftOperationText 左边操作按钮文案 * @param leftOperationTextSize 左边操作按钮文案字体大小 * @param leftOperationTextColor 左边操作按钮文案字体颜色 * @param rightOperationText 右边操作按钮文案 * @param rightOperationTextSize 右边操作按钮文案字体大小 * @param rightOperationTextColor 右边操作按钮文案字体颜色 * @param leftOperationBlock 回调事件 返回true dismiss * @param rightOperationBlock 回调事件 返回true dismiss */ fun showNoticeDialog( context: Context, cancelable: Boolean = true, cancelOnTouchOutside: Boolean = true, title: CharSequence, titleTextSize: Float = 18f, titleTextColor: Int = R.color.gray_10, content: CharSequence, contentTextSize: Float = 14f, contentTextColor: Int = R.color.gray_9, leftOperationText: String? = "取消", leftOperationTextSize: Float = 16f, leftOperationTextColor: Int = R.color.gray_10, rightOperationText: String? = "确定", rightOperationTextSize: Float = 16f, rightOperationTextColor: Int = R.color.blue_5, leftOperationBlock: () -> Boolean = { true }, rightOperationBlock: () -> Boolean = { true } ) { Handler(Looper.getMainLooper()).post { val dialog = MaterialDialog(context).apply { customView( R.layout.dialog_notice, noVerticalPadding = true, dialogWrapContent = false ) cancelable(cancelable) cancelOnTouchOutside(cancelOnTouchOutside) cornerRadius(5f) val tvTitle = findViewById<AppCompatTextView>(R.id.title) val tvContent = findViewById<AppCompatTextView>(R.id.content) val btnLeft = findViewById<Button>(R.id.btnLeft) val btnRight = findViewById<Button>(R.id.btnRight) tvTitle.text = title tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, titleTextSize) tvTitle.setTextColor(ContextCompat.getColor(context, titleTextColor)) tvContent.text = content tvContent.highlightColor = Color.TRANSPARENT tvContent.movementMethod = LinkMovementMethod.getInstance() tvContent.setTextSize(TypedValue.COMPLEX_UNIT_SP, contentTextSize) tvContent.setTextColor(ContextCompat.getColor(context, contentTextColor)) btnLeft.text = leftOperationText ?: "" btnLeft.setTextSize(TypedValue.COMPLEX_UNIT_SP, leftOperationTextSize) btnLeft.setTextColor(ContextCompat.getColor(context, leftOperationTextColor)) btnRight.text = rightOperationText ?: "" btnRight.setTextSize(TypedValue.COMPLEX_UNIT_SP, rightOperationTextSize) btnRight.setTextColor(ContextCompat.getColor(context, rightOperationTextColor)) btnLeft.isVisible = !leftOperationText.isNullOrEmpty() btnRight.isVisible = !rightOperationText.isNullOrEmpty() btnLeft.setOnClickListener { if (leftOperationBlock()) { dismiss() } } btnRight.setOnClickListener { if (rightOperationBlock()) { dismiss() } } } dialog.show() dialog.window?.setGravity(Gravity.CENTER) dialog.window?.setWindowAnimations(R.style.AnimLeft) } } } common/src/main/res/drawable/shape_while_bg_5.xml
New file @@ -0,0 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/white" /> <corners android:radius="@dimen/dp_5" /> </shape> common/src/main/res/layout/dialog_notice.xml
New file @@ -0,0 +1,77 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/shape_while_bg_5"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/title" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_32" android:layout_marginHorizontal="@dimen/dp_20" android:textColor="@color/gray_10" android:textSize="@dimen/sp_18" android:gravity="center" android:textStyle="bold" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="文本内容" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/content" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_16" android:layout_marginHorizontal="@dimen/dp_20" android:textColor="@color/gray_10" android:textSize="@dimen/sp_14" android:gravity="center" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/title" tools:text="文本内容" /> <View android:layout_width="match_parent" android:layout_height="1px" android:layout_marginTop="@dimen/dp_30" android:background="@color/gray_4" app:layout_constraintTop_toBottomOf="@+id/content" /> <Button android:id="@+id/btnLeft" android:layout_width="0dp" android:layout_height="@dimen/dp_60" android:layout_marginTop="@dimen/dp_30" android:background="@android:color/transparent" android:text="取消" android:textColor="@color/gray_10" android:textSize="@dimen/sp_16" android:textStyle="bold" app:layout_constraintHorizontal_weight="1" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/content" app:layout_constraintRight_toLeftOf="@+id/btnRight" /> <Button android:id="@+id/btnRight" android:layout_width="0dp" android:layout_height="@dimen/dp_60" android:background="@android:color/transparent" android:layout_marginTop="@dimen/dp_30" android:text="确定" android:textColor="@color/blue_5" android:textSize="@dimen/sp_16" android:textStyle="bold" app:layout_constraintHorizontal_weight="1" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toRightOf="@+id/btnLeft" app:layout_constraintTop_toBottomOf="@id/content" /> </androidx.constraintlayout.widget.ConstraintLayout>