diff --git a/app/src/main/java/sh/sar/basedbank/api/bml/BmlAccountClient.kt b/app/src/main/java/sh/sar/basedbank/api/bml/BmlAccountClient.kt index bc0e809..e5307fd 100644 --- a/app/src/main/java/sh/sar/basedbank/api/bml/BmlAccountClient.kt +++ b/app/src/main/java/sh/sar/basedbank/api/bml/BmlAccountClient.kt @@ -2,6 +2,7 @@ package sh.sar.basedbank.api.bml import org.json.JSONObject import sh.sar.basedbank.api.models.BankAccount +import sh.sar.basedbank.api.models.BankServerException data class BmlUserInfo( val fullName: String, @@ -27,6 +28,7 @@ class BmlAccountClient { val json = resp.body?.string() resp.close() if (code == 401 || code == 419) throw AuthExpiredException() + if (code in 500..599) throw BankServerException("BML") return parseDashboard(json ?: return emptyList(), loginTag, profileName, profileId) } @@ -36,6 +38,7 @@ class BmlAccountClient { val code = resp.code resp.close() if (code == 401 || code == 419) throw AuthExpiredException() + if (code in 500..599) throw BankServerException("BML") } fun fetchUserInfo(session: BmlSession): BmlUserInfo? { diff --git a/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayAccountClient.kt b/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayAccountClient.kt index 1107810..5735dd4 100644 --- a/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayAccountClient.kt +++ b/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayAccountClient.kt @@ -4,6 +4,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import org.json.JSONObject import sh.sar.basedbank.api.models.BankAccount +import sh.sar.basedbank.api.models.BankServerException import java.util.concurrent.TimeUnit class FahipayAccountClient { @@ -27,8 +28,10 @@ class FahipayAccountClient { Request.Builder().url("$BASE_URL/actions/getprofile/?lang=en") .auth(session).build() ).execute() + val code = resp.code val json = resp.body?.string() ?: throw Exception("Empty profile response") resp.close() + if (code in 500..599) throw BankServerException("Fahipay") val obj = JSONObject(json) val props = obj.optJSONObject("props") ?: JSONObject() return FahipayUserProfile( @@ -47,8 +50,10 @@ class FahipayAccountClient { Request.Builder().url("$BASE_URL/actions/getbalance/?lang=en") .auth(session).build() ).execute() + val code = resp.code val json = resp.body?.string() ?: return 0.0 resp.close() + if (code in 500..599) throw BankServerException("Fahipay") return try { val obj = JSONObject(json) if (obj.optBoolean("error")) 0.0 else obj.optDouble("balance", 0.0) diff --git a/app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt b/app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt index 6091916..dc9eef0 100644 --- a/app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt +++ b/app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt @@ -373,6 +373,7 @@ class MibLoginFlow(private val credentialStore: CredentialStore) { .build() val response = client.newCall(request).execute() if (response.code == 419) throw SessionExpiredException() + if (response.code in 500..599) throw sh.sar.basedbank.api.models.BankServerException("MIB") return response.body?.string() ?: throw IllegalStateException("Empty response body") } diff --git a/app/src/main/java/sh/sar/basedbank/api/models/BankModels.kt b/app/src/main/java/sh/sar/basedbank/api/models/BankModels.kt index edf673a..f00895f 100644 --- a/app/src/main/java/sh/sar/basedbank/api/models/BankModels.kt +++ b/app/src/main/java/sh/sar/basedbank/api/models/BankModels.kt @@ -1,5 +1,8 @@ package sh.sar.basedbank.api.models +/** Thrown by a bank API client when the server returns an HTTP 5xx response. */ +class BankServerException(val bankName: String) : Exception("Server error from $bankName") + /** * Unified account model used across all banks (MIB, BML, Fahipay, ...). * The [bank] field identifies which bank owns this account. diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt index 76cdd42..dfa64be 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt @@ -38,6 +38,8 @@ import okhttp3.RequestBody.Companion.toRequestBody import sh.sar.basedbank.BasedBankApp import sh.sar.basedbank.R import sh.sar.basedbank.api.bml.AuthExpiredException +import sh.sar.basedbank.api.models.BankServerException +import java.util.concurrent.ConcurrentLinkedQueue import sh.sar.basedbank.api.bml.BmlAccountClient import sh.sar.basedbank.api.bml.BmlContactsClient import sh.sar.basedbank.api.bml.BmlForeignLimitsClient @@ -547,14 +549,25 @@ fun applyNavLabelVisibility() { autoRefresh(store) } + private fun showConnectivityBanner(message: String) { + binding.connectivityBanner.text = message + binding.connectivityBanner.visibility = View.VISIBLE + } + + private fun hideConnectivityBanner() { + binding.connectivityBanner.visibility = View.GONE + } + private fun autoRefresh(store: CredentialStore) { val mibLoginIds = store.getMibLoginIds() val bmlLoginIds = store.getBmlLoginIds() val fahipayLoginIds = store.getFahipayLoginIds() if (mibLoginIds.isEmpty() && bmlLoginIds.isEmpty() && fahipayLoginIds.isEmpty()) return binding.refreshIndicator.visibility = View.VISIBLE + hideConnectivityBanner() lifecycleScope.launch { + val refreshErrors = ConcurrentLinkedQueue() // One async job per MIB login, all run in parallel val mibJobs = mibLoginIds.mapNotNull { loginId -> val creds = store.loadMibCredentials(loginId) ?: return@mapNotNull null @@ -568,7 +581,15 @@ fun applyNavLabelVisibility() { app.mibLoginFlows[loginId] = flow store.saveMibProfiles(loginId, flow.lastProfiles) accounts - } catch (_: Exception) { AccountCache.load(this@HomeActivity).filter { it.loginTag == "mib_$loginId" } } + } catch (e: java.io.IOException) { + refreshErrors.add("NO_INTERNET") + AccountCache.load(this@HomeActivity).filter { it.loginTag == "mib_$loginId" } + } catch (e: BankServerException) { + refreshErrors.add("SERVER:${e.bankName}") + AccountCache.load(this@HomeActivity).filter { it.loginTag == "mib_$loginId" } + } catch (_: Exception) { + AccountCache.load(this@HomeActivity).filter { it.loginTag == "mib_$loginId" } + } } } @@ -623,6 +644,14 @@ fun applyNavLabelVisibility() { tryRefresh() } } + } catch (e: java.io.IOException) { + refreshErrors.add("NO_INTERNET") + allAccounts += AccountCache.loadBml(this@HomeActivity, loginId) + .filter { it.profileId == profile.profileId } + } catch (e: BankServerException) { + refreshErrors.add("SERVER:${e.bankName}") + allAccounts += AccountCache.loadBml(this@HomeActivity, loginId) + .filter { it.profileId == profile.profileId } } catch (_: Exception) { allAccounts += AccountCache.loadBml(this@HomeActivity, loginId) .filter { it.profileId == profile.profileId } @@ -638,6 +667,12 @@ fun applyNavLabelVisibility() { val accounts = BmlAccountClient().fetchAccounts(session, loginTag) app.bmlSessions[loginId] = session allAccounts += accounts + } catch (e: java.io.IOException) { + refreshErrors.add("NO_INTERNET") + allAccounts += AccountCache.loadBml(this@HomeActivity, loginId) + } catch (e: BankServerException) { + refreshErrors.add("SERVER:${e.bankName}") + allAccounts += AccountCache.loadBml(this@HomeActivity, loginId) } catch (_: Exception) { allAccounts += AccountCache.loadBml(this@HomeActivity, loginId) } @@ -689,6 +724,12 @@ fun applyNavLabelVisibility() { app.fahipaySessions[loginId] = session AccountCache.saveFahipay(this@HomeActivity, loginId, accounts) accounts + } catch (e: java.io.IOException) { + refreshErrors.add("NO_INTERNET") + AccountCache.loadFahipay(this@HomeActivity, loginId) + } catch (e: BankServerException) { + refreshErrors.add("SERVER:${e.bankName}") + AccountCache.loadFahipay(this@HomeActivity, loginId) } catch (_: Exception) { AccountCache.loadFahipay(this@HomeActivity, loginId) } @@ -708,6 +749,17 @@ fun applyNavLabelVisibility() { viewModel.accounts.postValue((mibAccounts + bmlAccounts + fahipayAccounts).filterVisibleAccounts()) binding.refreshIndicator.visibility = View.GONE + val noInternet = refreshErrors.any { it == "NO_INTERNET" } + val serverErrors = refreshErrors.filter { it.startsWith("SERVER:") } + .map { it.removePrefix("SERVER:") }.distinct() + when { + noInternet -> showConnectivityBanner(getString(R.string.connectivity_no_internet)) + serverErrors.isNotEmpty() -> showConnectivityBanner( + getString(R.string.connectivity_server_error, serverErrors.joinToString(", ")) + ) + else -> hideConnectivityBanner() + } + for ((_, session) in app.bmlSessions) refreshBmlLimits(session) for ((loginId, session) in app.mibSessions) { val profiles = app.mibProfilesMap[loginId] ?: emptyList() diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 5f71137..3b76046 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -40,6 +40,20 @@ + + އެކައުންޓްތައް ލިބެން ހުރި ބެލެންސް + + + އިންޓަނެޓް ބައްލަވާ، ދެން ތިޖޫރީ ލޯޑް ކުރޭ + %s އާ ގުޅުމުގައި މައްސަލައެއް diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7040fc3..d01bebe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -302,4 +302,8 @@ Freeze Block No cards found + + + Please check your connection and reload Thijooree + Connectivity issue with %s