remove auto lock off and optimize session keepalive for mib

This commit is contained in:
2026-05-21 22:31:58 +05:00
parent 2d705457f8
commit 50150b826f
17 changed files with 223 additions and 102 deletions
@@ -68,6 +68,7 @@ class MibTransferClient {
.withWvHeaders(session) .withWvHeaders(session)
.build() .build()
return client.newCall(request).execute().use { response -> return client.newCall(request).execute().use { response ->
if (response.code == 419) throw SessionExpiredException()
val bodyStr = response.body?.string() ?: "" val bodyStr = response.body?.string() ?: ""
val json = try { JSONObject(bodyStr) } catch (_: Exception) { null } val json = try { JSONObject(bodyStr) } catch (_: Exception) { null }
if (json == null || !json.optBoolean("success")) { if (json == null || !json.optBoolean("success")) {
@@ -118,6 +118,14 @@ class AccountHistoryFragment : Fragment() {
} }
(activity as? HomeActivity)?.setRefreshing(true) (activity as? HomeActivity)?.setRefreshing(true)
loadNextPage() loadNextPage()
binding.swipeRefresh.setOnRefreshListener {
if (isLoading) {
binding.swipeRefresh.isRefreshing = false
} else {
resetAndReload()
}
}
} }
override fun onResume() { override fun onResume() {
@@ -135,6 +143,17 @@ class AccountHistoryFragment : Fragment() {
binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE
} }
private fun resetAndReload() {
allTransactions.clear()
pendingImageNames.clear()
pendingIconUrls.clear()
firstPageDone = false
fetcher = HistoryFetcher(account)
adapter.setTransactions(emptyList())
binding.emptyView.visibility = View.GONE
loadNextPage()
}
private fun loadNextPage() { private fun loadNextPage() {
if (isLoading || !fetcher.hasMore()) return if (isLoading || !fetcher.hasMore()) return
isLoading = true isLoading = true
@@ -153,6 +172,7 @@ class AccountHistoryFragment : Fragment() {
if (!firstPageDone) { if (!firstPageDone) {
firstPageDone = true firstPageDone = true
(activity as? HomeActivity)?.setRefreshing(false) (activity as? HomeActivity)?.setRefreshing(false)
binding.swipeRefresh.isRefreshing = false
} }
if (transactions.isNotEmpty()) { if (transactions.isNotEmpty()) {
@@ -45,6 +45,11 @@ class AccountsFragment : Fragment() {
viewModel.accounts.observe(viewLifecycleOwner) { adapter.updateAccounts(it) } viewModel.accounts.observe(viewLifecycleOwner) { adapter.updateAccounts(it) }
viewModel.hideAmounts.observe(viewLifecycleOwner) { adapter.setHideAmounts(it) } viewModel.hideAmounts.observe(viewLifecycleOwner) { adapter.setHideAmounts(it) }
binding.swipeRefresh.setOnRefreshListener {
(activity as? HomeActivity)?.triggerRefresh()
binding.swipeRefresh.isRefreshing = false
}
} }
override fun onResume() { override fun onResume() {
@@ -48,6 +48,7 @@ class ContactsFragment : Fragment() {
private var currentSearch: String = "" private var currentSearch: String = ""
private var mediator: TabLayoutMediator? = null private var mediator: TabLayoutMediator? = null
private lateinit var pagerAdapter: ContactsPagerAdapter private lateinit var pagerAdapter: ContactsPagerAdapter
private var contactsRefreshing = false
private data class TabPage(val categoryId: String?, val label: String) private data class TabPage(val categoryId: String?, val label: String)
@@ -134,6 +135,11 @@ class ContactsFragment : Fragment() {
(activity as? HomeActivity)?.loadAllContacts() (activity as? HomeActivity)?.loadAllContacts()
binding.swipeRefresh.setOnRefreshListener {
contactsRefreshing = true
(activity as? HomeActivity)?.loadAllContacts()
}
viewModel.contactCategories.observe(viewLifecycleOwner) { cats -> viewModel.contactCategories.observe(viewLifecycleOwner) { cats ->
rebuildPager(cats) rebuildPager(cats)
} }
@@ -143,6 +149,10 @@ class ContactsFragment : Fragment() {
pagerAdapter.updateContacts(allContacts) pagerAdapter.updateContacts(allContacts)
binding.emptyView.visibility = if (contacts.isEmpty()) View.VISIBLE else View.GONE binding.emptyView.visibility = if (contacts.isEmpty()) View.VISIBLE else View.GONE
binding.loadingView.visibility = View.GONE binding.loadingView.visibility = View.GONE
if (contactsRefreshing) {
contactsRefreshing = false
binding.swipeRefresh.isRefreshing = false
}
} }
} }
@@ -38,6 +38,11 @@ class DashboardFragment : Fragment() {
updateForeignLimits(viewModel.bmlLimits.value ?: emptyList()) updateForeignLimits(viewModel.bmlLimits.value ?: emptyList())
} }
binding.swipeRefresh.setOnRefreshListener {
(activity as? HomeActivity)?.triggerRefresh()
binding.swipeRefresh.isRefreshing = false
}
val bottomPaddingBase = (16 * resources.displayMetrics.density).toInt() val bottomPaddingBase = (16 * resources.displayMetrics.density).toInt()
ViewCompat.setOnApplyWindowInsetsListener(binding.buttonBar) { v, insets -> ViewCompat.setOnApplyWindowInsetsListener(binding.buttonBar) { v, insets ->
val isBottomNav = requireContext().getSharedPreferences("prefs", android.content.Context.MODE_PRIVATE).getBoolean("bottom_nav", false) val isBottomNav = requireContext().getSharedPreferences("prefs", android.content.Context.MODE_PRIVATE).getBoolean("bottom_nav", false)
@@ -18,6 +18,7 @@ class FinancingFragment : Fragment() {
private val binding get() = _binding!! private val binding get() = _binding!!
private val viewModel: HomeViewModel by activityViewModels() private val viewModel: HomeViewModel by activityViewModels()
private lateinit var adapter: FinancingAdapter private lateinit var adapter: FinancingAdapter
private var financingRefreshing = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentFinancingBinding.inflate(inflater, container, false) _binding = FragmentFinancingBinding.inflate(inflater, container, false)
@@ -38,11 +39,20 @@ class FinancingFragment : Fragment() {
insets insets
} }
binding.swipeRefresh.setOnRefreshListener {
financingRefreshing = true
(activity as? HomeActivity)?.triggerRefreshFinancing()
}
viewModel.financing.observe(viewLifecycleOwner) { deals -> viewModel.financing.observe(viewLifecycleOwner) { deals ->
adapter.updateDeals(deals) adapter.updateDeals(deals)
binding.recyclerView.visibility = if (deals.isEmpty()) View.GONE else View.VISIBLE binding.recyclerView.visibility = if (deals.isEmpty()) View.GONE else View.VISIBLE
binding.emptyView.visibility = if (deals.isEmpty()) View.VISIBLE else View.GONE binding.emptyView.visibility = if (deals.isEmpty()) View.VISIBLE else View.GONE
binding.loadingView.visibility = View.GONE binding.loadingView.visibility = View.GONE
if (financingRefreshing) {
financingRefreshing = false
binding.swipeRefresh.isRefreshing = false
}
} }
viewModel.hideAmounts.observe(viewLifecycleOwner) { adapter.setHideAmounts(it) } viewModel.hideAmounts.observe(viewLifecycleOwner) { adapter.setHideAmounts(it) }
} }
@@ -324,6 +324,18 @@ fun applyNavLabelVisibility() {
} }
} }
fun triggerRefresh() {
autoRefresh(CredentialStore(this))
}
fun triggerRefreshFinancing() {
val app = application as BasedBankApp
for ((loginId, session) in app.mibSessions) {
val profiles = app.mibProfilesMap[loginId] ?: emptyList()
refreshFinancing(loginId, session, profiles.filterVisibleProfiles(loginId))
}
}
fun setRefreshing(visible: Boolean) { fun setRefreshing(visible: Boolean) {
binding.refreshIndicator.visibility = if (visible) View.VISIBLE else View.GONE binding.refreshIndicator.visibility = if (visible) View.VISIBLE else View.GONE
} }
@@ -337,11 +349,12 @@ fun applyNavLabelVisibility() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// Returning from LockActivity — skip the elapsed check and reset state. // Returning from LockActivity — refresh sessions since they may have expired.
if (isLocked) { if (isLocked) {
isLocked = false isLocked = false
pauseTime = 0L pauseTime = 0L
resetAutolockTimer() resetAutolockTimer()
autoRefresh(CredentialStore(this))
return return
} }
// If we were away long enough to have hit the autolock timeout (e.g. while // If we were away long enough to have hit the autolock timeout (e.g. while
@@ -354,6 +367,9 @@ fun applyNavLabelVisibility() {
lock() lock()
return return
} }
if (elapsed > 45_000L) {
autoRefresh(CredentialStore(this))
}
} }
resetAutolockTimer() resetAutolockTimer()
} }
@@ -60,7 +60,6 @@ class SettingsSecurityFragment : Fragment() {
// Auto-lock // Auto-lock
binding.autolockToggle.check(when (prefs.getLong("autolock_timeout", 60_000L)) { binding.autolockToggle.check(when (prefs.getLong("autolock_timeout", 60_000L)) {
0L -> R.id.btnAutolockOff
30_000L -> R.id.btnAutolock30s 30_000L -> R.id.btnAutolock30s
180_000L -> R.id.btnAutolock3m 180_000L -> R.id.btnAutolock3m
300_000L -> R.id.btnAutolock5m 300_000L -> R.id.btnAutolock5m
@@ -69,7 +68,6 @@ class SettingsSecurityFragment : Fragment() {
binding.autolockToggle.addOnButtonCheckedListener { _, checkedId, isChecked -> binding.autolockToggle.addOnButtonCheckedListener { _, checkedId, isChecked ->
if (!isChecked) return@addOnButtonCheckedListener if (!isChecked) return@addOnButtonCheckedListener
val timeout = when (checkedId) { val timeout = when (checkedId) {
R.id.btnAutolockOff -> 0L
R.id.btnAutolock30s -> 30_000L R.id.btnAutolock30s -> 30_000L
R.id.btnAutolock3m -> 180_000L R.id.btnAutolock3m -> 180_000L
R.id.btnAutolock5m -> 300_000L R.id.btnAutolock5m -> 300_000L
@@ -652,7 +652,7 @@ class TransferFragment : Fragment() {
ReceiptStore.save(requireContext(), receipt) ReceiptStore.save(requireContext(), receipt)
clearForm() clearForm()
val activity = requireActivity() as HomeActivity val activity = requireActivity() as HomeActivity
activity.refreshBalances(src) activity.triggerRefresh()
activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar)) activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar))
} else if (!ok) { } else if (!ok) {
Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show() Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show()
@@ -138,6 +138,14 @@ class TransferHistoryFragment : Fragment() {
} }
(activity as? HomeActivity)?.setRefreshing(true) (activity as? HomeActivity)?.setRefreshing(true)
loadNextPages() loadNextPages()
binding.swipeRefresh.setOnRefreshListener {
if (isLoading) {
binding.swipeRefresh.isRefreshing = false
} else {
resetAndReload()
}
}
} }
override fun onResume() { override fun onResume() {
@@ -145,6 +153,19 @@ class TransferHistoryFragment : Fragment() {
requireActivity().title = getString(R.string.nav_transfer_history) requireActivity().title = getString(R.string.nav_transfer_history)
} }
private fun resetAndReload() {
allTransactions.clear()
pendingImageNames.clear()
pendingIconUrls.clear()
firstBatchDone = false
val accounts = accountStates.map { it.account }
accountStates.clear()
accounts.forEach { accountStates.add(AccountState(it)) }
adapter.setTransactions(emptyList())
binding.emptyView.visibility = View.GONE
loadNextPages()
}
private fun loadNextPages() { private fun loadNextPages() {
val activeStates = accountStates.filter { it.hasMore() } val activeStates = accountStates.filter { it.hasMore() }
if (isLoading || activeStates.isEmpty()) return if (isLoading || activeStates.isEmpty()) return
@@ -250,6 +271,7 @@ class TransferHistoryFragment : Fragment() {
if (!firstBatchDone) { if (!firstBatchDone) {
firstBatchDone = true firstBatchDone = true
(activity as? HomeActivity)?.setRefreshing(false) (activity as? HomeActivity)?.setRefreshing(false)
binding.swipeRefresh.isRefreshing = false
} }
if (newTransactions.isNotEmpty()) { if (newTransactions.isNotEmpty()) {
@@ -31,28 +31,35 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<FrameLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"> android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView <FrameLayout
android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:paddingBottom="16dp"
android:clipToPadding="false" />
<TextView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/emptyView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:paddingBottom="16dp"
android:text="No transactions found" android:clipToPadding="false" />
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" />
</FrameLayout> <TextView
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="No transactions found"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" />
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout> </LinearLayout>
+14 -7
View File
@@ -1,11 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerView" android:id="@+id/swipeRefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingHorizontal="16dp" android:background="?attr/colorSurface">
android:paddingTop="8dp"
android:paddingBottom="16dp" <androidx.recyclerview.widget.RecyclerView
android:clipToPadding="false" android:id="@+id/recyclerView"
android:background="?attr/colorSurface" /> android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
android:clipToPadding="false" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+27 -20
View File
@@ -41,34 +41,41 @@
app:tabMode="scrollable" app:tabMode="scrollable"
app:tabGravity="start" /> app:tabGravity="start" />
<FrameLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"> android:layout_weight="1">
<androidx.viewpager2.widget.ViewPager2 <FrameLayout
android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent">
<ProgressBar <androidx.viewpager2.widget.ViewPager2
android:id="@+id/loadingView" android:id="@+id/viewPager"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent" />
android:layout_gravity="center"
android:visibility="visible" />
<TextView <ProgressBar
android:id="@+id/emptyView" android:id="@+id/loadingView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:text="@string/contacts_empty" android:visibility="visible" />
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" />
</FrameLayout> <TextView
android:id="@+id/emptyView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/contacts_empty"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" />
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout> </LinearLayout>
@@ -7,11 +7,16 @@
android:orientation="vertical" android:orientation="vertical"
android:background="?attr/colorSurface"> android:background="?attr/colorSurface">
<androidx.core.widget.NestedScrollView <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"> android:layout_weight="1">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -172,6 +177,8 @@
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- Quick actions fixed at bottom --> <!-- Quick actions fixed at bottom -->
<LinearLayout <LinearLayout
android:id="@+id/buttonBar" android:id="@+id/buttonBar"
+36 -29
View File
@@ -1,41 +1,48 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipeRefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface"> android:background="?attr/colorSurface">
<androidx.recyclerview.widget.RecyclerView <FrameLayout
android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
android:clipToPadding="false" />
<LinearLayout <androidx.recyclerview.widget.RecyclerView
android:id="@+id/loadingView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:paddingHorizontal="16dp"
android:orientation="vertical" android:paddingTop="8dp"
android:visibility="gone"> android:paddingBottom="16dp"
android:clipToPadding="false" />
<ProgressBar <LinearLayout
android:layout_width="wrap_content" android:id="@+id/loadingView"
android:layout_height="wrap_content" /> android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
</LinearLayout> <ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView </LinearLayout>
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/financing_empty"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" />
</FrameLayout> <TextView
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/financing_empty"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" />
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@@ -112,14 +112,6 @@
app:singleSelection="true" app:singleSelection="true"
app:selectionRequired="true"> app:selectionRequired="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAutolockOff"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/autolock_off" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnAutolock30s" android:id="@+id/btnAutolock30s"
style="@style/Widget.Material3.Button.OutlinedButton" style="@style/Widget.Material3.Button.OutlinedButton"
@@ -31,29 +31,36 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<FrameLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"> android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView <FrameLayout
android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:paddingTop="4dp"
android:paddingBottom="16dp"
android:clipToPadding="false" />
<TextView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/emptyView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:paddingTop="4dp"
android:text="No transactions found" android:paddingBottom="16dp"
android:textAppearance="?attr/textAppearanceBodyMedium" android:clipToPadding="false" />
android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" />
</FrameLayout> <TextView
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="No transactions found"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" />
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout> </LinearLayout>