feat: 增加接口签名验证
- 增加接口签名验证 - 优化登陆界面以适应密码保险箱 - 修复表情面板高度错误
This commit is contained in:
parent
632cd5c4e6
commit
da1fc07599
@ -20,7 +20,7 @@ android {
|
|||||||
minSdk = 28
|
minSdk = 28
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "0.0.01"
|
versionName = "0.0.1"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@ -91,6 +91,9 @@ dependencies {
|
|||||||
|
|
||||||
// 自定义spannable
|
// 自定义spannable
|
||||||
implementation(libs.spannable)
|
implementation(libs.spannable)
|
||||||
|
|
||||||
|
implementation(libs.soft.input.event)
|
||||||
|
|
||||||
implementation(libs.scanplus)
|
implementation(libs.scanplus)
|
||||||
// implementation(libs.therouter)
|
// implementation(libs.therouter)
|
||||||
// ksp(libs.therouter.ksp)
|
// ksp(libs.therouter.ksp)
|
||||||
|
@ -14,7 +14,7 @@ import io.objectbox.Box
|
|||||||
|
|
||||||
class ContactRepository {
|
class ContactRepository {
|
||||||
|
|
||||||
private val contactApiService = RetrofitClient.contactApiService
|
private val friendApiService = RetrofitClient.friendApiService
|
||||||
|
|
||||||
private val contactBox: Box<Contact> by lazy {
|
private val contactBox: Box<Contact> by lazy {
|
||||||
getBoxStore().boxFor(Contact::class.java)
|
getBoxStore().boxFor(Contact::class.java)
|
||||||
@ -23,7 +23,7 @@ class ContactRepository {
|
|||||||
// 获取联系人请求列表
|
// 获取联系人请求列表
|
||||||
suspend fun getContactRequestList(username: String): Result<List<FriendRequestItem>?> {
|
suspend fun getContactRequestList(username: String): Result<List<FriendRequestItem>?> {
|
||||||
return apiCall(
|
return apiCall(
|
||||||
apiCall = { contactApiService.getContactRequestList(username) },
|
apiCall = { friendApiService.getContactRequestList(username) },
|
||||||
errorMessage = "获取好友申请列表失败"
|
errorMessage = "获取好友申请列表失败"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ class ContactRepository {
|
|||||||
remark: String
|
remark: String
|
||||||
): Result<Contact?> {
|
): Result<Contact?> {
|
||||||
return apiCall(
|
return apiCall(
|
||||||
apiCall = { contactApiService.acceptContactRequest(contactId, username, remark) },
|
apiCall = { friendApiService.acceptContactRequest(contactId, username, remark) },
|
||||||
errorMessage = "添加好友失败"
|
errorMessage = "添加好友失败"
|
||||||
).onSuccess {
|
).onSuccess {
|
||||||
it?.let {
|
it?.let {
|
||||||
@ -50,7 +50,7 @@ class ContactRepository {
|
|||||||
contactId: String,
|
contactId: String,
|
||||||
): Result<String?> {
|
): Result<String?> {
|
||||||
return apiCall(
|
return apiCall(
|
||||||
apiCall = { contactApiService.deleteContact(username, contactId) },
|
apiCall = { friendApiService.deleteContact(username, contactId) },
|
||||||
errorMessage = "删除好友失败"
|
errorMessage = "删除好友失败"
|
||||||
).onSuccess {
|
).onSuccess {
|
||||||
val con =
|
val con =
|
||||||
@ -62,7 +62,7 @@ class ContactRepository {
|
|||||||
// 添加联系人
|
// 添加联系人
|
||||||
suspend fun addContact(contactId: String, message: String): Result<String?> {
|
suspend fun addContact(contactId: String, message: String): Result<String?> {
|
||||||
return apiCall(
|
return apiCall(
|
||||||
apiCall = { contactApiService.addContact(getUsername(), contactId, message) },
|
apiCall = { friendApiService.addContact(getUsername(), contactId, message) },
|
||||||
errorMessage = "添加联系人失败"
|
errorMessage = "添加联系人失败"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -70,21 +70,16 @@ class ContactRepository {
|
|||||||
// 搜索联系人
|
// 搜索联系人
|
||||||
suspend fun searchContact(username: String): Result<SearchUser?> {
|
suspend fun searchContact(username: String): Result<SearchUser?> {
|
||||||
return apiCall(
|
return apiCall(
|
||||||
apiCall = { contactApiService.searchContact(username) },
|
apiCall = { friendApiService.searchContact(username) },
|
||||||
errorMessage = "搜索用户失败"
|
errorMessage = "搜索用户失败"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取联系人列表
|
// 获取联系人列表
|
||||||
suspend fun getContactList(username: String): Result<List<Contact>?> {
|
suspend fun getContactList(username: String): Result<List<Contact>?> {
|
||||||
// return safeApiCall(
|
|
||||||
// apiCall = { contactApiService.getContactList(username) },
|
|
||||||
// errorMessage = "获取好友列表失败"
|
|
||||||
// ).onSuccess {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
return try {
|
return try {
|
||||||
val response = contactApiService.getContactList(username)
|
val maps = mapOf("username" to username)
|
||||||
|
val response = friendApiService.getContactList(maps)
|
||||||
if (response.isSuccess()) {
|
if (response.isSuccess()) {
|
||||||
val searchUsers = response.getResponseData()
|
val searchUsers = response.getResponseData()
|
||||||
searchUsers?.let {
|
searchUsers?.let {
|
||||||
@ -113,7 +108,7 @@ class ContactRepository {
|
|||||||
|
|
||||||
suspend fun setRemark(userId: String, contactId: String, remark: String): Result<String?> {
|
suspend fun setRemark(userId: String, contactId: String, remark: String): Result<String?> {
|
||||||
return apiCall(
|
return apiCall(
|
||||||
apiCall = { contactApiService.setRemark(userId, contactId, remark) },
|
apiCall = { friendApiService.setRemark(userId, contactId, remark) },
|
||||||
errorMessage = "设置备注失败"
|
errorMessage = "设置备注失败"
|
||||||
).onSuccess {
|
).onSuccess {
|
||||||
val con = contactBox.query(Contact_.username.equal(contactId)).build().findFirst()
|
val con = contactBox.query(Contact_.username.equal(contactId)).build().findFirst()
|
||||||
|
@ -23,7 +23,10 @@ class UserAuthRepository {
|
|||||||
|
|
||||||
private val mmkv by lazy { MMKV.mmkvWithID(MMKV_USER_SESSION) }
|
private val mmkv by lazy { MMKV.mmkvWithID(MMKV_USER_SESSION) }
|
||||||
|
|
||||||
// 用户注册
|
/**
|
||||||
|
* 注册方法
|
||||||
|
* @param registerRequest 注册请求参数
|
||||||
|
*/
|
||||||
suspend fun register(registerRequest: RegisterRequest): Result<Register?> {
|
suspend fun register(registerRequest: RegisterRequest): Result<Register?> {
|
||||||
return apiCall(
|
return apiCall(
|
||||||
apiCall = { authApiService.register(registerRequest) },
|
apiCall = { authApiService.register(registerRequest) },
|
||||||
@ -35,10 +38,15 @@ class UserAuthRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录方法
|
/**
|
||||||
|
* 登录方法
|
||||||
|
* @param username 用户名
|
||||||
|
* @param password 密码
|
||||||
|
*/
|
||||||
suspend fun loginByUsername(username: String, password: String): Result<UserInfo?> {
|
suspend fun loginByUsername(username: String, password: String): Result<UserInfo?> {
|
||||||
|
val requestParams = mapOf("username" to username, "password" to password)
|
||||||
return apiCall(
|
return apiCall(
|
||||||
apiCall = { authApiService.loginByUsername(username, password) },
|
apiCall = { authApiService.loginByUsername(requestParams) },
|
||||||
errorMessage = "登录成功,但未返回用户数据"
|
errorMessage = "登录成功,但未返回用户数据"
|
||||||
).onSuccess { userInfo ->
|
).onSuccess { userInfo ->
|
||||||
userInfo?.let {
|
userInfo?.let {
|
||||||
@ -47,10 +55,15 @@ class UserAuthRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录方法
|
||||||
|
* @param telephone 手机号
|
||||||
|
* @param password 密码
|
||||||
|
*/
|
||||||
suspend fun loginByTelephone(telephone: String, password: String): Result<UserInfo?> {
|
suspend fun loginByTelephone(telephone: String, password: String): Result<UserInfo?> {
|
||||||
|
val requestParams = mapOf("telephone" to telephone, "password" to password)
|
||||||
return apiCall(
|
return apiCall(
|
||||||
apiCall = { authApiService.loginByTelephone(telephone, password) },
|
apiCall = { authApiService.loginByTelephone(requestParams) },
|
||||||
errorMessage = "登录成功,但未返回用户数据"
|
errorMessage = "登录成功,但未返回用户数据"
|
||||||
).onSuccess { userInfo ->
|
).onSuccess { userInfo ->
|
||||||
userInfo?.let {
|
userInfo?.let {
|
||||||
@ -58,7 +71,7 @@ class UserAuthRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertUserInfo(
|
private fun insertUserInfo(
|
||||||
register: Register,
|
register: Register,
|
||||||
telephone: String
|
telephone: String
|
||||||
|
@ -23,34 +23,21 @@ class UserProfileRepository {
|
|||||||
private val userApiService = RetrofitClient.userApiService
|
private val userApiService = RetrofitClient.userApiService
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
suspend fun getUserInfo(username: String): Result<SearchUser> {
|
suspend fun getUserInfo(username: String): Result<SearchUser?> {
|
||||||
return try {
|
val requestParams = mapOf("username" to username)
|
||||||
val response = userApiService.getUserInfo(username)
|
return apiCall(
|
||||||
if (response.isSuccess()) {
|
apiCall = { userApiService.getUserInfo(requestParams) },
|
||||||
val searchUser = response.getResponseData()
|
errorMessage = "获取用户信息失败"
|
||||||
searchUser?.let {
|
)
|
||||||
Result.success(searchUser)
|
|
||||||
} ?: Result.failure(Exception("用户不存在"))
|
|
||||||
} else {
|
|
||||||
Result.failure(Exception(response.getResponseMsg()))
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Result.failure(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改昵称
|
// 修改昵称
|
||||||
suspend fun changeNickname(userRequest: UserRequest): Result<Boolean> {
|
suspend fun changeNickname(userRequest: UserRequest): Result<Boolean?> {
|
||||||
return try {
|
return apiCall(
|
||||||
val response = userApiService.changeNickname(userRequest)
|
apiCall = { userApiService.changeNickname(userRequest) },
|
||||||
if (response.isSuccess()) {
|
errorMessage = "修改昵称失败"
|
||||||
updateNickname(userRequest.nickname!!)
|
).onSuccess {
|
||||||
Result.success(true)
|
updateNickname(userRequest.nickname!!)
|
||||||
} else {
|
|
||||||
Result.failure(Exception(response.getResponseMsg()))
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Result.failure(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,9 @@ class UserSearchRepository {
|
|||||||
|
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
suspend fun getUserList(username: String): Result<List<User>> {
|
suspend fun getUserList(username: String): Result<List<User>> {
|
||||||
|
val requestParams = mapOf("username" to username)
|
||||||
return try {
|
return try {
|
||||||
val response = userApiService.getUserListByNickname(username)
|
val response = userApiService.fetchUserList(requestParams)
|
||||||
if (response.isSuccess()) {
|
if (response.isSuccess()) {
|
||||||
val userList = response.getResponseData()
|
val userList = response.getResponseData()
|
||||||
userList?.let {
|
userList?.let {
|
||||||
|
@ -11,7 +11,7 @@ object NetworkInterface {
|
|||||||
// private const val URL = "49.233.105.103:6000"
|
// private const val URL = "49.233.105.103:6000"
|
||||||
// const val SERVER_URL = "https://$URL"
|
// const val SERVER_URL = "https://$URL"
|
||||||
const val SERVER_URL = "http://$URL"
|
const val SERVER_URL = "http://$URL"
|
||||||
const val WEBSOCKET_SERVER_URL = "wss://$URL"
|
const val WEBSOCKET_SERVER_URL = "ws://$URL"
|
||||||
const val WEBSOCKET = "/websocket/single/"
|
const val WEBSOCKET = "/websocket/single/"
|
||||||
const val USER_INFO = "/users/info/"
|
const val USER_INFO = "/users/info/"
|
||||||
const val USER_LOGIN_BY_USERNAME = "/users/login/username"
|
const val USER_LOGIN_BY_USERNAME = "/users/login/username"
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package com.kaixed.kchat.network
|
package com.kaixed.kchat.network
|
||||||
|
|
||||||
|
import com.kaixed.kchat.network.interceptor.SignInterceptor
|
||||||
import com.kaixed.kchat.network.interceptor.TokenRefreshInterceptor
|
import com.kaixed.kchat.network.interceptor.TokenRefreshInterceptor
|
||||||
import com.kaixed.kchat.network.service.AuthApiService
|
import com.kaixed.kchat.network.service.AuthApiService
|
||||||
import com.kaixed.kchat.network.service.ContactService
|
|
||||||
import com.kaixed.kchat.network.service.FileApiService
|
import com.kaixed.kchat.network.service.FileApiService
|
||||||
|
import com.kaixed.kchat.network.service.FriendApiService
|
||||||
import com.kaixed.kchat.network.service.UserApiService
|
import com.kaixed.kchat.network.service.UserApiService
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
@ -48,6 +49,7 @@ object RetrofitClient {
|
|||||||
private val client = OkHttpClient.Builder()
|
private val client = OkHttpClient.Builder()
|
||||||
.addInterceptor(loggingInterceptor)
|
.addInterceptor(loggingInterceptor)
|
||||||
.pingInterval(15, TimeUnit.SECONDS)
|
.pingInterval(15, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(SignInterceptor()) // 签名拦截器
|
||||||
.addInterceptor(TokenRefreshInterceptor()) // Token 拦截器
|
.addInterceptor(TokenRefreshInterceptor()) // Token 拦截器
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@ -65,8 +67,8 @@ object RetrofitClient {
|
|||||||
retrofit.create(UserApiService::class.java)
|
retrofit.create(UserApiService::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
val contactApiService: ContactService by lazy {
|
val friendApiService: FriendApiService by lazy {
|
||||||
retrofit.create(ContactService::class.java)
|
retrofit.create(FriendApiService::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
val fileApiService: FileApiService by lazy {
|
val fileApiService: FileApiService by lazy {
|
||||||
|
@ -1,37 +1,99 @@
|
|||||||
package com.kaixed.kchat.network.interceptor
|
package com.kaixed.kchat.network.interceptor
|
||||||
|
|
||||||
import com.kaixed.kchat.network.SignUtil.generateSign
|
import okhttp3.FormBody
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import okio.IOException
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class SignInterceptor : Interceptor {
|
class SignInterceptor : Interceptor {
|
||||||
|
|
||||||
|
private val secretKey = "YourSecretKey" // 后端秘钥
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val request = chain.request()
|
||||||
val urlPath = originalRequest.url.encodedPath
|
|
||||||
|
|
||||||
val skipSignPaths = listOf(
|
|
||||||
"/users/avatar",
|
|
||||||
)
|
|
||||||
|
|
||||||
// 检查是否需要跳过签名
|
|
||||||
if (skipSignPaths.any { urlPath.endsWith(it) }) {
|
|
||||||
// 直接放行请求,不做签名处理
|
|
||||||
return chain.proceed(originalRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前时间戳
|
// 获取当前时间戳
|
||||||
val timestamp = System.currentTimeMillis().toString()
|
val timestamp = System.currentTimeMillis().toString()
|
||||||
|
|
||||||
// 生成请求的签名
|
// 生成随机 nonce
|
||||||
val sign = generateSign(originalRequest, timestamp)
|
val nonce = UUID.randomUUID().toString()
|
||||||
|
|
||||||
// 创建新的请求,加入 sign 和 timestamp
|
// 获取请求参数
|
||||||
val newRequest = originalRequest.newBuilder()
|
val params = getRequestParams(request)
|
||||||
|
|
||||||
|
// 生成签名
|
||||||
|
val sign = generateServerSign(params, timestamp, nonce, secretKey)
|
||||||
|
|
||||||
|
// 构造新的请求,添加请求头
|
||||||
|
val newRequest = request.newBuilder()
|
||||||
.addHeader("sign", sign)
|
.addHeader("sign", sign)
|
||||||
.addHeader("timestamp", timestamp)
|
.addHeader("timestamp", timestamp)
|
||||||
|
.addHeader("nonce", nonce)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
// 继续请求
|
||||||
return chain.proceed(newRequest)
|
return chain.proceed(newRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取请求的参数,处理 POST 请求的 Map 格式数据,并确保按字典顺序排序
|
||||||
|
private fun getRequestParams(request: Request): Map<String, String> {
|
||||||
|
val params = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
// 处理 GET 请求
|
||||||
|
if (request.method == "GET") {
|
||||||
|
val url = request.url
|
||||||
|
for (i in 0 until url.querySize) {
|
||||||
|
params[url.queryParameterName(i)] = url.queryParameterValue(i) ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 POST 请求(Map 格式)
|
||||||
|
else if (request.method == "POST") {
|
||||||
|
val body = request.body
|
||||||
|
if (body is FormBody) {
|
||||||
|
// 获取所有参数,并确保按字典顺序排序
|
||||||
|
for (i in 0 until body.size) {
|
||||||
|
params[body.name(i)] = body.value(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按字典顺序排序参数
|
||||||
|
return params.toSortedMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成服务端签名
|
||||||
|
private fun generateServerSign(params: Map<String, String>, timestamp: String, nonce: String, secretKey: String): String {
|
||||||
|
val sortedParams = params.toSortedMap() // 确保是按字典顺序排序
|
||||||
|
|
||||||
|
// 拼接参数
|
||||||
|
val sb = StringBuilder()
|
||||||
|
for ((key, value) in sortedParams) {
|
||||||
|
sb.append("$key=$value&")
|
||||||
|
}
|
||||||
|
sb.append("timestamp=$timestamp&")
|
||||||
|
sb.append("nonce=$nonce&")
|
||||||
|
sb.append("secretKey=$secretKey")
|
||||||
|
|
||||||
|
// 使用 SHA-256 进行加密
|
||||||
|
return hashWithSHA256(sb.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 使用 SHA-256 进行哈希加密
|
||||||
|
private fun hashWithSHA256(input: String): String {
|
||||||
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
val hashBytes = digest.digest(input.toByteArray(Charsets.UTF_8))
|
||||||
|
val hexString = StringBuilder()
|
||||||
|
for (b in hashBytes) {
|
||||||
|
val hex = Integer.toHexString(0xff and b.toInt())
|
||||||
|
if (hex.length == 1) hexString.append('0')
|
||||||
|
hexString.append(hex)
|
||||||
|
}
|
||||||
|
return hexString.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import com.kaixed.kchat.data.model.request.RegisterRequest
|
|||||||
import com.kaixed.kchat.data.model.response.register.Register
|
import com.kaixed.kchat.data.model.response.register.Register
|
||||||
import com.kaixed.kchat.network.ApiResponse
|
import com.kaixed.kchat.network.ApiResponse
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.Field
|
|
||||||
import retrofit2.http.FormUrlEncoded
|
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
@ -17,36 +15,32 @@ import retrofit2.http.Query
|
|||||||
*/
|
*/
|
||||||
interface AuthApiService {
|
interface AuthApiService {
|
||||||
|
|
||||||
@POST("users/access-token")
|
@POST("auth/access-token")
|
||||||
suspend fun auth(
|
suspend fun auth(
|
||||||
@Header("Authorization") token: String,
|
@Header("Authorization") token: String,
|
||||||
@Query("username") username: String
|
@Query("username") username: String
|
||||||
): ApiResponse<String>
|
): ApiResponse<String>
|
||||||
|
|
||||||
@POST("users/refresh-token")
|
@POST("auth/refresh-token")
|
||||||
suspend fun refresh(
|
suspend fun refresh(
|
||||||
@Header("Authorization") token: String,
|
@Header("Authorization") token: String,
|
||||||
@Query("username") username: String
|
@Query("username") username: String
|
||||||
): ApiResponse<String>
|
): ApiResponse<String>
|
||||||
|
|
||||||
// 登录接口(根据用户名登录)
|
// 登录接口(根据用户名登录)
|
||||||
@FormUrlEncoded
|
@POST("auth/login/username")
|
||||||
@POST("users/login/username")
|
|
||||||
suspend fun loginByUsername(
|
suspend fun loginByUsername(
|
||||||
@Field("username") username: String,
|
@Body requestParams: Map<String, String>,
|
||||||
@Field("password") password: String
|
|
||||||
): ApiResponse<UserInfo?>
|
): ApiResponse<UserInfo?>
|
||||||
|
|
||||||
// 登录接口(根据电话登录)
|
// 登录接口(根据电话登录)
|
||||||
@FormUrlEncoded
|
@POST("auth/login/telephone")
|
||||||
@POST("users/login/telephone")
|
|
||||||
suspend fun loginByTelephone(
|
suspend fun loginByTelephone(
|
||||||
@Field("telephone") telephone: String,
|
@Body requestParams: Map<String, String>,
|
||||||
@Field("password") password: String
|
|
||||||
): ApiResponse<UserInfo>
|
): ApiResponse<UserInfo>
|
||||||
|
|
||||||
// 注册接口
|
// 注册接口
|
||||||
@POST("users/register")
|
@POST("auth/register")
|
||||||
suspend fun register(
|
suspend fun register(
|
||||||
@Body registerRequest: RegisterRequest
|
@Body registerRequest: RegisterRequest
|
||||||
): ApiResponse<Register>
|
): ApiResponse<Register>
|
||||||
|
@ -4,6 +4,7 @@ import com.kaixed.kchat.data.local.entity.Contact
|
|||||||
import com.kaixed.kchat.data.model.friend.FriendRequestItem
|
import com.kaixed.kchat.data.model.friend.FriendRequestItem
|
||||||
import com.kaixed.kchat.data.model.search.SearchUser
|
import com.kaixed.kchat.data.model.search.SearchUser
|
||||||
import com.kaixed.kchat.network.ApiResponse
|
import com.kaixed.kchat.network.ApiResponse
|
||||||
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.Field
|
import retrofit2.http.Field
|
||||||
import retrofit2.http.FormUrlEncoded
|
import retrofit2.http.FormUrlEncoded
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
@ -12,10 +13,9 @@ import retrofit2.http.Path
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: kaixed
|
* @Author: kaixed
|
||||||
* @Date: 2024/11/15 11:00
|
* @Date: 2025/1/25 16:38
|
||||||
*/
|
*/
|
||||||
interface ContactService {
|
interface FriendApiService {
|
||||||
|
|
||||||
// 获取联系人请求列表
|
// 获取联系人请求列表
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST("friend/request/list")
|
@POST("friend/request/list")
|
||||||
@ -48,15 +48,13 @@ interface ContactService {
|
|||||||
): ApiResponse<SearchUser?>
|
): ApiResponse<SearchUser?>
|
||||||
|
|
||||||
// 获取联系人列表
|
// 获取联系人列表
|
||||||
@FormUrlEncoded
|
@POST("friends/list")
|
||||||
@POST("friend/list")
|
|
||||||
suspend fun getContactList(
|
suspend fun getContactList(
|
||||||
@Field("userId") username: String
|
@Body requestParams: Map<String, String>,
|
||||||
): ApiResponse<List<Contact>?>
|
): ApiResponse<List<Contact>?>
|
||||||
|
|
||||||
// 删除联系人
|
// 删除联系人
|
||||||
@FormUrlEncoded
|
@POST("friends/delete")
|
||||||
@POST("friend/delete")
|
|
||||||
suspend fun deleteContact(
|
suspend fun deleteContact(
|
||||||
@Field("userId") username: String,
|
@Field("userId") username: String,
|
||||||
@Field("contactId") contactId: String,
|
@Field("contactId") contactId: String,
|
||||||
@ -70,4 +68,4 @@ interface ContactService {
|
|||||||
@Field("contactId") contactId: String,
|
@Field("contactId") contactId: String,
|
||||||
@Field("remark") remark: String,
|
@Field("remark") remark: String,
|
||||||
): ApiResponse<String?>
|
): ApiResponse<String?>
|
||||||
}
|
}
|
@ -1,22 +1,15 @@
|
|||||||
package com.kaixed.kchat.network.service
|
package com.kaixed.kchat.network.service
|
||||||
|
|
||||||
import com.kaixed.kchat.data.local.entity.UserInfo
|
|
||||||
import com.kaixed.kchat.data.model.request.RegisterRequest
|
|
||||||
import com.kaixed.kchat.data.model.request.UpdatePasswordRequest
|
import com.kaixed.kchat.data.model.request.UpdatePasswordRequest
|
||||||
import com.kaixed.kchat.data.model.request.UserRequest
|
import com.kaixed.kchat.data.model.request.UserRequest
|
||||||
import com.kaixed.kchat.data.model.response.register.Register
|
|
||||||
import com.kaixed.kchat.data.model.response.search.User
|
import com.kaixed.kchat.data.model.response.search.User
|
||||||
import com.kaixed.kchat.data.model.search.SearchUser
|
import com.kaixed.kchat.data.model.search.SearchUser
|
||||||
import com.kaixed.kchat.network.ApiResponse
|
import com.kaixed.kchat.network.ApiResponse
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.Field
|
|
||||||
import retrofit2.http.FormUrlEncoded
|
|
||||||
import retrofit2.http.GET
|
|
||||||
import retrofit2.http.Multipart
|
import retrofit2.http.Multipart
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Part
|
import retrofit2.http.Part
|
||||||
import retrofit2.http.Path
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: kaixed
|
* @Author: kaixed
|
||||||
@ -25,22 +18,22 @@ import retrofit2.http.Path
|
|||||||
interface UserApiService {
|
interface UserApiService {
|
||||||
|
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
@GET("userList/{username}")
|
@POST("friends/list")
|
||||||
suspend fun getUserListByNickname(
|
suspend fun fetchUserList(
|
||||||
@Path("username") username: String
|
@Body requestParams: Map<String, String>
|
||||||
): ApiResponse<List<User>>
|
): ApiResponse<List<User>>
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
@GET("users/{username}")
|
@POST("users/info/fetch")
|
||||||
suspend fun getUserInfo(
|
suspend fun getUserInfo(
|
||||||
@Path("username") username: String
|
@Body requestParams: Map<String, String>
|
||||||
): ApiResponse<SearchUser>
|
): ApiResponse<SearchUser>
|
||||||
|
|
||||||
// 更改昵称接口
|
// 更改昵称接口
|
||||||
@POST("users/info")
|
@POST("users/info")
|
||||||
suspend fun changeNickname(
|
suspend fun changeNickname(
|
||||||
@Body userRequest: UserRequest
|
@Body userRequest: UserRequest
|
||||||
): ApiResponse<String>
|
): ApiResponse<Boolean?>
|
||||||
|
|
||||||
// 上传头像接口
|
// 上传头像接口
|
||||||
@Multipart
|
@Multipart
|
||||||
@ -50,7 +43,7 @@ interface UserApiService {
|
|||||||
@Part file: MultipartBody.Part
|
@Part file: MultipartBody.Part
|
||||||
): ApiResponse<String>
|
): ApiResponse<String>
|
||||||
|
|
||||||
// 更改昵称接口
|
// 更改密码
|
||||||
@POST("users/password")
|
@POST("users/password")
|
||||||
suspend fun updatePassword(
|
suspend fun updatePassword(
|
||||||
@Body updatePasswordRequest: UpdatePasswordRequest
|
@Body updatePasswordRequest: UpdatePasswordRequest
|
||||||
|
@ -155,6 +155,7 @@ class WebSocketService : Service() {
|
|||||||
|
|
||||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||||
Log.d(TAG, "WebSocket closing: ${t.cause}")
|
Log.d(TAG, "WebSocket closing: ${t.cause}")
|
||||||
|
Log.d(TAG, "WebSocket closing: $t")
|
||||||
establishConnection()
|
establishConnection()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
@ -27,6 +26,9 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.drake.softinput.getSoftInputHeight
|
||||||
|
import com.drake.softinput.hasSoftInput
|
||||||
|
import com.drake.softinput.setWindowSoftInput
|
||||||
import com.kaixed.kchat.R
|
import com.kaixed.kchat.R
|
||||||
import com.kaixed.kchat.data.event.UnreadEvent
|
import com.kaixed.kchat.data.event.UnreadEvent
|
||||||
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
|
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
|
||||||
@ -46,12 +48,13 @@ import com.kaixed.kchat.ui.i.IOnItemClickListener
|
|||||||
import com.kaixed.kchat.ui.i.OnItemClickListener
|
import com.kaixed.kchat.ui.i.OnItemClickListener
|
||||||
import com.kaixed.kchat.ui.widget.LoadingDialogFragment
|
import com.kaixed.kchat.ui.widget.LoadingDialogFragment
|
||||||
import com.kaixed.kchat.utils.Constants.CURRENT_CONTACT_ID
|
import com.kaixed.kchat.utils.Constants.CURRENT_CONTACT_ID
|
||||||
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO
|
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT
|
||||||
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
|
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
|
||||||
import com.kaixed.kchat.utils.ConstantsUtils.getKeyboardHeight
|
import com.kaixed.kchat.utils.ConstantsUtils.getKeyboardHeight
|
||||||
import com.kaixed.kchat.utils.ConstantsUtils.getUsername
|
import com.kaixed.kchat.utils.ConstantsUtils.getUsername
|
||||||
import com.kaixed.kchat.utils.ImageEngines
|
import com.kaixed.kchat.utils.ImageEngines
|
||||||
import com.kaixed.kchat.utils.ImageSpanUtil.insertEmoji
|
import com.kaixed.kchat.utils.ImageSpanUtil.insertEmoji
|
||||||
|
import com.kaixed.kchat.utils.ScreenUtils
|
||||||
import com.kaixed.kchat.viewmodel.FileViewModel
|
import com.kaixed.kchat.viewmodel.FileViewModel
|
||||||
import com.luck.picture.lib.basic.PictureSelector
|
import com.luck.picture.lib.basic.PictureSelector
|
||||||
import com.luck.picture.lib.config.SelectMimeType
|
import com.luck.picture.lib.config.SelectMimeType
|
||||||
@ -66,6 +69,7 @@ import org.greenrobot.eventbus.Subscribe
|
|||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
|
class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
|
||||||
IOnItemClickListener {
|
IOnItemClickListener {
|
||||||
@ -90,8 +94,6 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
|
|||||||
|
|
||||||
private var softKeyboardHeight = 0
|
private var softKeyboardHeight = 0
|
||||||
|
|
||||||
private var keyboardShown = false
|
|
||||||
|
|
||||||
private var bound = false
|
private var bound = false
|
||||||
|
|
||||||
private val mmkv by lazy { MMKV.mmkvWithID(MMKV_USER_SESSION) }
|
private val mmkv by lazy { MMKV.mmkvWithID(MMKV_USER_SESSION) }
|
||||||
@ -120,7 +122,6 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
|
|||||||
setListener()
|
setListener()
|
||||||
bindWebSocketService()
|
bindWebSocketService()
|
||||||
setPanelChange()
|
setPanelChange()
|
||||||
getKeyBoardVisibility()
|
|
||||||
observeStateFlow()
|
observeStateFlow()
|
||||||
if (isSearchHistory) {
|
if (isSearchHistory) {
|
||||||
val size = MessagesManager.queryHistory(msgLocalId)
|
val size = MessagesManager.queryHistory(msgLocalId)
|
||||||
@ -158,18 +159,6 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getKeyBoardVisibility() {
|
|
||||||
val rootView = binding.root
|
|
||||||
|
|
||||||
rootView.viewTreeObserver.addOnGlobalLayoutListener {
|
|
||||||
val r = Rect()
|
|
||||||
rootView.getWindowVisibleDisplayFrame(r)
|
|
||||||
val screenHeight = rootView.rootView.height
|
|
||||||
val keypadHeight = screenHeight - r.bottom
|
|
||||||
keyboardShown = keypadHeight > screenHeight * KEYBOARD_HEIGHT_RATIO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setBackPressListener() {
|
private fun setBackPressListener() {
|
||||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
@ -220,7 +209,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun switchPanel(isSwitchToEmoji: Boolean) {
|
private fun switchPanel(isSwitchToEmoji: Boolean) {
|
||||||
if (keyboardShown) {
|
if (hasSoftInput()) {
|
||||||
lockContentViewHeight()
|
lockContentViewHeight()
|
||||||
handlePanelSwitch(isSwitchToEmoji)
|
handlePanelSwitch(isSwitchToEmoji)
|
||||||
unlockContentViewHeight()
|
unlockContentViewHeight()
|
||||||
@ -278,6 +267,12 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setListener() {
|
private fun setListener() {
|
||||||
|
binding.etInput.setOnClickListener {
|
||||||
|
if (hasSoftInput()){
|
||||||
|
MMKV.defaultMMKV().encode(KEYBOARD_HEIGHT, max(getSoftInputHeight(), 300))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setBackPressListener()
|
setBackPressListener()
|
||||||
|
|
||||||
binding.gvFunctionPanel.selector = ColorDrawable(Color.TRANSPARENT)
|
binding.gvFunctionPanel.selector = ColorDrawable(Color.TRANSPARENT)
|
||||||
|
@ -2,188 +2,215 @@ package com.kaixed.kchat.ui.activity
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Rect
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.drake.softinput.getSoftInputHeight
|
||||||
|
import com.drake.softinput.hasSoftInput
|
||||||
import com.kaixed.kchat.R
|
import com.kaixed.kchat.R
|
||||||
import com.kaixed.kchat.data.LocalDatabase
|
import com.kaixed.kchat.data.LocalDatabase
|
||||||
|
import com.kaixed.kchat.data.local.entity.UserInfo
|
||||||
import com.kaixed.kchat.databinding.ActivityLoginBinding
|
import com.kaixed.kchat.databinding.ActivityLoginBinding
|
||||||
import com.kaixed.kchat.ui.base.BaseActivity
|
import com.kaixed.kchat.ui.base.BaseActivity
|
||||||
|
import com.kaixed.kchat.ui.widget.MyBottomSheetFragment
|
||||||
import com.kaixed.kchat.utils.Constants.ACCESS_TOKEN
|
import com.kaixed.kchat.utils.Constants.ACCESS_TOKEN
|
||||||
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT
|
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT
|
||||||
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO
|
|
||||||
import com.kaixed.kchat.utils.Constants.REFRESH_TOKEN
|
import com.kaixed.kchat.utils.Constants.REFRESH_TOKEN
|
||||||
import com.kaixed.kchat.utils.DrawableUtil.createDrawable
|
import com.kaixed.kchat.utils.DrawableUtil.createDrawable
|
||||||
import com.kaixed.kchat.utils.ScreenUtils.dp2px
|
import com.kaixed.kchat.utils.ScreenUtils.dp2px
|
||||||
import com.kaixed.kchat.viewmodel.UserViewModel
|
import com.kaixed.kchat.viewmodel.UserViewModel
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class LoginActivity : BaseActivity<ActivityLoginBinding>() {
|
class LoginActivity : BaseActivity<ActivityLoginBinding>() {
|
||||||
|
|
||||||
private val userViewModel: UserViewModel by viewModels()
|
private val userViewModel: UserViewModel by viewModels()
|
||||||
|
|
||||||
private var previousKeyboardHeight = 0
|
private var isLoginByTelephone = true
|
||||||
|
|
||||||
private var loginByUsername = false
|
|
||||||
|
|
||||||
private lateinit var etUsername: EditText
|
|
||||||
|
|
||||||
private lateinit var etPassword: EditText
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
setupEdittext()
|
|
||||||
|
|
||||||
etUsername.addTextChangedListener(textWatcher)
|
|
||||||
etPassword.addTextChangedListener(textWatcher)
|
|
||||||
|
|
||||||
initView()
|
initView()
|
||||||
setListener()
|
setListeners()
|
||||||
|
setObservers()
|
||||||
|
|
||||||
binding.tvLogin.setOnClickListener {
|
binding.tvLogin.setOnClickListener {
|
||||||
val username = etUsername.text.toString().trim()
|
val username =
|
||||||
val password = etPassword.text.toString().trim()
|
if (isLoginByTelephone) binding.etTelephone.text.toString() else binding.etUsername.text.toString()
|
||||||
|
val password =
|
||||||
|
if (isLoginByTelephone) binding.etTelephonePassword.text.toString() else binding.etPassword.text.toString()
|
||||||
login(username, password)
|
login(username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeyboardHeight()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupEdittext() {
|
private fun setObservers() {
|
||||||
etUsername = if (loginByUsername) binding.etUsername else binding.etTelephone
|
|
||||||
etPassword = binding.etPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initData() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun login(username: String, password: String) {
|
|
||||||
if (!loginByUsername) {
|
|
||||||
if (etUsername.text.toString().length != 11) {
|
|
||||||
toast("请输入正确的手机号码")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (username.isEmpty() || password.isEmpty()) {
|
|
||||||
toast("请输入${if (loginByUsername) "用户名" else "手机号"}或密码")
|
|
||||||
}
|
|
||||||
userViewModel.loginResult.observe(this) { loginResult ->
|
userViewModel.loginResult.observe(this) { loginResult ->
|
||||||
loginResult.onSuccess {
|
loginResult.onSuccess {
|
||||||
it?.let {
|
it?.let { saveUserInfo(it) }
|
||||||
LocalDatabase.saveUserInfo(it)
|
|
||||||
MMKV.defaultMMKV().putString(ACCESS_TOKEN, it.accessToken)
|
|
||||||
MMKV.defaultMMKV().putString(REFRESH_TOKEN, it.refreshToken)
|
|
||||||
}
|
|
||||||
navigateToMain()
|
navigateToMain()
|
||||||
}
|
}
|
||||||
loginResult.onFailure {
|
loginResult.onFailure { toast(it.message.toString()) }
|
||||||
toast(it.message.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (loginByUsername) {
|
|
||||||
userViewModel.loginByUsername(username, password)
|
|
||||||
} else {
|
|
||||||
userViewModel.loginByTelephone(username, password)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
setView()
|
setupLoginView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setListener() {
|
private fun setListeners() {
|
||||||
binding.ivClose.setOnClickListener {
|
binding.ivClose.setOnClickListener { finish() }
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.tvChangeLoginWay.setOnClickListener {
|
binding.tvChangeLoginWay.setOnClickListener {
|
||||||
loginByUsername = !loginByUsername
|
isLoginByTelephone = !isLoginByTelephone
|
||||||
setView()
|
setupLoginView()
|
||||||
}
|
if (isLoginByTelephone) {
|
||||||
}
|
binding.etTelephone.requestFocus()
|
||||||
|
} else {
|
||||||
private fun setView() {
|
binding.etUsername.requestFocus()
|
||||||
setupEdittext()
|
|
||||||
|
|
||||||
binding.tvTitle.text = if (loginByUsername) "用户名登录" else "手机号登录"
|
|
||||||
binding.etTelephone.visibility = if (loginByUsername) View.INVISIBLE else View.VISIBLE
|
|
||||||
binding.etUsername.visibility = if (loginByUsername) View.VISIBLE else View.INVISIBLE
|
|
||||||
|
|
||||||
binding.tvUsername.text = if (loginByUsername) "用户名" else "手机号"
|
|
||||||
binding.tvChangeLoginWay.text = if (loginByUsername) "手机号登录" else "用户名登录"
|
|
||||||
binding.tvTip.text =
|
|
||||||
if (loginByUsername) "上述账号仅用于登陆验证" else "上述手机号仅用于登陆验证"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getKeyboardHeight() {
|
|
||||||
val rootLayout = binding.root
|
|
||||||
|
|
||||||
rootLayout.viewTreeObserver.addOnGlobalLayoutListener {
|
|
||||||
val rect = Rect()
|
|
||||||
rootLayout.getWindowVisibleDisplayFrame(rect)
|
|
||||||
|
|
||||||
val screenHeight = rootLayout.rootView.height
|
|
||||||
val keypadHeight = screenHeight - rect.bottom
|
|
||||||
|
|
||||||
// 通过15%的屏幕高度差来判断是否为键盘
|
|
||||||
if (keypadHeight > (screenHeight * KEYBOARD_HEIGHT_RATIO)) {
|
|
||||||
if (keypadHeight != previousKeyboardHeight) {
|
|
||||||
previousKeyboardHeight = keypadHeight
|
|
||||||
|
|
||||||
val kv = MMKV.defaultMMKV()
|
|
||||||
kv.encode(KEYBOARD_HEIGHT, keypadHeight)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.tvMore.setOnClickListener { showBottomSheet() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupLoginView() {
|
||||||
|
resetFields()
|
||||||
|
updateViewBasedOnLoginType()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetFields() {
|
||||||
|
if (isLoginByTelephone) {
|
||||||
|
binding.etTelephone.setText("")
|
||||||
|
binding.etTelephonePassword.setText("")
|
||||||
|
} else {
|
||||||
|
binding.etUsername.setText("")
|
||||||
|
binding.etPassword.setText("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateViewBasedOnLoginType() {
|
||||||
|
if (isLoginByTelephone) {
|
||||||
|
configureForPhoneLogin()
|
||||||
|
} else {
|
||||||
|
configureForUsernameLogin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureForPhoneLogin() {
|
||||||
|
binding.etTelephone.setOnClickListener {
|
||||||
|
if (hasSoftInput()) {
|
||||||
|
MMKV.defaultMMKV().encode(KEYBOARD_HEIGHT, max(getSoftInputHeight(), 400))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.etTelephonePassword.addTextChangedListener(textWatcher)
|
||||||
|
binding.etTelephone.addTextChangedListener(textWatcher)
|
||||||
|
binding.clUsername.visibility = View.GONE
|
||||||
|
binding.clTelephone.visibility = View.VISIBLE
|
||||||
|
binding.tvTitle.text = "手机号登录"
|
||||||
|
binding.tvUsername.text = "手机号"
|
||||||
|
binding.tvChangeLoginWay.text = "用户名登录"
|
||||||
|
binding.tvTip.text = "上述手机号仅用于登陆验证"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureForUsernameLogin() {
|
||||||
|
binding.etUsername.setOnClickListener {
|
||||||
|
if (hasSoftInput()) {
|
||||||
|
MMKV.defaultMMKV().encode(KEYBOARD_HEIGHT, max(getSoftInputHeight(), 400))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.etPassword.addTextChangedListener(textWatcher)
|
||||||
|
binding.etUsername.addTextChangedListener(textWatcher)
|
||||||
|
binding.clUsername.visibility = View.VISIBLE
|
||||||
|
binding.clTelephone.visibility = View.GONE
|
||||||
|
binding.tvTitle.text = "用户名登录"
|
||||||
|
binding.tvUsername.text = "用户名"
|
||||||
|
binding.tvChangeLoginWay.text = "手机号登录"
|
||||||
|
binding.tvTip.text = "上述账号仅用于登陆验证"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun login(username: String, password: String) {
|
||||||
|
if (isInputValid(username, password)) {
|
||||||
|
if (isLoginByTelephone) {
|
||||||
|
userViewModel.loginByTelephone(username, password)
|
||||||
|
} else {
|
||||||
|
userViewModel.loginByUsername(username, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInputValid(username: String, password: String): Boolean {
|
||||||
|
if (username.isEmpty() || password.isEmpty()) {
|
||||||
|
toast("请输入${if (!isLoginByTelephone) "用户名" else "手机号"}或密码")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (isLoginByTelephone && username.length != 11) {
|
||||||
|
toast("请输入正确的手机号码")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveUserInfo(user: UserInfo) {
|
||||||
|
LocalDatabase.saveUserInfo(user)
|
||||||
|
MMKV.defaultMMKV().putString(ACCESS_TOKEN, user.accessToken)
|
||||||
|
MMKV.defaultMMKV().putString(REFRESH_TOKEN, user.refreshToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBottomSheet() {
|
||||||
|
val bottomSheetFragment = MyBottomSheetFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putStringArrayList("list", arrayListOf("紧急冻结", "安全中心", "反馈问题", "取消"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val textWatcher = object : TextWatcher {
|
private val textWatcher = object : TextWatcher {
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
override fun afterTextChanged(s: Editable) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
|
val isInputValid = when {
|
||||||
val isInputValid = etUsername.text.isNotEmpty() && etPassword.text.isNotEmpty()
|
isLoginByTelephone -> binding.etTelephonePassword.text.isNotEmpty() && binding.etTelephone.text.length == 11
|
||||||
|
else -> binding.etUsername.text.isNotEmpty() && binding.etPassword.text.isNotEmpty()
|
||||||
binding.tvLogin.apply {
|
|
||||||
setTextColor(
|
|
||||||
if (isInputValid) {
|
|
||||||
ContextCompat.getColor(
|
|
||||||
this@LoginActivity, R.color.white
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Color.parseColor("#B4B4B4")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
background = createDrawable(
|
|
||||||
if (isInputValid) {
|
|
||||||
ContextCompat.getColor(
|
|
||||||
this@LoginActivity, R.color.green
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Color.parseColor("#E1E1E1")
|
|
||||||
}, dp2px(8)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
updateLoginButtonState(isInputValid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLoginButtonState(isInputValid: Boolean) {
|
||||||
|
binding.tvLogin.apply {
|
||||||
|
isEnabled = isInputValid
|
||||||
|
setTextColor(
|
||||||
|
if (isInputValid) ContextCompat.getColor(
|
||||||
|
this@LoginActivity,
|
||||||
|
R.color.white
|
||||||
|
) else Color.parseColor("#B4B4B4")
|
||||||
|
)
|
||||||
|
background = createDrawable(
|
||||||
|
if (isInputValid) ContextCompat.getColor(
|
||||||
|
this@LoginActivity,
|
||||||
|
R.color.green
|
||||||
|
) else Color.parseColor("#E1E1E1"), dp2px(8)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToMain() {
|
private fun navigateToMain() {
|
||||||
toast("登录成功")
|
toast("登录成功")
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
startActivity(intent)
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
binding.root.viewTreeObserver.removeOnGlobalLayoutListener { }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initData() {}
|
||||||
|
|
||||||
override fun inflateBinding(): ActivityLoginBinding {
|
override fun inflateBinding(): ActivityLoginBinding {
|
||||||
return ActivityLoginBinding.inflate(layoutInflater)
|
return ActivityLoginBinding.inflate(layoutInflater)
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,14 @@ package com.kaixed.kchat.ui.widget
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.kaixed.kchat.R
|
import com.kaixed.kchat.R
|
||||||
import com.kaixed.kchat.data.DataBase
|
import com.kaixed.kchat.data.DataBase
|
||||||
@ -13,6 +18,7 @@ import com.kaixed.kchat.databinding.BottomSheetLayoutBinding
|
|||||||
import com.kaixed.kchat.ui.activity.LoginActivity
|
import com.kaixed.kchat.ui.activity.LoginActivity
|
||||||
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
|
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
|
||||||
import com.kaixed.kchat.utils.Constants.USER_LOGIN_STATUS
|
import com.kaixed.kchat.utils.Constants.USER_LOGIN_STATUS
|
||||||
|
import com.kaixed.kchat.utils.ScreenUtils
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,32 +45,56 @@ class MyBottomSheetFragment : BottomSheetDialogFragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
binding.tvQuitAccount.setOnClickListener {
|
val list = arguments?.getStringArrayList("list")
|
||||||
deleteAllUserData()
|
if (list != null) {
|
||||||
activity?.finish()
|
initList(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
activity?.startActivity(Intent(
|
private fun initList(list: ArrayList<String>) {
|
||||||
activity, LoginActivity::class.java
|
list.forEach {
|
||||||
|
addTextToContainer(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addTextToContainer(item: String) {
|
||||||
|
val textView = TextView(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
ScreenUtils.dp2px(50)
|
||||||
).apply {
|
).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
topMargin = if (item == "取消") ScreenUtils.dp2px(8) else ScreenUtils.dp2px(1)
|
||||||
})
|
}
|
||||||
}
|
setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white))
|
||||||
|
text = item
|
||||||
|
setTextColor(ContextCompat.getColor(requireContext(), R.color.black))
|
||||||
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
|
||||||
|
gravity = Gravity.CENTER_HORIZONTAL or Gravity.CENTER_VERTICAL
|
||||||
|
|
||||||
binding.tvCancel.setOnClickListener {
|
setOnClickListener {
|
||||||
dismiss()
|
when (item) {
|
||||||
}
|
"退出账号" -> {
|
||||||
|
deleteAllUserData()
|
||||||
|
activity?.finish()
|
||||||
|
activity?.startActivity(Intent(
|
||||||
|
activity, LoginActivity::class.java
|
||||||
|
).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
binding.tvQuitApp.setOnClickListener {
|
"取消" -> dismiss()
|
||||||
activity?.finishAffinity()
|
"退出应用" -> activity?.finishAffinity()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
binding.llBottomSheet.addView(textView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteAllUserData() {
|
private fun deleteAllUserData() {
|
||||||
val mmkv = MMKV.mmkvWithID(MMKV_USER_SESSION)
|
val mmkv = MMKV.mmkvWithID(MMKV_USER_SESSION)
|
||||||
mmkv.clearAll()
|
mmkv.clearAll()
|
||||||
|
|
||||||
MMKV.defaultMMKV().putBoolean(USER_LOGIN_STATUS, false)
|
MMKV.defaultMMKV().putBoolean(USER_LOGIN_STATUS, false)
|
||||||
|
|
||||||
DataBase.cleanAllData()
|
DataBase.cleanAllData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.kaixed.kchat.utils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: kaixed
|
||||||
|
* @Date: 2025/1/24 20:41
|
||||||
|
*/
|
||||||
|
class AccountUtils {
|
||||||
|
|
||||||
|
}
|
@ -26,6 +26,6 @@ object Constants {
|
|||||||
const val REFRESH_TOKEN = "refreshToken"
|
const val REFRESH_TOKEN = "refreshToken"
|
||||||
|
|
||||||
const val KEYBOARD_HEIGHT_RATIO = 0.15F
|
const val KEYBOARD_HEIGHT_RATIO = 0.15F
|
||||||
const val KEYBOARD_DEFAULT_HEIGHT = 200
|
const val KEYBOARD_DEFAULT_HEIGHT = 300
|
||||||
const val STATUS_BAR_DEFAULT_HEIGHT: Int = 50
|
const val STATUS_BAR_DEFAULT_HEIGHT: Int = 50
|
||||||
}
|
}
|
||||||
|
145
app/src/main/kotlin/com/kaixed/kchat/utils/KeyboardUtils.kt
Normal file
145
app/src/main/kotlin/com/kaixed/kchat/utils/KeyboardUtils.kt
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package com.kaixed.kchat.utils
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
import android.view.WindowInsets
|
||||||
|
import android.view.WindowInsetsAnimation
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
|
||||||
|
object KeyboardUtils {
|
||||||
|
|
||||||
|
private var sDecorViewInvisibleHeightPre: Int = 0
|
||||||
|
private var onGlobalLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null
|
||||||
|
private var mNavHeight: Int = 0
|
||||||
|
private var sDecorViewDelta: Int = 0
|
||||||
|
|
||||||
|
private fun getDecorViewInvisibleHeight(activity: Activity): Int {
|
||||||
|
val decorView = activity.window.decorView ?: return sDecorViewInvisibleHeightPre
|
||||||
|
val outRect = Rect()
|
||||||
|
decorView.getWindowVisibleDisplayFrame(outRect)
|
||||||
|
|
||||||
|
val delta = Math.abs(decorView.bottom - outRect.bottom)
|
||||||
|
if (delta <= mNavHeight) {
|
||||||
|
sDecorViewDelta = delta
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return delta - sDecorViewDelta
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerKeyboardHeightListener(activity: Activity, listener: KeyboardHeightListener) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
invokeAbove31(activity, listener)
|
||||||
|
} else {
|
||||||
|
invokeBelow31(activity, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
|
private fun invokeAbove31(activity: Activity, listener: KeyboardHeightListener) {
|
||||||
|
activity.window.decorView.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
|
||||||
|
override fun onProgress(windowInsets: WindowInsets, animations: List<WindowInsetsAnimation>): WindowInsets {
|
||||||
|
val imeHeight = windowInsets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
||||||
|
val navHeight = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
|
||||||
|
val hasNavigationBar = windowInsets.isVisible(WindowInsetsCompat.Type.navigationBars()) &&
|
||||||
|
windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0
|
||||||
|
|
||||||
|
listener.onKeyboardHeightChanged(if (hasNavigationBar) Math.max(imeHeight - navHeight, 0) else imeHeight)
|
||||||
|
return windowInsets
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invokeBelow31(activity: Activity, listener: KeyboardHeightListener) {
|
||||||
|
val flags = activity.window.attributes.flags
|
||||||
|
|
||||||
|
if (flags and WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS != 0) {
|
||||||
|
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentView = activity.findViewById<FrameLayout>(android.R.id.content)
|
||||||
|
sDecorViewInvisibleHeightPre = getDecorViewInvisibleHeight(activity)
|
||||||
|
|
||||||
|
onGlobalLayoutListener = object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
override fun onGlobalLayout() {
|
||||||
|
val height = getDecorViewInvisibleHeight(activity)
|
||||||
|
if (sDecorViewInvisibleHeightPre != height) {
|
||||||
|
listener.onKeyboardHeightChanged(height)
|
||||||
|
sDecorViewInvisibleHeightPre = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getNavigationBarHeight(activity) { height, hasNav ->
|
||||||
|
mNavHeight = height
|
||||||
|
contentView.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unregisterKeyboardHeightListener(activity: Activity) {
|
||||||
|
onGlobalLayoutListener = null
|
||||||
|
val contentView = activity.window.decorView.findViewById<View>(android.R.id.content) ?: return
|
||||||
|
contentView.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalLayoutListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNavBarHeight(): Int {
|
||||||
|
val res = Resources.getSystem()
|
||||||
|
val resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android")
|
||||||
|
return if (resourceId != 0) {
|
||||||
|
res.getDimensionPixelSize(resourceId)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNavigationBarHeight(activity: Activity, callback: (height: Int, hasNav: Boolean) -> Unit) {
|
||||||
|
val view = activity.window.decorView
|
||||||
|
val attachedToWindow = view.isAttachedToWindow
|
||||||
|
|
||||||
|
if (attachedToWindow) {
|
||||||
|
val windowInsets = ViewCompat.getRootWindowInsets(view)
|
||||||
|
val height = windowInsets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0
|
||||||
|
val hasNavigationBar = windowInsets?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true &&
|
||||||
|
windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0
|
||||||
|
|
||||||
|
if (height > 0) {
|
||||||
|
callback(height, hasNavigationBar)
|
||||||
|
} else {
|
||||||
|
callback(getNavBarHeight(), hasNavigationBar)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
|
||||||
|
override fun onViewAttachedToWindow(v: View) {
|
||||||
|
val windowInsets = ViewCompat.getRootWindowInsets(v)
|
||||||
|
val height = windowInsets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0
|
||||||
|
val hasNavigationBar = windowInsets?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true &&
|
||||||
|
windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0
|
||||||
|
|
||||||
|
if (height > 0) {
|
||||||
|
callback(height, hasNavigationBar)
|
||||||
|
} else {
|
||||||
|
callback(getNavBarHeight(), hasNavigationBar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow(v: View) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface KeyboardHeightListener {
|
||||||
|
fun onKeyboardHeightChanged(height: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NavigationBarCallback {
|
||||||
|
fun onHeight(height: Int, hasNav: Boolean)
|
||||||
|
}
|
||||||
|
}
|
@ -76,8 +76,8 @@ class UserViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _userInfoResult = MutableLiveData<Result<SearchUser>>()
|
private val _userInfoResult = MutableLiveData<Result<SearchUser?>>()
|
||||||
val userInfoResult: LiveData<Result<SearchUser>> = _userInfoResult
|
val userInfoResult: LiveData<Result<SearchUser?>> = _userInfoResult
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
fun getUserInfo(username: String) {
|
fun getUserInfo(username: String) {
|
||||||
@ -87,8 +87,8 @@ class UserViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _changeNicknameResult = MutableLiveData<Result<Boolean>>()
|
private val _changeNicknameResult = MutableLiveData<Result<Boolean?>>()
|
||||||
val changeNicknameResult: LiveData<Result<Boolean>> = _changeNicknameResult
|
val changeNicknameResult: LiveData<Result<Boolean?>> = _changeNicknameResult
|
||||||
|
|
||||||
// 修改昵称
|
// 修改昵称
|
||||||
fun changeNickname(userRequest: UserRequest) {
|
fun changeNickname(userRequest: UserRequest) {
|
||||||
|
@ -38,106 +38,178 @@
|
|||||||
android:layout_marginTop="50dp"
|
android:layout_marginTop="50dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tv_title" />
|
app:layout_constraintTop_toBottomOf="@id/tv_title" />
|
||||||
|
|
||||||
<TextView
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/tv_username"
|
android:id="@+id/cl_telephone"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="15dp"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:text="手机号"
|
|
||||||
android:textColor="@color/black"
|
|
||||||
android:textSize="17sp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/view" />
|
|
||||||
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/et_username"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:maxLength="16"
|
|
||||||
android:layout_marginStart="35dp"
|
|
||||||
android:background="@null"
|
|
||||||
android:hint="请填写用户名"
|
|
||||||
android:textCursorDrawable="@drawable/cursor"
|
|
||||||
android:textSize="17sp"
|
|
||||||
android:visibility="invisible"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/view1"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/tv_username"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/view" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/et_telephone"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginStart="35dp"
|
|
||||||
android:background="@null"
|
|
||||||
android:hint="请填写手机号"
|
|
||||||
android:maxLength="11"
|
|
||||||
android:textCursorDrawable="@drawable/cursor"
|
|
||||||
android:textSize="17sp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/view1"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/tv_username"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/view" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/view1"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0.2dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="10dp"
|
|
||||||
android:layout_marginTop="15dp"
|
android:layout_marginTop="15dp"
|
||||||
android:background="#D8D8D8"
|
app:layout_constraintTop_toBottomOf="@id/view">
|
||||||
app:layout_constraintTop_toBottomOf="@id/tv_username" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_password"
|
android:id="@+id/tv_telephone"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="45dp"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="手机号"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_telephone"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:hint="请填写手机号"
|
||||||
|
android:maxLength="16"
|
||||||
|
android:textCursorDrawable="@drawable/cursor"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tv_telephone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/tv_telephone"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tv_telephone" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/view1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0.2dp"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:background="#D8D8D8"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/tv_telephone_password"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_telephone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_telephone_password"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="45dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="密码"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_telephone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_telephone" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_telephone_password"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:hint="请填写密码"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLength="16"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="#A5A5A5"
|
||||||
|
android:textCursorDrawable="@drawable/cursor"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tv_telephone_password"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/tv_telephone_password"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tv_telephone_password" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/cl_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:visibility="invisible"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/view">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="45dp"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="账号"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_username"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:hint="请填写账号"
|
||||||
|
android:maxLength="16"
|
||||||
|
android:textCursorDrawable="@drawable/cursor"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tv_username"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/tv_username"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tv_username" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0.2dp"
|
||||||
|
android:background="#D8D8D8"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/tv_password"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_username" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_password"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="45dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="密码"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_username"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_username" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_password"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:hint="请填写密码"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLength="16"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="#A5A5A5"
|
||||||
|
android:textCursorDrawable="@drawable/cursor"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tv_password"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/tv_password"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tv_password" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="15dp"
|
app:barrierDirection="bottom"
|
||||||
android:layout_marginTop="15dp"
|
app:constraint_referenced_ids="cl_username, cl_telephone" />
|
||||||
android:text="密码"
|
|
||||||
android:textColor="@color/black"
|
|
||||||
android:textSize="17sp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/view1" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/et_password"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:background="@null"
|
|
||||||
android:hint="请填写密码"
|
|
||||||
android:inputType="textPassword"
|
|
||||||
android:maxLength="16"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textCursorDrawable="@drawable/cursor"
|
|
||||||
android:textSize="17sp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/view2"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/et_username"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/view1" />
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/view2"
|
android:id="@+id/view2"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0.2dp"
|
android:layout_height="0.2dp"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:background="#D8D8D8"
|
android:background="#D8D8D8"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tv_password" />
|
app:layout_constraintTop_toBottomOf="@id/barrier" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_change_login_way"
|
android:id="@+id/tv_change_login_way"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
android:layout_marginTop="15dp"
|
android:layout_marginTop="15dp"
|
||||||
android:text="用户名登录"
|
android:text="用户名登录"
|
||||||
android:textColor="@color/normal"
|
android:textColor="@color/normal"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="@id/tv_username"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/view2" />
|
app:layout_constraintTop_toBottomOf="@id/barrier" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_tip"
|
android:id="@+id/tv_tip"
|
||||||
@ -166,62 +238,42 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@id/view2"
|
app:layout_constraintTop_toBottomOf="@id/view2"
|
||||||
app:layout_constraintVertical_bias="0.7" />
|
app:layout_constraintVertical_bias="0.7" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_urgent_freeze"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="紧急冻结"
|
|
||||||
android:textColor="@color/normal"
|
|
||||||
android:textSize="15sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/tv_login"
|
|
||||||
app:layout_constraintVertical_bias="0.8" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_forgot_password"
|
android:id="@+id/tv_forgot_password"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="找回密码"
|
android:text="找回密码"
|
||||||
android:textColor="@color/normal"
|
android:textColor="@color/normal"
|
||||||
android:textSize="15sp"
|
android:textSize="14sp"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/tv_urgent_freeze"
|
app:layout_constraintBottom_toBottomOf="@id/view_2"
|
||||||
app:layout_constraintEnd_toStartOf="@id/tv_urgent_freeze"
|
app:layout_constraintEnd_toStartOf="@id/view_2"
|
||||||
app:layout_constraintHorizontal_bias="0.7"
|
app:layout_constraintHorizontal_bias="0.9"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/tv_urgent_freeze" />
|
app:layout_constraintTop_toTopOf="@id/view_2" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_security_center"
|
android:id="@+id/tv_more"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="安全中心"
|
android:text="更多选项"
|
||||||
android:textColor="@color/normal"
|
android:textColor="@color/normal"
|
||||||
android:textSize="15sp"
|
android:textSize="14sp"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/tv_urgent_freeze"
|
app:layout_constraintBottom_toBottomOf="@id/view_2"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.3"
|
app:layout_constraintHorizontal_bias="0.1"
|
||||||
app:layout_constraintStart_toEndOf="@id/tv_urgent_freeze"
|
app:layout_constraintStart_toEndOf="@id/view_2"
|
||||||
app:layout_constraintTop_toTopOf="@id/tv_urgent_freeze" />
|
app:layout_constraintTop_toTopOf="@id/view_2" />
|
||||||
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
android:id="@+id/view_2"
|
||||||
android:layout_width="1dp"
|
android:layout_width="1dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="20dp"
|
||||||
android:background="#e5e5e5"
|
android:background="#e5e5e5"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/tv_urgent_freeze"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/tv_urgent_freeze"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/tv_forgot_password"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/tv_urgent_freeze" />
|
app:layout_constraintTop_toBottomOf="@id/tv_login"
|
||||||
|
app:layout_constraintVertical_bias="0.7" />
|
||||||
<View
|
|
||||||
android:layout_width="1dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:background="#e5e5e5"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/tv_urgent_freeze"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/tv_security_center"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/tv_urgent_freeze"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/tv_urgent_freeze" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -8,47 +8,11 @@
|
|||||||
app:cardMaxElevation="0dp">
|
app:cardMaxElevation="0dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_bottom_sheet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="#F7F7F7"
|
android:background="#F7F7F7"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_quit_account"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="55dp"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:gravity="center_horizontal|center_vertical"
|
|
||||||
android:text="退出登录"
|
|
||||||
android:textColor="@color/black"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_quit_app"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="55dp"
|
|
||||||
android:layout_marginTop="1dp"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:gravity="center_horizontal|center_vertical"
|
|
||||||
android:text="关闭应用"
|
|
||||||
android:textColor="@color/black"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/divider_height"
|
|
||||||
android:background="#E5E5E5" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_cancel"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="55dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:gravity="center_horizontal|center_vertical"
|
|
||||||
android:text="取消"
|
|
||||||
android:textColor="@color/black"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
@ -25,6 +25,7 @@ pinyin4j = "2.5.1"
|
|||||||
preference = "1.2.1"
|
preference = "1.2.1"
|
||||||
retrofit = "2.11.0"
|
retrofit = "2.11.0"
|
||||||
scanplus = "2.12.0.301"
|
scanplus = "2.12.0.301"
|
||||||
|
softInputEvent = "1.0.9"
|
||||||
spannable = "1.2.7"
|
spannable = "1.2.7"
|
||||||
therouter = "1.2.2"
|
therouter = "1.2.2"
|
||||||
window = "1.3.0"
|
window = "1.3.0"
|
||||||
@ -49,6 +50,7 @@ pinyin4j = { module = "com.belerweb:pinyin4j", version.ref = "pinyin4j" }
|
|||||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
retrofit2-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
|
retrofit2-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
|
||||||
scanplus = { module = "com.huawei.hms:scanplus", version.ref = "scanplus" }
|
scanplus = { module = "com.huawei.hms:scanplus", version.ref = "scanplus" }
|
||||||
|
soft-input-event = { module = "com.github.liangjingkanji:soft-input-event", version.ref = "softInputEvent" }
|
||||||
spannable = { module = "com.github.liangjingkanji:spannable", version.ref = "spannable" }
|
spannable = { module = "com.github.liangjingkanji:spannable", version.ref = "spannable" }
|
||||||
therouter-ksp = { module = "cn.therouter:apt", version.ref = "therouter" }
|
therouter-ksp = { module = "cn.therouter:apt", version.ref = "therouter" }
|
||||||
objectbox-android-objectbrowser = { group = "io.objectbox", name = "objectbox-android-objectbrowser", version.ref = "objectbox" }
|
objectbox-android-objectbrowser = { group = "io.objectbox", name = "objectbox-android-objectbrowser", version.ref = "objectbox" }
|
||||||
|
Loading…
Reference in New Issue
Block a user