logd加载动画优化
This commit is contained in:
48
.kotlin/errors/errors-1783643668109.log
Normal file
48
.kotlin/errors/errors-1783643668109.log
Normal 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-tsmarker11134052769962658558.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)
|
||||||
|
|
||||||
|
|
||||||
48
.kotlin/errors/errors-1783645418169.log
Normal file
48
.kotlin/errors/errors-1783645418169.log
Normal 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-tsmarker4236714666597388016.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)
|
||||||
|
|
||||||
|
|
||||||
48
.kotlin/errors/errors-1783645640346.log
Normal file
48
.kotlin/errors/errors-1783645640346.log
Normal 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-tsmarker12754528945444950713.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)
|
||||||
|
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ import com.ckkj.defense_device.utils.RetrofitNetwork
|
|||||||
import com.ckkj.defense_device.utils.UserinfoUtil
|
import com.ckkj.defense_device.utils.UserinfoUtil
|
||||||
import com.ckkj.defense_device.view.DefenseDialogs
|
import com.ckkj.defense_device.view.DefenseDialogs
|
||||||
import com.ckkj.defense_device.view.LoadingDialog
|
import com.ckkj.defense_device.view.LoadingDialog
|
||||||
|
import com.ckkj.defense_device.view.PullRefreshLayout
|
||||||
import com.ckkj.defense_device.view.showDefenseDialog
|
import com.ckkj.defense_device.view.showDefenseDialog
|
||||||
import com.gyf.immersionbar.ImmersionBar
|
import com.gyf.immersionbar.ImmersionBar
|
||||||
import com.maning.mlkitscanner.scan.MNScanManager
|
import com.maning.mlkitscanner.scan.MNScanManager
|
||||||
@@ -62,6 +63,9 @@ class HomeMenuFragment : Fragment() {
|
|||||||
private val pageSize = 10
|
private val pageSize = 10
|
||||||
private var hasMore = true
|
private var hasMore = true
|
||||||
private var isLoading = false
|
private var isLoading = false
|
||||||
|
private var loadingDialog: LoadingDialog? = null
|
||||||
|
private var lastNoMoreToastTime: Long = 0L
|
||||||
|
private val noMoreToastInterval: Long = 1500L
|
||||||
|
|
||||||
private val requestPermissionsLauncher = registerForActivityResult(
|
private val requestPermissionsLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
@@ -162,7 +166,7 @@ class HomeMenuFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchInviterInfo(inviteCode: String) {
|
private fun fetchInviterInfo(inviteCode: String) {
|
||||||
val loadingDialog = LoadingDialog(requireActivity()).apply { show() }
|
val loadingDialog = LoadingDialog(requireActivity()).setMessage(this.getString(R.string.loading)).apply { show() }
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
val response = RetrofitNetwork.getNetworkService()
|
val response = RetrofitNetwork.getNetworkService()
|
||||||
@@ -297,9 +301,11 @@ class HomeMenuFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
binding.swipeRefreshLayout.setOnRefreshListener(object : PullRefreshLayout.OnRefreshListener {
|
||||||
refreshDeviceList()
|
override fun onRefresh() {
|
||||||
}
|
refreshDeviceList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
binding.rvDeviceList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
binding.rvDeviceList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
@@ -307,9 +313,24 @@ class HomeMenuFragment : Fragment() {
|
|||||||
val layoutManager = recyclerView.layoutManager as GridLayoutManager
|
val layoutManager = recyclerView.layoutManager as GridLayoutManager
|
||||||
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
|
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
|
||||||
val totalItemCount = layoutManager.itemCount
|
val totalItemCount = layoutManager.itemCount
|
||||||
|
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
||||||
|
|
||||||
|
if (firstVisibleItemPosition == 0) {
|
||||||
|
val firstChild = layoutManager.findViewByPosition(0)
|
||||||
|
firstChild?.let { child ->
|
||||||
|
val top = child.top
|
||||||
|
if (top >= 0) {
|
||||||
|
val offset = (top.toFloat() / dpToPx(80f)).coerceIn(0f, 1f)
|
||||||
|
binding.gradientLayout.translationY = -offset * dpToPx(20f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isLoading && hasMore && lastVisibleItemPosition >= totalItemCount - 1 && dy > 0) {
|
if (!isLoading && hasMore && lastVisibleItemPosition >= totalItemCount - 1 && dy > 0) {
|
||||||
loadMoreDevices()
|
loadMoreDevices()
|
||||||
|
} else if (!hasMore && !isLoading && lastVisibleItemPosition >= totalItemCount - 1
|
||||||
|
&& dy > 0 && totalItemCount > 0) {
|
||||||
|
showNoMoreToast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -425,7 +446,7 @@ class HomeMenuFragment : Fragment() {
|
|||||||
canViewDevice = userInfo?.role == "appadmin" && familyBean.role != "member"
|
canViewDevice = userInfo?.role == "appadmin" && familyBean.role != "member"
|
||||||
updateAdminUI()
|
updateAdminUI()
|
||||||
updateFamilyDisplayText(familyName)
|
updateFamilyDisplayText(familyName)
|
||||||
fetchDeviceList()
|
fetchDeviceList(showLoading = true)
|
||||||
FamilySwitchEvent.notifyFamilySwitched(familyName)
|
FamilySwitchEvent.notifyFamilySwitched(familyName)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(requireContext(), response.msg, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), response.msg, Toast.LENGTH_SHORT).show()
|
||||||
@@ -439,15 +460,20 @@ class HomeMenuFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchDeviceList(page: Int = 1, isLoadMore: Boolean = false) {
|
private fun fetchDeviceList(page: Int = 1, isLoadMore: Boolean = false, showLoading: Boolean = false) {
|
||||||
if (!canViewDevice || isLoading) return
|
if (!canViewDevice || isLoading) return
|
||||||
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
|
loadingDialog = if (showLoading) LoadingDialog(requireActivity()).setMessage("加载中...").apply { show() } else null
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
val response = RetrofitNetwork.getNetworkService().getDeviceList()
|
val response = RetrofitNetwork.getNetworkService().getDeviceList()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
if (!isAdded) return@withContext
|
||||||
|
loadingDialog?.dismiss()
|
||||||
|
loadingDialog = null
|
||||||
Log.d("DeviceList", "code = ${response.code}, data = ${response.data}")
|
Log.d("DeviceList", "code = ${response.code}, data = ${response.data}")
|
||||||
if (response.code == 200 && response.data != null) {
|
if (response.code == 200 && response.data != null) {
|
||||||
val newDevices = response.data
|
val newDevices = response.data
|
||||||
@@ -470,25 +496,36 @@ class HomeMenuFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
isLoading = false
|
isLoading = false
|
||||||
|
binding.swipeRefreshLayout.finishRefresh()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
if (!isAdded) return@withContext
|
||||||
|
loadingDialog?.dismiss()
|
||||||
|
loadingDialog = null
|
||||||
noNetwork()
|
noNetwork()
|
||||||
Toast.makeText(requireContext(), R.string.network_error, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.network_error, Toast.LENGTH_SHORT).show()
|
||||||
isLoading = false
|
isLoading = false
|
||||||
if (isLoadMore) {
|
if (isLoadMore) {
|
||||||
hasMore = false
|
hasMore = false
|
||||||
}
|
}
|
||||||
|
binding.swipeRefreshLayout.finishRefresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showNoMoreToast() {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (now - lastNoMoreToastTime < noMoreToastInterval) return
|
||||||
|
lastNoMoreToastTime = now
|
||||||
|
ToastUtils.showToast(requireContext(), getString(R.string.no_more_data))
|
||||||
|
}
|
||||||
|
|
||||||
private fun refreshDeviceList() {
|
private fun refreshDeviceList() {
|
||||||
currentPage = 1
|
currentPage = 1
|
||||||
hasMore = true
|
hasMore = true
|
||||||
fetchDeviceList(currentPage, false)
|
fetchDeviceList(currentPage, false)
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadMoreDevices() {
|
private fun loadMoreDevices() {
|
||||||
@@ -566,6 +603,14 @@ class HomeMenuFragment : Fragment() {
|
|||||||
).toInt()
|
).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun dpToPx(dp: Float): Float {
|
||||||
|
return TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
dp,
|
||||||
|
resources.displayMetrics
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private class GridHorizontalSpaceItemDecoration(
|
private class GridHorizontalSpaceItemDecoration(
|
||||||
private val horizontalSpace: Int
|
private val horizontalSpace: Int
|
||||||
) : RecyclerView.ItemDecoration() {
|
) : RecyclerView.ItemDecoration() {
|
||||||
@@ -588,6 +633,8 @@ class HomeMenuFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
loadingDialog?.dismiss()
|
||||||
|
loadingDialog = null
|
||||||
binding.rvFamilyList.adapter = null
|
binding.rvFamilyList.adapter = null
|
||||||
binding.rvDeviceList.adapter = null
|
binding.rvDeviceList.adapter = null
|
||||||
MessageStatusManager.removeListener(unreadCountChangeListener)
|
MessageStatusManager.removeListener(unreadCountChangeListener)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.app.Dialog
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
@@ -13,6 +15,13 @@ class LoadingDialog(context: Context) {
|
|||||||
|
|
||||||
private val dialog: Dialog = Dialog(context)
|
private val dialog: Dialog = Dialog(context)
|
||||||
private val binding: DialogLoadingBinding
|
private val binding: DialogLoadingBinding
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private var showTime: Long = 0
|
||||||
|
private val dismissRunnable = Runnable {
|
||||||
|
if (dialog.isShowing) {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding = DialogLoadingBinding.inflate(LayoutInflater.from(context))
|
binding = DialogLoadingBinding.inflate(LayoutInflater.from(context))
|
||||||
@@ -35,14 +44,23 @@ class LoadingDialog(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun show() {
|
fun show() {
|
||||||
|
handler.removeCallbacks(dismissRunnable)
|
||||||
|
showTime = System.currentTimeMillis()
|
||||||
if (!dialog.isShowing) {
|
if (!dialog.isShowing) {
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dismiss() {
|
fun dismiss() {
|
||||||
if (dialog.isShowing) {
|
handler.removeCallbacks(dismissRunnable)
|
||||||
dialog.dismiss()
|
val elapsed = System.currentTimeMillis() - showTime
|
||||||
|
val MIN_SHOW_TIME = 1000L
|
||||||
|
if (elapsed >= MIN_SHOW_TIME) {
|
||||||
|
if (dialog.isShowing) {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handler.postDelayed(dismissRunnable, MIN_SHOW_TIME - elapsed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,288 @@
|
|||||||
|
package com.ckkj.defense_device.view
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewConfiguration
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.LinearInterpolator
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.ckkj.defense_device.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义下拉刷新控件
|
||||||
|
* 仿网易云/抖音风格:下拉时列表跟随向下移动,顶部出现 Logo + 文字下拉头。
|
||||||
|
*
|
||||||
|
* 使用方式:直接包裹 RecyclerView 等可滚动子 View,调用 [setOnRefreshListener] 监听刷新。
|
||||||
|
*/
|
||||||
|
class PullRefreshLayout @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : ViewGroup(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||||
|
|
||||||
|
/** 触发刷新的下拉阈值(Header 的高度) */
|
||||||
|
private val headerHeight: Int = dp(120)
|
||||||
|
|
||||||
|
/** 触发刷新的最大下拉距离(Header 高度的 1.5 倍) */
|
||||||
|
private val maxPullDistance: Int = (headerHeight * 1.5f).toInt()
|
||||||
|
|
||||||
|
private var headerView: View
|
||||||
|
private val ivRefreshIcon: ImageView
|
||||||
|
private val tvHint: TextView
|
||||||
|
|
||||||
|
/** 子内容 View(通常是 RecyclerView) */
|
||||||
|
private var contentView: View? = null
|
||||||
|
|
||||||
|
private var downY = 0f
|
||||||
|
private var isDragging = false
|
||||||
|
|
||||||
|
/** 当前下拉偏移量(>= 0) */
|
||||||
|
private var currentOffset = 0f
|
||||||
|
|
||||||
|
/** 是否处于刷新状态 */
|
||||||
|
private var isRefreshing = false
|
||||||
|
set(value) {
|
||||||
|
if (field == value) return
|
||||||
|
field = value
|
||||||
|
if (value) {
|
||||||
|
startLoadingAnimation()
|
||||||
|
} else {
|
||||||
|
stopLoadingAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var refreshListener: OnRefreshListener? = null
|
||||||
|
private var resetAnimator: ValueAnimator? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
clipToPadding = false
|
||||||
|
// 添加 Header 视图
|
||||||
|
headerView = LayoutInflater.from(context).inflate(R.layout.layout_refresh_header, this, false)
|
||||||
|
addView(headerView)
|
||||||
|
ivRefreshIcon = headerView.findViewById(R.id.ivRefreshIcon)
|
||||||
|
tvHint = headerView.findViewById(R.id.tvRefreshHint)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||||
|
val height = MeasureSpec.getSize(heightMeasureSpec)
|
||||||
|
|
||||||
|
// 测量 Header
|
||||||
|
val headerHeightSpec = MeasureSpec.makeMeasureSpec(headerHeight, MeasureSpec.EXACTLY)
|
||||||
|
headerView.measure(
|
||||||
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||||
|
headerHeightSpec
|
||||||
|
)
|
||||||
|
|
||||||
|
// 测量内容 View(仅第一个非 Header 子 View)
|
||||||
|
for (i in 0 until childCount) {
|
||||||
|
val child = getChildAt(i)
|
||||||
|
if (child === headerView) continue
|
||||||
|
child.measure(
|
||||||
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||||
|
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
||||||
|
)
|
||||||
|
contentView = child
|
||||||
|
}
|
||||||
|
setMeasuredDimension(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||||
|
// Header 布局在容器顶部之上(初始隐藏,下拉时滑入)
|
||||||
|
headerView.layout(
|
||||||
|
0,
|
||||||
|
-headerHeight + currentOffset.toInt(),
|
||||||
|
width,
|
||||||
|
currentOffset.toInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
// 内容 View 跟随偏移
|
||||||
|
contentView?.layout(
|
||||||
|
0,
|
||||||
|
currentOffset.toInt(),
|
||||||
|
width,
|
||||||
|
height + currentOffset.toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
if (isRefreshing) return false
|
||||||
|
|
||||||
|
when (ev.actionMasked) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
downY = ev.y
|
||||||
|
isDragging = false
|
||||||
|
cancelResetAnimator()
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
val dy = ev.y - downY
|
||||||
|
if (dy > touchSlop && canPullDown()) {
|
||||||
|
isDragging = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
|
isDragging = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
if (isRefreshing) return false
|
||||||
|
|
||||||
|
when (ev.actionMasked) {
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
val dy = ev.y - downY
|
||||||
|
if (dy > 0) {
|
||||||
|
if (!isDragging) {
|
||||||
|
if (dy > touchSlop) {
|
||||||
|
isDragging = true
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 添加阻尼效果:越往下拉越吃力
|
||||||
|
val damped = computePullOffset(dy)
|
||||||
|
currentOffset = damped.coerceAtMost(maxPullDistance.toFloat())
|
||||||
|
updateHeaderState()
|
||||||
|
requestLayout()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
|
if (currentOffset >= headerHeight * 0.9f) {
|
||||||
|
// 触发刷新
|
||||||
|
triggerRefresh()
|
||||||
|
} else {
|
||||||
|
// 回弹
|
||||||
|
resetToIdle()
|
||||||
|
}
|
||||||
|
isDragging = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isDragging
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加阻尼效果,让下拉越深越慢,模拟弹性手感
|
||||||
|
*/
|
||||||
|
private fun computePullOffset(rawDy: Float): Float {
|
||||||
|
// 原始距离的 0.5 倍为基本偏移,超过阈值后逐渐衰减
|
||||||
|
val resistance = 1.8f
|
||||||
|
return (rawDy / resistance).coerceAtMost(maxPullDistance.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否可以从当前位置下拉(列表已经滚动到顶部)
|
||||||
|
*/
|
||||||
|
private fun canPullDown(): Boolean {
|
||||||
|
val content = contentView ?: return false
|
||||||
|
return !content.canScrollVertically(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据下拉距离更新 Header 的视觉状态(旋转角度、提示文字)
|
||||||
|
*/
|
||||||
|
private fun updateHeaderState() {
|
||||||
|
val ratio = (currentOffset / headerHeight).coerceIn(0f, 1f)
|
||||||
|
|
||||||
|
// 刷新图标根据下拉比例旋转 0~360 度
|
||||||
|
ivRefreshIcon.rotation = ratio * 360f
|
||||||
|
|
||||||
|
// 文字提示
|
||||||
|
tvHint.text = when {
|
||||||
|
currentOffset >= headerHeight * 0.9f -> context.getString(R.string.refresh_hint_release)
|
||||||
|
else -> context.getString(R.string.refresh_hint_pull)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun triggerRefresh() {
|
||||||
|
// 平滑动画到 header 高度
|
||||||
|
val animator = ValueAnimator.ofFloat(currentOffset, headerHeight.toFloat())
|
||||||
|
animator.duration = 200L
|
||||||
|
animator.interpolator = LinearInterpolator()
|
||||||
|
animator.addUpdateListener {
|
||||||
|
currentOffset = it.animatedValue as Float
|
||||||
|
requestLayout()
|
||||||
|
}
|
||||||
|
animator.start()
|
||||||
|
resetAnimator = animator
|
||||||
|
|
||||||
|
isRefreshing = true
|
||||||
|
tvHint.text = context.getString(R.string.refresh_hint_refreshing)
|
||||||
|
refreshListener?.onRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束刷新并回弹
|
||||||
|
*/
|
||||||
|
fun finishRefresh() {
|
||||||
|
if (!isRefreshing) return
|
||||||
|
isRefreshing = false
|
||||||
|
resetToIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetToIdle() {
|
||||||
|
cancelResetAnimator()
|
||||||
|
val animator = ValueAnimator.ofFloat(currentOffset, 0f)
|
||||||
|
animator.duration = 300L
|
||||||
|
animator.interpolator = LinearInterpolator()
|
||||||
|
animator.addUpdateListener {
|
||||||
|
currentOffset = it.animatedValue as Float
|
||||||
|
if (!isRefreshing) {
|
||||||
|
// 刷新结束后,重置图标旋转角度
|
||||||
|
ivRefreshIcon.rotation = 0f
|
||||||
|
tvHint.text = context.getString(R.string.refresh_hint_pull)
|
||||||
|
}
|
||||||
|
requestLayout()
|
||||||
|
}
|
||||||
|
animator.start()
|
||||||
|
resetAnimator = animator
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelResetAnimator() {
|
||||||
|
resetAnimator?.cancel()
|
||||||
|
resetAnimator = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startLoadingAnimation() {
|
||||||
|
// 持续旋转动画
|
||||||
|
ivRefreshIcon.animate()
|
||||||
|
.rotation(ivRefreshIcon.rotation + 360f)
|
||||||
|
.setDuration(800L)
|
||||||
|
.setInterpolator(LinearInterpolator())
|
||||||
|
.withEndAction {
|
||||||
|
if (isRefreshing) startLoadingAnimation()
|
||||||
|
}
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopLoadingAnimation() {
|
||||||
|
ivRefreshIcon.animate().cancel()
|
||||||
|
ivRefreshIcon.rotation = 0f
|
||||||
|
tvHint.text = context.getString(R.string.refresh_hint_pull)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnRefreshListener(listener: OnRefreshListener) {
|
||||||
|
this.refreshListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnRefreshListener {
|
||||||
|
fun onRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dp(value: Int): Int {
|
||||||
|
val density = resources.displayMetrics.density
|
||||||
|
return (value * density).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="@string/uploading"
|
android:text="@string/uploading"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/green"
|
||||||
android:textSize="16sp"/>
|
android:textSize="16sp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<com.ckkj.defense_device.view.PullRefreshLayout
|
||||||
android:id="@+id/swipeRefreshLayout"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_marginLeft="12dp"
|
android:layout_marginLeft="12dp"
|
||||||
@@ -152,11 +152,11 @@
|
|||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:nestedScrollingEnabled="false"
|
android:overScrollMode="never"
|
||||||
android:paddingBottom="16dp"
|
android:paddingBottom="16dp"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
tools:listitem="@layout/item_device" />
|
tools:listitem="@layout/item_device" />
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</com.ckkj.defense_device.view.PullRefreshLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layoutNoPermission"
|
android:id="@+id/layoutNoPermission"
|
||||||
|
|||||||
24
app/src/main/res/layout/layout_refresh_header.xml
Normal file
24
app/src/main/res/layout/layout_refresh_header.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="24dp"
|
||||||
|
android:paddingBottom="20dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivRefreshIcon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@mipmap/setting" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRefreshHint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="@string/refresh_hint_pull"
|
||||||
|
android:textColor="@color/green"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
</LinearLayout>
|
||||||
BIN
app/src/main/res/mipmap-xxxhdpi/setting.png
Normal file
BIN
app/src/main/res/mipmap-xxxhdpi/setting.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
@@ -512,4 +512,11 @@
|
|||||||
<string name="cancel_account_btn">注销</string>
|
<string name="cancel_account_btn">注销</string>
|
||||||
<string name="confirm_cancel_account_user_account">确定注销该用户吗?</string>
|
<string name="confirm_cancel_account_user_account">确定注销该用户吗?</string>
|
||||||
|
|
||||||
|
<!-- HomeMenuFragment 列表刷新 -->
|
||||||
|
<string name="no_more_data">没有更多了</string>
|
||||||
|
<string name="refreshing">正在刷新…</string>
|
||||||
|
<string name="refresh_hint_pull">下拉刷新</string>
|
||||||
|
<string name="refresh_hint_release">松开刷新</string>
|
||||||
|
<string name="refresh_hint_refreshing">正在刷新…</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -519,4 +519,11 @@
|
|||||||
<string name="current_account">Current Account</string>
|
<string name="current_account">Current Account</string>
|
||||||
<string name="cancel_account_btn">Cancel</string>
|
<string name="cancel_account_btn">Cancel</string>
|
||||||
<string name="confirm_cancel_account_user_account">Confirm Cancel Account User Account</string>
|
<string name="confirm_cancel_account_user_account">Confirm Cancel Account User Account</string>
|
||||||
|
|
||||||
|
<!-- Home Menu refresh / load more -->
|
||||||
|
<string name="no_more_data">No more data</string>
|
||||||
|
<string name="refreshing">Refreshing…</string>
|
||||||
|
<string name="refresh_hint_pull">Pull down to refresh</string>
|
||||||
|
<string name="refresh_hint_release">Release to refresh</string>
|
||||||
|
<string name="refresh_hint_refreshing">Refreshing…</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user