Compare commits

...

14 Commits

73 changed files with 1232 additions and 206 deletions

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker7700090853507788345.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker12680940025563006602.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker3894271637508823813.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -24,6 +24,7 @@
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.DefenseApplication">
@@ -42,6 +43,9 @@
<activity
android:name=".personal.InvitationCodeActivity"
android:exported="false" />
<activity
android:name=".personal.JoinFamilyInviteActivity"
android:exported="false" />
<activity
android:name=".liveVideo.StrikePositionActivity"
android:exported="false" />

View File

@@ -18,6 +18,8 @@ class AddDeviceAdapter(
private val onItemClick: (Device) -> Unit
) : RecyclerView.Adapter<AddDeviceAdapter.ViewHolder>() {
private val defaultIcons = listOf(R.mipmap.img1, R.mipmap.img2, R.mipmap.img3)
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val ivIcon: ImageView = itemView.findViewById(R.id.ivDeviceIcon2)
val tvName: TextView = itemView.findViewById(R.id.tvDeviceName2)
@@ -47,7 +49,9 @@ class AddDeviceAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val device = devices[position]
holder.tvName.text = device.name
holder.ivIcon.setImageResource(R.mipmap.img)
val iconResId = defaultIcons[position % defaultIcons.size]
holder.ivIcon.setImageResource(iconResId)
// 点击事件
holder.itemView.setOnClickListener { onItemClick(device) }

View File

@@ -3,25 +3,18 @@ package com.ckkj.defense_device.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.ckkj.defense_device.R
import com.ckkj.defense_device.bean.DeviceBean
import com.ckkj.defense_device.databinding.ItemDeviceBinding
data class DeviceUi(
val name: String,
val status: String,
val deviceId: String = ""
) {
val isOnline: Boolean
get() = status == "1"
}
class DeviceAdapter(
private val onItemClick: (DeviceUi) -> Unit = {}
private val onItemClick: (DeviceBean) -> Unit = {}
) : RecyclerView.Adapter<DeviceAdapter.DeviceViewHolder>() {
private val items = mutableListOf<DeviceUi>()
private val items = mutableListOf<DeviceBean>()
fun submitList(deviceList: List<DeviceUi>) {
fun submitList(deviceList: List<DeviceBean>) {
items.clear()
items.addAll(deviceList)
notifyDataSetChanged()
@@ -33,15 +26,25 @@ class DeviceAdapter(
}
override fun onBindViewHolder(holder: DeviceViewHolder, position: Int) {
holder.bind(items[position])
holder.bind(items[position], position)
}
override fun getItemCount(): Int = items.size
private val defaultIcons = listOf(R.mipmap.img1, R.mipmap.img2, R.mipmap.img3)
inner class DeviceViewHolder(private val binding: ItemDeviceBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: DeviceUi) {
fun bind(item: DeviceBean, position: Int) {
if (item.deviceImg.isNullOrEmpty()) {
val iconResId = defaultIcons[position % defaultIcons.size]
binding.ivDeviceIcon.setImageResource(iconResId)
} else {
Glide.with(binding.root.context)
.load(item.deviceImg)
.into(binding.ivDeviceIcon)
}
binding.tvDeviceType.text = item.name
when (item.status) {
"1" -> {

View File

@@ -4,7 +4,7 @@ package com.ckkj.defense_device.bean
* 统一返回数据基类(根据你的后端实际字段调整)
*/
data class BaseInfo(
val message: String? = null,
val msg: String? = null,
val code: Int = 0,
val data: Any? = null
)

View File

@@ -27,4 +27,13 @@ data class DeviceBean(
val wifiPassword: String? = null,//WiFi密码
val workMode: String? = null,//工作模式 0-自动 1-半自动
val optionPassword: String? = null, //操作密码
)
) {
val deviceId: String
get() = id ?: ""
val name: String
get() = deviceName ?: ""
val isOnline: Boolean
get() = status == "1"
}

View File

@@ -0,0 +1,7 @@
package com.ckkj.defense_device.bean
data class JoinTheFamilyBean(
val parentId: String,
val inviteCode: String,
)

View File

@@ -0,0 +1,8 @@
package com.ckkj.defense_device.bean
data class RecommendationCodeBean(
val inviteCode: String? = null,
val code: String? = null,
val expireTime: Long? = null,
val expire: Long? = null
)

View File

@@ -0,0 +1,5 @@
package com.ckkj.defense_device.bean
data class SelectInviterInfoBean(
val code: String
)

View File

@@ -0,0 +1,8 @@
package com.ckkj.defense_device.bean
data class UpdateFamilyUserRequest(
val id: String,
val optionPassword: String,
val optionStatus: String,
val status: String,
)

View File

@@ -1,5 +1,6 @@
package com.ckkj.defense_device.home
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.ckkj.defense_device.R
@@ -7,6 +8,10 @@ import com.ckkj.defense_device.base.BaseActivity
import com.ckkj.defense_device.databinding.HomeActivityBinding
class HomeActivity : BaseActivity() {
companion object {
const val EXTRA_REFRESH = "extra_refresh"
}
// ViewBinding 核心对象
private lateinit var binding: HomeActivityBinding
@@ -49,6 +54,28 @@ class HomeActivity : BaseActivity() {
binding.ivProfile.setImageResource(R.mipmap.profile_checked)
binding.tvProfile.setTextColor(resources.getColor(R.color.ic_selected))
}
handleRefreshIntent(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null) {
setIntent(intent)
handleRefreshIntent(intent)
}
}
private fun handleRefreshIntent(intent: Intent) {
if (intent.getBooleanExtra(EXTRA_REFRESH, false)) {
intent.removeExtra(EXTRA_REFRESH)
switchFragment(homeMenuFragment)
binding.ivHome.setImageResource(R.mipmap.home_checked)
binding.tvHome.setTextColor(resources.getColor(R.color.ic_selected))
binding.ivProfile.setImageResource(R.mipmap.profile)
binding.tvProfile.setTextColor(resources.getColor(R.color.ic_normal))
homeMenuFragment.refreshHomeData()
}
}
/**

View File

@@ -23,13 +23,14 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ckkj.defense_device.R
import com.ckkj.defense_device.adapter.DeviceAdapter
import com.ckkj.defense_device.adapter.DeviceUi
import com.ckkj.defense_device.adapter.FamilyAdapter
import com.ckkj.defense_device.bean.DeviceBean
import com.ckkj.defense_device.bean.FamilyBean
import com.ckkj.defense_device.bean.SwitchFamilyRequest
import com.ckkj.defense_device.bean.SelectInviterInfoBean
import com.ckkj.defense_device.databinding.HomeMenuFragmentBinding
import com.ckkj.defense_device.liveVideo.LiveVideoActivity
import com.ckkj.defense_device.personal.JoinFamilyInviteActivity
import com.ckkj.defense_device.utils.DeviceIdEvent
import com.ckkj.defense_device.utils.DeviceUpdateEvent
import com.ckkj.defense_device.utils.FamilySwitchEvent
@@ -37,6 +38,7 @@ import com.ckkj.defense_device.utils.MessageStatusManager
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.utils.UserinfoUtil
import com.ckkj.defense_device.view.DefenseDialogs
import com.ckkj.defense_device.view.LoadingDialog
import com.ckkj.defense_device.view.showDefenseDialog
import com.gyf.immersionbar.ImmersionBar
import com.maning.mlkitscanner.scan.MNScanManager
@@ -45,6 +47,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
//主页碎片
class HomeMenuFragment : Fragment() {
// ViewBinding
@@ -122,23 +125,86 @@ class HomeMenuFragment : Fragment() {
MNScanManager.RESULT_SUCCESS -> {
val result = data?.getStringArrayListExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS)
if (!result.isNullOrEmpty()) {
ToastUtils.showToast(requireContext(), "扫码成功: $result")
val inviteCode = parseInviteCode(result[0])
if (inviteCode.isNullOrEmpty()) {
Toast.makeText(requireContext(), "无效的邀请码", Toast.LENGTH_SHORT).show()
} else {
ToastUtils.showToast(requireContext(), "扫码结果为空")
fetchInviterInfo(inviteCode)
}
} else {
Toast.makeText(requireContext(), "扫码结果为空", Toast.LENGTH_SHORT).show()
}
}
MNScanManager.RESULT_FAIL -> {
val error = data?.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR)
ToastUtils.showToast(requireContext(), "扫码失败: $error")
Toast.makeText(requireContext(), "扫码失败: $error", Toast.LENGTH_SHORT).show()
}
MNScanManager.RESULT_CANCLE -> {
ToastUtils.showToast(requireContext(), "扫码取消")
Toast.makeText(requireContext(), "扫码取消", Toast.LENGTH_SHORT).show()
}
}
}
})
}
private fun parseInviteCode(scanResult: String): String? {
val trimmed = scanResult.trim()
if (trimmed.isEmpty()) return null
return try {
if (trimmed.startsWith("{")) {
JSONObject(trimmed).optString("code").takeIf { it.isNotEmpty() }
} else {
trimmed
}
} catch (e: Exception) {
trimmed
}
}
private fun fetchInviterInfo(inviteCode: String) {
val loadingDialog = LoadingDialog(requireActivity()).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.selectInviterInfo(SelectInviterInfoBean(code = inviteCode))
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
if (response.code == 200 && response.data != null) {
val inviter = response.data
val parentId = inviter.resolveParentId()
if (parentId.isNullOrEmpty()) {
Toast.makeText(
requireContext(),
getString(R.string.network_error),
Toast.LENGTH_SHORT
).show()
return@withContext
}
val intent = Intent(requireContext(), JoinFamilyInviteActivity::class.java).apply {
putExtra(JoinFamilyInviteActivity.EXTRA_INVITE_CODE, inviteCode)
putExtra(JoinFamilyInviteActivity.EXTRA_NICK_NAME, inviter.nickName)
putExtra(JoinFamilyInviteActivity.EXTRA_AVATAR, inviter.avatar)
putExtra(JoinFamilyInviteActivity.EXTRA_PARENT_ID, parentId)
putExtra(JoinFamilyInviteActivity.EXTRA_USER_ID, inviter.userId)
}
startActivity(intent)
} else {
Toast.makeText(
requireContext(),
response.msg.ifBlank { getString(R.string.network_error) },
Toast.LENGTH_SHORT
).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
Toast.makeText(requireContext(), R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
private val unreadCountChangeListener = object : MessageStatusManager.UnreadCountChangeListener {
override fun onUnreadCountChanged(count: Int) {
updateUnreadCountUI(count)
@@ -200,8 +266,8 @@ class HomeMenuFragment : Fragment() {
// 为 LinearLayout 设置渐变背景
setGradientBackground(
view = binding.gradientLayout,
startColor = Color.parseColor("#EBDCED"),
endColor = Color.parseColor("#CAF4FF")
startColor = resources.getColor(R.color.gradientLayout1),
endColor = resources.getColor(R.color.gradientLayout2)
)
binding.message.setOnClickListener {
val intent = Intent(requireContext(), MessageActivity::class.java)
@@ -285,6 +351,10 @@ class HomeMenuFragment : Fragment() {
}
}
fun refreshHomeData() {
fetchFamilyList()
}
private fun fetchFamilyList() {
CoroutineScope(Dispatchers.IO).launch {
try {
@@ -321,18 +391,22 @@ class HomeMenuFragment : Fragment() {
Log.d("HomeMenuFragment", "canViewDevice = $canViewDevice")
if (canViewDevice) {
binding.tvDeviceTitle.visibility = View.VISIBLE
binding.swipeRefreshLayout.visibility = View.VISIBLE
binding.rvDeviceList.visibility = View.VISIBLE
binding.message.visibility = View.VISIBLE
binding.addFamily.visibility = View.VISIBLE
binding.add.visibility = View.VISIBLE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.GONE
} else {
binding.tvDeviceTitle.visibility = View.GONE
binding.swipeRefreshLayout.visibility = View.GONE
binding.rvDeviceList.visibility = View.GONE
binding.message.visibility = View.GONE
binding.addFamily.visibility = View.GONE
binding.add.visibility = View.GONE
binding.layoutNoPermission.visibility = View.VISIBLE
binding.layoutNoNetwork.visibility = View.GONE
}
}
@@ -390,9 +464,12 @@ class HomeMenuFragment : Fragment() {
hasMore = newDevices.size >= pageSize
}
Log.d("DeviceList", "deviceBeanList size = ${deviceBeanList.size}")
showDeviceContent()
updateDeviceList()
} else {
Toast.makeText(requireContext(), response.msg, Toast.LENGTH_SHORT).show()
noNetwork()
val errorMsg = response.msg.ifBlank { getString(R.string.network_error) }
Toast.makeText(requireContext(), errorMsg, Toast.LENGTH_SHORT).show()
if (isLoadMore) {
hasMore = false
}
@@ -401,6 +478,7 @@ class HomeMenuFragment : Fragment() {
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
noNetwork()
Toast.makeText(requireContext(), R.string.network_error, Toast.LENGTH_SHORT).show()
isLoading = false
if (isLoadMore) {
@@ -445,21 +523,31 @@ class HomeMenuFragment : Fragment() {
}
}
private fun updateDeviceList() {
val deviceUiList = deviceBeanList.map {
DeviceUi(it.deviceName ?: "", it.status ?: "", it.id ?: "")
private fun showDeviceContent() {
binding.swipeRefreshLayout.visibility = View.VISIBLE
binding.rvDeviceList.visibility = View.VISIBLE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.GONE
}
if (deviceUiList.isEmpty()) {
private fun noNetwork() {
binding.swipeRefreshLayout.visibility = View.GONE
binding.rvDeviceList.visibility = View.GONE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.VISIBLE
}
private fun updateDeviceList() {
if (deviceBeanList.isEmpty()) {
deviceAdapter.submitList(listOf(
DeviceUi(getString(R.string.no_device), "0")
DeviceBean(deviceName = getString(R.string.no_device), status = "0")
))
} else {
deviceAdapter.submitList(deviceUiList)
deviceAdapter.submitList(deviceBeanList)
}
}
private fun onDeviceClicked(device: DeviceUi) {
private fun onDeviceClicked(device: DeviceBean) {
if (device.isOnline) {
DeviceIdEvent.setDeviceId(device.deviceId)
val intent = Intent(requireContext(), LiveVideoActivity::class.java)

View File

@@ -125,7 +125,7 @@ class MessageActivity : AppCompatActivity() {
e.printStackTrace()
withContext(Dispatchers.Main) {
if (allMessages.isEmpty()) {
showEmptyState()
noNetwork()
}
}
} finally {
@@ -152,6 +152,12 @@ class MessageActivity : AppCompatActivity() {
binding.emptyLayout.visibility = View.GONE
}
private fun noNetwork() {
binding.rvMessageList.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.VISIBLE
binding.tvNoMoreData.visibility = View.GONE
}
private fun showNoMoreDataHint() {
binding.tvNoMoreData.visibility = View.VISIBLE
}

View File

@@ -21,6 +21,7 @@ import com.ckkj.defense_device.utils.LanguageUtil
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.utils.UserinfoUtil
import com.ckkj.defense_device.utils.ActivityManager
import com.ckkj.defense_device.view.CustomDialogFragmentText
import com.ckkj.defense_device.view.FamilySwitchDialog
import com.ckkj.defense_device.view.LanguageDialog
import com.gyf.immersionbar.ImmersionBar
@@ -100,6 +101,13 @@ class PersonalFragment : Fragment() {
}
// 退出登录点击事件
binding.btnLogout.setOnClickListener {
val dialog = CustomDialogFragmentText()
.setTitle(getString(R.string.tip))
.setMessage(getString(R.string.logout_confirm))
.setShowInput(false)
.setNegativeText(getString(R.string.cancel))
.setPositiveText(getString(R.string.confirm))
.setOnConfirmListener {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().logout()
@@ -122,6 +130,8 @@ class PersonalFragment : Fragment() {
}
}
}
dialog.show(childFragmentManager, "logout_dialog")
}
fetchCurrentFamily()
return binding.root
}

View File

@@ -3,6 +3,7 @@ package com.ckkj.defense_device.liveVideo
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
@@ -95,19 +96,33 @@ class AddLinkedUserActivity : AppCompatActivity() {
Toast.makeText(this@AddLinkedUserActivity, "暂无家庭成员数据", Toast.LENGTH_SHORT).show()
}
} else {
hide()
Toast.makeText(this@AddLinkedUserActivity, "请求失败: ${response.msg}", Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
android.util.Log.e("AddLinkedUserActivity", "网络请求异常", e)
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
noNetwork()
Toast.makeText(this@AddLinkedUserActivity, "网络请求失败: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun hide() {
binding.searchLayout.visibility = View.GONE
binding.rvAddLinkedUser.visibility = View.GONE
binding.butting?.visibility = View.GONE
binding.layoutNoPermission?.visibility = View.VISIBLE
}
private fun noNetwork() {
binding.searchLayout.visibility = View.GONE
binding.rvAddLinkedUser.visibility = View.GONE
binding.butting?.visibility = View.GONE
binding.layoutNoNetwork?.visibility = View.VISIBLE
}
private fun updateFamilyMemberList(familyMembersList: List<FamilyMembersBean>) {
fullUserItemList.clear()
familyMembersList.forEach { member ->

View File

@@ -257,6 +257,7 @@ class LinkedUserActivity : AppCompatActivity() {
updateFamilyMembersList(response.data as List<FamilyMembersBean>)
}
} else {
hide()
Toast.makeText(this@LinkedUserActivity, response.msg, Toast.LENGTH_SHORT).show()
}
}
@@ -264,11 +265,24 @@ class LinkedUserActivity : AppCompatActivity() {
e.printStackTrace()
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
Toast.makeText(this@LinkedUserActivity, "网络请求失败", Toast.LENGTH_SHORT).show()
noNetwork()
Toast.makeText(this@LinkedUserActivity, R.string.network_request_failed, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun hide() {
binding.searchLayout.visibility = View.GONE
binding.rvLinkedUser.visibility = View.GONE
binding.butting?.visibility = View.GONE
binding.layoutNoPermission?.visibility = View.VISIBLE
}
private fun noNetwork() {
binding.searchLayout.visibility = View.GONE
binding.rvLinkedUser.visibility = View.GONE
binding.butting?.visibility = View.GONE
binding.layoutNoNetwork?.visibility = View.VISIBLE
}
private fun updateDeviceUserList(deviceUserList: List<DeviceUserBean>) {
fullUserList.clear()

View File

@@ -80,7 +80,7 @@ class LiveVideoActivity : BaseActivity() {
}
private fun initVideoPlayer() {
val videoUrl = "https://www.w3schools.com/html/movie.mp4"//"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
val videoUrl ="http://220.203.23.195:9090/live/test001.live.flv"
orientationUtils = OrientationUtils(this, binding.exoPlayContextId)
orientationUtils?.isEnable = true

View File

@@ -7,6 +7,7 @@ import androidx.lifecycle.lifecycleScope
import com.ckkj.defense_device.R
import com.ckkj.defense_device.bean.DeleteDeviceUserRequest
import com.ckkj.defense_device.bean.ObtainInviterInformationRequest
import com.ckkj.defense_device.bean.UpdateFamilyUserRequest
import com.ckkj.defense_device.bean.VerifyOptionPasswordRequest
import com.ckkj.defense_device.personal.PasswordInputDialog
import com.ckkj.defense_device.utils.DeviceIdEvent
@@ -26,6 +27,7 @@ class UserDetailsActivity : AppCompatActivity() {
private var userId: String = ""
private var isPasswordVerified: Boolean = false
private lateinit var passwordDialog: PasswordInputDialog
private var optionPassword: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -87,10 +89,12 @@ class UserDetailsActivity : AppCompatActivity() {
binding.swUserStatus.setOnCheckedChangeListener { _, isChecked ->
if (!isPasswordVerified) {
binding.swUserStatus.isChecked = !isChecked
showPasswordDialog {
showPasswordDialog(isChecked) {
isPasswordVerified = true
binding.swUserStatus.isChecked = isChecked
}
} else {
updateFamilyUserStatus(isChecked)
}
}
@@ -99,7 +103,7 @@ class UserDetailsActivity : AppCompatActivity() {
}
}
private fun showPasswordDialog(onVerified: () -> Unit) {
private fun showPasswordDialog(status: Boolean, onVerified: () -> Unit) {
passwordDialog = PasswordInputDialog(this).apply {
setOnConfirmListener { inputPassword ->
lifecycleScope.launch {
@@ -109,6 +113,7 @@ class UserDetailsActivity : AppCompatActivity() {
withContext(Dispatchers.Main) {
if (response.code == 200) {
dismiss()
updateFamilyUserStatus(status, inputPassword)
onVerified()
} else {
Toast.makeText(
@@ -135,6 +140,48 @@ class UserDetailsActivity : AppCompatActivity() {
}
private fun updateFamilyUserStatus(status: Boolean) {
updateFamilyUserStatus(status, optionPassword)
}
private fun updateFamilyUserStatus(status: Boolean, password: String) {
if (userId.isEmpty()) {
Toast.makeText(this, "用户ID为空", Toast.LENGTH_SHORT).show()
return
}
if (password.isNotEmpty()) {
optionPassword = password
}
lifecycleScope.launch {
try {
val request = UpdateFamilyUserRequest(
id = userId,
optionPassword = optionPassword,
optionStatus = "status",
status = if (status) "1" else "0"
)
val response = RetrofitNetwork.getNetworkService().updateFamilyUser(request)
withContext(Dispatchers.Main) {
if (response.code == 200) {
Toast.makeText(this@UserDetailsActivity, "状态修改成功", Toast.LENGTH_SHORT).show()
DeviceUserUpdateEvent.notifyUpdate()
} else {
Toast.makeText(this@UserDetailsActivity, response.msg ?: "状态修改失败", Toast.LENGTH_SHORT).show()
binding.swUserStatus.isChecked = !status
}
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(this@UserDetailsActivity, "网络请求失败", Toast.LENGTH_SHORT).show()
binding.swUserStatus.isChecked = !status
}
}
}
}
private fun showDeleteDialog() {
val dialog = CustomDialogFragmentText()
dialog.setTitle(getString(R.string.tip))
@@ -146,7 +193,7 @@ class UserDetailsActivity : AppCompatActivity() {
if (isPasswordVerified) {
deleteDeviceUser()
} else {
showPasswordDialog {
showPasswordDialog(false) {
isPasswordVerified = true
deleteDeviceUser()
}

View File

@@ -79,8 +79,7 @@ class VoiceSettingsActivity : AppCompatActivity() {
private fun updateDeviceAlertAudio() {
deviceId?.let { id ->
val newAlertAudio = if (binding.swWarningVoice.isChecked) "1" else "0"
// 只有状态发生变化时才请求
if (newAlertAudio != originalAlertAudio) {
// 点击返回时总是触发网络请求
CoroutineScope(Dispatchers.IO).launch {
try {
val deviceBean = DeviceBean(id = id, alertAudio = newAlertAudio)
@@ -89,20 +88,23 @@ class VoiceSettingsActivity : AppCompatActivity() {
if (response.code == 200 && response.data != null) {
Toast.makeText(this@VoiceSettingsActivity, "修改成功", Toast.LENGTH_SHORT).show()
} else {
// 仅在状态实际发生变化时才显示错误提示
if (newAlertAudio != originalAlertAudio) {
Toast.makeText(this@VoiceSettingsActivity, response.msg, Toast.LENGTH_SHORT).show()
}
}
finish()
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
// 仅在状态实际发生变化时才显示失败提示
if (newAlertAudio != originalAlertAudio) {
Toast.makeText(this@VoiceSettingsActivity, "修改失败", Toast.LENGTH_SHORT).show()
}
finish()
}
}
}
} else {
finish()
}
}
}
}

View File

@@ -10,6 +10,7 @@ import bean.ForgotRequest
import com.ckkj.defense_device.R
import com.ckkj.defense_device.databinding.ForgetPasswordActivityBinding
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.util.TextUtils
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
@@ -20,6 +21,7 @@ import java.util.regex.Pattern
class ForgetPasswordActivity : AppCompatActivity() {
private lateinit var binding: ForgetPasswordActivityBinding
private var isLoading = false
private var countDownTimer: CountDownTimer? = null
private var isSendingCode = false
@@ -63,9 +65,11 @@ class ForgetPasswordActivity : AppCompatActivity() {
}
binding.btnNext.setOnClickListener {
if (!isLoading) {
performResetPassword()
}
}
}
private fun isPhoneNumberValid(phone: String): Boolean {
if (TextUtils.isEmpty(phone)) return false
@@ -163,8 +167,11 @@ class ForgetPasswordActivity : AppCompatActivity() {
return
}
isLoading = true
binding.btnNext.isEnabled = false
binding.btnNext.text = "重置中..."
val loadingDialog = LoadingDialog(this)
.setMessage(getString(R.string.resetting))
.apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
@@ -175,8 +182,9 @@ class ForgetPasswordActivity : AppCompatActivity() {
)
val response = RetrofitNetwork.getNetworkService().forgotPassword(forgotRequest)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
binding.btnNext.isEnabled = true
binding.btnNext.text = "确定"
if (response.code == 200) {
Toast.makeText(this@ForgetPasswordActivity, R.string.reset_success, Toast.LENGTH_SHORT).show()
@@ -187,8 +195,9 @@ class ForgetPasswordActivity : AppCompatActivity() {
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
binding.btnNext.isEnabled = true
binding.btnNext.text = "确定"
Toast.makeText(this@ForgetPasswordActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}

View File

@@ -5,6 +5,7 @@ import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.os.CountDownTimer
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
@@ -194,6 +195,7 @@ class LoginActivity : AppCompatActivity() {
try {
val response = RetrofitNetwork.getNetworkService().getCode(phone)
withContext(Dispatchers.Main) {
Log.d("VerificationCode", "code = ${response.code}, msg = ${response.msg}")
if (response.code == 200) {
Toast.makeText(this@LoginActivity, getString(R.string.code_sent_to, phone), Toast.LENGTH_SHORT).show()
startCountDown()

View File

@@ -11,6 +11,7 @@ import bean.RegisterRequest
import com.ckkj.defense_device.R
import com.ckkj.defense_device.databinding.RegisterActivityBinding
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -158,7 +159,9 @@ class RegisterActivity : AppCompatActivity() {
isLoading = true
binding.btnNext.isEnabled = false
binding.btnNext.text = getString(R.string.registering)
val loadingDialog = LoadingDialog(this)
.setMessage(getString(R.string.registering))
.apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
@@ -174,8 +177,8 @@ class RegisterActivity : AppCompatActivity() {
val response = RetrofitNetwork.getNetworkService().register(registerRequest)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
binding.btnNext.isEnabled = true
binding.btnNext.text = getString(R.string.next_step)
if (response.code == 200) {
Toast.makeText(this@RegisterActivity, R.string.register_success, Toast.LENGTH_SHORT).show()
@@ -190,8 +193,8 @@ class RegisterActivity : AppCompatActivity() {
} catch (e: Exception) {
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
binding.btnNext.isEnabled = true
binding.btnNext.text = getString(R.string.next_step)
Toast.makeText(this@RegisterActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}

View File

@@ -1,32 +1,45 @@
package com.ckkj.defense_device.personal
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.bottomsheet.BottomSheetDialog
import androidx.core.content.ContextCompat
import com.ckkj.defense_device.R
import com.ckkj.defense_device.bean.RecommendationCodeBean
import com.ckkj.defense_device.bean.SelectInviterInfoBean
import com.ckkj.defense_device.bean.VerifyOptionPasswordRequest
import com.ckkj.defense_device.databinding.DialogExpireTimeBinding
import com.ckkj.defense_device.databinding.InvitationCodeActivityBinding
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.google.gson.Gson
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.gyf.immersionbar.ImmersionBar
import com.maning.mlkitscanner.scan.MNScanManager
import com.maning.mlkitscanner.scan.callback.act.MNScanCallback
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.io.File
import java.io.FileOutputStream
import java.util.Hashtable
@@ -44,7 +57,24 @@ class InvitationCodeActivity : AppCompatActivity() {
private var isLoading = false // 是否正在加载
private var hasGeneratedQrCode = false // 是否已经生成过二维码
private val requestPermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val cameraGranted = permissions[Manifest.permission.CAMERA] ?: false
val storageGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions[Manifest.permission.READ_MEDIA_IMAGES] ?: false
} else {
permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: false
}
if (cameraGranted && storageGranted) {
startScan()
} else {
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_SHORT).show()
}
}
companion object {
private const val TAG = "InvitationCodeActivity"
const val EXPIRE_24H = 24 * 60 * 60 * 1000L
const val EXPIRE_7D = 7 * 24 * 60 * 60 * 1000L
}
@@ -90,15 +120,136 @@ class InvitationCodeActivity : AppCompatActivity() {
// 撤销邀请按钮
binding.btnCancelInvite.setOnClickListener {
showPasswordDialog {
cancelInvitation()
if (generatedInviteCode.isEmpty()) {
ToastUtils.showError(this, getString(R.string.qr_code_expired))
return@setOnClickListener
}
showPasswordDialog { password, dialog ->
cancelInvitation(password, dialog)
}
}
// 生成/重新生成按钮
binding.btnRegenerate.setOnClickListener {
showPasswordDialog {
generateOrRegenerateInvitation()
showPasswordDialog { password, dialog ->
generateOrRegenerateInvitation(password, dialog)
}
}
binding.sys.setOnClickListener {
checkPermissionsAndScan()
}
}
private fun checkPermissionsAndScan() {
val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
val storagePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
} else {
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (cameraPermission == PackageManager.PERMISSION_GRANTED &&
storagePermission == PackageManager.PERMISSION_GRANTED
) {
startScan()
} else {
val permissionsToRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_MEDIA_IMAGES
)
} else {
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
requestPermissionsLauncher.launch(permissionsToRequest)
}
}
private fun startScan() {
MNScanManager.startScan(this, object : MNScanCallback {
override fun onActivityResult(resultCode: Int, data: Intent?) {
when (resultCode) {
MNScanManager.RESULT_SUCCESS -> {
val result = data?.getStringArrayListExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS)
if (!result.isNullOrEmpty()) {
val inviteCode = parseInviteCode(result[0])
if (inviteCode.isNullOrEmpty()) {
Toast.makeText(this@InvitationCodeActivity, "无效的邀请码", Toast.LENGTH_SHORT).show()
} else {
fetchInviterInfo(inviteCode)
}
} else {
Toast.makeText(this@InvitationCodeActivity, "扫码结果为空", Toast.LENGTH_SHORT).show()
}
}
MNScanManager.RESULT_FAIL -> {
val error = data?.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR)
Toast.makeText(this@InvitationCodeActivity, "扫码失败: $error", Toast.LENGTH_SHORT).show()
}
MNScanManager.RESULT_CANCLE -> {
Toast.makeText(this@InvitationCodeActivity, "扫码取消", Toast.LENGTH_SHORT).show()
}
}
}
})
}
private fun parseInviteCode(scanResult: String): String? {
val trimmed = scanResult.trim()
if (trimmed.isEmpty()) return null
return try {
if (trimmed.startsWith("{")) {
JSONObject(trimmed).optString("code").takeIf { it.isNotEmpty() }
} else {
trimmed
}
} catch (e: Exception) {
trimmed
}
}
private fun fetchInviterInfo(inviteCode: String) {
val loadingDialog = LoadingDialog(this).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.selectInviterInfo(SelectInviterInfoBean(code = inviteCode))
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
if (response.code == 200 && response.data != null) {
val inviter = response.data
val parentId = inviter.resolveParentId()
if (parentId.isNullOrEmpty()) {
Toast.makeText(
this@InvitationCodeActivity,
getString(R.string.network_error),
Toast.LENGTH_SHORT
).show()
return@withContext
}
val intent = Intent(this@InvitationCodeActivity, JoinFamilyInviteActivity::class.java).apply {
putExtra(JoinFamilyInviteActivity.EXTRA_INVITE_CODE, inviteCode)
putExtra(JoinFamilyInviteActivity.EXTRA_NICK_NAME, inviter.nickName)
putExtra(JoinFamilyInviteActivity.EXTRA_AVATAR, inviter.avatar)
putExtra(JoinFamilyInviteActivity.EXTRA_PARENT_ID, parentId)
putExtra(JoinFamilyInviteActivity.EXTRA_USER_ID, inviter.userId)
}
startActivity(intent)
} else {
Toast.makeText(
this@InvitationCodeActivity,
response.msg.ifBlank { getString(R.string.network_error) },
Toast.LENGTH_SHORT
).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
Toast.makeText(this@InvitationCodeActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
@@ -106,14 +257,56 @@ class InvitationCodeActivity : AppCompatActivity() {
/**
* 生成或重新生成邀请码
*/
private fun generateOrRegenerateInvitation() {
// 根据当前选择的时效生成邀请码
private fun generateOrRegenerateInvitation(password: String, passwordDialog: PasswordInputDialog) {
if (isLoading) return
isLoading = true
val loadingDialog = LoadingDialog(this).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.generateRecommendationCode(VerifyOptionPasswordRequest(password))
Log.d(TAG, "生成推荐码响应: code=${response.code}, msg=${response.msg}, data=${response.data}")
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
if (response.code == 200) {
val parsed = parseRecommendationCode(response.data)
if (parsed != null) {
passwordDialog.dismiss()
generatedInviteCode = parsed.first
expireTimestamp = parsed.second
qrCodeContent =
"{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$expireTimestamp}"
Log.d(
TAG,
"推荐码生成成功: Code=$generatedInviteCode, expireTimestamp=$expireTimestamp, qrContent=$qrCodeContent"
)
generateQRCode()
showQrCode()
// 更新按钮文字为"重新生成"
binding.btnRegenerate.text = getString(R.string.regenerate)
hasGeneratedQrCode = true
ToastUtils.showSuccess(this, getString(R.string.operation_success))
ToastUtils.showSuccess(this@InvitationCodeActivity, getString(R.string.operation_success))
} else {
Log.w(TAG, "推荐码解析失败, raw data=${response.data}")
passwordDialog.showError(getString(R.string.network_error))
}
} else {
Log.w(TAG, "生成推荐码失败: code=${response.code}, msg=${response.msg}")
passwordDialog.showError(
response.msg.ifEmpty { getString(R.string.password_error) }
)
}
}
} catch (e: Exception) {
Log.e(TAG, "生成推荐码异常", e)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
passwordDialog.showError(getString(R.string.network_error))
}
}
}
}
private fun showExpireTimeDialog() {
@@ -151,9 +344,6 @@ class InvitationCodeActivity : AppCompatActivity() {
binding.tvExpireTime.text = getString(R.string.expire_7d)
}
}
// 显示二维码并生成
showQrCode()
generateQRCode()
expireTimeDialog?.dismiss()
}
@@ -173,22 +363,28 @@ class InvitationCodeActivity : AppCompatActivity() {
}
private fun generateQRCode() {
// 检查二维码是否过期,如果过期则生成新的邀请码
val isExpired = expireTimestamp > 0 && System.currentTimeMillis() > expireTimestamp
val content = if (qrCodeContent.isNotEmpty() && !isExpired) qrCodeContent else generateInviteCode()
val qrBitmap = createQRCodeWithLogo(content, 400, 400)
if (qrCodeContent.isEmpty()) return
val qrBitmap = createQRCodeWithLogo(qrCodeContent, 400, 400)
binding.ivQrCode.setImageBitmap(qrBitmap)
}
private fun generateInviteCode(): String {
val timestamp = System.currentTimeMillis()
expireTimestamp = timestamp + expireTimeInMillis
generatedInviteCode = "INV${timestamp}"
qrCodeContent = "{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$expireTimestamp}"
// 输出日志,方便调试验证
android.util.Log.d("InvitationCode", "生成的二维码内容: $qrCodeContent")
android.util.Log.d("InvitationCode", "推荐码: $generatedInviteCode, 过期时间戳: $expireTimestamp")
return qrCodeContent
private fun parseRecommendationCode(data: Any?): Pair<String, Long>? {
if (data == null) return null
return when (data) {
is String -> {
if (data.isEmpty()) null
else Pair(data, System.currentTimeMillis() + expireTimeInMillis)
}
else -> {
val bean = Gson().fromJson(Gson().toJson(data), RecommendationCodeBean::class.java)
val code = bean.inviteCode?.takeIf { it.isNotEmpty() }
?: bean.code?.takeIf { it.isNotEmpty() }
?: return null
val expire = bean.expireTime ?: bean.expire
?: (System.currentTimeMillis() + expireTimeInMillis)
Pair(code, expire)
}
}
}
/*
* 这段代码生成带Logo的二维码
@@ -415,55 +611,61 @@ class InvitationCodeActivity : AppCompatActivity() {
/**
* 撤销邀请
*/
private fun cancelInvitation() {
// 撤销邀请逻辑
private fun cancelInvitation(password: String, passwordDialog: PasswordInputDialog) {
if (isLoading) return
if (generatedInviteCode.isEmpty()) {
passwordDialog.showError(getString(R.string.qr_code_expired))
return
}
isLoading = true
val loadingDialog = LoadingDialog(this).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.revokeInvitation(VerifyOptionPasswordRequest(password))
Log.d(TAG, "撤销邀请响应: code=${response.code}, msg=${response.msg}")
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
if (response.code == 200) {
passwordDialog.dismiss()
hideQrCode()
generatedInviteCode = ""
qrCodeContent = ""
expireTimestamp = 0
hasGeneratedQrCode = false
// 恢复按钮文字为"生成邀请码"
binding.btnRegenerate.text = getString(R.string.generate_invite_code)
// 显示提示
ToastUtils.showSuccess(this, getString(R.string.operation_success))
ToastUtils.showSuccess(this@InvitationCodeActivity, getString(R.string.operation_success))
} else {
passwordDialog.showError(
response.msg.ifEmpty { getString(R.string.password_error) }
)
}
}
} catch (e: Exception) {
Log.e(TAG, "撤销邀请异常", e)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
passwordDialog.showError(getString(R.string.network_error))
}
}
}
}
/**
* 显示密码输入对话框
* @param onSuccess 验证成功后的回调
*/
private fun showPasswordDialog(onSuccess: () -> Unit) {
private fun showPasswordDialog(onConfirm: (String, PasswordInputDialog) -> Unit) {
val dialog = PasswordInputDialog(this)
dialog.setTitle(getString(R.string.input_op_pwd))
dialog.setOnConfirmListener { password ->
verifyPassword(password, dialog, onSuccess)
onConfirm(password, dialog)
}
dialog.show()
}
/**
* 验证操作密码
*/
private fun verifyPassword(password: String, dialog: PasswordInputDialog, onSuccess: () -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.verifyOptionPassword(VerifyOptionPasswordRequest(password))
withContext(Dispatchers.Main) {
if (response.code == 200) {
dialog.dismiss()
onSuccess.invoke()
} else {
dialog.showError(getString(R.string.password_error))
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
dialog.showError(getString(R.string.network_error))
}
}
}
}
override fun onDestroy() {
super.onDestroy()

View File

@@ -0,0 +1,123 @@
package com.ckkj.defense_device.personal
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.ckkj.defense_device.R
import com.ckkj.defense_device.bean.JoinTheFamilyBean
import com.ckkj.defense_device.databinding.JoinFamilyInviteActivityBinding
import com.ckkj.defense_device.home.HomeActivity
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
// 邀请加入家庭
class JoinFamilyInviteActivity : AppCompatActivity() {
private lateinit var binding: JoinFamilyInviteActivityBinding
private var isLoading = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = JoinFamilyInviteActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
ImmersionBar.with(this)
.statusBarDarkFont(true)
.titleBar(binding.topBar)
.init()
binding.ivBack.setOnClickListener { finish() }
binding.btnJoinNow.setOnClickListener { joinFamily() }
displayInviterInfo()
}
private fun joinFamily() {
if (isLoading) return
val parentId = intent.getStringExtra(EXTRA_USER_ID).orEmpty()
val inviteCode = intent.getStringExtra(EXTRA_INVITE_CODE).orEmpty()
if (parentId.isEmpty() || inviteCode.isEmpty()) {
Log.e(TAG, "加入家庭参数无效: parentId=$parentId, inviteCode=$inviteCode")
Toast.makeText(this, R.string.network_error, Toast.LENGTH_SHORT).show()
return
}
Log.e(TAG, "加入家庭请求: parentId=$parentId, inviteCode=$inviteCode")
isLoading = true
val loadingDialog = LoadingDialog(this).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.joinTheFamily(JoinTheFamilyBean(parentId = parentId, inviteCode = inviteCode))
Log.e(TAG, "加入家庭响应: code=${response.code}, msg=${response.msg}, data=${response.data}")
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
if (response.code == 200) {
Log.e(TAG, "加入家庭成功,返回首页刷新")
ToastUtils.showSuccess(this@JoinFamilyInviteActivity, getString(R.string.operation_success))
val homeIntent = Intent(this@JoinFamilyInviteActivity, HomeActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
putExtra(HomeActivity.EXTRA_REFRESH, true)
}
startActivity(homeIntent)
finish()
} else {
Log.e(TAG, "加入家庭失败: code=${response.code}, msg=${response.msg}")
Toast.makeText(
this@JoinFamilyInviteActivity,
response.msg.ifBlank { getString(R.string.network_error) },
Toast.LENGTH_SHORT
).show()
}
}
} catch (e: Exception) {
Log.e(TAG, "加入家庭请求异常", e)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
Toast.makeText(this@JoinFamilyInviteActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun displayInviterInfo() {
val nickName = intent.getStringExtra(EXTRA_NICK_NAME).orEmpty()
val avatar = intent.getStringExtra(EXTRA_AVATAR)
binding.tvNickname.text = nickName.ifEmpty { getString(R.string.nickname_placeholder) }
binding.tvInviteMessage.text = getString(
R.string.invite_join_family_message,
nickName.ifEmpty { getString(R.string.nickname_placeholder) }
)
val requestOptions = RequestOptions()
.placeholder(R.mipmap.img)
.error(R.mipmap.img)
Glide.with(this)
.load(avatar)
.apply(requestOptions)
.circleCrop()
.into(binding.ivAvatar)
}
companion object {
private const val TAG = "JoinFamilyInviteActivity"
const val EXTRA_INVITE_CODE = "invite_code"
const val EXTRA_NICK_NAME = "nick_name"
const val EXTRA_AVATAR = "avatar"
const val EXTRA_PARENT_ID = "parent_id"
const val EXTRA_USER_ID = "user_id"
}
}

View File

@@ -138,7 +138,7 @@ object ApiConfig {
*/
private fun buildFakeErrorResponse(request: Request, msg: String, code: Int = 1): Response {
val baseInfo = BaseInfo(
message = msg,
msg = msg,
code = code,
data = null,
)

View File

@@ -14,11 +14,15 @@ import com.ckkj.defense_device.bean.DeleteBean
import com.ckkj.defense_device.bean.DeleteDeviceUserRequest
import com.ckkj.defense_device.bean.DeviceAlarmResponse
import com.ckkj.defense_device.bean.FamilyMembersBean
import com.ckkj.defense_device.bean.InviterInfoBean
import com.ckkj.defense_device.bean.JoinTheFamilyBean
import com.ckkj.defense_device.bean.MessageBean
import com.ckkj.defense_device.bean.MessageListResponse
import com.ckkj.defense_device.bean.ObtainInviterInformationBean
import com.ckkj.defense_device.bean.ObtainInviterInformationRequest
import com.ckkj.defense_device.bean.SelectInviterInfoBean
import com.ckkj.defense_device.bean.SwitchFamilyRequest
import com.ckkj.defense_device.bean.UpdateFamilyUserRequest
import com.ckkj.defense_device.bean.UpdatePassword
import com.ckkj.defense_device.bean.UpdateUserInfo
import com.ckkj.defense_device.bean.UploadResponse
@@ -191,7 +195,7 @@ interface ApiService {
/**
* 获取邀请人信息
* 获取家庭成员信息
*/
@POST("/app/obtainInviterInformation")
suspend fun obtainInviterInformation(@Body request: ObtainInviterInformationRequest): GetCodeBean<ObtainInviterInformationBean>
@@ -250,8 +254,22 @@ interface ApiService {
/**
* 修改家庭用户状态
*/
//@POST("/app/updateFamilyUser")
@POST("/app/updateFamilyUser")
suspend fun updateFamilyUser(@Body request: UpdateFamilyUserRequest): GetCodeBean<Any>
/**
* 获取邀请人信息
*/
@POST("/app/selectInviterInfo")
suspend fun selectInviterInfo(@Body request: SelectInviterInfoBean): GetCodeBean<InviterInfoBean>
/**
* 加入xxx家庭
*/
@POST("/app/joinTheFamily")
suspend fun joinTheFamily(@Body request: JoinTheFamilyBean): GetCodeBean<Any>
}

View File

@@ -13,6 +13,7 @@ object RetrofitNetwork {
private const val DEFAULT_TIMEOUT = 30L
const val BASE_URL = "https://stem-boozy-margarine.ngrok-free.dev"
//const val BASE_URL = "http://service.reinkun.com:8001"
private var retrofit: Retrofit? = null
private val gson = Gson()

View File

@@ -5,5 +5,5 @@
<stroke
android:width="1dp"
android:color="@color/green" />
<corners android:radius="8dp" />
<corners android:radius="25dp" />
</shape>

View File

@@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/green" />
<corners android:radius="8dp" />
<corners android:radius="25dp" />
</shape>

View File

@@ -164,6 +164,8 @@
android:background="@drawable/rounded_blue_bg"
android:paddingHorizontal="12dp"
android:maxWidth="120dp"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -223,6 +225,8 @@
android:layout_marginTop="40dp"
android:text="@string/login"
android:textSize="18sp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
android:textColor="@color/white"
app:layout_constraintTop_toBottomOf="@+id/smsRowLayout"

View File

@@ -123,6 +123,8 @@
android:textColor="@color/white"
android:textSize="16dp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -80,6 +80,8 @@
android:background="@drawable/rounded_blue_bg"
android:text="@string/select_wifi"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="14sp" />
</LinearLayout>

View File

@@ -88,8 +88,41 @@
android:paddingBottom="16dp"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- 空页面 -->
<LinearLayout
android:id="@+id/layoutNoPermission"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/default_page"/>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="32dp"
android:background="@color/white"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/no_network_img_name" />
</LinearLayout>
<!-- 底部按钮 -->
<LinearLayout
android:id="@+id/butting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
@@ -102,6 +135,8 @@
android:text="@string/confirm"
android:textColor="@color/white"
android:textSize="14dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg" />
</LinearLayout>

View File

@@ -105,6 +105,8 @@
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etConfirmPassword" />

View File

@@ -287,6 +287,8 @@
android:textColor="@color/white"
android:textSize="14dp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -107,6 +107,8 @@
android:textSize="17sp"
android:textStyle="bold"
android:background="@drawable/bg_capture_face_button"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textColor="#F2FFFFFF" />
</LinearLayout>

View File

@@ -153,6 +153,8 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:elevation="0dp"
android:stateListAnimator="@null"
android:layout_marginStart="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -251,6 +253,8 @@
android:textSize="16sp"
app:cornerRadius="12dp"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -168,6 +168,22 @@
android:src="@mipmap/default_page" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="32dp"
android:background="@color/white"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/no_network_img_name" />
</LinearLayout>
</LinearLayout>
<View

View File

@@ -118,6 +118,7 @@
</FrameLayout>
<!-- 使用智能文字和图标 -->
<LinearLayout
android:id="@+id/sys"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@@ -125,16 +126,16 @@
android:gravity="center_vertical">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/syis" />
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/syis" />
<TextView
android:textColor="@color/green"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/use_smart_defense_app"
android:textColor="@color/black"
android:textSize="14sp" />
</LinearLayout>
@@ -211,6 +212,8 @@
android:layout_marginEnd="12dp"
android:text="@string/cancel_invite"
android:textColor="@color/green"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="16sp"
android:background="@drawable/bg_blue_border_round"
android:textAllCaps="false" />
@@ -223,6 +226,8 @@
android:text="@string/regenerate"
android:textColor="@color/white"
android:textSize="16sp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/bg_blue_round"
android:textAllCaps="false" />
</LinearLayout>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".personal.JoinFamilyInviteActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/topBar"
android:layout_width="match_parent"
android:layout_height="56dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/ivBack"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:contentDescription="@string/back"
android:src="@mipmap/back"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingHorizontal="32dp"
app:layout_constraintBottom_toTopOf="@id/btnJoinNow"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/topBar"
app:layout_constraintVertical_bias="0.35">
<ImageView
android:id="@+id/ivAvatar"
android:layout_width="88dp"
android:layout_height="88dp"
android:contentDescription="@string/avatar"
android:scaleType="centerCrop"
android:src="@mipmap/img"/>
<TextView
android:id="@+id/tvNickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/nickname_placeholder"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvInviteMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="@string/invite_join_family_message_preview"
android:textColor="@color/gray"
android:textSize="14sp" />
</LinearLayout>
<android.widget.Button
android:id="@+id/btnJoinNow"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="40dp"
android:background="@drawable/rounded_blue_bg"
android:text="@string/join_now"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -35,6 +35,12 @@
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<!-- 搜索区域 -->
<LinearLayout
android:id="@+id/searchLayout"
@@ -72,23 +78,64 @@
android:singleLine="true"/>
</LinearLayout>
</LinearLayout>
<!-- 列表区域 -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
<!-- 内容区域 -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<!-- 列表 -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.yanzhenjie.recyclerview.SwipeRecyclerView
android:id="@+id/rvLinkedUser"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"/>
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- 空页面 -->
<LinearLayout
android:id="@+id/layoutNoPermission"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/default_page"/>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="32dp"
android:background="@color/white"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/no_network_img_name" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
<!-- 底部按钮 -->
<LinearLayout
android:id="@+id/butting"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -102,6 +149,8 @@
android:text="@string/add_linked_user"
android:textColor="@color/white"
android:textSize="14dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg" />
<!-- 悬浮加号按钮 -->
<ImageView

View File

@@ -58,7 +58,22 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/no_message" />
android:src="@mipmap/default_page_img_name" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="32dp"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/no_network_img_name" />
</LinearLayout>
<TextView

View File

@@ -148,6 +148,8 @@
android:background="@drawable/rounded_blue_bg"
android:paddingHorizontal="12dp"
android:maxWidth="120dp"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -293,6 +295,8 @@
android:textSize="16sp"
app:cornerRadius="12dp"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -35,6 +35,7 @@ tools:context=".login.RegisterSuccessActivity">
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -80,6 +81,8 @@ tools:context=".login.RegisterSuccessActivity">
android:textColor="@color/white"
android:textSize="14dp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -91,6 +91,8 @@
android:paddingHorizontal="16dp"
android:text="@string/get_verification_code"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="14sp" />
</RelativeLayout>
@@ -131,6 +133,8 @@
android:background="@drawable/rounded_blue_bg"
android:text="@string/confirm_reset_password_btn"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="16sp" />
</LinearLayout>

View File

@@ -81,6 +81,8 @@
android:text="@string/complete"
android:textColor="@color/white"
android:textSize="16dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textStyle="bold" />
</LinearLayout>

View File

@@ -76,6 +76,8 @@
android:background="@drawable/rounded_blue_bg"
android:text="@string/submit"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="16sp" />
</LinearLayout>

View File

@@ -182,6 +182,8 @@
android:textSize="16dp"
android:background="@drawable/rounded_blue_bg"
android:padding="0dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:layout_margin="0dp"/>
</LinearLayout>

View File

@@ -72,7 +72,7 @@
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
<android.widget.Button
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -80,7 +80,9 @@
android:layout_marginEnd="20dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="90dp"
android:backgroundTint="@color/green" />
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg" />
</LinearLayout>

View File

@@ -80,6 +80,8 @@
android:background="@drawable/rounded_blue_bg"
android:text="@string/select_wifi"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="14sp" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

View File

@@ -48,6 +48,7 @@
<string name="reset_password">重置密码</string>
<string name="new_password">新密码</string>
<string name="reset_success">密码重置成功</string>
<string name="resetting">重置中...</string>
<!-- Face Login/Verification -->
<string name="face_login">人脸登录</string>
@@ -109,6 +110,10 @@
<string name="enter_family_name">请输入家庭名称</string>
<string name="create_family">创建家庭</string>
<string name="join_family">加入家庭</string>
<string name="join_now">立即加入</string>
<string name="nickname_placeholder">这里是昵称</string>
<string name="invite_join_family_message">邀请你加入%s的家庭</string>
<string name="invite_join_family_message_preview">邀请你加入这里是昵称的家庭</string>
<string name="family_code">家庭码</string>
<string name="enter_family_code">请输入家庭码</string>
<string name="family_with_normal">的家庭</string>

View File

@@ -20,5 +20,7 @@
<color name="green_light">#E8F7F7</color>
<color name="gray_divider">#E5E5E5</color>
<color name="orange">#FF9800</color>
<color name="gradientLayout1">#EBDCED</color>
<color name="gradientLayout2">#CAF4FF</color>
</resources>

View File

@@ -44,6 +44,7 @@
<string name="reset_password">Reset Password</string>
<string name="new_password">New Password</string>
<string name="reset_success">Password reset successful</string>
<string name="resetting">Resetting...</string>
<!-- Face Login/Verification -->
<string name="face_capture_hint">Please align your face within the frame</string>
@@ -104,6 +105,10 @@
<string name="enter_family_name">Enter family name</string>
<string name="create_family">Create Family</string>
<string name="join_family">Join Family</string>
<string name="join_now">Join Now</string>
<string name="nickname_placeholder">Nickname</string>
<string name="invite_join_family_message">Inviting you to join %s\'s family</string>
<string name="invite_join_family_message_preview">Inviting you to join Nickname\'s family</string>
<string name="family_code">Family Code</string>
<string name="enter_family_code">Enter family code</string>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">service.reinkun.com</domain>
</domain-config>
</network-security-config>