parent
71e9917da3
commit
19dc61e9af
|
|
@ -4,6 +4,14 @@
|
||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2026-02-26T05:37:22.871237900Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=JRBI89BIE6AI5TG6" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ android {
|
||||||
manifestPlaceholders.putAll(mapOf("UMENG_CHANNEL" to name))
|
manifestPlaceholders.putAll(mapOf("UMENG_CHANNEL" to name))
|
||||||
}
|
}
|
||||||
manifestPlaceholders.putAll(mapOf(
|
manifestPlaceholders.putAll(mapOf(
|
||||||
"GETUI_APPID" to "40qbPjPkYs7TnVAYCX0Ig6",
|
"GETUI_APPID" to (project.findProperty("GETUI_APPID") as? String ?: ""),
|
||||||
"GT_INSTALL_CHANNEL" to "general",
|
"GT_INSTALL_CHANNEL" to "general",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
@ -145,11 +145,33 @@ dependencies {
|
||||||
implementation(libs.face.detection)
|
implementation(libs.face.detection)
|
||||||
implementation(libs.android.gif.drawable)
|
implementation(libs.android.gif.drawable)
|
||||||
implementation(libs.gif.encoder)
|
implementation(libs.gif.encoder)
|
||||||
implementation("com.caverock:androidsvg:1.4")
|
//implementation("com.caverock:androidsvg:1.4")
|
||||||
implementation("io.github.lucksiege:pictureselector:v3.11.2")
|
implementation(libs.pictureselector)
|
||||||
// 压缩库 (可选,建议长图拼接前先压缩防止OOM)
|
implementation(libs.compress)
|
||||||
implementation("io.github.lucksiege:compress:v3.11.2")
|
implementation(libs.matisse)
|
||||||
implementation("io.github.leavesczy:matisse:2.3.0")
|
implementation(libs.cropify)
|
||||||
implementation("com.github.moyuruaizawa:cropify:0.5.2")
|
//noinspection GradleDynamicVersion
|
||||||
|
api("com.alipay.sdk:alipaysdk-android:+@aar")
|
||||||
|
implementation(libs.wechat.sdk) //微信
|
||||||
|
//Retrofit 依赖
|
||||||
|
implementation(libs.retrofit)
|
||||||
|
implementation(libs.retrofit.kotlin.serialization)
|
||||||
|
implementation (libs.retrofit.converter.gson)
|
||||||
|
implementation(libs.okhttp)
|
||||||
|
implementation(libs.okhttp.logging.interceptor)
|
||||||
|
|
||||||
|
//友盟
|
||||||
|
implementation (libs.umeng.umsdk.common)// 必选
|
||||||
|
implementation (libs.umeng.umsdk.asms)// 必选
|
||||||
|
implementation (libs.umeng.umsdk.apm) // U-APM包依赖(必选)
|
||||||
|
implementation (libs.umeng.umsdk.share.core)//分享核心库,必选
|
||||||
|
implementation (libs.umeng.umsdk.share.wx) //微信完整版
|
||||||
|
//分包
|
||||||
|
implementation (libs.tencent.helper) //腾讯分包
|
||||||
|
implementation (files("libs/channelsdk-0.2.2.aar")) //快手分包
|
||||||
|
implementation (files("libs/humesdk-1.0.0.aar")) //巨量分包
|
||||||
|
|
||||||
|
implementation(libs.android.cn.oaid) //获取手机设备id
|
||||||
|
implementation(libs.fastaes) //解密
|
||||||
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -27,6 +27,22 @@
|
||||||
<!--开关wifi状态,解决国内机型移动网络权限问题需要-->
|
<!--开关wifi状态,解决国内机型移动网络权限问题需要-->
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||||
|
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<package android:name="com.eg.android.AlipayGphone" /> <!-- 支付宝 -->
|
||||||
|
<package android:name="hk.alipay.wallet" /> <!-- AlipayHK -->
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<package android:name="com.tencent.mm" />
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="com.getui.sdk.action" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".BaseApplication"
|
android:name=".BaseApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|
@ -48,7 +64,8 @@
|
||||||
<activity
|
<activity
|
||||||
android:name="com.img.rabbit.MainActivity"
|
android:name="com.img.rabbit.MainActivity"
|
||||||
android:theme="@style/SplashTheme"
|
android:theme="@style/SplashTheme"
|
||||||
android:exported="true">
|
android:exported="true"
|
||||||
|
android:launchMode="singleTask">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
|
@ -56,6 +73,22 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".WebViewActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".wxapi.WXEntryActivity"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
|
android:exported="true"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||||
|
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
|
||||||
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
|
@ -65,6 +98,7 @@
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/filepath_data" />
|
android:resource="@xml/filepath_data" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -4,7 +4,11 @@ import android.app.Application
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.img.rabbit.utils.NetworkMonitor
|
import com.img.rabbit.utils.NetworkMonitor
|
||||||
import com.g.gysdk.GYManager
|
import com.g.gysdk.GYManager
|
||||||
|
import com.img.rabbit.config.Constants
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.umeng.analytics.MobclickAgent
|
||||||
|
import com.umeng.commonsdk.UMConfigure
|
||||||
|
import com.umeng.socialize.PlatformConfig
|
||||||
|
|
||||||
|
|
||||||
class BaseApplication : Application() {
|
class BaseApplication : Application() {
|
||||||
|
|
@ -18,6 +22,21 @@ class BaseApplication : Application() {
|
||||||
initMMKV()
|
initMMKV()
|
||||||
// 初始化个推SDK
|
// 初始化个推SDK
|
||||||
initGeTuiOneKeyLogin()
|
initGeTuiOneKeyLogin()
|
||||||
|
// 初始化友盟
|
||||||
|
initUM()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化友盟
|
||||||
|
*/
|
||||||
|
private fun initUM() {
|
||||||
|
UMConfigure.setLogEnabled(true)
|
||||||
|
|
||||||
|
PlatformConfig.setFileProvider("${BuildConfig.APPLICATION_ID}.fileprovider")
|
||||||
|
PlatformConfig.setWeixin(Constants.WechatAppId, Constants.WechatAppSecret)
|
||||||
|
MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO)
|
||||||
|
|
||||||
|
UMConfigure.setProcessEvent(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initGeTuiOneKeyLogin() {
|
private fun initGeTuiOneKeyLogin() {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.img.rabbit
|
package com.img.rabbit
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
|
@ -7,11 +8,21 @@ import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.animation.core.Animatable
|
import androidx.compose.animation.core.Animatable
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.ClickableText
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -21,23 +32,43 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.img.rabbit.config.Constants
|
||||||
|
import com.img.rabbit.config.Constants.agreementUrl
|
||||||
|
import com.img.rabbit.config.Constants.privacyUrl
|
||||||
import com.img.rabbit.pages.LoginScreen
|
import com.img.rabbit.pages.LoginScreen
|
||||||
|
import com.img.rabbit.pages.LoginScreenType
|
||||||
import com.img.rabbit.pages.MainScreen
|
import com.img.rabbit.pages.MainScreen
|
||||||
|
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||||
|
import com.img.rabbit.utils.ChannelUtils
|
||||||
|
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
|
||||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||||
import com.img.rabbit.viewmodel.LoginViewModel
|
import com.img.rabbit.viewmodel.LoginViewModel
|
||||||
import com.img.rabbit.viewmodel.SplashViewModel
|
import com.img.rabbit.viewmodel.SplashViewModel
|
||||||
|
import com.umeng.analytics.MobclickAgent
|
||||||
|
import com.umeng.commonsdk.UMConfigure
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
// 必须在 super.onCreate 之前调用
|
// 必须在 super.onCreate 之前调用
|
||||||
val splashScreen = installSplashScreen()
|
val splashScreen = installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
initUM()
|
||||||
|
|
||||||
// 启用Edge-to-Edge模式(沉浸模式)
|
// 启用Edge-to-Edge模式(沉浸模式)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
@ -46,6 +77,15 @@ class MainActivity : ComponentActivity() {
|
||||||
val splashViewModel: SplashViewModel = viewModel()
|
val splashViewModel: SplashViewModel = viewModel()
|
||||||
val generalViewModel: GeneralViewModel = viewModel()
|
val generalViewModel: GeneralViewModel = viewModel()
|
||||||
val loginViewModel: LoginViewModel = viewModel()
|
val loginViewModel: LoginViewModel = viewModel()
|
||||||
|
val context = LocalContext.current
|
||||||
|
var showSplash by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
//获取服务器时间
|
||||||
|
generalViewModel.getServerTime()
|
||||||
|
// 获取用户配置
|
||||||
|
loginViewModel.requestUserConfig()
|
||||||
|
//初始化微信登录
|
||||||
|
loginViewModel.initWXApi(this)
|
||||||
|
|
||||||
// 设置启动页显示条件
|
// 设置启动页显示条件
|
||||||
splashScreen.setKeepOnScreenCondition {
|
splashScreen.setKeepOnScreenCondition {
|
||||||
|
|
@ -53,26 +93,69 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
AppTheme {
|
AppTheme {
|
||||||
SplashScreenContent {
|
SplashScreenContent{
|
||||||
val token = generalViewModel.kv.decodeString("token")
|
//未同意提示政策弹窗
|
||||||
// 未登录,显示登录页
|
if (generalViewModel.agreementStatus.value == false){
|
||||||
if (token?.isNotEmpty() == false && !loginViewModel.isLogin.value) {
|
//同意继续
|
||||||
// 显示登录页
|
PrivacyPolicyScreen(
|
||||||
LoginScreen(generalViewModel = generalViewModel, loginViewModel = loginViewModel)
|
viewModel = loginViewModel,
|
||||||
} else {
|
) { isAllowPrivacyPolicy ->
|
||||||
//已登录,显示主页面
|
if (isAllowPrivacyPolicy) {
|
||||||
MainScreen(generalViewModel = generalViewModel, loginViewModel = loginViewModel)
|
generalViewModel.setIsAgreement(true)
|
||||||
|
showSplash = true
|
||||||
|
} else {
|
||||||
|
// 不同意隐私协议政策,直接退出应用
|
||||||
|
(context as MainActivity).finish()
|
||||||
|
// 强制退出应用进程
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
showSplash = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if(showSplash){
|
||||||
|
val token = PreferenceUtil.getAccessToken()
|
||||||
|
// 未登录,显示登录页
|
||||||
|
if (token.isNullOrEmpty() && !loginViewModel.isLogin.value) {
|
||||||
|
// 同意隐私协议政策,检验是否有一键登录权限
|
||||||
|
loginViewModel.oneKeyLoginForGeTuiSdk(context as Activity) { isAllowShowOneKeyScreen ->
|
||||||
|
if (isAllowShowOneKeyScreen) {
|
||||||
|
loginViewModel.loginScreenType.value = LoginScreenType.LOGIN_ONE_KEY
|
||||||
|
} else {
|
||||||
|
// 检验是否有一键登录权限失败,显示验证码登录
|
||||||
|
loginViewModel.loginScreenType.value = LoginScreenType.LOGIN_CAPTCHA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示登录页
|
||||||
|
LoginScreen(generalViewModel = generalViewModel, loginViewModel = loginViewModel, isVisibilityBreak = false)
|
||||||
|
} else {
|
||||||
|
//已登录,显示主页面
|
||||||
|
MainScreen(generalViewModel = generalViewModel, loginViewModel = loginViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟加载过程,2秒后关闭启动页
|
// 模拟加载过程,2秒后关闭启动页
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
delay(100L)
|
delay(500L)
|
||||||
splashViewModel.setLoading(false)
|
splashViewModel.setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化友盟
|
||||||
|
*/
|
||||||
|
private fun initUM() {
|
||||||
|
UMConfigure.preInit(applicationContext, Constants.UmengAppkey, ChannelUtils.getChannel(applicationContext))
|
||||||
|
UMConfigure.init(this, Constants.UmengAppkey, ChannelUtils.getChannel(applicationContext), UMConfigure.DEVICE_TYPE_PHONE, "")
|
||||||
|
MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -128,6 +211,212 @@ fun SplashScreenContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PrivacyPolicyScreen(viewModel: LoginViewModel, onAgreementChange: (Boolean) -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
){
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color(0xCC000000))
|
||||||
|
){
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 38.dp, vertical = 213.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.background(Color.White, shape = RoundedCornerShape(26.dp))
|
||||||
|
){
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.ic_privacy_policy_top_mask),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
alignment = Alignment.TopCenter
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 36.dp)
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "用户协议与隐私政策",
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentSize()
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = Color(0xFF1A1A1A)
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
val agreement = "请您务必审慎阅读、充分理解《服务协议》与《隐私政策》各条款,包括但不限于:为了更好的向您提供服务,我们需要访问您的相册、相机等。您可以阅读《隐私政策》了解详细信息。如果您同意,请点击下面同意按钮开始接受我们的服务。"
|
||||||
|
val annotatedText = buildAnnotatedString {
|
||||||
|
append(agreement)
|
||||||
|
val startIndexForService = agreement.indexOf("《服务协议》")
|
||||||
|
val startIndexPrivacy1 = agreement.indexOf("《隐私政策》")
|
||||||
|
val startIndexPrivacy2 = agreement.lastIndexOf("《隐私政策》")
|
||||||
|
|
||||||
|
val serviceLength = "《服务协议》".length
|
||||||
|
val privacyLength = "《服务协议》".length
|
||||||
|
|
||||||
|
// 高亮显示 "《服务协议》"
|
||||||
|
addStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color(0xFF0066CC),
|
||||||
|
textDecoration = TextDecoration.None
|
||||||
|
),
|
||||||
|
start = startIndexForService, // "《服务协议》" 开始下标13
|
||||||
|
end = startIndexForService + serviceLength // "《服务协议》" 结束下标19
|
||||||
|
)
|
||||||
|
addStringAnnotation(
|
||||||
|
tag = "AGREEMENT",
|
||||||
|
annotation = "service_agreement",
|
||||||
|
start = startIndexForService,
|
||||||
|
end = startIndexForService + serviceLength
|
||||||
|
)
|
||||||
|
|
||||||
|
// 高亮显示 "《隐私政策》"
|
||||||
|
addStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color(0xFF0066CC),
|
||||||
|
textDecoration = TextDecoration.None
|
||||||
|
),
|
||||||
|
start = startIndexPrivacy1, // "《隐私政策》" 开始下标20
|
||||||
|
end = startIndexPrivacy1 + privacyLength // "《隐私政策》" 结束下标26
|
||||||
|
)
|
||||||
|
addStringAnnotation(
|
||||||
|
tag = "PRIVACY",
|
||||||
|
annotation = "privacy_policy",
|
||||||
|
start = startIndexPrivacy1,
|
||||||
|
end = startIndexPrivacy1 + privacyLength
|
||||||
|
)
|
||||||
|
|
||||||
|
// 高亮显示 "《隐私政策》"
|
||||||
|
addStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color(0xFF0066CC),
|
||||||
|
textDecoration = TextDecoration.None
|
||||||
|
),
|
||||||
|
start = startIndexPrivacy2, // "《隐私政策》" 开始下标
|
||||||
|
end = startIndexPrivacy2 + privacyLength // "《隐私政策》" 结束下标
|
||||||
|
)
|
||||||
|
addStringAnnotation(
|
||||||
|
tag = "PRIVACY",
|
||||||
|
annotation = "privacy_policy",
|
||||||
|
start = startIndexPrivacy2,
|
||||||
|
end = startIndexPrivacy2 + privacyLength
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ClickableText(
|
||||||
|
text = annotatedText,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
style = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color(0xFF1A1A1A)
|
||||||
|
),
|
||||||
|
onClick = { offset ->
|
||||||
|
val agreementAnnotation = annotatedText.getStringAnnotations("AGREEMENT", offset, offset).firstOrNull()
|
||||||
|
val privacyAnnotation = annotatedText.getStringAnnotations("PRIVACY", offset, offset).firstOrNull()
|
||||||
|
|
||||||
|
when {
|
||||||
|
agreementAnnotation != null -> {
|
||||||
|
openAgreement(context, "服务协议", agreementUrl, false)
|
||||||
|
}
|
||||||
|
privacyAnnotation != null -> {
|
||||||
|
openAgreement(context, "隐私政策", privacyUrl, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
) {
|
||||||
|
//同意按钮,用户协议与隐私政策
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 33.dp, end = 33.dp)
|
||||||
|
.background(
|
||||||
|
Color(0xFF252525),
|
||||||
|
shape = RoundedCornerShape(359.dp),
|
||||||
|
)
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
onAgreementChange(true)
|
||||||
|
viewModel.setIsPolicyAgreement(true)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"同意",
|
||||||
|
color = Color(0xFFC2FF43),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(vertical = 12.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//不同按钮,意用户协议与隐私政策
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 33.dp, end = 33.dp)
|
||||||
|
.background(
|
||||||
|
Color(0x00000000),
|
||||||
|
shape = RoundedCornerShape(359.dp),
|
||||||
|
)
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
onAgreementChange(false)
|
||||||
|
viewModel.setIsPolicyAgreement(false)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"不同意",
|
||||||
|
color = Color(0xFFAAAAAA),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(vertical = 12.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.img.rabbit
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
class WebViewActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|
||||||
|
// 启用Edge-to-Edge模式(沉浸模式)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
setContentView(R.layout.layout_web)
|
||||||
|
|
||||||
|
val webView: WebView = findViewById(R.id.webView)
|
||||||
|
val ivWebBreak: ImageView = findViewById(R.id.iv_web_break)
|
||||||
|
val ivWebTitle: TextView = findViewById(R.id.iv_web_title)
|
||||||
|
|
||||||
|
val title = intent.getStringExtra("title") ?: ""
|
||||||
|
val url = intent.getStringExtra("url") ?: ""
|
||||||
|
|
||||||
|
ivWebBreak.setOnClickListener { finish() }
|
||||||
|
ivWebTitle.text = title
|
||||||
|
|
||||||
|
webView.settings.javaScriptEnabled = true
|
||||||
|
webView.webViewClient = WebViewClient()
|
||||||
|
webView.loadUrl(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package com.img.rabbit.bean
|
|
||||||
|
|
||||||
data class UserInfo(
|
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
|
||||||
val login: Boolean,
|
|
||||||
)
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
|
data class AlipayBean(
|
||||||
|
val resultStatus: String?, // 状态码:9000-成功,6001-取消,4000-失败
|
||||||
|
val result: String?, // 包含 auth_code 等关键信息
|
||||||
|
val memo: String? // 提示语
|
||||||
|
)
|
||||||
|
fun Map<String, String>.toAlipayResult(): AlipayBean {
|
||||||
|
return AlipayBean(
|
||||||
|
resultStatus = this["resultStatus"],
|
||||||
|
result = this["result"],
|
||||||
|
memo = this["memo"]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.img.rabbit.bean
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
data class ClothingBean(
|
data class ClothingBean(
|
||||||
//衣服索引(区分男女)
|
//衣服索引(区分男女)
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
|
data class ErrorBean(var code: String,var message: String)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.img.rabbit.bean
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
data class FormatBean(
|
data class FormatBean(
|
||||||
//格式id
|
//格式id
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.img.rabbit.bean
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
data class HairstyleBean(
|
data class HairstyleBean(
|
||||||
//发型索引(区分男女)
|
//发型索引(区分男女)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.img.rabbit.bean
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.img.rabbit.bean
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.img.rabbit.bean
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
data class ResizeBean(
|
data class ResizeBean(
|
||||||
//尺寸id
|
//尺寸id
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
|
data class UserInfo(
|
||||||
|
val user_id: Int,
|
||||||
|
val name: String,
|
||||||
|
val avater: String,
|
||||||
|
val token: String,
|
||||||
|
val login: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.img.rabbit.bean.local
|
||||||
|
|
||||||
|
data class WxBean(
|
||||||
|
val code: String,
|
||||||
|
val state: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AlipayParamEntity(
|
||||||
|
val param: String = ""
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CaptchaCodeEntity(
|
||||||
|
val timestamp: String = ""
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ConfigEntity {
|
||||||
|
@SerializedName("client.popup.display") //显示开关控制
|
||||||
|
var popupConfig: PopupConfigEntity? = null
|
||||||
|
|
||||||
|
@SerializedName("client.time.setting") //弹窗时间控制
|
||||||
|
var popupTimeConfig: PopupTimeConfigEntity? = null
|
||||||
|
|
||||||
|
@SerializedName("client.weixin.open.appid") //微信appid
|
||||||
|
var wxAppId: String = ""
|
||||||
|
|
||||||
|
@SerializedName("client.version.upgrade") //版本更新
|
||||||
|
var versionEntity: VersionEntity? = null
|
||||||
|
|
||||||
|
@SerializedName("client.weixin.share") //微信分享
|
||||||
|
var wxShareEntity: WxShareEntity? = null
|
||||||
|
|
||||||
|
@SerializedName("client.guide.enable")
|
||||||
|
var guideEnable: Boolean? = true //是否开启引导页
|
||||||
|
|
||||||
|
@SerializedName("client.pay.agreement") //是否显示支付协议
|
||||||
|
var payAgreementEnable: Boolean? = true
|
||||||
|
|
||||||
|
@SerializedName("client.login.type") //登录方式
|
||||||
|
var loginType: List<String>? = emptyList()
|
||||||
|
|
||||||
|
@SerializedName("client.ad.switch") //广告总开关
|
||||||
|
var adSwitch: Boolean = false
|
||||||
|
|
||||||
|
@SerializedName("client.service.phone") //客服电话
|
||||||
|
var servicePhoneList: List<String> = emptyList()
|
||||||
|
|
||||||
|
@SerializedName("client.chatwarning") //聊天安全提示
|
||||||
|
var chatWarning: String? = null
|
||||||
|
|
||||||
|
@SerializedName("client.travel.ad") //聊天安全提示
|
||||||
|
val travelAd: List<String> = emptyList()
|
||||||
|
|
||||||
|
// 圈子-顶部banner占位图配置
|
||||||
|
@SerializedName("client.team.ad") //聊天安全提示
|
||||||
|
val teamAd: List<String> = emptyList()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PopupConfigEntity(
|
||||||
|
val android_alipay: Boolean = true,
|
||||||
|
val android_wxpay: Boolean = true,
|
||||||
|
val guest_payment: Boolean = false,
|
||||||
|
val honor_tip_double_install: Boolean = false,
|
||||||
|
val huawei_map: Boolean = false,
|
||||||
|
val nonvip_search_results: Boolean = true,
|
||||||
|
val search_phone: Boolean = true,
|
||||||
|
val sos_entry: Boolean = true,
|
||||||
|
val timelimit_discount: Boolean = true,
|
||||||
|
val vip_back_popup: Boolean = true,
|
||||||
|
val vivo_login_before_payment: Boolean = false,
|
||||||
|
val chat_non_vip_Banned: Boolean = false,
|
||||||
|
val hot_group: Boolean = false,
|
||||||
|
val group_list: Boolean = false,
|
||||||
|
val search_location: Boolean = true,
|
||||||
|
val service_display: Boolean = true,
|
||||||
|
val home_tip_double_install: Boolean = false,
|
||||||
|
// Vip优惠全屏页面,默认不显示:从进入程序开始计算到弹出框的时间(当pay_pop=true时生效,需要PopupTimeConfigEntity下的pay_pop_time进行倒计时)
|
||||||
|
val pay_pop: Boolean = false,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PopupTimeConfigEntity(
|
||||||
|
val buy_vip_tip: Long = 45L,
|
||||||
|
val search_phone: Long = 7L,
|
||||||
|
val signup_tip: Long = 45L,
|
||||||
|
val min_discount: Long = 28L,
|
||||||
|
val min_member_tip: Long = 10000L,
|
||||||
|
val order_item_top: Int = 3,
|
||||||
|
/// 进入APP后是否弹出开通会员界面弹出,默认20s后显示
|
||||||
|
val pay_pop_time: Long = 20L,
|
||||||
|
//// 默认支付类型,1支付宝,2微信
|
||||||
|
val pay_pop_type: Int = 1,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserConfigEntity(
|
||||||
|
var token: String = "",
|
||||||
|
var temp: Boolean = false,
|
||||||
|
var name: String = "",
|
||||||
|
var user_id: String = "",
|
||||||
|
var nowtime: String = "",
|
||||||
|
var config: ConfigEntity? = null
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class UserEntity {
|
||||||
|
val user_id: String = ""
|
||||||
|
val name: String = ""
|
||||||
|
val avater: String = ""
|
||||||
|
val token: String = ""
|
||||||
|
|
||||||
|
//是否登录,有本地登录后写入(非接口字段)
|
||||||
|
var isLogin: Boolean = false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class VersionEntity(
|
||||||
|
var description: String = "",
|
||||||
|
var force: Boolean = false,
|
||||||
|
var last_version_force: String = "",
|
||||||
|
var title: String = "",
|
||||||
|
var url: String = "",
|
||||||
|
var app_size: String = "",
|
||||||
|
var version: String = ""
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class WxShareEntity(
|
||||||
|
val content: String = "",
|
||||||
|
val image: String = "",
|
||||||
|
val link: String = "",
|
||||||
|
val title: String = ""
|
||||||
|
)
|
||||||
|
|
@ -43,10 +43,10 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.img.rabbit.R
|
import com.img.rabbit.R
|
||||||
import com.img.rabbit.bean.ClothingBean
|
import com.img.rabbit.bean.local.ClothingBean
|
||||||
import com.img.rabbit.bean.FormatBean
|
import com.img.rabbit.bean.local.FormatBean
|
||||||
import com.img.rabbit.bean.HairstyleBean
|
import com.img.rabbit.bean.local.HairstyleBean
|
||||||
import com.img.rabbit.bean.ResizeBean
|
import com.img.rabbit.bean.local.ResizeBean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 底部画板(证件)选择器
|
* 底部画板(证件)选择器
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package com.img.rabbit.config
|
|
||||||
|
|
||||||
object Common {
|
|
||||||
const val privacyUrl = "https://www.baidu.com"
|
|
||||||
const val agreementUrl = "https://www.baidu.com"
|
|
||||||
}
|
|
||||||
|
|
@ -2,10 +2,10 @@ package com.img.rabbit.config
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.img.rabbit.R
|
import com.img.rabbit.R
|
||||||
import com.img.rabbit.bean.ClothingBean
|
import com.img.rabbit.bean.local.ClothingBean
|
||||||
import com.img.rabbit.bean.FormatBean
|
import com.img.rabbit.bean.local.FormatBean
|
||||||
import com.img.rabbit.bean.HairstyleBean
|
import com.img.rabbit.bean.local.HairstyleBean
|
||||||
import com.img.rabbit.bean.ResizeBean
|
import com.img.rabbit.bean.local.ResizeBean
|
||||||
|
|
||||||
object CommonData {
|
object CommonData {
|
||||||
//背景颜色
|
//背景颜色
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.img.rabbit.config
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val RELEASE_BASE_URL = "https://jitutu.batiao8.com" //release
|
||||||
|
const val DEBUG_BASE_URL = "https://jitutu.batiao8.com"
|
||||||
|
const val LOG_REQUEST = "RabbitRequest"
|
||||||
|
const val agreementUrl = "https://jitutu.batiao8.com/static/policy-jietutu/user.html"//用户协议
|
||||||
|
const val privacyUrl = "https://jitutu.batiao8.com/static/policy-jietutu/privacy-ios.html"//隐私政策
|
||||||
|
|
||||||
|
//const val getuiAppId = "40qbPjPkYs7TnVAYCX0Ig6"//个推appid (gradle.properties)
|
||||||
|
const val WechatAppId = "wx7d1a7d1507482cef"// 微信APPID
|
||||||
|
const val WechatAppSecret = ""//微信secret
|
||||||
|
const val UmengAppkey = ""//TODO 友盟appKey
|
||||||
|
|
||||||
|
//解密
|
||||||
|
const val AESDecrypt = "e4rOtnF8tJjtHO7ecZeJHN1rapED5ImB"
|
||||||
|
//加密字符
|
||||||
|
const val Signature = "xn08hYoizXhZ1zHP8DVqfCm2yHxPmhil"
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,6 @@ import android.view.LayoutInflater
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.core.Animatable
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
|
|
@ -28,7 +27,6 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
|
@ -38,11 +36,13 @@ import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -64,6 +64,7 @@ import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.img.rabbit.R
|
import com.img.rabbit.R
|
||||||
import com.img.rabbit.utils.AgreementTextHelper
|
import com.img.rabbit.utils.AgreementTextHelper
|
||||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||||
|
|
@ -72,19 +73,25 @@ import com.g.gysdk.EloginActivityParam
|
||||||
import com.g.gysdk.GYManager
|
import com.g.gysdk.GYManager
|
||||||
import com.g.gysdk.GYResponse
|
import com.g.gysdk.GYResponse
|
||||||
import com.g.gysdk.GyCallBack
|
import com.g.gysdk.GyCallBack
|
||||||
import com.img.rabbit.config.Common.agreementUrl
|
import com.img.rabbit.bean.local.toAlipayResult
|
||||||
import com.img.rabbit.config.Common.privacyUrl
|
import com.img.rabbit.config.Constants.agreementUrl
|
||||||
|
import com.img.rabbit.config.Constants.privacyUrl
|
||||||
import com.img.rabbit.pages.toolbar.TitleBar
|
import com.img.rabbit.pages.toolbar.TitleBar
|
||||||
|
import com.img.rabbit.provider.storage.GlobalStateManager
|
||||||
|
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||||
|
import com.img.rabbit.utils.StringUtils
|
||||||
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
|
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("UnrememberedMutableState")
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(navController: NavHostController? = null, generalViewModel: GeneralViewModel, loginViewModel: LoginViewModel) {
|
fun LoginScreen(navController: NavHostController? = null, generalViewModel: GeneralViewModel, loginViewModel: LoginViewModel, isVisibilityBreak: Boolean) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scale = remember { Animatable(0f) }
|
|
||||||
val networkStatus by generalViewModel.networkStatus.observeAsState(initial = true)
|
val networkStatus by generalViewModel.networkStatus.observeAsState(initial = true)
|
||||||
var showNetworkDisconnected by remember { mutableStateOf(false) }
|
var showNetworkDisconnected by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// 网络状态监听
|
// 网络状态监听
|
||||||
LaunchedEffect(networkStatus) {
|
LaunchedEffect(networkStatus) {
|
||||||
if (!networkStatus) {
|
if (!networkStatus) {
|
||||||
|
|
@ -94,8 +101,53 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
||||||
Log.w("NetworkStatus","网络已连接")
|
Log.w("NetworkStatus","网络已连接")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scaffold{
|
|
||||||
|
|
||||||
|
// 登录成功后,保存 token
|
||||||
|
LaunchedEffect(loginViewModel.loginState.value) {
|
||||||
|
if (loginViewModel.loginState.value !=null && loginViewModel.loginState.value?.data?.token != null) {
|
||||||
|
//登录成功
|
||||||
|
PreferenceUtil.saveAccessToken(loginViewModel.loginState.value?.data?.token)
|
||||||
|
loginViewModel.setLogin(true)
|
||||||
|
|
||||||
|
Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show()
|
||||||
|
}else if(loginViewModel.loginState.value !=null && loginViewModel.loginState.value?.data?.token == null){
|
||||||
|
//登录失败
|
||||||
|
loginViewModel.setLogin(false)
|
||||||
|
Log.w("LoginScreen","登录失败,无有效的Token")
|
||||||
|
Toast.makeText(context, "登录失败,请重新登录", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalWxAuthorization by mutableStateOf(GlobalStateManager(context).globalWxAuthorizationFlow().collectAsState(initial = ""))
|
||||||
|
LaunchedEffect(globalWxAuthorization.value) {
|
||||||
|
globalWxAuthorization.value?.let { loginViewModel.requestWxLogin(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(loginViewModel.authInfoForAlipay.value) {
|
||||||
|
if(loginViewModel.authInfoForAlipay.value.isEmpty()) return@LaunchedEffect
|
||||||
|
loginViewModel.loginWithAliPay(context){rawResult ->
|
||||||
|
Log.i("loginWithAliPay", "支付宝登录结果:$rawResult")
|
||||||
|
val alipayResult = rawResult.toAlipayResult()
|
||||||
|
// 处理支付宝登录结果
|
||||||
|
when (alipayResult.resultStatus) {
|
||||||
|
"9000" -> {
|
||||||
|
// 登录成功,result 字段中包含 auth_code
|
||||||
|
val authCode = StringUtils.parseAlipayResult(alipayResult.result ?: "")["auth_code"] ?: ""
|
||||||
|
loginViewModel.requestAlipayLogin(authCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
"6001" -> {
|
||||||
|
"用户取消登录"
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
alipayResult.memo ?: "登录失败"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold{
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
|
|
@ -109,78 +161,46 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
||||||
)
|
)
|
||||||
|
|
||||||
// 顶部栏
|
// 顶部栏
|
||||||
TitleBar(navController = navController, paddingValues = it, title = "", showSave = false)
|
TitleBar(navController = navController, paddingValues = it, title = "", showSave = false, showBreak = isVisibilityBreak)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
when (loginViewModel.loginScreenType.value) {
|
Box(modifier = Modifier.fillMaxSize()){
|
||||||
LoginScreenType.LOGIN_ONE_KEY -> {
|
when(loginViewModel.loginScreenType.value){
|
||||||
Box(
|
LoginScreenType.LOGIN_ONE_KEY -> {
|
||||||
modifier = Modifier.fillMaxSize()
|
//一键登录
|
||||||
) {
|
|
||||||
// 检验是否有一键登录权限成功,显示一键登录页
|
|
||||||
OneKeyLoginScreen(context, loginViewModel, generalViewModel)
|
OneKeyLoginScreen(context, loginViewModel, generalViewModel)
|
||||||
|
|
||||||
// 其他登录方式
|
|
||||||
Column (
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(top = 27.dp),
|
|
||||||
verticalArrangement = Arrangement.Bottom
|
|
||||||
){
|
|
||||||
OtherLoginBar(context = context, viewModel = loginViewModel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
LoginScreenType.LOGIN_WX -> {
|
||||||
LoginScreenType.LOGIN_CAPTCHA -> {
|
//微信登录
|
||||||
Box(
|
Box(modifier = Modifier.align(Alignment.Center).padding(bottom = 100.dp)){
|
||||||
modifier = Modifier.fillMaxSize()
|
WxLoginScreen(context, loginViewModel)
|
||||||
) {
|
}
|
||||||
// 显示验证码登录页
|
|
||||||
|
}
|
||||||
|
LoginScreenType.LOGIN_ALIPAY -> {
|
||||||
|
//支付宝登录
|
||||||
|
Box(modifier = Modifier.align(Alignment.Center).padding(bottom = 100.dp)){
|
||||||
|
AliPayLoginScreen(context, loginViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
//默认验证码登录
|
||||||
CaptchaLoginScreen(context, loginViewModel, generalViewModel)
|
CaptchaLoginScreen(context, loginViewModel, generalViewModel)
|
||||||
|
|
||||||
|
|
||||||
// 其他登录方式
|
|
||||||
Column (
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(top = 27.dp),
|
|
||||||
verticalArrangement = Arrangement.Bottom
|
|
||||||
){
|
|
||||||
OtherLoginBar(context = context, viewModel = loginViewModel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
// 显示隐私协议政策(同意后才能继续登录)
|
// 其他登录方式Bar
|
||||||
PrivacyPolicyScreen(viewModel = loginViewModel) { //isAllowPrivacyPolicy ->
|
Column (
|
||||||
loginViewModel.oneKeyLoginForGeTuiSdk(context as Activity) { isAllowShowOneKeyScreen ->
|
modifier = Modifier
|
||||||
if (isAllowShowOneKeyScreen) {
|
.fillMaxSize()
|
||||||
loginViewModel.loginScreenType.value = LoginScreenType.LOGIN_ONE_KEY
|
.padding(top = 27.dp),
|
||||||
} else {
|
verticalArrangement = Arrangement.Bottom
|
||||||
// 检验是否有一键登录权限失败,显示验证码登录
|
){
|
||||||
loginViewModel.loginScreenType.value = LoginScreenType.LOGIN_CAPTCHA
|
OtherLoginBar(context = context, viewModel = loginViewModel)
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if (isAllowPrivacyPolicy) {
|
|
||||||
// 同意隐私协议政策,检验是否有一键登录权限
|
|
||||||
viewModel.oneKeyLoginForGeTuiSdk(context as Activity) { isAllowShowOneKeyScreen ->
|
|
||||||
if (isAllowShowOneKeyScreen) {
|
|
||||||
viewModel.loginScreenType.value = LoginScreenType.LOGIN_ONE_KEY
|
|
||||||
} else {
|
|
||||||
// 检验是否有一键登录权限失败,显示验证码登录
|
|
||||||
viewModel.loginScreenType.value = LoginScreenType.LOGIN_CAPTCHA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 不同意隐私协议政策,直接退出应用
|
|
||||||
(context as MainActivity).onBackPressed()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,139 +211,21 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
||||||
}
|
}
|
||||||
if(showNetworkDisconnected){
|
if(showNetworkDisconnected){
|
||||||
if(!networkStatus){
|
if(!networkStatus){
|
||||||
NetworkDisconnectedPage(onNetworkStatus = {
|
NetworkDisconnectedPage(onNetworkStatus = {isNetworkAvailable->
|
||||||
if(it){
|
if(isNetworkAvailable){
|
||||||
Toast.makeText(context, "网络已连接", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "网络已连接", Toast.LENGTH_SHORT).show()
|
||||||
}else{
|
}else{
|
||||||
Toast.makeText(context, "网络已断开", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "网络已断开", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
generalViewModel.setNetworkStatus(it)
|
generalViewModel.setNetworkStatus(isNetworkAvailable)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun PrivacyPolicyScreen(viewModel: LoginViewModel, onAgreementChange: (Boolean) -> Unit) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
){
|
|
||||||
// 其他登录方式
|
|
||||||
Column (
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(top = 27.dp),
|
|
||||||
verticalArrangement = Arrangement.Bottom
|
|
||||||
){
|
|
||||||
OtherLoginBar(context = context, viewModel = viewModel)
|
|
||||||
}
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(Color(0xCC000000))
|
|
||||||
){
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 38.dp, vertical = 213.dp)
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.background(Color.White, shape = RoundedCornerShape(26.dp))
|
|
||||||
){
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.ic_privacy_policy_top_mask),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize(),
|
|
||||||
alignment = Alignment.TopCenter
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "用户协议与隐私政策",
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 54.dp)
|
|
||||||
.align(Alignment.TopCenter),
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
color = Color(0xFF1A1A1A)
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight()
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
) {
|
|
||||||
//同意按钮,用户协议与隐私政策
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight()
|
|
||||||
.padding(start = 33.dp, end = 33.dp)
|
|
||||||
.background(
|
|
||||||
Color(0xFF252525),
|
|
||||||
shape = RoundedCornerShape(359.dp),
|
|
||||||
)
|
|
||||||
.clickable(
|
|
||||||
indication = null,
|
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
|
||||||
) {
|
|
||||||
onAgreementChange(true)
|
|
||||||
viewModel.setIsPolicyAgreement(true)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
"同意",
|
|
||||||
color = Color(0xFFC2FF43),
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier
|
|
||||||
.wrapContentWidth()
|
|
||||||
.wrapContentHeight()
|
|
||||||
.padding(vertical = 12.dp)
|
|
||||||
.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
//不同按钮,意用户协议与隐私政策
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight()
|
|
||||||
.padding(start = 33.dp, end = 33.dp)
|
|
||||||
.background(
|
|
||||||
Color(0x00000000),
|
|
||||||
shape = RoundedCornerShape(359.dp),
|
|
||||||
)
|
|
||||||
.clickable(
|
|
||||||
indication = null,
|
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
|
||||||
) {
|
|
||||||
onAgreementChange(false)
|
|
||||||
viewModel.setIsPolicyAgreement(false)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
"不同意",
|
|
||||||
color = Color(0xFFAAAAAA),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier
|
|
||||||
.wrapContentWidth()
|
|
||||||
.wrapContentHeight()
|
|
||||||
.padding(vertical = 12.dp)
|
|
||||||
.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证码登录
|
* 验证码登录
|
||||||
*/
|
*/
|
||||||
|
|
@ -542,8 +444,8 @@ private fun CaptchaLoginScreen(context: Context, viewModel: LoginViewModel, gene
|
||||||
showToast = true
|
showToast = true
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
//TODO 请求验证码(请完善requestCaptcha函数)
|
// 请求验证码(请完善requestCaptcha函数)
|
||||||
viewModel.requestCaptcha()
|
viewModel.requestCaptcha(viewModel.userName.value)
|
||||||
|
|
||||||
// 开始倒计时(倒计时应该在requestCaptcha完成后开始)
|
// 开始倒计时(倒计时应该在requestCaptcha完成后开始)
|
||||||
isCaptchaCountdown = true
|
isCaptchaCountdown = true
|
||||||
|
|
@ -597,12 +499,11 @@ private fun CaptchaLoginScreen(context: Context, viewModel: LoginViewModel, gene
|
||||||
showToast = true
|
showToast = true
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
//TODO 验证码登录请求
|
// 验证通过(通过验证码验证),请求登录
|
||||||
Toast.makeText(context, "登录成功!", Toast.LENGTH_SHORT).show()
|
viewModel.requestLoginForCaptcha(
|
||||||
//TODO 登录成功后,保存 token
|
viewModel.userName.value,
|
||||||
generalViewModel.kv.encode("token", "123232123231231")
|
viewModel.captcha.value
|
||||||
// 登录成功后,设置登录状态为 true
|
)
|
||||||
viewModel.setLogin(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
@ -685,11 +586,11 @@ private fun CaptchaLoginScreen(context: Context, viewModel: LoginViewModel, gene
|
||||||
when (annotation.tag) {
|
when (annotation.tag) {
|
||||||
"USER_AGREEMENT" -> {
|
"USER_AGREEMENT" -> {
|
||||||
// 打开用户协议
|
// 打开用户协议
|
||||||
openAgreement(context, agreementUrl)
|
openAgreement(context = context, title = "用户协议", url = agreementUrl)
|
||||||
}
|
}
|
||||||
"PRIVACY_POLICY" -> {
|
"PRIVACY_POLICY" -> {
|
||||||
// 打开隐私政策
|
// 打开隐私政策
|
||||||
openAgreement(context, privacyUrl)
|
openAgreement(context = context, title = "隐私政策", url = privacyUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -783,9 +684,9 @@ private fun OneKeyLoginScreen(context: Context, viewModel: LoginViewModel, gener
|
||||||
// 设置可点击的协议文本
|
// 设置可点击的协议文本
|
||||||
AgreementTextHelper.setupAgreementTextView(agreementText, targets, agreementTextView, isUnderlineText = false) { agreementType ->
|
AgreementTextHelper.setupAgreementTextView(agreementText, targets, agreementTextView, isUnderlineText = false) { agreementType ->
|
||||||
when (agreementType) {
|
when (agreementType) {
|
||||||
"serviceAgreement" -> openAgreement(context, privacyUrl)
|
"serviceAgreement" -> openAgreement(context = context, title = privacyName, url = privacyUrl)
|
||||||
"userAgreement" -> openAgreement(context, agreementUrl)
|
"userAgreement" -> openAgreement(context = context, title = "用户协议", url = agreementUrl)
|
||||||
"privacyAgreement" -> openAgreement(context, privacyUrl)
|
"privacyAgreement" -> openAgreement(context = context, title = "隐私政策", url = privacyUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -808,6 +709,357 @@ private fun OneKeyLoginScreen(context: Context, viewModel: LoginViewModel, gener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun WxLoginScreen(
|
||||||
|
context: Context,
|
||||||
|
viewModel: LoginViewModel,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.ic_launcher_logo),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(86.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "截图兔",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = Color(0xFF1A1A1A),
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "为了更好地为您提供服务,请先完成微信授权",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color(0xFFAAAAAA),
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 30.dp, end = 30.dp, top = 46.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 登录按钮
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 30.dp, end = 30.dp, top = 8.dp)
|
||||||
|
.background(
|
||||||
|
Color(0xFF252525),
|
||||||
|
shape = RoundedCornerShape(359.dp),
|
||||||
|
)
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
// 启动微信验证,请求登录
|
||||||
|
if (viewModel.isPolicyAgreement.value) {
|
||||||
|
//TODO 打开微信登录
|
||||||
|
viewModel.loginWithWechat(context)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"请先同意用户协议和隐私政策",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.ic_wx_icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.padding(start = 12.dp)
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"微信授权登录",
|
||||||
|
color = Color(0xFFC2FF43),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 30.dp, end = 30.dp, top = 14.dp)
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = viewModel.isPolicyAgreement.value,
|
||||||
|
onCheckedChange = { isChecked ->
|
||||||
|
viewModel.setIsPolicyAgreement(isChecked)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.scale(0.35f)
|
||||||
|
.padding(start = 6.dp)
|
||||||
|
.background(
|
||||||
|
if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
|
||||||
|
else Color.Transparent,
|
||||||
|
shape = RoundedCornerShape(36.dp)
|
||||||
|
)
|
||||||
|
.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
|
||||||
|
else Color(0xFFCCCCCC),
|
||||||
|
shape = RoundedCornerShape(36.dp)
|
||||||
|
)
|
||||||
|
.align(Alignment.CenterVertically),
|
||||||
|
colors = androidx.compose.material3.CheckboxDefaults.colors(
|
||||||
|
checkedColor = Color.Transparent, // 隐藏默认背景
|
||||||
|
uncheckedColor = Color.Transparent, // 隐藏默认背景
|
||||||
|
checkmarkColor = Color.White
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
val annotatedText = buildAnnotatedString {
|
||||||
|
append("我已阅读并同意")
|
||||||
|
|
||||||
|
// 用户协议部分
|
||||||
|
pushStringAnnotation(tag = "USER_AGREEMENT", annotation = "user_agreement")
|
||||||
|
withStyle(style = SpanStyle(
|
||||||
|
color = Color(0xFF767676),
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
//textDecoration = TextDecoration.Underline
|
||||||
|
)) {
|
||||||
|
append("《用户协议》")
|
||||||
|
}
|
||||||
|
pop()
|
||||||
|
|
||||||
|
append("和")
|
||||||
|
|
||||||
|
// 隐私政策部分
|
||||||
|
pushStringAnnotation(tag = "PRIVACY_POLICY", annotation = "privacy_policy")
|
||||||
|
withStyle(style = SpanStyle(
|
||||||
|
color = Color(0xFF767676),
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
//textDecoration = TextDecoration.Underline
|
||||||
|
)) {
|
||||||
|
append("《隐私政策》")
|
||||||
|
}
|
||||||
|
pop()
|
||||||
|
}
|
||||||
|
ClickableText(
|
||||||
|
text = annotatedText,
|
||||||
|
onClick = { offset ->
|
||||||
|
annotatedText.getStringAnnotations(offset, offset)
|
||||||
|
.firstOrNull()?.let { annotation ->
|
||||||
|
when (annotation.tag) {
|
||||||
|
"USER_AGREEMENT" -> {
|
||||||
|
// 打开用户协议
|
||||||
|
openAgreement(context = context, title = "用户协议", url = agreementUrl)
|
||||||
|
}
|
||||||
|
"PRIVACY_POLICY" -> {
|
||||||
|
// 打开隐私政策
|
||||||
|
openAgreement(context = context, title = "隐私政策", url = privacyUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style = androidx.compose.ui.text.TextStyle(
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color.Gray
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 4.dp)
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AliPayLoginScreen(
|
||||||
|
context: Context,
|
||||||
|
viewModel: LoginViewModel,
|
||||||
|
) {
|
||||||
|
Column{
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.ic_launcher_logo),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(86.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "截图兔",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = Color(0xFF1A1A1A),
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "为了更好地为您提供服务,请先完成支付宝授权",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color(0xFFAAAAAA),
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 30.dp, end = 30.dp, top = 46.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
// 登录按钮
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 30.dp, end = 30.dp, top = 8.dp)
|
||||||
|
.background(
|
||||||
|
Color(0xFF252525),
|
||||||
|
shape = RoundedCornerShape(359.dp),
|
||||||
|
)
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
// 启动支付宝验证,请求登录
|
||||||
|
if (viewModel.isPolicyAgreement.value) {
|
||||||
|
// 打开支付宝登录
|
||||||
|
viewModel.requestAliPayAuthParam()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"请先同意用户协议和隐私政策",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.ic_alipay_icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.padding(start = 12.dp)
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"支付宝授权登录",
|
||||||
|
color = Color(0xFFC2FF43),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 30.dp, end = 30.dp, top = 14.dp)
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = viewModel.isPolicyAgreement.value,
|
||||||
|
onCheckedChange = { isChecked ->
|
||||||
|
viewModel.setIsPolicyAgreement(isChecked)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.scale(0.35f)
|
||||||
|
.padding(start = 6.dp)
|
||||||
|
.background(
|
||||||
|
if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
|
||||||
|
else Color.Transparent,
|
||||||
|
shape = RoundedCornerShape(36.dp)
|
||||||
|
)
|
||||||
|
.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
|
||||||
|
else Color(0xFFCCCCCC),
|
||||||
|
shape = RoundedCornerShape(36.dp)
|
||||||
|
)
|
||||||
|
.align(Alignment.CenterVertically),
|
||||||
|
colors = androidx.compose.material3.CheckboxDefaults.colors(
|
||||||
|
checkedColor = Color.Transparent, // 隐藏默认背景
|
||||||
|
uncheckedColor = Color.Transparent, // 隐藏默认背景
|
||||||
|
checkmarkColor = Color.White
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
val annotatedText = buildAnnotatedString {
|
||||||
|
append("我已阅读并同意")
|
||||||
|
|
||||||
|
// 用户协议部分
|
||||||
|
pushStringAnnotation(tag = "USER_AGREEMENT", annotation = "user_agreement")
|
||||||
|
withStyle(style = SpanStyle(
|
||||||
|
color = Color(0xFF767676),
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
//textDecoration = TextDecoration.Underline
|
||||||
|
)) {
|
||||||
|
append("《用户协议》")
|
||||||
|
}
|
||||||
|
pop()
|
||||||
|
|
||||||
|
append("和")
|
||||||
|
|
||||||
|
// 隐私政策部分
|
||||||
|
pushStringAnnotation(tag = "PRIVACY_POLICY", annotation = "privacy_policy")
|
||||||
|
withStyle(style = SpanStyle(
|
||||||
|
color = Color(0xFF767676),
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
//textDecoration = TextDecoration.Underline
|
||||||
|
)) {
|
||||||
|
append("《隐私政策》")
|
||||||
|
}
|
||||||
|
pop()
|
||||||
|
}
|
||||||
|
ClickableText(
|
||||||
|
text = annotatedText,
|
||||||
|
onClick = { offset ->
|
||||||
|
annotatedText.getStringAnnotations(offset, offset)
|
||||||
|
.firstOrNull()?.let { annotation ->
|
||||||
|
when (annotation.tag) {
|
||||||
|
"USER_AGREEMENT" -> {
|
||||||
|
// 打开用户协议
|
||||||
|
openAgreement(context = context, title = "用户协议", url = agreementUrl)
|
||||||
|
}
|
||||||
|
"PRIVACY_POLICY" -> {
|
||||||
|
// 打开隐私政策
|
||||||
|
openAgreement(context = context, title = "隐私政策", url = privacyUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style = androidx.compose.ui.text.TextStyle(
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color.Gray
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 4.dp)
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一键登录页
|
* 一键登录页
|
||||||
* 至少包含号码栏(NumberTextview)、品牌露出(SloganTextview)、登录按钮(LoginButton)、隐私确认(PrivacyCheckbox)、隐私标题(PrivacyTextview)
|
* 至少包含号码栏(NumberTextview)、品牌露出(SloganTextview)、登录按钮(LoginButton)、隐私确认(PrivacyCheckbox)、隐私标题(PrivacyTextview)
|
||||||
|
|
@ -993,7 +1245,7 @@ private fun OneKeyLoginScreen(context: Context, viewModel: LoginViewModel) {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun OtherLoginBar(context: Context, viewModel: LoginViewModel) {
|
private fun OtherLoginBar(context: Context, viewModel: LoginViewModel) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -1057,8 +1309,20 @@ private fun OtherLoginBar(context: Context, viewModel: LoginViewModel) {
|
||||||
indication = null,
|
indication = null,
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
//TODO 打开微信登录
|
/*
|
||||||
Toast.makeText(context, "打开微信登录", Toast.LENGTH_SHORT).show()
|
if (viewModel.isPolicyAgreement.value) {
|
||||||
|
//TODO 打开微信登录
|
||||||
|
viewModel.loginWithWechat(context)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"请先同意用户协议和隐私政策",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// 微信登录
|
||||||
|
viewModel.loginScreenType.value = LoginScreenType.LOGIN_WX
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Image(
|
Image(
|
||||||
|
|
@ -1072,8 +1336,20 @@ private fun OtherLoginBar(context: Context, viewModel: LoginViewModel) {
|
||||||
indication = null,
|
indication = null,
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
//TODO 打开支付宝登录
|
/*
|
||||||
Toast.makeText(context, "打开支付宝登录", Toast.LENGTH_SHORT).show()
|
if (viewModel.isPolicyAgreement.value) {
|
||||||
|
// 打开支付宝登录
|
||||||
|
viewModel.requestAliPayAuthParam()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"请先同意用户协议和隐私政策",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// 支付宝登录
|
||||||
|
viewModel.loginScreenType.value = LoginScreenType.LOGIN_ALIPAY
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Image(
|
Image(
|
||||||
|
|
@ -1152,9 +1428,14 @@ private fun oneKeyLogin(
|
||||||
override fun onSuccess(response: GYResponse?) {
|
override fun onSuccess(response: GYResponse?) {
|
||||||
//TODO 登录成功,需要与后端交互
|
//TODO 登录成功,需要与后端交互
|
||||||
Log.i("OneKeyLogin", "onSuccess:$response")
|
Log.i("OneKeyLogin", "onSuccess:$response")
|
||||||
//TODO 登录成功后,保存 token
|
try {
|
||||||
generalViewModel.kv.encode("token", "123232123231231")
|
val jsonObject = JSONObject(response?.msg?:"{}")
|
||||||
viewModel.setLogin(true)
|
val data = jsonObject.getJSONObject("data")
|
||||||
|
val token = data.getString("token")
|
||||||
|
viewModel.requestOneKeyLogin(response?.gyuid?:"", token)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailed(p0: GYResponse?) {
|
override fun onFailed(p0: GYResponse?) {
|
||||||
|
|
@ -1169,10 +1450,32 @@ enum class LoginScreenType {
|
||||||
LOGIN_NORMAL,
|
LOGIN_NORMAL,
|
||||||
LOGIN_ONE_KEY,
|
LOGIN_ONE_KEY,
|
||||||
LOGIN_CAPTCHA,
|
LOGIN_CAPTCHA,
|
||||||
|
LOGIN_WX,
|
||||||
|
LOGIN_ALIPAY,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
private fun PreviewOneKeyLoginScreen() {
|
private fun PreviewOneKeyLoginScreen() {
|
||||||
OneKeyLoginScreen(LocalContext.current, viewModel(), viewModel())
|
OneKeyLoginScreen(LocalContext.current, viewModel(), viewModel())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun PreviewWxLoginScreen() {
|
||||||
|
WxLoginScreen(LocalContext.current, viewModel())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun PreviewAliPayLoginScreen() {
|
||||||
|
AliPayLoginScreen(LocalContext.current, viewModel())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun PreviewLoginScreen() {
|
||||||
|
LoginScreen(navController = rememberNavController(), generalViewModel = viewModel(), loginViewModel = viewModel(), isVisibilityBreak = false)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,8 @@ fun MainScreen(generalViewModel: GeneralViewModel, loginViewModel: LoginViewMode
|
||||||
LoginScreen(
|
LoginScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
generalViewModel = generalViewModel,
|
generalViewModel = generalViewModel,
|
||||||
loginViewModel = loginViewModel
|
loginViewModel = loginViewModel,
|
||||||
|
isVisibilityBreak = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
|
@ -37,10 +38,13 @@ import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
import com.img.rabbit.R
|
import com.img.rabbit.R
|
||||||
|
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -50,6 +54,7 @@ fun MineScreen(
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val vipMember by remember { mutableStateOf(false) }
|
val vipMember by remember { mutableStateOf(false) }
|
||||||
|
val userInfo by remember { mutableStateOf(PreferenceUtil.loginUserInfo()) }
|
||||||
|
|
||||||
// 监听返回事件
|
// 监听返回事件
|
||||||
val currentBackStackEntry = navController.currentBackStackEntry
|
val currentBackStackEntry = navController.currentBackStackEntry
|
||||||
|
|
@ -62,6 +67,7 @@ fun MineScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize().background(Color(0xFFF9F9F9))
|
modifier = Modifier.fillMaxSize().background(Color(0xFFF9F9F9))
|
||||||
){
|
){
|
||||||
|
|
@ -80,21 +86,41 @@ fun MineScreen(
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Image(
|
if(userInfo == null){
|
||||||
painter = painterResource(id = R.mipmap.ic_user_avatar_default),
|
Image(
|
||||||
contentDescription = null,
|
painter = painterResource(id = R.mipmap.ic_user_avatar_default),
|
||||||
contentScale = ContentScale.FillWidth,
|
contentDescription = "用户头像",
|
||||||
modifier = Modifier
|
contentScale = ContentScale.FillWidth,
|
||||||
.size(64.dp)
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(90.dp))
|
.size(64.dp)
|
||||||
.clickable(
|
.clip(RoundedCornerShape(90.dp))
|
||||||
indication = null,
|
.clickable(
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
indication = null,
|
||||||
) {
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
// 处理点击事件
|
) {
|
||||||
Toast.makeText(context, "头像", Toast.LENGTH_SHORT).show()
|
// 处理点击事件
|
||||||
}
|
Toast.makeText(context, "头像", Toast.LENGTH_SHORT).show()
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
AsyncImage(
|
||||||
|
model = userInfo?.avater,
|
||||||
|
contentDescription = "用户头像",
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
.clip(RoundedCornerShape(90.dp))
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
// 处理点击事件
|
||||||
|
Toast.makeText(context, "头像", Toast.LENGTH_SHORT).show()
|
||||||
|
},
|
||||||
|
fallback = painterResource(id = R.mipmap.ic_user_avatar_default),
|
||||||
|
error = painterResource(id = R.mipmap.ic_user_avatar_default)
|
||||||
|
)
|
||||||
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
@ -103,28 +129,62 @@ fun MineScreen(
|
||||||
indication = null,
|
indication = null,
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
// 隐藏TabBar
|
if(userInfo == null){
|
||||||
generalViewModel.setNavigationBarVisible(false)
|
// 隐藏TabBar
|
||||||
// 跳转登录页面
|
generalViewModel.setNavigationBarVisible(false)
|
||||||
navController.navigate("login")
|
// 跳转登录页面
|
||||||
|
navController.navigate("login")
|
||||||
|
} else {
|
||||||
|
//TODO 已登录,跳转个人信息页面
|
||||||
|
//navController.navigate("userInfo")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "登录/注册",
|
text = if(userInfo == null){ "登录/注册" }else{ userInfo?.name?:"" },
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = Color(0xFF1A1A1A),
|
color = Color(0xFF1A1A1A),
|
||||||
modifier = Modifier.wrapContentSize()
|
modifier = Modifier.wrapContentSize()
|
||||||
)
|
)
|
||||||
Text(
|
Row(
|
||||||
text = "登录体验更多功能哦~",
|
|
||||||
fontSize = 14.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = Color(0xFF767676),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.wrapContentSize()
|
|
||||||
.padding(top = 10.dp)
|
.padding(top = 10.dp)
|
||||||
)
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
){
|
||||||
|
// 点击复制ID
|
||||||
|
if (userInfo != null) {
|
||||||
|
val clipboardManager = android.content.Context.CLIPBOARD_SERVICE
|
||||||
|
val clipboard = context.getSystemService(clipboardManager) as android.content.ClipboardManager
|
||||||
|
val clip = android.content.ClipData.newPlainText("User ID", userInfo?.user_id)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
Toast.makeText(context, "已复制到剪贴板", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
){
|
||||||
|
Text(
|
||||||
|
text = if(userInfo == null){ "登录体验更多功能哦~" }else{ "ID:${userInfo?.user_id?:""}" },
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color(0xFF767676),
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentSize()
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(4.dp)
|
||||||
|
)
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.ic_copy),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(12.dp)
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,8 @@ import androidx.navigation.NavController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.img.rabbit.R
|
import com.img.rabbit.R
|
||||||
import com.img.rabbit.bean.ClothingBean
|
import com.img.rabbit.bean.local.ClothingBean
|
||||||
import com.img.rabbit.bean.HairstyleBean
|
import com.img.rabbit.bean.local.HairstyleBean
|
||||||
import com.img.rabbit.components.AppearanceType
|
import com.img.rabbit.components.AppearanceType
|
||||||
import com.img.rabbit.components.DrawingBoardPicker
|
import com.img.rabbit.components.DrawingBoardPicker
|
||||||
import com.img.rabbit.config.CommonData.clothingForFemales
|
import com.img.rabbit.config.CommonData.clothingForFemales
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,46 @@
|
||||||
package com.img.rabbit.pages.screen.make
|
package com.img.rabbit.pages.screen.make
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectDragGestures
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Slider
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.img.rabbit.bean.LongImageBean
|
import com.img.rabbit.bean.local.LongImageBean
|
||||||
import com.img.rabbit.pages.toolbar.TitleBar
|
import com.img.rabbit.pages.toolbar.TitleBar
|
||||||
import com.img.rabbit.utils.ExportFormat
|
import com.img.rabbit.utils.ExportFormat
|
||||||
import com.img.rabbit.utils.ImageUtils.getBitmapFromUri
|
import com.img.rabbit.utils.ImageUtils.getBitmapFromUri
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -34,8 +33,8 @@ import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.img.rabbit.R
|
import com.img.rabbit.R
|
||||||
import com.img.rabbit.config.Common.agreementUrl
|
import com.img.rabbit.config.Constants.agreementUrl
|
||||||
import com.img.rabbit.config.Common.privacyUrl
|
import com.img.rabbit.config.Constants.privacyUrl
|
||||||
import com.img.rabbit.pages.toolbar.TitleBar
|
import com.img.rabbit.pages.toolbar.TitleBar
|
||||||
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
|
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
|
||||||
|
|
||||||
|
|
@ -101,7 +100,7 @@ fun AboutScreen(navController: NavHostController) {
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
// 跳转用户协议页面
|
// 跳转用户协议页面
|
||||||
openAgreement(context, agreementUrl)
|
openAgreement(context = context, title = "用户协议", url = agreementUrl)
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
|
@ -144,7 +143,7 @@ fun AboutScreen(navController: NavHostController) {
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
// 跳转隐私政策页面
|
// 跳转隐私政策页面
|
||||||
openAgreement(context, privacyUrl)
|
openAgreement(context = context, title = "隐私政策", url = privacyUrl)
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -40,7 +39,7 @@ import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.img.rabbit.R
|
import com.img.rabbit.R
|
||||||
import com.img.rabbit.bean.UserInfo
|
import com.img.rabbit.bean.local.UserInfo
|
||||||
import com.img.rabbit.pages.toolbar.TitleBar
|
import com.img.rabbit.pages.toolbar.TitleBar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -48,9 +47,9 @@ fun AccountManagerScreen(navController: NavHostController) {
|
||||||
val userList by remember {
|
val userList by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
listOf(
|
listOf(
|
||||||
UserInfo(1, "张三", true),
|
UserInfo(1, "张三", "https://cdn.batiao8.com/jietutu/logo.png","",true),
|
||||||
UserInfo(2, "李四", false),
|
UserInfo(2, "李四", "https://cdn.batiao8.com/jietutu/logo.png","",false),
|
||||||
UserInfo(3, "王五", false),
|
UserInfo(3, "王五", "https://cdn.batiao8.com/jietutu/logo.png","",false),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import androidx.navigation.compose.rememberNavController
|
||||||
import com.img.rabbit.R
|
import com.img.rabbit.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TitleBar(navController: NavController?, paddingValues: PaddingValues, title: String? = "", showSave: Boolean = false, onSubmit: (() -> Unit)? = null) {
|
fun TitleBar(navController: NavController?, paddingValues: PaddingValues, title: String? = "", showSave: Boolean = false, showBreak: Boolean = true, onSubmit: (() -> Unit)? = null) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxWidth().padding(paddingValues)
|
modifier = Modifier.fillMaxWidth().padding(paddingValues)
|
||||||
){
|
){
|
||||||
|
|
@ -40,17 +40,19 @@ fun TitleBar(navController: NavController?, paddingValues: PaddingValues, title:
|
||||||
.fillMaxWidth().padding(16.dp),
|
.fillMaxWidth().padding(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// 返回按钮
|
if(showBreak){
|
||||||
Icon(
|
// 返回按钮
|
||||||
painter = painterResource(id = R.mipmap.ic_back),
|
Icon(
|
||||||
contentDescription = "返回",
|
painter = painterResource(id = R.mipmap.ic_back),
|
||||||
modifier = Modifier
|
contentDescription = "返回",
|
||||||
.clickable(
|
modifier = Modifier
|
||||||
indication = null,
|
.clickable(
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
indication = null,
|
||||||
) { navController?.popBackStack() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
.padding(end = 26.dp)
|
) { navController?.popBackStack() }
|
||||||
)
|
.padding(end = 26.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth().weight(1f)
|
modifier = Modifier.fillMaxWidth().weight(1f)
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
@file:Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||||
|
|
||||||
|
package com.img.rabbit.provider.api
|
||||||
|
|
||||||
|
import com.img.rabbit.BuildConfig
|
||||||
|
import com.img.rabbit.config.Constants
|
||||||
|
import com.img.rabbit.provider.utils.HeaderInterceptor
|
||||||
|
import com.img.rabbit.provider.utils.RequestInterceptor
|
||||||
|
import com.img.rabbit.provider.utils.ResponseInterceptor
|
||||||
|
import com.img.rabbit.provider.utils.getUnsafeOkHttpClient
|
||||||
|
import com.img.rabbit.viewmodel.`interface`.ServiceVo
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
object ApiManager {
|
||||||
|
private lateinit var retrofit: Retrofit
|
||||||
|
private lateinit var unsafeRetrofit: Retrofit
|
||||||
|
lateinit var serviceVo: ServiceVo
|
||||||
|
|
||||||
|
init {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化方法,抽离出来以便重新初始化
|
||||||
|
private fun initialize() {
|
||||||
|
// 获取基础URL并确保其包含http/https协议
|
||||||
|
val baseUrl = if (BuildConfig.DEBUG) {
|
||||||
|
Constants.DEBUG_BASE_URL
|
||||||
|
} else {
|
||||||
|
Constants.RELEASE_BASE_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!::retrofit.isInitialized) {
|
||||||
|
val client = OkHttpClient().newBuilder()
|
||||||
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(20, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(20, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(RequestInterceptor())
|
||||||
|
.addInterceptor(HeaderInterceptor())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.client(client)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!::unsafeRetrofit.isInitialized) {
|
||||||
|
val client = getUnsafeOkHttpClient().newBuilder()
|
||||||
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(20, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(20, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(HeaderInterceptor())
|
||||||
|
.addInterceptor(RequestInterceptor())
|
||||||
|
.addInterceptor(ResponseInterceptor())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
unsafeRetrofit = Retrofit.Builder()
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.client(client)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!::serviceVo.isInitialized) {
|
||||||
|
serviceVo = unsafeRetrofit.create(ServiceVo::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加重新初始化方法,用于在baseUrl改变时调用
|
||||||
|
fun reinitialize() {
|
||||||
|
// 重置所有实例,使其在下一次访问时重新初始化
|
||||||
|
if (::retrofit.isInitialized) {
|
||||||
|
synchronized(this) {
|
||||||
|
// 标记为未初始化
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||||
|
(this as Object).getClass().getDeclaredField("retrofit").apply {
|
||||||
|
isAccessible = true
|
||||||
|
set(this@ApiManager, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (::unsafeRetrofit.isInitialized) {
|
||||||
|
synchronized(this) {
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||||
|
(this as Object).getClass().getDeclaredField("unsafeRetrofit").apply {
|
||||||
|
isAccessible = true
|
||||||
|
set(this@ApiManager, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (::serviceVo.isInitialized) {
|
||||||
|
synchronized(this) {
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||||
|
(this as Object).getClass().getDeclaredField("serviceVo").apply {
|
||||||
|
isAccessible = true
|
||||||
|
set(this@ApiManager, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新初始化
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.img.rabbit.provider.api
|
||||||
|
|
||||||
|
class ResultVo<T>(val code: Int, val data: T) {
|
||||||
|
val status: Boolean
|
||||||
|
get() {
|
||||||
|
return code == 0
|
||||||
|
}
|
||||||
|
val message: String = ""
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.img.rabbit.provider.storage
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
private val Context.storeData: DataStore<Preferences> by preferencesDataStore(name = "global_state")
|
||||||
|
|
||||||
|
class GlobalStateManager(
|
||||||
|
private val context: Context
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val GLOBAL_LOADING = booleanPreferencesKey("global_loading")
|
||||||
|
private val GLOBAL_WX_AUTHORIZATION = stringPreferencesKey("global_wx_authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun storeGlobalLoading(value: Boolean) {
|
||||||
|
context.storeData.edit { preferences ->
|
||||||
|
preferences[GLOBAL_LOADING] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun isGlobalLoadingFlow(): Flow<Boolean?> {
|
||||||
|
return context.storeData.data.map {
|
||||||
|
preferences ->
|
||||||
|
preferences[GLOBAL_LOADING]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun storeGlobalWxAuthorization(value: String) {
|
||||||
|
context.storeData.edit { preferences ->
|
||||||
|
preferences[GLOBAL_WX_AUTHORIZATION] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun globalWxAuthorizationFlow(): Flow<String?> {
|
||||||
|
return context.storeData.data.map {
|
||||||
|
preferences ->
|
||||||
|
preferences[GLOBAL_WX_AUTHORIZATION]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.img.rabbit.provider.storage
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.img.rabbit.bean.response.UserEntity
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SharedPreferences工具类,用于简化数据持久化操作
|
||||||
|
*/
|
||||||
|
object PreferenceUtil {
|
||||||
|
private const val KEY_ACCESS_TOKEN = "access_token"
|
||||||
|
private const val KEY_X_TOKEN = "x_token"
|
||||||
|
|
||||||
|
private const val KEY_USER_INFO = "user_info"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Gson实例
|
||||||
|
private val gson: Gson = GsonBuilder().create()
|
||||||
|
|
||||||
|
val kv by lazy { MMKV.defaultMMKV() }
|
||||||
|
|
||||||
|
|
||||||
|
//服务器时间和本地时间的偏移量
|
||||||
|
private var timeDiff = 0L
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存AccessToken
|
||||||
|
*/
|
||||||
|
fun saveAccessToken(token: String?) {
|
||||||
|
kv.encode(KEY_ACCESS_TOKEN, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取保存的AccessToken
|
||||||
|
*/
|
||||||
|
fun getAccessToken(): String? {
|
||||||
|
return kv.decodeString(KEY_ACCESS_TOKEN, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveXToken(token: String?) {
|
||||||
|
kv.encode(KEY_X_TOKEN, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getXToken(): String? {
|
||||||
|
return kv.decodeString(KEY_X_TOKEN, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getUserInfos(): MutableList<UserEntity>? {
|
||||||
|
/**
|
||||||
|
*[{"user_id":"25","name":"手机用户0253","avater":"https://cdn.batiao8.com/jietutu/logo.png","token":"45731e27-d101-4ec3-975c-e665cf86a579"},{"user_id":"25","name":"手机用户0253","avater":"https://cdn.batiao8.com/jietutu/logo.png","token":"45731e27-d101-4ec3-975c-e665cf86a579"}]
|
||||||
|
*/
|
||||||
|
return gson.fromJson(kv.decodeString(KEY_USER_INFO, "[]"), Array<UserEntity>::class.java)?.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loginUserInfo(): UserEntity?{
|
||||||
|
return getUserInfos()?.find { it.isLogin }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveUserInfo(userEntity: UserEntity) {
|
||||||
|
val userInfos = getUserInfos()?: mutableListOf()
|
||||||
|
val isContain = userInfos.find { it.user_id == userEntity.user_id } != null
|
||||||
|
if(!isContain){
|
||||||
|
userInfos.add(userEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
kv.encode(KEY_USER_INFO, gson.toJson(userInfos))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeUserInfo(userEntity: UserEntity) {
|
||||||
|
val userInfos = getUserInfos()?: mutableListOf()
|
||||||
|
userInfos.removeIf { it.user_id == userEntity.user_id }
|
||||||
|
kv.encode(KEY_USER_INFO, gson.toJson(userInfos))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有数据
|
||||||
|
*/
|
||||||
|
fun clearAll() {
|
||||||
|
kv.clearAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
//真实的服务器时间
|
||||||
|
fun serverTimeMillis(): Long {
|
||||||
|
return System.currentTimeMillis() + timeDiff * 1000
|
||||||
|
}
|
||||||
|
fun setTimeDiff(timeDiff: Long) {
|
||||||
|
this.timeDiff = timeDiff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.img.rabbit.provider.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import javax.net.ssl.HostnameVerifier
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.TrustManager
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
|
@SuppressLint("CustomX509TrustManager")
|
||||||
|
fun getUnsafeOkHttpClient(): OkHttpClient {
|
||||||
|
try {
|
||||||
|
// 创建一个信任所有证书的 TrustManager
|
||||||
|
val trustAllCerts = arrayOf<TrustManager>(
|
||||||
|
|
||||||
|
object : X509TrustManager {
|
||||||
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
|
override fun checkClientTrusted(
|
||||||
|
chain: Array<out X509Certificate>?,
|
||||||
|
authType: String?
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
|
override fun checkServerTrusted(
|
||||||
|
chain: Array<out X509Certificate>?,
|
||||||
|
authType: String?
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAcceptedIssuers(): Array<X509Certificate> =
|
||||||
|
arrayOf()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 初始化 SSLContext
|
||||||
|
val sslContext = SSLContext.getInstance("TLS")
|
||||||
|
sslContext.init(null, trustAllCerts, SecureRandom())
|
||||||
|
|
||||||
|
// 创建一个允许所有主机名验证的 HostnameVerifier
|
||||||
|
val allHostsValid = HostnameVerifier { _, _ -> true }
|
||||||
|
|
||||||
|
// 创建 OkHttpClient 并配置 SSL 和主机名验证
|
||||||
|
return OkHttpClient.Builder()
|
||||||
|
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
|
||||||
|
.hostnameVerifier(allHostsValid)
|
||||||
|
.build()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.img.rabbit.provider.utils
|
||||||
|
|
||||||
|
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||||
|
import com.img.rabbit.BuildConfig
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class HeaderInterceptor : Interceptor {
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val original = chain.request()
|
||||||
|
try {
|
||||||
|
val mBuilder = original.newBuilder()
|
||||||
|
val accessToken = PreferenceUtil.getXToken()?:""
|
||||||
|
mBuilder.header("x-token", accessToken)
|
||||||
|
mBuilder.header("x-platform", "android")
|
||||||
|
mBuilder.header("x-mobile-brand", android.os.Build.BRAND)
|
||||||
|
mBuilder.header("x-mobile-model", android.os.Build.MODEL)
|
||||||
|
mBuilder.header("x-package", BuildConfig.APPLICATION_ID)
|
||||||
|
|
||||||
|
val request = mBuilder
|
||||||
|
.method(original.method, original.body)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val response = chain.proceed(request)
|
||||||
|
/*
|
||||||
|
// 获取响应头
|
||||||
|
val headers = response.headers
|
||||||
|
// 处理响应头
|
||||||
|
headers.forEach { header ->
|
||||||
|
//Log.d(LOG_REQUEST, "ResponseHeader:${header.first} ${header.second}")
|
||||||
|
if (header.first == "Access-Token") {
|
||||||
|
authorization = header.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return response
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return chain.proceed(original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
package com.img.rabbit.provider.utils
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
|
import com.img.rabbit.BuildConfig
|
||||||
|
import com.img.rabbit.config.Constants
|
||||||
|
import com.img.rabbit.config.Constants.LOG_REQUEST
|
||||||
|
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||||
|
import com.img.rabbit.utils.StringUtils
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import okio.Buffer
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Arrays
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密数据
|
||||||
|
*/
|
||||||
|
class RequestInterceptor : Interceptor {
|
||||||
|
private val TAG = "Encryption"
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
val request = chain.request()
|
||||||
|
|
||||||
|
// 记录请求信息
|
||||||
|
logRequest(request)
|
||||||
|
|
||||||
|
var modifiedRequest = request
|
||||||
|
val method = request.method.lowercase(Locale.getDefault()).trim { it <= ' ' }
|
||||||
|
val url = request.url
|
||||||
|
val apiPath = String.format("%s", url)
|
||||||
|
|
||||||
|
val baseUrl = if (BuildConfig.DEBUG) {
|
||||||
|
Constants.DEBUG_BASE_URL
|
||||||
|
} else {
|
||||||
|
Constants.RELEASE_BASE_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果请求的不是服务端的接口则不用加密
|
||||||
|
if (!apiPath.startsWith(baseUrl)) {
|
||||||
|
val response = chain.proceed(modifiedRequest)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
L.d("TAG-->>url=$apiPath")
|
||||||
|
//如果请求的不是服务端的接口则不用加密
|
||||||
|
if (!apiPath.startsWith(Constants.BaseUrl)) {
|
||||||
|
L.d("TAG-->>content-type=${request.headers["Content-Type"]}")
|
||||||
|
val requestBody = request.body
|
||||||
|
L.d("TAG-->>${requestBody.toString()}")
|
||||||
|
return chain.proceed(request)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var queryString = url.encodedQuery
|
||||||
|
queryString = if (!TextUtils.isEmpty(queryString)) {
|
||||||
|
(queryString + "&nonce=" + StringUtils.createUUID()) + "×tamp=" + PreferenceUtil.serverTimeMillis() / 1000
|
||||||
|
} else {
|
||||||
|
("nonce=" + StringUtils.createUUID()) + "×tamp=" + PreferenceUtil.serverTimeMillis() / 1000
|
||||||
|
}
|
||||||
|
val sortQueryString = Arrays.stream(queryString.split("&".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
|
||||||
|
.sorted { obj: String, anotherString: String? -> obj.compareTo(anotherString!!) }
|
||||||
|
.reduce { x: String, y: String -> "$x&$y" }
|
||||||
|
.get()
|
||||||
|
//如果请求方式是get或者delete 则没有body
|
||||||
|
var paramsStr = ""
|
||||||
|
var bytes = ByteArray(0)
|
||||||
|
var signature: String? = ""
|
||||||
|
if (method == "put" || method == "post") {
|
||||||
|
val requestBody = request.body
|
||||||
|
Log.w(TAG, "TAG-->>${requestBody.toString()}")
|
||||||
|
val buffer = Buffer()
|
||||||
|
requestBody!!.writeTo(buffer)
|
||||||
|
bytes = StringUtils.addByte(bytes, buffer.readByteArray())
|
||||||
|
Log.w(TAG, "签名后bodyByte的长度为" + bytes.size)
|
||||||
|
paramsStr = String(bytes, StandardCharsets.UTF_8)
|
||||||
|
// paramsStr = buffer.readUtf8();
|
||||||
|
Log.w(TAG, "签名后body的长度为" + paramsStr.length)
|
||||||
|
}
|
||||||
|
signature = if (bytes.isNotEmpty()) {
|
||||||
|
Log.w(TAG, "当前的数组长度为----" + bytes.size)
|
||||||
|
StringUtils.getMD5Byte(
|
||||||
|
StringUtils.addByte(
|
||||||
|
StringUtils.addByte("$sortQueryString&".toByteArray(), bytes),
|
||||||
|
("&" + StringUtils.getMD5String(Constants.Signature)).toByteArray()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
StringUtils.getMD5String(sortQueryString + "&" + StringUtils.getMD5String(Constants.Signature))
|
||||||
|
}
|
||||||
|
val newUrl = apiPath.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] + "?" + queryString + "&signature=" + signature
|
||||||
|
Log.w(TAG, "签名后的路径--->$newUrl")
|
||||||
|
/*
|
||||||
|
request = request.newBuilder().url(newUrl).build()
|
||||||
|
return chain.proceed(request)
|
||||||
|
*/
|
||||||
|
|
||||||
|
modifiedRequest = request.newBuilder().url(newUrl).build()
|
||||||
|
val response = chain.proceed(modifiedRequest)
|
||||||
|
logResponse(response, startTime)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------> 以下为格式化请求日志打印 <---------------------------------
|
||||||
|
private fun logRequest(request: okhttp3.Request) {
|
||||||
|
val timestamp = dateFormat.format(Date())
|
||||||
|
val method = request.method
|
||||||
|
val url = request.url.toString()
|
||||||
|
|
||||||
|
Log.i(LOG_REQUEST,"┌─────────────────────────────────────────────────────────────────────────────")
|
||||||
|
Log.i(LOG_REQUEST,"│ 📤 请求部分 [$timestamp]")
|
||||||
|
Log.i(LOG_REQUEST,"├─────────────────────────────────────────────────────────────────────────────")
|
||||||
|
Log.i(LOG_REQUEST,"│ -->方法: $method")
|
||||||
|
Log.i(LOG_REQUEST,"│ -->URL: $url")
|
||||||
|
|
||||||
|
// 记录请求体(如果有)
|
||||||
|
val requestBody = request.body
|
||||||
|
if (requestBody != null) {
|
||||||
|
try {
|
||||||
|
val buffer = Buffer()
|
||||||
|
requestBody.writeTo(buffer)
|
||||||
|
val bodyString = buffer.readUtf8()
|
||||||
|
if (bodyString.isNotEmpty()) {
|
||||||
|
Log.i(LOG_REQUEST,"│ -->请求参数:")
|
||||||
|
Log.i(LOG_REQUEST,"│ --> $bodyString")
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(LOG_REQUEST,"│ -->读取请求体失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
Log.i(LOG_REQUEST,"│ -->无请求参数")
|
||||||
|
}
|
||||||
|
Log.i(LOG_REQUEST,"└─────────────────────────────────────────────────────────────────────────────")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logResponse(response: Response, startTime: Long) {
|
||||||
|
val timestamp = dateFormat.format(Date())
|
||||||
|
val duration = System.currentTimeMillis() - startTime
|
||||||
|
val code = response.code
|
||||||
|
val message = response.message
|
||||||
|
val url = response.request.url.toString()
|
||||||
|
val apiPath = url.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
|
||||||
|
|
||||||
|
Log.i(LOG_REQUEST,"┌─────────────────────────────────────────────────────────────────────────────")
|
||||||
|
Log.i(LOG_REQUEST,"│ 📥 响应部分 [$timestamp]")
|
||||||
|
Log.i(LOG_REQUEST,"├─────────────────────────────────────────────────────────────────────────────")
|
||||||
|
Log.i(LOG_REQUEST,"│ URL: $url")
|
||||||
|
Log.i(LOG_REQUEST,"│ API: $apiPath")
|
||||||
|
Log.i(LOG_REQUEST,"│ 状态码: $code $message")
|
||||||
|
Log.i(LOG_REQUEST,"│ 耗时: ${duration}ms")
|
||||||
|
|
||||||
|
// 记录响应体
|
||||||
|
try {
|
||||||
|
val responseBody = response.peekBody(1024 * 1024L) // 最多读取1MB
|
||||||
|
val bodyString = responseBody.string()
|
||||||
|
if (bodyString.isNotEmpty()) {
|
||||||
|
Log.i(LOG_REQUEST,"│ 响应内容: (前${minOf(bodyString.length, 2000)}字符):")
|
||||||
|
val preview = if (bodyString.length > 2000) {
|
||||||
|
bodyString.take(2000) + "..."
|
||||||
|
} else {
|
||||||
|
bodyString
|
||||||
|
}
|
||||||
|
Log.i(LOG_REQUEST,"│ $preview")
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(LOG_REQUEST,"│ 读取响应体失败: ${e.message}")
|
||||||
|
}
|
||||||
|
Log.i(LOG_REQUEST,"└─────────────────────────────────────────────────────────────────────────────")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.img.rabbit.provider.utils
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
|
import com.img.rabbit.BuildConfig
|
||||||
|
import com.img.rabbit.config.Constants
|
||||||
|
import com.img.rabbit.utils.AESpkcs7paddingUtil
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.charset.UnsupportedCharsetException
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密数据
|
||||||
|
*/
|
||||||
|
class ResponseInterceptor : Interceptor {
|
||||||
|
private val TAG = "Decode"
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
val url = request.url
|
||||||
|
val apiPath = String.format("%s", url)
|
||||||
|
val baseUrl = if (BuildConfig.DEBUG) {
|
||||||
|
Constants.DEBUG_BASE_URL
|
||||||
|
} else {
|
||||||
|
Constants.RELEASE_BASE_URL
|
||||||
|
}
|
||||||
|
//如果请求的不是服务端的接口则不用加密
|
||||||
|
if (!apiPath.startsWith(baseUrl)) {
|
||||||
|
return chain.proceed(request)
|
||||||
|
}
|
||||||
|
val response = chain.proceed(request)
|
||||||
|
var charset = Charset.forName("UTF-8")
|
||||||
|
val contentType = response.header("Content-Type")
|
||||||
|
if (contentType != null && contentType.contains("application/json")) {
|
||||||
|
val responseBody = response.body
|
||||||
|
val source = responseBody.source()
|
||||||
|
source.request(Long.MAX_VALUE)
|
||||||
|
val buffer = source.buffer
|
||||||
|
val mediaType = responseBody.contentType()
|
||||||
|
if (mediaType != null) {
|
||||||
|
try {
|
||||||
|
charset = mediaType.charset(Charset.forName("UTF-8"))
|
||||||
|
} catch (e: UnsupportedCharsetException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val respBody = buffer.clone().readString(charset!!)
|
||||||
|
Log.w(TAG, "response=${respBody}")
|
||||||
|
if (TextUtils.isEmpty(respBody)) {
|
||||||
|
val responseCode = if (response.code == 400) 200 else response.code //兼容某些接口400的情况
|
||||||
|
return response.newBuilder().code(responseCode).body(responseBody).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
val isEncrypt = JSONObject(respBody).optBoolean("encrypt")
|
||||||
|
Log.w(TAG, "是否需要解密:$isEncrypt")
|
||||||
|
if (!isEncrypt) { //如果不需要加密 直接返回
|
||||||
|
Log.w(TAG, "response=${response.body}")
|
||||||
|
val responseCode = if (response.code == 400) 200 else response.code //兼容某些接口400的情况
|
||||||
|
return response.newBuilder().code(responseCode).body(responseBody).build()
|
||||||
|
}
|
||||||
|
val decrybody = JSONObject(respBody).optString("data")
|
||||||
|
Log.w(TAG, "Json解析后的字符串为---->$decrybody")
|
||||||
|
var decryString: String?
|
||||||
|
try {
|
||||||
|
decryString = AESpkcs7paddingUtil.decryptNormal(decrybody, Constants.AESDecrypt)
|
||||||
|
Log.e("ResponseInterceptor", "解密后返回的字符串为---->$decryString")
|
||||||
|
|
||||||
|
//这里可以通过code处理token过去过期的情况
|
||||||
|
//val decCode = JSONObject(decryString).optInt("code")
|
||||||
|
|
||||||
|
//返回新创建的response
|
||||||
|
val responseCode = if (response.code == 400 && decrybody != null) 200 else response.code //兼容某些接口400的情况
|
||||||
|
return response.newBuilder().code(responseCode).body(decryString.toResponseBody("text/plain".toMediaType())).build()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.img.rabbit.utils
|
||||||
|
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import io.github.fastaes.FastAES
|
||||||
|
|
||||||
|
object AESpkcs7paddingUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码格式
|
||||||
|
*/
|
||||||
|
const val ENCODING = "utf-8"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES解密
|
||||||
|
*
|
||||||
|
* @param encryptStr 加密后的密文
|
||||||
|
* @param key 密钥
|
||||||
|
* @return 源字符串
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun decryptNormal(encryptStr: String?, key: String): String {
|
||||||
|
val sourceBytes = Base64.decode(encryptStr, Base64.NO_WRAP)
|
||||||
|
val keyBytes = key.toByteArray(charset(ENCODING))
|
||||||
|
val plain: ByteArray = FastAES.decrypt(sourceBytes, keyBytes, key.substring(0, 16).toByteArray(charset(ENCODING)))
|
||||||
|
return String(plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -39,8 +39,8 @@ public class Bitmap2SVG
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean trc_del;
|
private final boolean trc_del;
|
||||||
private PrintWriter pw;
|
private final PrintWriter pw;
|
||||||
private int w, h;
|
private int w, h;
|
||||||
|
|
||||||
private Bitmap2SVG( PrintWriter pw, boolean trc_delete )
|
private Bitmap2SVG( PrintWriter pw, boolean trc_delete )
|
||||||
|
|
@ -116,11 +116,13 @@ public class Bitmap2SVG
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
int len;
|
int len;
|
||||||
while ((len = fis.read(buffer)) != -1) {
|
while ((len = fis.read(buffer)) != -1) {
|
||||||
|
assert os != null;
|
||||||
os.write(buffer, 0, len);
|
os.write(buffer, 0, len);
|
||||||
}
|
}
|
||||||
// 此时文件已带上正确的 MIME 类型存入相册
|
// 此时文件已带上正确的 MIME 类型存入相册
|
||||||
Toast.makeText(context, "SVG已保存", Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, "SVG已保存", Toast.LENGTH_SHORT).show();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
//noinspection CallToPrintStackTrace
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.img.rabbit.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.bytedance.hume.readapk.HumeSDK
|
||||||
|
import com.img.rabbit.BuildConfig
|
||||||
|
import com.kwai.monitor.payload.TurboHelper
|
||||||
|
import com.tencent.vasdolly.helper.ChannelReaderUtil
|
||||||
|
|
||||||
|
|
||||||
|
object ChannelUtils {
|
||||||
|
|
||||||
|
fun getChannel(context: Context): String {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
MMKVUtils.put("app_channel", "test")
|
||||||
|
} else {
|
||||||
|
if (TextUtils.isEmpty(MMKVUtils.getString("app_channel"))) {
|
||||||
|
MMKVUtils.put("app_channel", getChannelBy(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MMKVUtils.getString("app_channel") ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getChannelBy(context: Context): String? {
|
||||||
|
val kuaishou = TurboHelper.getChannel(context)
|
||||||
|
if (!TextUtils.isEmpty(kuaishou)) {
|
||||||
|
return kuaishou
|
||||||
|
}
|
||||||
|
val tengxun = ChannelReaderUtil.getChannel(context)
|
||||||
|
if (!TextUtils.isEmpty(tengxun)) {
|
||||||
|
return tengxun
|
||||||
|
}
|
||||||
|
val juliang = HumeSDK.getChannel(context)
|
||||||
|
if (!TextUtils.isEmpty(juliang)) {
|
||||||
|
return juliang
|
||||||
|
}
|
||||||
|
return channel(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun channel(context: Context): String {
|
||||||
|
try {
|
||||||
|
val pm = context.packageManager
|
||||||
|
val appInfo = pm.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
|
||||||
|
val channel = appInfo.metaData.getString("UMENG_CHANNEL")
|
||||||
|
if (!TextUtils.isEmpty(channel)) {
|
||||||
|
return channel!!
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -20,10 +20,9 @@ import kotlin.apply
|
||||||
|
|
||||||
import android.graphics.*
|
import android.graphics.*
|
||||||
import androidx.core.graphics.createBitmap
|
import androidx.core.graphics.createBitmap
|
||||||
import com.img.rabbit.bean.LongImageBean
|
import com.img.rabbit.bean.local.LongImageBean
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import androidx.core.graphics.withClip
|
|
||||||
|
|
||||||
object ImageUtils {
|
object ImageUtils {
|
||||||
fun decodeSampledBitmapFromResource(
|
fun decodeSampledBitmapFromResource(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package com.img.rabbit.utils
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
|
object MMKVUtils {
|
||||||
|
|
||||||
|
var mmkv: MMKV? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
mmkv = MMKV.defaultMMKV()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun put(key: String, value: Any?): Boolean {
|
||||||
|
return when (value) {
|
||||||
|
is String -> mmkv?.encode(key, value)!!
|
||||||
|
is Float -> mmkv?.encode(key, value)!!
|
||||||
|
is Boolean -> mmkv?.encode(key, value)!!
|
||||||
|
is Int -> mmkv?.encode(key, value)!!
|
||||||
|
is Long -> mmkv?.encode(key, value)!!
|
||||||
|
is Double -> mmkv?.encode(key, value)!!
|
||||||
|
is ByteArray -> mmkv?.encode(key, value)!!
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这里使用安卓自带的Parcelable序列化,它比java支持的Serializer序列化性能好些
|
||||||
|
*/
|
||||||
|
fun <T : Parcelable> put(key: String, t: T?): Boolean {
|
||||||
|
if (t == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return mmkv?.encode(key, t)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun put(key: String, sets: Set<String>?): Boolean {
|
||||||
|
if (sets == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return mmkv?.encode(key, sets)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInt(key: String): Int? {
|
||||||
|
return mmkv?.decodeInt(key, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDouble(key: String): Double? {
|
||||||
|
return mmkv?.decodeDouble(key, 0.00)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLong(key: String): Long? {
|
||||||
|
return mmkv?.decodeLong(key, 0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBoolean(key: String, defaultValue: Boolean = false): Boolean {
|
||||||
|
return mmkv?.decodeBool(key, defaultValue) ?: defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFloat(key: String): Float? {
|
||||||
|
return mmkv?.decodeFloat(key, 0F)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getByteArray(key: String): ByteArray? {
|
||||||
|
return mmkv?.decodeBytes(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getString(key: String): String? {
|
||||||
|
return mmkv?.decodeString(key, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SpUtils.getParcelable<Class>("")
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Parcelable> getParcelable(key: String): T? {
|
||||||
|
return mmkv?.decodeParcelable(key, T::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStringSet(key: String): Set<String>? {
|
||||||
|
return mmkv?.decodeStringSet(key, Collections.emptySet())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已经存在
|
||||||
|
*/
|
||||||
|
fun contains(key: String): Boolean? {
|
||||||
|
return mmkv?.containsKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeKey(key: String) {
|
||||||
|
mmkv?.removeValueForKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAll() {
|
||||||
|
mmkv?.clearAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,494 @@
|
||||||
|
package com.img.rabbit.utils
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import java.text.ParseException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.GregorianCalendar
|
||||||
|
import java.util.Hashtable
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
|
import java.util.regex.Matcher
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
|
||||||
|
object StringUtils {
|
||||||
|
/**
|
||||||
|
* 功能:身份证的有效验证
|
||||||
|
*
|
||||||
|
* @param IDStr 身份证号
|
||||||
|
* @return 有效:返回"" 无效:返回String信息
|
||||||
|
* @throws ParseException
|
||||||
|
*/
|
||||||
|
@Throws(ParseException::class)
|
||||||
|
fun IDCardValidate(IDStr: String): Boolean {
|
||||||
|
var errorInfo = "" // 记录错误信息
|
||||||
|
val ValCodeArr = arrayOf(
|
||||||
|
"1", "0", "x", "9", "8", "7", "6", "5", "4",
|
||||||
|
"3", "2"
|
||||||
|
)
|
||||||
|
val Wi = arrayOf(
|
||||||
|
"7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7",
|
||||||
|
"9", "10", "5", "8", "4", "2"
|
||||||
|
)
|
||||||
|
var Ai = ""
|
||||||
|
// ================号码的长度 15位或18位 ================
|
||||||
|
if (IDStr.length != 15 && IDStr.length != 18) {
|
||||||
|
errorInfo = "身份证号码长度应该为15位或18位。"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// =======================(end)========================
|
||||||
|
|
||||||
|
// ================ 数字 除最后以为都为数字================
|
||||||
|
if (IDStr.length == 18) {
|
||||||
|
Ai = IDStr.substring(0, 17)
|
||||||
|
} else if (IDStr.length == 15) {
|
||||||
|
Ai = IDStr.substring(0, 6) + "19" + IDStr.substring(6, 15)
|
||||||
|
}
|
||||||
|
if (isNumeric(Ai) == false) {
|
||||||
|
errorInfo = "身份证15位号码都应为数字 ; 18位号码除最后一位外,都应为数字。"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// =======================(end)========================
|
||||||
|
|
||||||
|
// ================ 出生年月是否有效 ================
|
||||||
|
val strYear = Ai.substring(6, 10) // 年份
|
||||||
|
val strMonth = Ai.substring(10, 12) // 月份
|
||||||
|
val strDay = Ai.substring(12, 14) // 月份
|
||||||
|
if (isDataFormat("$strYear-$strMonth-$strDay") == false) {
|
||||||
|
errorInfo = "身份证生日无效。"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val gc = GregorianCalendar()
|
||||||
|
val s = SimpleDateFormat("yyyy-MM-dd")
|
||||||
|
try {
|
||||||
|
if (gc[Calendar.YEAR] - strYear.toInt() > 150
|
||||||
|
|| gc.time.time - s.parse(
|
||||||
|
"$strYear-$strMonth-$strDay"
|
||||||
|
).time < 0
|
||||||
|
) {
|
||||||
|
errorInfo = "身份证生日不在有效范围。"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace()
|
||||||
|
} catch (e: ParseException) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
if (strMonth.toInt() > 12 || strMonth.toInt() == 0) {
|
||||||
|
errorInfo = "身份证月份无效"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (strDay.toInt() > 31 || strDay.toInt() == 0) {
|
||||||
|
errorInfo = "身份证日期无效"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// =====================(end)=====================
|
||||||
|
|
||||||
|
// ================ 地区码时候有效================
|
||||||
|
val h = GetAreaCode()
|
||||||
|
if (h[Ai.substring(0, 2)] == null) {
|
||||||
|
errorInfo = "身份证地区编码错误。"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// ==============================================
|
||||||
|
|
||||||
|
// ================ 判断最后一位的值================
|
||||||
|
var TotalmulAiWi = 0
|
||||||
|
for (i in 0..16) {
|
||||||
|
TotalmulAiWi = (TotalmulAiWi
|
||||||
|
+ Ai[i].toString().toInt() * Wi[i].toInt())
|
||||||
|
}
|
||||||
|
val modValue = TotalmulAiWi % 11
|
||||||
|
val strVerifyCode = ValCodeArr[modValue]
|
||||||
|
Ai = Ai + strVerifyCode
|
||||||
|
if (IDStr.length == 18) {
|
||||||
|
if (Ai == IDStr == false) {
|
||||||
|
errorInfo = "身份证无效,不是合法的身份证号码"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// =====================(end)=====================
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能:判断字符串是否为数字
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private fun isNumeric(str: String): Boolean {
|
||||||
|
val pattern = Pattern.compile("[0-9]*")
|
||||||
|
val isNum = pattern.matcher(str)
|
||||||
|
return if (isNum.matches()) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能:设置地区编码
|
||||||
|
*
|
||||||
|
* @return Hashtable 对象
|
||||||
|
*/
|
||||||
|
private fun GetAreaCode(): Hashtable<String, String> {
|
||||||
|
val hashtable = Hashtable<String, String>()
|
||||||
|
hashtable["11"] = "北京"
|
||||||
|
hashtable["12"] = "天津"
|
||||||
|
hashtable["13"] = "河北"
|
||||||
|
hashtable["14"] = "山西"
|
||||||
|
hashtable["15"] = "内蒙古"
|
||||||
|
hashtable["21"] = "辽宁"
|
||||||
|
hashtable["22"] = "吉林"
|
||||||
|
hashtable["23"] = "黑龙江"
|
||||||
|
hashtable["31"] = "上海"
|
||||||
|
hashtable["32"] = "江苏"
|
||||||
|
hashtable["33"] = "浙江"
|
||||||
|
hashtable["34"] = "安徽"
|
||||||
|
hashtable["35"] = "福建"
|
||||||
|
hashtable["36"] = "江西"
|
||||||
|
hashtable["37"] = "山东"
|
||||||
|
hashtable["41"] = "河南"
|
||||||
|
hashtable["42"] = "湖北"
|
||||||
|
hashtable["43"] = "湖南"
|
||||||
|
hashtable["44"] = "广东"
|
||||||
|
hashtable["45"] = "广西"
|
||||||
|
hashtable["46"] = "海南"
|
||||||
|
hashtable["50"] = "重庆"
|
||||||
|
hashtable["51"] = "四川"
|
||||||
|
hashtable["52"] = "贵州"
|
||||||
|
hashtable["53"] = "云南"
|
||||||
|
hashtable["54"] = "西藏"
|
||||||
|
hashtable["61"] = "陕西"
|
||||||
|
hashtable["62"] = "甘肃"
|
||||||
|
hashtable["63"] = "青海"
|
||||||
|
hashtable["64"] = "宁夏"
|
||||||
|
hashtable["65"] = "新疆"
|
||||||
|
hashtable["71"] = "台湾"
|
||||||
|
hashtable["81"] = "香港"
|
||||||
|
hashtable["82"] = "澳门"
|
||||||
|
hashtable["91"] = "国外"
|
||||||
|
return hashtable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证日期字符串是否是YYYY-MM-DD格式
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private fun isDataFormat(str: String): Boolean {
|
||||||
|
var flag = false
|
||||||
|
// String
|
||||||
|
// regxStr="[1-9][0-9]{3}-[0-1][0-2]-((0[1-9])|([12][0-9])|(3[01]))";
|
||||||
|
val regxStr =
|
||||||
|
"^((\\d{2}(([02468][048])|([13579][26]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])))))|(\\d{2}(([02468][1235679])|([13579][01345789]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))(\\s(((0?[0-9])|([1-2][0-3]))\\:([0-5]?[0-9])((\\s)|(\\:([0-5]?[0-9])))))?$"
|
||||||
|
val pattern1 = Pattern.compile(regxStr)
|
||||||
|
val isNo = pattern1.matcher(str)
|
||||||
|
if (isNo.matches()) {
|
||||||
|
flag = true
|
||||||
|
}
|
||||||
|
return flag
|
||||||
|
}
|
||||||
|
//2.判断字符串是否是邮箱:
|
||||||
|
/**
|
||||||
|
* 描述:是否是邮箱.
|
||||||
|
*
|
||||||
|
* @param str 指定的字符串
|
||||||
|
* @return 是否是邮箱:是为true,否则false
|
||||||
|
*/
|
||||||
|
fun isEmail(str: String): Boolean {
|
||||||
|
var isEmail = false
|
||||||
|
val expr = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"
|
||||||
|
if (str.matches(expr.toRegex())) {
|
||||||
|
isEmail = true
|
||||||
|
}
|
||||||
|
return isEmail
|
||||||
|
}
|
||||||
|
//3.判断字符串是否是银行卡
|
||||||
|
/**
|
||||||
|
* 判断是否是银行卡号
|
||||||
|
*
|
||||||
|
* @param cardId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun checkBankCard(cardId: String): Boolean {
|
||||||
|
val bit = getBankCardCheckCode(
|
||||||
|
cardId
|
||||||
|
.substring(0, cardId.length - 1)
|
||||||
|
)
|
||||||
|
return if (bit == 'N') {
|
||||||
|
false
|
||||||
|
} else cardId[cardId.length - 1] == bit
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBankCardCheckCode(nonCheckCodeCardId: String?): Char {
|
||||||
|
if (nonCheckCodeCardId == null || nonCheckCodeCardId.trim { it <= ' ' }.length == 0 || !nonCheckCodeCardId.matches("\\d+".toRegex())) {
|
||||||
|
// 如果传的不是数据返回N
|
||||||
|
return 'N'
|
||||||
|
}
|
||||||
|
val chs = nonCheckCodeCardId.trim { it <= ' ' }.toCharArray()
|
||||||
|
var luhmSum = 0
|
||||||
|
var i = chs.size - 1
|
||||||
|
var j = 0
|
||||||
|
while (i >= 0) {
|
||||||
|
var k = chs[i].code - '0'.code
|
||||||
|
if (j % 2 == 0) {
|
||||||
|
k *= 2
|
||||||
|
k = k / 10 + k % 10
|
||||||
|
}
|
||||||
|
luhmSum += k
|
||||||
|
i--
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
return if (luhmSum % 10 == 0) '0' else (10 - luhmSum % 10 + '0'.code).toChar()
|
||||||
|
}
|
||||||
|
//4、判断字符串是否是手机号
|
||||||
|
/**
|
||||||
|
* 判断是否是手机号
|
||||||
|
*
|
||||||
|
* @param phone
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun checkPhone(phone: String?): Boolean {
|
||||||
|
val pattern = Pattern
|
||||||
|
.compile("^(13[0-9]|15[0-3]|15[5-9]|18[0-9]|14[57]|17[0678])\\d{8}$")
|
||||||
|
val matcher = pattern.matcher(phone)
|
||||||
|
return if (matcher.matches()) {
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
//5.判断字符串是否是中文或者包含中文
|
||||||
|
/**
|
||||||
|
* 描述:判断一个字符串是否为null或空值.
|
||||||
|
*
|
||||||
|
* @param str 指定的字符串
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
fun isEmpty(str: String?): Boolean {
|
||||||
|
return str == null || str.trim { it <= ' ' }.length == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述:是否是中文.
|
||||||
|
*
|
||||||
|
* @param str 指定的字符串
|
||||||
|
* @return 是否是中文:是为true,否则false
|
||||||
|
*/
|
||||||
|
fun isChinese(str: String): Boolean {
|
||||||
|
var isChinese = true
|
||||||
|
val chinese = "[\u0391-\uFFE5]"
|
||||||
|
if (!isEmpty(str)) {
|
||||||
|
//获取字段值的长度,如果含中文字符,则每个中文字符长度为2,否则为1
|
||||||
|
for (i in 0 until str.length) {
|
||||||
|
//获取一个字符
|
||||||
|
val temp = str.substring(i, i + 1)
|
||||||
|
//判断是否为中文字符
|
||||||
|
if (temp.matches(chinese.toRegex())) {
|
||||||
|
} else {
|
||||||
|
isChinese = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isChinese
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述:是否包含中文.
|
||||||
|
*
|
||||||
|
* @param str 指定的字符串
|
||||||
|
* @return 是否包含中文:是为true,否则false
|
||||||
|
*/
|
||||||
|
fun isContainChinese(str: String): Boolean {
|
||||||
|
var isChinese = false
|
||||||
|
val chinese = "[\u0391-\uFFE5]"
|
||||||
|
if (!isEmpty(str)) {
|
||||||
|
//获取字段值的长度,如果含中文字符,则每个中文字符长度为2,否则为1
|
||||||
|
for (i in 0 until str.length) {
|
||||||
|
//获取一个字符
|
||||||
|
val temp = str.substring(i, i + 1)
|
||||||
|
//判断是否为中文字符
|
||||||
|
if (temp.matches(chinese.toRegex())) {
|
||||||
|
isChinese = true
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isChinese
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个String的list 是否改变过
|
||||||
|
*
|
||||||
|
* @param listNew
|
||||||
|
* @param listOld
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun compareList(listNew: List<String?>?, listOld: List<String?>?): Boolean {
|
||||||
|
return if (listOld == null && listNew == null) {
|
||||||
|
false
|
||||||
|
} else if (listOld != null && listNew != null) {
|
||||||
|
if (listNew.size != listOld.size) {
|
||||||
|
true
|
||||||
|
} else !(listOld.containsAll(listNew) && listNew.containsAll(listOld))
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个String 是否改变过
|
||||||
|
*
|
||||||
|
* @param newStr
|
||||||
|
* @param oldStr
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun compareString(newStr: String?, oldStr: String?): Boolean {
|
||||||
|
return if (newStr == null && oldStr == null) {
|
||||||
|
false
|
||||||
|
} else if (newStr != null && oldStr != null) {
|
||||||
|
newStr != oldStr
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较签名 是否改变过
|
||||||
|
*
|
||||||
|
* @param newStr
|
||||||
|
* @param oldStr
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun compareSign(newStr: String, oldStr: String): Boolean {
|
||||||
|
return if (TextUtils.isEmpty(oldStr)) { //当原始值没有的时候 无需验证 因为上报时需验证是否已签过字 而且当原始值有的时候 依据现有业务新值不可能清空 故无需再判断其他情况
|
||||||
|
true
|
||||||
|
} else newStr != oldStr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成一个UUID
|
||||||
|
*/
|
||||||
|
fun createUUID(): String {
|
||||||
|
return UUID.randomUUID().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createUUIDFromLong(): String {
|
||||||
|
return UUID(ThreadLocalRandom.current().nextLong(), ThreadLocalRandom.current().nextLong()).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MD5加密
|
||||||
|
*/
|
||||||
|
fun getMD5String(data: String): String? {
|
||||||
|
try {
|
||||||
|
val md = MessageDigest.getInstance("MD5")
|
||||||
|
md.update(data.toByteArray())
|
||||||
|
val result = md.digest()
|
||||||
|
val stringBuffer = StringBuffer()
|
||||||
|
for (i in result.indices) {
|
||||||
|
val hex = Integer.toHexString(0xff and result[i].toInt())
|
||||||
|
if (hex.length == 1) stringBuffer.append('0')
|
||||||
|
stringBuffer.append(hex)
|
||||||
|
}
|
||||||
|
return stringBuffer.toString()
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对数组进行MD5加密
|
||||||
|
*
|
||||||
|
* @param bytes
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun getMD5Byte(bytes: ByteArray?): String? {
|
||||||
|
try {
|
||||||
|
val md = MessageDigest.getInstance("MD5")
|
||||||
|
md.update(bytes)
|
||||||
|
val result = md.digest()
|
||||||
|
val stringBuffer = StringBuffer()
|
||||||
|
for (i in result.indices) {
|
||||||
|
val hex = Integer.toHexString(0xff and result[i].toInt())
|
||||||
|
if (hex.length == 1) stringBuffer.append('0')
|
||||||
|
stringBuffer.append(hex)
|
||||||
|
}
|
||||||
|
return stringBuffer.toString()
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
//两个数组进行相加
|
||||||
|
fun addByte(array1: ByteArray, array2: ByteArray): ByteArray {
|
||||||
|
val combined = ByteArray(array1.size + array2.size)
|
||||||
|
System.arraycopy(array1, 0, combined, 0, array1.size)
|
||||||
|
System.arraycopy(array2, 0, combined, array1.size, array2.size)
|
||||||
|
return combined
|
||||||
|
}
|
||||||
|
|
||||||
|
//格式化时间yyyy-mm-dd
|
||||||
|
fun getSimpleYYYYMMDD(str: String): String {
|
||||||
|
if (TextUtils.isEmpty(str)) return ""
|
||||||
|
|
||||||
|
val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA)
|
||||||
|
val date = simpleDateFormat.parse(str)
|
||||||
|
return simpleDateFormat.format(date!!)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字符串中的unicode字符转换为中文字符
|
||||||
|
*/
|
||||||
|
fun convertUnicodeToCh(str: String): String {
|
||||||
|
var newStr = str
|
||||||
|
val pattern: Pattern = Pattern.compile("(\\\\u(\\w{4}))")
|
||||||
|
val matcher: Matcher = pattern.matcher(newStr)
|
||||||
|
|
||||||
|
// 迭代,将str中的所有unicode转换为正常字符
|
||||||
|
while (matcher.find()) {
|
||||||
|
val unicodeFull = matcher.group(1) // 匹配出的每个字的unicode,比如\u83b7
|
||||||
|
val unicodeNum = matcher.group(2) // 匹配出每个字的数字,比如\u83b7,会匹配出u83b7
|
||||||
|
|
||||||
|
// 将匹配出的数字按照16进制转换为10进制,转换为char类型,就是对应的正常字符了
|
||||||
|
@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||||
|
val singleChar = unicodeNum.toInt(16).toChar()
|
||||||
|
|
||||||
|
// 替换原始字符串中的unicode码
|
||||||
|
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||||
|
newStr = newStr.replace(unicodeFull, singleChar.toString() + "")
|
||||||
|
}
|
||||||
|
return newStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析函数
|
||||||
|
fun parseAlipayResult(rawResult: String): Map<String, String> {
|
||||||
|
val resultMap = mutableMapOf<String, String>()
|
||||||
|
val pairs = rawResult.split("&")
|
||||||
|
|
||||||
|
for (pair in pairs) {
|
||||||
|
val keyValue = pair.split("=")
|
||||||
|
if (keyValue.size == 2) {
|
||||||
|
val key = URLDecoder.decode(keyValue[0], "UTF-8")
|
||||||
|
val value = URLDecoder.decode(keyValue[1], "UTF-8")
|
||||||
|
resultMap[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -3,13 +3,22 @@ package com.img.rabbit.utils
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import com.img.rabbit.WebViewActivity
|
||||||
|
|
||||||
object UrlLinkUtils {
|
object UrlLinkUtils {
|
||||||
fun openAgreement(context: Context, url: String) {
|
fun openAgreement(context: Context, title: String, url: String, isExternal:Boolean = false) {
|
||||||
// 打开服务协议
|
if(isExternal){
|
||||||
Intent(Intent.ACTION_VIEW, url.toUri()).apply {
|
// 打开服务协议
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
Intent(Intent.ACTION_VIEW, url.toUri()).apply {
|
||||||
}.let { intent ->
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}.let { intent ->
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
val intent = Intent(context, WebViewActivity::class.java).apply {
|
||||||
|
putExtra("url", url)
|
||||||
|
putExtra("title", title)
|
||||||
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.img.rabbit.viewmodel
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
open class BaseViewModel : ViewModel() {
|
||||||
|
var isShowMsg = mutableStateOf(false)
|
||||||
|
var msgContent = mutableStateOf("")
|
||||||
|
val isLoading = mutableStateOf(false)
|
||||||
|
|
||||||
|
fun mLaunch(block: suspend () -> Unit) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
block()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
isShowMsg.value = true
|
||||||
|
msgContent.value = "接口请求失败"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,12 @@ import android.os.Build
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.img.rabbit.provider.api.ApiManager
|
||||||
|
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@SuppressLint("ObsoleteSdkInt")
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
class GeneralViewModel(application: Application) : AndroidViewModel(application) {
|
class GeneralViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
@ -38,6 +43,12 @@ class GeneralViewModel(application: Application) : AndroidViewModel(application)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val _agreementStatus = MutableLiveData<Boolean>()
|
||||||
|
val agreementStatus: LiveData<Boolean> = _agreementStatus
|
||||||
|
private fun getIsAgreement(): Boolean{
|
||||||
|
return kv.getBoolean("isAgreement", false)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// 注册网络监听
|
// 注册网络监听
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
|
@ -51,6 +62,8 @@ class GeneralViewModel(application: Application) : AndroidViewModel(application)
|
||||||
|
|
||||||
// 初始化状态
|
// 初始化状态
|
||||||
_networkStatus.value = isNetworkAvailable()
|
_networkStatus.value = isNetworkAvailable()
|
||||||
|
// 初始化隐私政策状态
|
||||||
|
_agreementStatus.value = getIsAgreement()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isNetworkAvailable(): Boolean {
|
private fun isNetworkAvailable(): Boolean {
|
||||||
|
|
@ -69,8 +82,23 @@ class GeneralViewModel(application: Application) : AndroidViewModel(application)
|
||||||
_isNavigationBarVisible.value = visible
|
_isNavigationBarVisible.value = visible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setIsAgreement(agreement: Boolean){
|
||||||
|
kv.putBoolean("isAgreement", agreement)
|
||||||
|
_agreementStatus.value = agreement
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
connectivityManager.unregisterNetworkCallback(networkCallback)
|
connectivityManager.unregisterNetworkCallback(networkCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun getServerTime() {
|
||||||
|
GlobalScope.launch {
|
||||||
|
val response = ApiManager.serviceVo.getServerTime()
|
||||||
|
if (response.status) {
|
||||||
|
PreferenceUtil.setTimeDiff(response.data - System.currentTimeMillis() / 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,29 +1,69 @@
|
||||||
package com.img.rabbit.viewmodel
|
package com.img.rabbit.viewmodel
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.alipay.sdk.app.AuthTask
|
||||||
import com.img.rabbit.pages.LoginScreenType
|
import com.img.rabbit.pages.LoginScreenType
|
||||||
import com.g.gysdk.GYManager
|
import com.g.gysdk.GYManager
|
||||||
import com.g.gysdk.GYResponse
|
import com.g.gysdk.GYResponse
|
||||||
import com.g.gysdk.GyCallBack
|
import com.g.gysdk.GyCallBack
|
||||||
import com.g.gysdk.GyConfig
|
import com.g.gysdk.GyConfig
|
||||||
import com.img.rabbit.bean.OnekeyPreLogin
|
import com.github.gzuliyujiang.oaid.DeviceIdentifier
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.img.rabbit.bean.local.ErrorBean
|
||||||
|
import com.img.rabbit.bean.local.OnekeyPreLogin
|
||||||
|
import com.img.rabbit.bean.local.WxBean
|
||||||
|
import com.img.rabbit.bean.response.UserEntity
|
||||||
|
import com.img.rabbit.bean.response.UserConfigEntity
|
||||||
|
import com.img.rabbit.config.Constants
|
||||||
|
import com.img.rabbit.provider.api.ApiManager
|
||||||
|
import com.img.rabbit.provider.api.ResultVo
|
||||||
|
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||||
|
import com.img.rabbit.utils.MMKVUtils
|
||||||
|
import com.tencent.mm.opensdk.modelmsg.SendAuth
|
||||||
|
import com.tencent.mm.opensdk.openapi.IWXAPI
|
||||||
|
import com.tencent.mm.opensdk.openapi.WXAPIFactory
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||||
|
|
||||||
class LoginViewModel : ViewModel() {
|
class LoginViewModel : BaseViewModel() {
|
||||||
private val TAG = "LoginViewModel"
|
private val TAG = "LoginViewModel"
|
||||||
private val ONEKEY_TAG = "OneKeyLoginViewModel"
|
private val ONEKEY_TAG = "OneKeyLoginViewModel"
|
||||||
|
|
||||||
|
private lateinit var api: IWXAPI
|
||||||
|
|
||||||
|
private val _wxState = MutableLiveData<WxBean?>()
|
||||||
|
val wxState: LiveData<WxBean?> = _wxState
|
||||||
|
|
||||||
|
fun updateWxState(newState: WxBean) {
|
||||||
|
_wxState.value = newState
|
||||||
|
}
|
||||||
|
|
||||||
|
// private val _authInfo = MutableLiveData<String>()
|
||||||
|
val authInfoForAlipay: MutableState<String> = mutableStateOf("")
|
||||||
|
|
||||||
val loginScreenType = mutableStateOf(LoginScreenType.LOGIN_NORMAL)
|
val loginScreenType = mutableStateOf(LoginScreenType.LOGIN_NORMAL)
|
||||||
// 登录用户名
|
// 登录用户名
|
||||||
val userName = mutableStateOf("")
|
val userName = mutableStateOf("")
|
||||||
// 登录验证码
|
// 登录验证码
|
||||||
val captcha = mutableStateOf("")
|
val captcha = mutableStateOf("")
|
||||||
|
// 登录验证码发送时间戳
|
||||||
|
val captchaTimestamp = mutableStateOf("")
|
||||||
|
|
||||||
// 是否同意政策协议
|
// 是否同意政策协议
|
||||||
private val _policyAgreement = mutableStateOf(false)
|
private val _policyAgreement = mutableStateOf(true)
|
||||||
val isPolicyAgreement: State<Boolean> = _policyAgreement
|
val isPolicyAgreement: State<Boolean> = _policyAgreement
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -44,7 +84,10 @@ class LoginViewModel : ViewModel() {
|
||||||
_policyAgreement.value = isAgreement
|
_policyAgreement.value = isAgreement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//用户配置
|
||||||
|
val _userConfig = MutableLiveData<UserConfigEntity?>()
|
||||||
|
|
||||||
|
val userConfig: UserConfigEntity? get() = _userConfig.value
|
||||||
|
|
||||||
private val _isLogin = mutableStateOf(false)
|
private val _isLogin = mutableStateOf(false)
|
||||||
val isLogin: State<Boolean> = _isLogin
|
val isLogin: State<Boolean> = _isLogin
|
||||||
|
|
@ -53,6 +96,31 @@ class LoginViewModel : ViewModel() {
|
||||||
_isLogin.value = isLogin
|
_isLogin.value = isLogin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val loginState = mutableStateOf<ResultVo<UserEntity>?>(null)
|
||||||
|
val errorState = mutableStateOf<ErrorBean?>(null)
|
||||||
|
|
||||||
|
fun requestUserConfig(){
|
||||||
|
mLaunch {
|
||||||
|
val oaid = MMKVUtils.getString("oaid") ?: ""
|
||||||
|
val response = ApiManager.serviceVo.getUserConfig(oaid, Build.VERSION.SDK_INT, "", DeviceIdentifier.getAndroidID(applicationContext), MMKVUtils.getString("gt_cid") ?: "")
|
||||||
|
if (response.status) {
|
||||||
|
PreferenceUtil.saveXToken(response.data.token)
|
||||||
|
PreferenceUtil.setTimeDiff(response.data.nowtime.toLong() - System.currentTimeMillis() / 1000)
|
||||||
|
_userConfig.postValue(response.data)
|
||||||
|
|
||||||
|
val resultJson = Gson().toJson(response.data)
|
||||||
|
MMKVUtils.put("userConfig", resultJson)
|
||||||
|
|
||||||
|
Log.w("LoginViewModel", "获取配置成功: $resultJson")
|
||||||
|
}else{
|
||||||
|
Log.w("LoginViewModel", "获取配置失败: code=${response.code}, message=${response.message}")
|
||||||
|
}
|
||||||
|
isLoading.value = false // 加载完成
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun oneKeyLoginForGeTuiSdk(activity: Activity, onShowOneKeyScreen:(Boolean)->Unit) {
|
fun oneKeyLoginForGeTuiSdk(activity: Activity, onShowOneKeyScreen:(Boolean)->Unit) {
|
||||||
// 初始化 SDK
|
// 初始化 SDK
|
||||||
GYManager.getInstance().init(GyConfig.with(activity.applicationContext).callBack(object : GyCallBack {
|
GYManager.getInstance().init(GyConfig.with(activity.applicationContext).callBack(object : GyCallBack {
|
||||||
|
|
@ -141,11 +209,203 @@ class LoginViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun requestOneKeyLogin(gyuid: String, token: String) {
|
||||||
|
isLoading.value = true // 开始加载
|
||||||
|
|
||||||
|
// 调用 API 获取数据
|
||||||
|
val jsonOneKey = JsonObject()
|
||||||
|
// jsonOneKey.addProperty("gyuid", gyuid)
|
||||||
|
// jsonOneKey.addProperty("token", token)
|
||||||
|
// val jsonObject = JsonObject()
|
||||||
|
// jsonObject.addProperty("login_type", "onekey")
|
||||||
|
// jsonObject.add("onekey", jsonOneKey)
|
||||||
|
// jsonObject.addProperty("bind", false)
|
||||||
|
|
||||||
|
jsonOneKey.addProperty("gyuid", gyuid)
|
||||||
|
jsonOneKey.addProperty("token", token)
|
||||||
|
val jsonObject = JsonObject()
|
||||||
|
jsonObject.addProperty("type", "onekey")
|
||||||
|
jsonObject.addProperty("bind", "")
|
||||||
|
jsonObject.add("data", jsonOneKey)
|
||||||
|
|
||||||
|
requestLogin(jsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求验证码
|
* 请求验证码
|
||||||
*/
|
*/
|
||||||
fun requestCaptcha() {
|
fun requestCaptcha(phone: String) {
|
||||||
// TODO: 发送请求获取验证码
|
// 发送请求获取验证码
|
||||||
|
isLoading.value = true // 开始加载
|
||||||
|
|
||||||
|
mLaunch {
|
||||||
|
// 调用 API 获取数据
|
||||||
|
val jsonObject = JsonObject()
|
||||||
|
jsonObject.addProperty("phone", phone)
|
||||||
|
val response = ApiManager.serviceVo.sendCode(jsonObject.toString().toRequestBody())
|
||||||
|
if (response.status) {
|
||||||
|
Log.w("LoginViewModel", "请求验证码成功: ${response.data.timestamp}")
|
||||||
|
captchaTimestamp.value = response.data.timestamp
|
||||||
|
}else{
|
||||||
|
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "获取验证码失败" })
|
||||||
|
}
|
||||||
|
isLoading.value = false // 加载完成
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求登录(验证码)
|
||||||
|
*/
|
||||||
|
fun requestLoginForCaptcha(phone: String, code: String) {
|
||||||
|
isLoading.value = true // 开始加载
|
||||||
|
|
||||||
|
// 调用 API 获取数据
|
||||||
|
val jsonPhone = JsonObject()
|
||||||
|
jsonPhone.addProperty("timestamp", captchaTimestamp.value)
|
||||||
|
jsonPhone.addProperty("phone", phone)
|
||||||
|
jsonPhone.addProperty("code", code)
|
||||||
|
|
||||||
|
val jsonObject = JsonObject()
|
||||||
|
jsonObject.addProperty("login_type", "phone")
|
||||||
|
jsonObject.add("phone", jsonPhone)
|
||||||
|
|
||||||
|
requestLogin(jsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun initWXApi(context: Context) {
|
||||||
|
api = WXAPIFactory.createWXAPI(context, Constants.WechatAppId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loginWithWechat(context: Context) {
|
||||||
|
if (isPolicyAgreement.value) {
|
||||||
|
doWxAuth(context)
|
||||||
|
}else{
|
||||||
|
Toast.makeText(context, "请先同意用户协议和隐私政策", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拿着微信授权码完成登录(在WXEntryActivity中调用)
|
||||||
|
* @param wechatCode 微信授权码
|
||||||
|
*/
|
||||||
|
fun requestWxLogin(wechatCode: String) {
|
||||||
|
if(wechatCode.isEmpty()){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isLoading.value = true // 开始加载
|
||||||
|
|
||||||
|
// 调用 API 获取数据
|
||||||
|
val jsonWx = JsonObject()
|
||||||
|
jsonWx.addProperty("code", wechatCode)
|
||||||
|
jsonWx.addProperty("code_type", "")
|
||||||
|
|
||||||
|
val jsonObject = JsonObject()
|
||||||
|
jsonObject.addProperty("login_type", "weixin")
|
||||||
|
jsonObject.add("weixin", jsonWx)
|
||||||
|
|
||||||
|
requestLogin(jsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取微信授权
|
||||||
|
private fun doWxAuth(context: Context) {
|
||||||
|
if (!api.isWXAppInstalled) {
|
||||||
|
Toast.makeText(context, "您没有安装微信客户端,请先下载安装", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val req = SendAuth.Req()
|
||||||
|
req.scope = "snsapi_userinfo" // 只能填 snsapi_userinfo
|
||||||
|
req.state = context.packageName + Math.random() * 1000 + "_phone"
|
||||||
|
api.sendReq(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝登录
|
||||||
|
* authInfo: 该参数需由后端生成并加签,包含 app_id、pid、target_id 等信息
|
||||||
|
*/
|
||||||
|
fun loginWithAliPay(context: Context,onAuthResult: (Map<String, String>) -> Unit) {
|
||||||
|
if(authInfoForAlipay.value.isEmpty()){
|
||||||
|
Toast.makeText(context, "请先获取支付宝登录授权码", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 发送请求获取支付宝登录授权码
|
||||||
|
mLaunch {
|
||||||
|
onAuthResult(doAlipayLogin(context as Activity, authInfoForAlipay.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求支付宝登录参数
|
||||||
|
*/
|
||||||
|
fun requestAliPayAuthParam() {
|
||||||
|
isLoading.value = true // 开始加载
|
||||||
|
|
||||||
|
mLaunch {
|
||||||
|
val response = ApiManager.serviceVo.getAlipayAuthParam()
|
||||||
|
val data = response.data
|
||||||
|
val param = data.param
|
||||||
|
|
||||||
|
authInfoForAlipay.value = param
|
||||||
|
isLoading.value = false // 加载完成
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装支付宝登录逻辑
|
||||||
|
* @param activity 当前 Activity 上下文
|
||||||
|
* @param authInfo 后端生成的授权字符串
|
||||||
|
* @return 支付宝返回的原始 Map 结果
|
||||||
|
*/
|
||||||
|
private suspend fun doAlipayLogin(activity: Activity, authInfo: String): Map<String, String> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
// 初始化 AuthTask
|
||||||
|
val authTask = AuthTask(activity)
|
||||||
|
|
||||||
|
// 调用 authV2。第二个参数为 true 表示如果未安装支付宝则展示 Loading 界面, 该方法会阻塞当前线程直到用户操作结束
|
||||||
|
val result = authTask.authV2(authInfo, true)
|
||||||
|
Log.i(TAG, "doAlipayLogin: $result")
|
||||||
|
|
||||||
|
result ?: emptyMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拿着微信授权码完成登录(在WXEntryActivity中调用)
|
||||||
|
* @param authCode 微信授权码
|
||||||
|
*/
|
||||||
|
fun requestAlipayLogin(authCode: String) {
|
||||||
|
if(authCode.isEmpty()){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isLoading.value = true // 开始加载
|
||||||
|
|
||||||
|
// 调用 API 获取数据
|
||||||
|
val jsonWx = JsonObject()
|
||||||
|
jsonWx.addProperty("auth_code", authCode)//code
|
||||||
|
|
||||||
|
val jsonObject = JsonObject()
|
||||||
|
jsonObject.addProperty("type", "alipay")
|
||||||
|
jsonObject.addProperty("bind", "")
|
||||||
|
jsonObject.add("data", jsonWx)
|
||||||
|
|
||||||
|
requestLogin(jsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestLogin(jsonObject: JsonObject){
|
||||||
|
mLaunch {
|
||||||
|
val response = ApiManager.serviceVo.login(jsonObject.toString().toRequestBody())
|
||||||
|
if (response.status) {
|
||||||
|
loginState.value = response
|
||||||
|
val userEntity = response.data
|
||||||
|
userEntity.isLogin = true
|
||||||
|
//记录登录数据
|
||||||
|
PreferenceUtil.saveUserInfo(userEntity)
|
||||||
|
}else{
|
||||||
|
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "登录失败" })
|
||||||
|
}
|
||||||
|
isLoading.value = false // 加载完成
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.img.rabbit.viewmodel.`interface`
|
||||||
|
|
||||||
|
import com.img.rabbit.bean.response.AlipayParamEntity
|
||||||
|
import com.img.rabbit.bean.response.CaptchaCodeEntity
|
||||||
|
import com.img.rabbit.bean.response.UserEntity
|
||||||
|
import com.img.rabbit.bean.response.UserConfigEntity
|
||||||
|
import com.img.rabbit.provider.api.ResultVo
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface ServiceVo {
|
||||||
|
/*
|
||||||
|
@POST("/dictionary/getTsSyspara")
|
||||||
|
suspend fun requestHospitalName(): Response<ResultListVo<MutableList<HospitalNameData>>>
|
||||||
|
@POST("/dictionary/getLogo")
|
||||||
|
suspend fun requestHospitalLogo(): Response<ResultListVo<HospitalLogoData>>
|
||||||
|
@POST("/nurse/login")
|
||||||
|
suspend fun requestLogin(@Body request: LoginRequest): Response<ResultListVo<LoginData>>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务器时间
|
||||||
|
*/
|
||||||
|
@GET("/api/time")
|
||||||
|
suspend fun getServerTime(): ResultVo<Long>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端配置
|
||||||
|
*/
|
||||||
|
@GET("/api/user/config")
|
||||||
|
suspend fun getUserConfig(
|
||||||
|
@Query("oaid") oaid: String,
|
||||||
|
@Query("os_version") osVersion: Int,
|
||||||
|
@Query("ua") ua: String,
|
||||||
|
@Query("imei") imei: String,
|
||||||
|
@Query("cid") cid: String,
|
||||||
|
): ResultVo<UserConfigEntity>
|
||||||
|
|
||||||
|
@GET("api/alipay/app_param")
|
||||||
|
suspend fun getAlipayAuthParam(): ResultVo<AlipayParamEntity>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码
|
||||||
|
*/
|
||||||
|
@POST("api/user/code")
|
||||||
|
suspend fun sendCode(@Body requestBody: RequestBody): ResultVo<CaptchaCodeEntity>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录
|
||||||
|
*/
|
||||||
|
@POST("/api/user/login")
|
||||||
|
suspend fun login(@Body requestBody: RequestBody): ResultVo<UserEntity>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*/
|
||||||
|
@POST("/mapi/user/logout")
|
||||||
|
suspend fun logout(): ResultVo<Any>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
@file:OptIn(DelicateCoroutinesApi::class)
|
||||||
|
|
||||||
|
package com.img.rabbit.wxapi
|
||||||
|
|
||||||
|
import com.img.rabbit.provider.storage.GlobalStateManager
|
||||||
|
import com.tencent.mm.opensdk.constants.ConstantsAPI
|
||||||
|
import com.tencent.mm.opensdk.modelbase.BaseResp
|
||||||
|
import com.tencent.mm.opensdk.modelmsg.SendAuth
|
||||||
|
import com.umeng.socialize.weixin.view.WXCallbackActivity
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
class WXEntryActivity : WXCallbackActivity() {
|
||||||
|
override fun onResp(resp: BaseResp?) {
|
||||||
|
if (resp?.type == ConstantsAPI.COMMAND_SENDAUTH) {
|
||||||
|
when (resp.errCode) {
|
||||||
|
BaseResp.ErrCode.ERR_OK -> {
|
||||||
|
val authResp = resp as SendAuth.Resp
|
||||||
|
//val wxState = WxBean(code = authResp.code,state = authResp.state)
|
||||||
|
|
||||||
|
GlobalScope.launch {
|
||||||
|
GlobalStateManager(this@WXEntryActivity).storeGlobalWxAuthorization(authResp.code)
|
||||||
|
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="1024"
|
||||||
|
android:viewportHeight="1024">
|
||||||
|
<path
|
||||||
|
android:pathData="M902.1,652.9l-251,-84.4s19.3,-28.9 39.9,-85.5c20.6,-56.6 23.5,-87.7 23.5,-87.7l-162.5,-1.3v-55.5l196.7,-1.4v-39.2L552.1,297.9v-89.3h-96.4v89.3L272.1,297.9v39.2l183.6,-1.3v59.5h-147.2v31.1h303.1s-3.3,25.2 -15,56.6c-11.6,31.4 -23.6,58.9 -23.6,58.9s-142.3,-49.8 -217.3,-49.8c-75,0 -166.2,30.1 -175,117.6 -8.8,87.4 42.5,134.7 114.7,152.1 72.3,17.5 139,-0.2 197,-28.6 58.1,-28.4 115.1,-92.9 115.1,-92.9l292.5,142c-11.9,69.3 -72.1,119.9 -142.4,119.8L266.4,902.1c-79.7,0.1 -144.4,-64.5 -144.5,-144.2L121.9,266.4c-0.1,-79.7 64.5,-144.4 144.2,-144.5h491.5c79.7,-0.1 144.4,64.5 144.5,144.2v386.8zM536.3,604s-91.3,115.3 -198.9,115.3c-107.6,0 -130.2,-54.8 -130.2,-94.2 0,-39.3 22.4,-82.1 113.9,-88.3 91.5,-6.2 215.2,67.2 215.2,67.2h-0z"
|
||||||
|
android:fillColor="#02A9F1"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="1024"
|
||||||
|
android:viewportHeight="1024">
|
||||||
|
<path
|
||||||
|
android:pathData="M669.3,369.4c9.8,0 19.6,0 29.4,1.6C671,245.2 536.9,152 383.2,152 211.6,152 71,269.7 71,416.8c0,85 45.8,156.9 124.2,210.9l-31.1,93.2L273.6,667c39.2,8.2 70.3,16.3 109.5,16.3 9.8,0 19.6,0 31.1,-1.6 -6.5,-21.3 -9.8,-42.5 -9.8,-65.4 0.1,-135.7 116.2,-246.9 264.9,-246.9zM500.9,284.4c24.5,0 39.2,16.3 39.2,39.2 0,22.9 -16.3,39.2 -39.2,39.2 -24.5,0 -47.4,-16.4 -47.4,-39.2 0,-24.5 24.6,-39.2 47.4,-39.2zM284.6,357.5c-24.7,0 -47.8,-16.2 -47.8,-38.8 0,-24.3 24.7,-38.8 47.8,-38.8s39.5,16.2 39.5,38.8c0.1,22.7 -16.4,38.8 -39.5,38.8z"
|
||||||
|
android:fillColor="#24DB5A"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M953.8,613c0,-125.9 -124.2,-227.2 -264.8,-227.2 -148.8,0 -266.5,103 -266.5,227.2 0,125.9 117.7,227.2 266.5,227.2 31.1,0 62.1,-8.2 93.2,-16.3l85,47.4 -22.9,-78.5c62.1,-47.4 109.5,-109.5 109.5,-179.8zM602.3,573.8c-14.7,0 -31.1,-14.7 -31.1,-31.1 0,-14.7 16.3,-31.1 31.1,-31.1 22.9,0 39.2,16.3 39.2,31.1 0,16.4 -14.7,31.1 -39.2,31.1zM780.3,566.2c-14.8,0 -31.3,-14.6 -31.3,-30.7 0,-14.6 16.5,-30.7 31.3,-30.7 23.1,0 39.5,16.2 39.5,30.7 0,16.2 -16.4,30.7 -39.5,30.7z"
|
||||||
|
android:fillColor="#24DB5A"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="36dp"
|
||||||
|
android:minHeight="46dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_web_break"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:src="@mipmap/ic_back"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/iv_web_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="服务协议"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/webView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</LinearLayout>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -25,6 +25,10 @@ androidx-test-ext-junit = "1.1.5"
|
||||||
androidx-test-espresso-core = "3.5.1"
|
androidx-test-espresso-core = "3.5.1"
|
||||||
navigationRuntimeKtx = "2.9.7"
|
navigationRuntimeKtx = "2.9.7"
|
||||||
|
|
||||||
|
# Network Version
|
||||||
|
retrofit = "3.0.0"
|
||||||
|
okhttp = "5.1.0"
|
||||||
|
|
||||||
# Third-party version
|
# Third-party version
|
||||||
gson = "2.13.2"
|
gson = "2.13.2"
|
||||||
gysdk = "3.2.3.0"
|
gysdk = "3.2.3.0"
|
||||||
|
|
@ -37,6 +41,20 @@ faceDetection = "16.1.5"
|
||||||
foundation = "1.10.2"
|
foundation = "1.10.2"
|
||||||
androidGifDrawableEncoder = "1.2.30"
|
androidGifDrawableEncoder = "1.2.30"
|
||||||
gifeEncoder = "0.10.1"
|
gifeEncoder = "0.10.1"
|
||||||
|
cropify = "0.5.2"
|
||||||
|
matisse = "2.3.0"
|
||||||
|
pictureselector = "v3.11.2"
|
||||||
|
compress = "v3.11.2"
|
||||||
|
wechatSdkAndroidWithoutMta = "6.8.0"
|
||||||
|
# Umeng version
|
||||||
|
umengUmsdkCommon = "9.8.9"
|
||||||
|
umengUmsdkAsms = "1.8.7.2"
|
||||||
|
umengUmsdkApm = "2.0.6"
|
||||||
|
umengUmsdkShareCore = "7.3.7"
|
||||||
|
tencentHelper = "3.0.6"
|
||||||
|
#oaid
|
||||||
|
android_cn_oaid = "4.2.12"
|
||||||
|
fastaes = "1.1.5"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
@ -70,17 +88,41 @@ androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "a
|
||||||
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso-core" }
|
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso-core" }
|
||||||
androidx-navigation-runtime-ktx = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "navigationRuntimeKtx" }
|
androidx-navigation-runtime-ktx = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "navigationRuntimeKtx" }
|
||||||
|
|
||||||
|
# Network dependencies
|
||||||
|
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
||||||
|
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
|
||||||
|
retrofit-kotlin-serialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version = "1.0.0" }
|
||||||
|
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
||||||
|
okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
|
||||||
|
|
||||||
|
|
||||||
# Third-party dependencies
|
# Third-party dependencies
|
||||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||||
gysdk = { module = "com.getui:gysdk", version.ref = "gysdk" }
|
gysdk = { module = "com.getui:gysdk", version.ref = "gysdk" }
|
||||||
|
matisse = { module = "io.github.leavesczy:matisse", version.ref = "matisse" }
|
||||||
mmkv = { module = "com.tencent:mmkv", version.ref = "mmkv" }
|
mmkv = { module = "com.tencent:mmkv", version.ref = "mmkv" }
|
||||||
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
|
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
|
||||||
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilCompose" }
|
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilCompose" }
|
||||||
|
pictureselector = { module = "io.github.lucksiege:pictureselector", version.ref = "pictureselector" }
|
||||||
|
compress = { module = "io.github.lucksiege:compress", version.ref = "compress" }
|
||||||
segmentation-selfie = { module = "com.google.mlkit:segmentation-selfie", version.ref = "segmentationSelfie" }
|
segmentation-selfie = { module = "com.google.mlkit:segmentation-selfie", version.ref = "segmentationSelfie" }
|
||||||
face-detection = { module = "com.google.mlkit:face-detection", version.ref = "faceDetection" }
|
face-detection = { module = "com.google.mlkit:face-detection", version.ref = "faceDetection" }
|
||||||
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
|
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
|
||||||
android-gif-drawable = { module = "pl.droidsonroids.gif:android-gif-drawable", version.ref = "androidGifDrawableEncoder" }
|
android-gif-drawable = { module = "pl.droidsonroids.gif:android-gif-drawable", version.ref = "androidGifDrawableEncoder" }
|
||||||
gif-encoder = { module = "com.squareup:gifencoder", version.ref = "gifeEncoder" }
|
gif-encoder = { module = "com.squareup:gifencoder", version.ref = "gifeEncoder" }
|
||||||
|
cropify = { module = "com.github.moyuruaizawa:cropify", version.ref = "cropify" }
|
||||||
|
wechat-sdk = { module = "com.tencent.mm.opensdk:wechat-sdk-android-without-mta", version.ref = "wechatSdkAndroidWithoutMta" }
|
||||||
|
#Umeng Sdk
|
||||||
|
umeng-umsdk-common = { module = "com.umeng.umsdk:common", version.ref = "umengUmsdkCommon" }
|
||||||
|
umeng-umsdk-asms = { module = "com.umeng.umsdk:asms", version.ref = "umengUmsdkAsms" }
|
||||||
|
#noinspection Aligned16KB
|
||||||
|
umeng-umsdk-apm = { module = "com.umeng.umsdk:apm", version.ref = "umengUmsdkApm" }
|
||||||
|
umeng-umsdk-share-core = { module = "com.umeng.umsdk:share-core", version.ref = "umengUmsdkShareCore" }
|
||||||
|
umeng-umsdk-share-wx = { module = "com.umeng.umsdk:share-wx", version.ref = "umengUmsdkShareCore" }
|
||||||
|
tencent-helper = { module = "com.tencent.vasdolly:helper", version.ref = "tencentHelper" }
|
||||||
|
android_cn_oaid = { module = "com.github.gzu-liyujiang:Android_CN_OAID", version.ref = "android_cn_oaid" }
|
||||||
|
#Decrypt
|
||||||
|
fastaes = { module = "io.github.billywei01:fastaes", version.ref = "fastaes" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ pluginManagement {
|
||||||
includeGroupByRegex("androidx.*")
|
includeGroupByRegex("androidx.*")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/public") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/google") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
|
||||||
|
maven { url = uri("https://developer.huawei.com/repo/") }
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
|
|
@ -17,9 +21,19 @@ dependencyResolutionManagement {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = uri("https://jitpack.io") }
|
maven { url = uri("https://jitpack.io") }
|
||||||
|
maven { url = uri("https://company/com/maven2") }
|
||||||
maven { url = uri("https://repo1.maven.org/maven2/") }
|
maven { url = uri("https://repo1.maven.org/maven2/") }
|
||||||
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
|
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
|
||||||
maven { url = uri("https://mvn.getui.com/nexus/content/repositories/releases") }
|
maven { url = uri("https://mvn.getui.com/nexus/content/repositories/releases") } //个推
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/public") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/google") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
|
||||||
|
maven { url = uri("https://developer.huawei.com/repo/") }//添加华为仓库 获取oaid
|
||||||
|
maven { url = uri("https://developer.hihonor.com/repo/") }
|
||||||
|
maven { url = uri("https://artifact.bytedance.com/repository/Volcengine/") } //巨量融合
|
||||||
|
maven { url = uri("https://repo.eclipse.org/content/repositories/paho-snapshots/") } //mqtt
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue