├── .gitignore
├── .idea
├── .name
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── mnsons
│ │ └── offlinebank
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── actions.json
│ ├── java
│ │ └── com
│ │ │ └── mnsons
│ │ │ └── offlinebank
│ │ │ ├── ApplicationClass.kt
│ │ │ ├── contracts
│ │ │ ├── BuyAirtimeContract.kt
│ │ │ ├── CheckBankBalanceContract.kt
│ │ │ ├── MoneyTransferContract.kt
│ │ │ ├── USSDResult.kt
│ │ │ ├── access
│ │ │ │ └── FetchAccessBankOtherBanksContract.kt
│ │ │ ├── base
│ │ │ │ └── BaseStringOnlyInputContract.kt
│ │ │ └── gtb
│ │ │ │ └── FetchGTBankOtherBanksFirstPageContract.kt
│ │ │ ├── data
│ │ │ └── cache
│ │ │ │ ├── impl
│ │ │ │ ├── BanksCache.kt
│ │ │ │ ├── CoreSharedPrefManager.kt
│ │ │ │ ├── SettingsCache.kt
│ │ │ │ └── TransactionsCache.kt
│ │ │ │ └── room
│ │ │ │ ├── Converters.kt
│ │ │ │ ├── DBClass.kt
│ │ │ │ ├── dao
│ │ │ │ ├── BankMenuDao.kt
│ │ │ │ ├── BanksDao.kt
│ │ │ │ └── TransactionsDao.kt
│ │ │ │ └── entities
│ │ │ │ ├── BankCacheModel.kt
│ │ │ │ ├── BankMenuCacheModel.kt
│ │ │ │ └── TransactionCacheModel.kt
│ │ │ ├── di
│ │ │ └── modules
│ │ │ │ ├── CacheModule.kt
│ │ │ │ └── UtilsModule.kt
│ │ │ ├── model
│ │ │ ├── BankMenuModel.kt
│ │ │ ├── Mappers.kt
│ │ │ ├── MoneyTransferModel.kt
│ │ │ ├── bank
│ │ │ │ └── BankModel.kt
│ │ │ ├── buyairtime
│ │ │ │ └── BuyAirtimeModel.kt
│ │ │ ├── transaction
│ │ │ │ ├── SectionedTransactionModel.kt
│ │ │ │ └── TransactionModel.kt
│ │ │ └── user
│ │ │ │ └── UserModel.kt
│ │ │ ├── ui
│ │ │ ├── commons
│ │ │ │ ├── adapters
│ │ │ │ │ ├── AccountBalanceAdapter.kt
│ │ │ │ │ ├── BankMenuAdapter.kt
│ │ │ │ │ ├── SelectionListener.kt
│ │ │ │ │ ├── bank
│ │ │ │ │ │ └── BankSelectionAdapter.kt
│ │ │ │ │ └── transaction
│ │ │ │ │ │ ├── SectionedTransactionsAdapter.kt
│ │ │ │ │ │ └── TransactionsAdapter.kt
│ │ │ │ ├── banks
│ │ │ │ │ └── BanksPopulator.kt
│ │ │ │ ├── base
│ │ │ │ │ └── BaseRoundedBottomSheetDialogFragment.kt
│ │ │ │ └── dialogs
│ │ │ │ │ ├── SelectBottomSheet.kt
│ │ │ │ │ ├── SelectFromMenuBottomSheet.kt
│ │ │ │ │ └── TransactionStatusDialog.kt
│ │ │ ├── main
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── accountbalance
│ │ │ │ │ ├── AccountBalanceFragment.kt
│ │ │ │ │ ├── AccountBalanceState.kt
│ │ │ │ │ └── AccountBalanceViewModel.kt
│ │ │ │ ├── buyairtime
│ │ │ │ │ ├── BuyAirtimeFragment.kt
│ │ │ │ │ ├── BuyAirtimeState.kt
│ │ │ │ │ └── BuyAirtimeViewModel.kt
│ │ │ │ ├── dashboard
│ │ │ │ │ ├── DashboardFragment.kt
│ │ │ │ │ ├── DashboardState.kt
│ │ │ │ │ └── DashboardViewModel.kt
│ │ │ │ ├── home
│ │ │ │ │ ├── HomeFragment.kt
│ │ │ │ │ ├── HomeViewModel.kt
│ │ │ │ │ └── menu
│ │ │ │ │ │ ├── MenuAction.kt
│ │ │ │ │ │ ├── MenuAdapter.kt
│ │ │ │ │ │ └── MenuItemClickListener.kt
│ │ │ │ ├── presentation
│ │ │ │ │ ├── MainState.kt
│ │ │ │ │ └── MainViewModel.kt
│ │ │ │ ├── profile
│ │ │ │ │ ├── addbank
│ │ │ │ │ │ ├── AddBankFragment.kt
│ │ │ │ │ │ ├── AddBankState.kt
│ │ │ │ │ │ └── AddBankViewModel.kt
│ │ │ │ │ └── userdetails
│ │ │ │ │ │ └── UserDetailsFragment.kt
│ │ │ │ └── transfermoney
│ │ │ │ │ ├── TransferMoneyFragment.kt
│ │ │ │ │ ├── TransferMoneyState.kt
│ │ │ │ │ └── TransferMoneyViewModel.kt
│ │ │ └── onboarding
│ │ │ │ ├── OnBoardingActivity.kt
│ │ │ │ ├── collectuserdetails
│ │ │ │ └── CollectUserDetailsFragment.kt
│ │ │ │ ├── done
│ │ │ │ └── AllDoneFragment.kt
│ │ │ │ ├── presentation
│ │ │ │ ├── OnBoardingState.kt
│ │ │ │ └── OnBoardingViewModel.kt
│ │ │ │ ├── selectuserbanks
│ │ │ │ ├── BankTransferMenuIndexer.kt
│ │ │ │ └── SelectUserBanksFragment.kt
│ │ │ │ └── setingup
│ │ │ │ └── SettingUpFragment.kt
│ │ │ └── utils
│ │ │ ├── BuyAirtimeUtil.kt
│ │ │ ├── CheckBalanceUtil.kt
│ │ │ ├── Constants.kt
│ │ │ ├── TransactionUtil.kt
│ │ │ ├── TransferMoneyUtil.kt
│ │ │ ├── ext
│ │ │ ├── AutoClearedValue.kt
│ │ │ ├── ContextExtensions.kt
│ │ │ ├── FragmentExtensions.kt
│ │ │ ├── LifecycleOwnerExtensions.kt
│ │ │ ├── ListExt.kt
│ │ │ ├── LiveDataExtension.kt
│ │ │ ├── RecyclerViewExtensions.kt
│ │ │ ├── StringExt.kt
│ │ │ └── ViewExtensions.kt
│ │ │ ├── flow
│ │ │ ├── PostExecutionThread.kt
│ │ │ └── PostExecutionThreadImpl.kt
│ │ │ └── livedata
│ │ │ └── NonNullObserver.kt
│ └── res
│ │ ├── anim
│ │ ├── slide_in_up.xml
│ │ └── slide_out_down.xml
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── activity.xml
│ │ ├── bottom_sheet_background_drawable.xml
│ │ ├── home.xml
│ │ ├── ic_access.xml
│ │ ├── ic_airtime.xml
│ │ ├── ic_airtime_purchase_icon.xml
│ │ ├── ic_airtime_purchase_summary.xml
│ │ ├── ic_bank_logo.xml
│ │ ├── ic_bank_not_selected.xml
│ │ ├── ic_bank_selected.xml
│ │ ├── ic_bank_transfer_icon.xml
│ │ ├── ic_bank_transfer_summary.xml
│ │ ├── ic_bankable_.xml
│ │ ├── ic_baseline_arrow_back_24.xml
│ │ ├── ic_bills.xml
│ │ ├── ic_dashboard_black_24dp.xml
│ │ ├── ic_dummy_bank_logo.png
│ │ ├── ic_dummy_user_avatar.png
│ │ ├── ic_ellipses.xml
│ │ ├── ic_filter.xml
│ │ ├── ic_gtbank.xml
│ │ ├── ic_home_black_24dp.xml
│ │ ├── ic_home_indicator.xml
│ │ ├── ic_internet.xml
│ │ ├── ic_item_bank_background.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_notifications_black_24dp.xml
│ │ ├── ic_official_building.xml
│ │ ├── ic_profile_circle.xml
│ │ ├── ic_search.xml
│ │ ├── ic_transaction_failure.xml
│ │ ├── ic_transaction_success.xml
│ │ ├── ic_uba.xml
│ │ ├── ic_zenith.xml
│ │ ├── profile.xml
│ │ └── transfer.xml
│ │ ├── font
│ │ ├── maisonneue_bold.ttf
│ │ ├── maisonneue_bold_italic.ttf
│ │ ├── maisonneue_book.ttf
│ │ ├── maisonneue_book_italic.ttf
│ │ ├── maisonneue_demi.ttf
│ │ ├── maisonneue_demi_italic.ttf
│ │ ├── maisonneue_light.ttf
│ │ ├── maisonneue_light_italic.ttf
│ │ ├── maisonneue_medium.ttf
│ │ └── maisonneue_medium_italic.ttf
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_on_boarding.xml
│ │ ├── fragment_account_balance.xml
│ │ ├── fragment_add_bank.xml
│ │ ├── fragment_all_done.xml
│ │ ├── fragment_buy_airtime.xml
│ │ ├── fragment_collect_user_details.xml
│ │ ├── fragment_dashboard.xml
│ │ ├── fragment_home.xml
│ │ ├── fragment_notifications.xml
│ │ ├── fragment_select_user_banks.xml
│ │ ├── fragment_setting_up.xml
│ │ ├── fragment_transfer_money.xml
│ │ ├── fragment_user_details.xml
│ │ ├── item_account_balance.xml
│ │ ├── item_bank.xml
│ │ ├── item_sectioned_transaction.xml
│ │ ├── item_transaction.xml
│ │ ├── layout_buy_airtime_details.xml
│ │ ├── layout_enter_ussd_pin.xml
│ │ ├── layout_save_as_beneficiary.xml
│ │ ├── layout_select_bank.xml
│ │ ├── layout_select_bank_bottom_sheet.xml
│ │ ├── layout_successful_transaction.xml
│ │ ├── layout_transaction_outcome.xml
│ │ ├── layout_transfer_money_details.xml
│ │ └── menu_item_card.xml
│ │ ├── menu
│ │ └── bottom_nav_menu.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── navigation
│ │ ├── mobile_navigation.xml
│ │ └── onboarding_navigation.xml
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values-v23
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── com
│ └── mnsons
│ └── offlinebank
│ └── ExampleUnitTest.kt
├── app_icon.png
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── readme.md
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | OfflineBank
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/mnsons/offlinebank/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.mnsons.offlinebank", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
17 |
18 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/assets/actions.json:
--------------------------------------------------------------------------------
1 | {
2 | "Transfer Money": [
3 | {
4 | "bankName": "GT Bank",
5 | "bankRootCode": "*737#",
6 | "hoverActionId": "",
7 | "setup": [
8 | "Dial RootCode",
9 | "Select Option 5"
10 | ],
11 | "steps": [
12 | "Enter Amount",
13 | "Enter Account Number",
14 | "Display Menu Select Recipient Bank",
15 | "Enter Pin",
16 | "Display Menu Save Beneficiary"
17 | ]
18 | },
19 | {
20 | "bankName": "Union Bank",
21 | "bankRootCode": "*826#",
22 | "hoverActionId": "",
23 | "setup": [
24 | "Dial RootCode",
25 | "Select Option 5"
26 | ],
27 | "steps": [
28 | "Enter Amount",
29 | "Enter Account Number",
30 | "Display Menu Select Recipient Bank",
31 | "Enter Pin",
32 | "Display Menu Save Beneficiary"
33 | ]
34 | }
35 | ],
36 | "Buy Airtime": [
37 | {
38 | "bankName": "GT Bank",
39 | "bankRootCode": "*737#",
40 | "hoverActionId": "",
41 | "setup": [
42 | "Dial RootCode",
43 | "Select Option 2"
44 | ],
45 | "steps": [
46 | "Enter Phone Number",
47 | "Enter Amount",
48 | "Enter Pin",
49 | "Display Menu Save Beneficiary"
50 | ]
51 | },
52 | {
53 | "bankName": "Union Bank",
54 | "bankRootCode": "*826#",
55 | "hoverActionId": "",
56 | "setup": [
57 | "Dial RootCode",
58 | "Select Option 2"
59 | ],
60 | "steps": [
61 | "Enter Phone Number",
62 | "Enter Amount",
63 | "Enter Pin",
64 | "Display Menu Save Beneficiary"
65 | ]
66 | }
67 | ]
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ApplicationClass.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank
17 |
18 | import android.app.Application
19 | import dagger.hilt.android.HiltAndroidApp
20 |
21 |
22 | @HiltAndroidApp
23 | class ApplicationClass : Application() {
24 |
25 | override fun onCreate() {
26 | super.onCreate()
27 |
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/contracts/BuyAirtimeContract.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.contracts
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 | import androidx.activity.result.contract.ActivityResultContract
8 | import com.hover.sdk.api.HoverParameters
9 | import com.mnsons.offlinebank.R
10 | import com.mnsons.offlinebank.model.buyairtime.BuyAirtimeModel
11 | import com.mnsons.offlinebank.utils.Constants
12 | import java.security.InvalidParameterException
13 |
14 | class BuyAirtimeContract : ActivityResultContract>() {
15 |
16 | private lateinit var context: Context
17 |
18 | override fun createIntent(context: Context, input: BuyAirtimeModel?): Intent {
19 | this.context = context
20 |
21 | input?.let {
22 | return HoverParameters.Builder(context)
23 | .request(it.actionId)
24 | .extra(Constants.EXTRA_AMOUNT, it.amount)
25 | .extra(Constants.EXTRA_PHONE_NUMBER, it.phoneNumber)
26 | .style(R.style.Theme_Hover)
27 | .buildIntent()
28 | } ?: throw InvalidParameterException("Please enter the correct parameters")
29 | }
30 |
31 | override fun parseResult(resultCode: Int, intent: Intent?): USSDResult {
32 | if (resultCode != Activity.RESULT_OK) return USSDResult(context.getString(R.string.error_index_))
33 | if (intent == null) return USSDResult(context.getString(R.string.error_index_))
34 |
35 | val sessionTextArr: Array =
36 | intent.getStringArrayExtra("session_messages") ?: emptyArray()
37 | sessionTextArr.forEach {
38 | Log.d(javaClass.simpleName, it)
39 | }
40 | Log.d(javaClass.simpleName, intent.toString())
41 |
42 | return if (sessionTextArr.isNotEmpty()) {
43 | return parseSessionMessage(sessionTextArr.last())
44 | } else {
45 | USSDResult(context.getString(R.string.error_index_))
46 | }
47 | }
48 |
49 | private fun parseSessionMessage(text: String): USSDResult {
50 | return when {
51 | text.contains("processing", ignoreCase = true) or text.contains(
52 | "successful",
53 | ignoreCase = true
54 | ) -> {
55 | USSDResult(context.getString(R.string.transaction_successful), Unit)
56 | }
57 | text.contains("No Account is funded") -> {
58 | USSDResult(context.getString(R.string.transaction_n0t_successful))
59 | }
60 | else -> {
61 | USSDResult(context.getString(R.string.error_index_))
62 | }
63 | }
64 | }
65 |
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/contracts/CheckBankBalanceContract.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.contracts
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 | import androidx.activity.result.contract.ActivityResultContract
8 | import com.hover.sdk.api.HoverParameters
9 | import com.mnsons.offlinebank.R
10 | import java.security.InvalidParameterException
11 |
12 | class CheckBankBalanceContract : ActivityResultContract>() {
13 |
14 | private lateinit var context: Context
15 |
16 | override fun createIntent(context: Context, input: String?): Intent {
17 | this.context = context
18 | input?.let {
19 | return HoverParameters.Builder(context)
20 | .request(it)
21 | .style(R.style.Theme_Hover)
22 | .buildIntent()
23 | } ?: throw InvalidParameterException("Please enter the correct parameters")
24 | }
25 |
26 | override fun parseResult(resultCode: Int, intent: Intent?): USSDResult {
27 | if (resultCode != Activity.RESULT_OK) return USSDResult(context.getString(R.string.error_index_))
28 | if (intent == null) return USSDResult(context.getString(R.string.error_index_))
29 |
30 | val sessionTextArr: Array =
31 | intent.getStringArrayExtra("session_messages") ?: emptyArray()
32 | sessionTextArr.forEach {
33 | Log.d(CheckBankBalanceContract::class.java.simpleName, it)
34 | }
35 | Log.d(CheckBankBalanceContract::class.java.simpleName, intent.toString())
36 |
37 | return if (sessionTextArr.isNotEmpty()) {
38 | parseSessionMessage(sessionTextArr.last())
39 | } else {
40 | USSDResult(context.getString(R.string.error_index_))
41 | }
42 | }
43 |
44 | private fun parseSessionMessage(text: String): USSDResult {
45 | return when {
46 | text.contains("NAME") or text.contains("BVN") -> {
47 | USSDResult(context.getString(R.string.transaction_successful), text)
48 | }
49 | text.contains("No Account is funded") -> {
50 | USSDResult(context.getString(R.string.transaction_n0t_successful))
51 | }
52 | else -> {
53 | USSDResult(context.getString(R.string.error_index_))
54 | }
55 | }
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/contracts/MoneyTransferContract.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.contracts
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 | import androidx.activity.result.contract.ActivityResultContract
8 | import com.hover.sdk.api.HoverParameters
9 | import com.mnsons.offlinebank.R
10 | import com.mnsons.offlinebank.model.MoneyTransferModel
11 | import java.security.InvalidParameterException
12 |
13 |
14 | class MoneyTransferContract : ActivityResultContract>() {
15 |
16 | private lateinit var context: Context
17 |
18 | override fun createIntent(context: Context, input: MoneyTransferModel?): Intent {
19 | this.context = context
20 | input?.let {
21 | return HoverParameters.Builder(context)
22 | .request(it.actionId)
23 | .extra("amount", it.amount)
24 | .extra("bankAccountNumber", it.accountNumber)
25 | .extra("recipientBank", it.recipientBank.toString())
26 | .style(R.style.Theme_Hover)
27 | .buildIntent()
28 | } ?: throw InvalidParameterException()
29 | }
30 |
31 | override fun parseResult(resultCode: Int, intent: Intent?): USSDResult {
32 | if (resultCode != Activity.RESULT_OK) return USSDResult(context.getString(R.string.transaction_n0t_successful))
33 | if (intent == null) return USSDResult(context.getString(R.string.transaction_n0t_successful))
34 |
35 | val sessionTextArr: Array =
36 | intent.getStringArrayExtra("session_messages") ?: emptyArray()
37 | sessionTextArr.forEach {
38 | Log.d(MoneyTransferContract::class.java.simpleName, it)
39 | }
40 | Log.d(MoneyTransferContract::class.java.simpleName, intent.toString())
41 |
42 | return if (sessionTextArr.isNotEmpty()) {
43 | return parseSessionMessage(sessionTextArr.last())
44 | } else {
45 | USSDResult(context.getString(R.string.transaction_n0t_successful))
46 | }
47 | }
48 |
49 |
50 | private fun parseSessionMessage(text: String): USSDResult {
51 | return when {
52 | text.contains("successful") or text.contains("beneficiary") or text.contains("success") -> {
53 | USSDResult(context.getString(R.string.transaction_successful), Unit)
54 | }
55 | text.contains("No Account is funded") -> {
56 | USSDResult(context.getString(R.string.transaction_n0t_successful))
57 | }
58 | else -> {
59 | USSDResult(context.getString(R.string.error_index_))
60 | }
61 | }
62 | }
63 |
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/contracts/USSDResult.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.contracts
2 |
3 | data class USSDResult(
4 | val message: String? = null,
5 | var data: T? = null
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/contracts/access/FetchAccessBankOtherBanksContract.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.contracts.access
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import androidx.activity.result.contract.ActivityResultContract
7 | import com.hover.sdk.api.HoverParameters
8 | import com.mnsons.offlinebank.R
9 | import com.mnsons.offlinebank.contracts.USSDResult
10 | import com.mnsons.offlinebank.model.BankMenuModel
11 |
12 | class FetchAccessBankOtherBanksContract :
13 | ActivityResultContract>>() {
14 |
15 | private lateinit var context: Context
16 |
17 | override fun createIntent(context: Context, input: Unit?): Intent {
18 | this.context = context
19 | return HoverParameters.Builder(context)
20 | .request("51dfe430")
21 | .style(R.style.Theme_Hover)
22 | .buildIntent()
23 | }
24 |
25 | override fun parseResult(resultCode: Int, intent: Intent?): USSDResult> {
26 | if (resultCode != Activity.RESULT_OK) return USSDResult(context.getString(R.string.error_fetching_))
27 | if (intent == null) return USSDResult(context.getString(R.string.error_fetching_))
28 |
29 | val sessionTextArr: Array =
30 | intent.getStringArrayExtra("session_messages") ?: emptyArray()
31 | return if (sessionTextArr.isNotEmpty()) {
32 | return parseResult(sessionTextArr.last())
33 | } else {
34 | USSDResult(context.getString(R.string.error_fetching_bank_acc_balance))
35 | }
36 | }
37 |
38 | private fun parseResult(result: String): USSDResult> {
39 | val list = mutableListOf()
40 | result.lines().forEach {
41 | //Could be improved with a regex to add only strings that match gtbanks menu style
42 | if ((it.contains("more", true) or it.contains("Please", true)).not()) {
43 | val data = it.split(">")
44 | if(data.size > 1){
45 | list.add(BankMenuModel(data[1], data[0].toInt()))
46 | }
47 | }
48 | }
49 | return USSDResult(data = list)
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/contracts/base/BaseStringOnlyInputContract.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.contracts.base
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import androidx.activity.result.contract.ActivityResultContract
6 | import com.hover.sdk.api.HoverParameters
7 | import java.security.InvalidParameterException
8 |
9 | abstract class BaseStringOnlyInputContract : ActivityResultContract() {
10 |
11 | override fun createIntent(context: Context, input: String?): Intent {
12 | input?.let {
13 | return HoverParameters.Builder(context)
14 | .request(it)
15 | .buildIntent()
16 | } ?: throw InvalidParameterException("Please enter the correct parameters")
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/contracts/gtb/FetchGTBankOtherBanksFirstPageContract.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.contracts.gtb
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import androidx.activity.result.contract.ActivityResultContract
7 | import com.hover.sdk.api.HoverParameters
8 | import com.mnsons.offlinebank.R
9 | import com.mnsons.offlinebank.contracts.USSDResult
10 | import com.mnsons.offlinebank.model.BankMenuModel
11 |
12 | class FetchGTBankOtherBanksFirstPageContract :
13 | ActivityResultContract>>() {
14 |
15 | private lateinit var context: Context
16 |
17 | override fun createIntent(context: Context, input: Unit?): Intent {
18 | this.context = context
19 | return HoverParameters.Builder(context)
20 | .request("896119e1")
21 | .style(R.style.Theme_Hover)
22 | .buildIntent()
23 | }
24 |
25 | override fun parseResult(resultCode: Int, intent: Intent?): USSDResult> {
26 | if (resultCode != Activity.RESULT_OK) return USSDResult(context.getString(R.string.error_fetching_bank_acc_balance))
27 | if (intent == null) return USSDResult(context.getString(R.string.error_fetching_bank_acc_balance))
28 |
29 | val sessionTextArr: Array =
30 | intent.getStringArrayExtra("session_messages") ?: emptyArray()
31 | return if (sessionTextArr.isNotEmpty()) {
32 | return parseResult(sessionTextArr.last())
33 | } else {
34 | USSDResult(context.getString(R.string.error_fetching_bank_acc_balance))
35 | }
36 | }
37 |
38 | private fun parseResult(result: String): USSDResult> {
39 | val list = mutableListOf()
40 | result.lines().forEach {
41 | //Could be improved with a regex to add only strings that match gtbanks menu style
42 | if ((it.contains("more", true) or it.contains("Please", true)).not()) {
43 | val data = it.split(".")
44 | if(data.size > 1){
45 | list.add(BankMenuModel(data[1], data[0].toInt()))
46 | }
47 | }
48 | }
49 | return USSDResult(data = list)
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/impl/BanksCache.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.data.cache.impl
2 |
3 | import com.mnsons.offlinebank.data.cache.room.dao.BankMenuDao
4 | import com.mnsons.offlinebank.data.cache.room.dao.BanksDao
5 | import com.mnsons.offlinebank.data.cache.room.entities.BankCacheModel
6 | import com.mnsons.offlinebank.data.cache.room.entities.BankMenuCacheModel
7 | import com.mnsons.offlinebank.model.BankMenuModel
8 | import kotlinx.coroutines.flow.Flow
9 | import kotlinx.coroutines.flow.flow
10 | import javax.inject.Inject
11 |
12 | class BanksCache @Inject constructor(
13 | private val banksDao: BanksDao,
14 | private val bankMenuDao: BankMenuDao
15 | ) {
16 |
17 | suspend fun saveBanks(list: List) {
18 | banksDao.insert(list)
19 | }
20 |
21 | suspend fun saveBank(bank: BankCacheModel) {
22 | banksDao.insert(bank)
23 | }
24 |
25 | suspend fun saveBankMenuData(bankId: Int, list: List) {
26 | var bankMenu = bankMenuDao.getBankMenu(bankId)
27 | if (bankMenu != null) {
28 | val menuItems = bankMenu.menuItems.toMutableList()
29 | menuItems.addAll(list)
30 | bankMenuDao.insert(bankMenu.copy(menuItems = menuItems))
31 | } else {
32 | bankMenu = BankMenuCacheModel(bankId, list)
33 | bankMenuDao.insert(bankMenu)
34 | }
35 | }
36 |
37 | fun getBankMenu(id: Int): Flow> {
38 | return flow {
39 | //emit(bankMenuDao.getBankMenu(id)?.menuItems ?: emptyList())
40 | }
41 | }
42 |
43 | fun getBanks(): Flow> {
44 | return banksDao.getAllBanks()
45 | }
46 |
47 | suspend fun isCacheEmpty(): Boolean {
48 | return banksDao.getAllBanksCount() > 0
49 | }
50 |
51 | suspend fun clearBanks() {
52 | return banksDao.deleteAllBanks()
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/impl/CoreSharedPrefManager.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.data.cache.impl
17 |
18 | import android.content.Context
19 | import android.content.SharedPreferences
20 |
21 | open class CoreSharedPrefManager(context: Context) {
22 |
23 | private val sharedPreferences: SharedPreferences = context.getSharedPreferences(
24 | APP_NAME,
25 | Context.MODE_PRIVATE
26 | )
27 |
28 | private val sharedPreferencesEditor: SharedPreferences.Editor = sharedPreferences.edit()
29 |
30 | private fun delete(key: String) {
31 | if (sharedPreferences.contains(key)) {
32 | sharedPreferencesEditor.remove(key).commit()
33 | }
34 | }
35 |
36 | protected fun savePref(key: String, value: Any?) {
37 | delete(key)
38 |
39 | when {
40 | value is Boolean -> sharedPreferencesEditor.putBoolean(key, (value as Boolean?)!!)
41 | value is Int -> sharedPreferencesEditor.putInt(key, (value as Int?)!!)
42 | value is Float -> sharedPreferencesEditor.putFloat(key, (value as Float?)!!)
43 | value is Long -> sharedPreferencesEditor.putLong(key, (value as Long?)!!)
44 | value is String -> sharedPreferencesEditor.putString(key, value as String?)
45 | value is Enum<*> -> sharedPreferencesEditor.putString(key, value.toString())
46 | value != null -> throw RuntimeException("Attempting to save non-primitive preference")
47 | }
48 |
49 | sharedPreferencesEditor.commit()
50 | }
51 |
52 | protected fun getPref(key: String): T {
53 | return sharedPreferences.all[key] as T
54 | }
55 |
56 | protected fun getPref(key: String, defValue: T?): T? {
57 | val returnValue = sharedPreferences.all[key] as T
58 | return returnValue ?: defValue
59 | }
60 |
61 | fun clearAll() {
62 | sharedPreferencesEditor.clear()
63 | }
64 |
65 |
66 | companion object {
67 | const val APP_NAME = "BANKABLE"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/impl/SettingsCache.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.data.cache.impl
2 |
3 | import android.content.Context
4 | import dagger.hilt.android.qualifiers.ApplicationContext
5 | import javax.inject.Inject
6 |
7 | class SettingsCache @Inject constructor(@ApplicationContext val context: Context) : CoreSharedPrefManager(context) {
8 |
9 | fun fetchUserFirstName(): String? {
10 | return getPref(USER_FIRST_NAME)
11 | }
12 |
13 | fun fetchUserLastName(): String? {
14 | return getPref(USER_LAST_NAME)
15 | }
16 |
17 | fun fetchUserPhone(): String? {
18 | return getPref(USER_PHONE)
19 | }
20 |
21 | fun setUserPhone(phone: String) {
22 | savePref(USER_PHONE, phone)
23 | }
24 |
25 | fun setUserLastName(userName: String) {
26 | savePref(USER_LAST_NAME, userName)
27 | }
28 |
29 | fun setUserFirstName(userName: String) {
30 | savePref(USER_FIRST_NAME, userName)
31 | }
32 |
33 | fun userDataExists():Boolean{
34 | return fetchUserFirstName() != null && fetchUserLastName()!=null && fetchUserPhone()!=null
35 | }
36 |
37 | companion object {
38 | private const val USER_FIRST_NAME = "USER_FIRST_NAME"
39 | private const val USER_LAST_NAME = "USER_LAST_NAME"
40 | private const val USER_PHONE = "USER_PHONE"
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/impl/TransactionsCache.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.data.cache.impl
2 |
3 | import com.mnsons.offlinebank.data.cache.room.dao.TransactionsDao
4 | import com.mnsons.offlinebank.model.mapInto
5 | import com.mnsons.offlinebank.model.toTransactionCacheModel
6 | import com.mnsons.offlinebank.model.toTransactionModel
7 | import com.mnsons.offlinebank.model.transaction.TransactionModel
8 | import kotlinx.coroutines.flow.Flow
9 | import kotlinx.coroutines.flow.map
10 | import javax.inject.Inject
11 |
12 | class TransactionsCache @Inject constructor(
13 | private val transactionsDao: TransactionsDao
14 | ) {
15 |
16 | suspend fun saveTransactions(list: List) {
17 | transactionsDao.insert(list.mapInto {
18 | it.toTransactionCacheModel()
19 | })
20 | }
21 |
22 | suspend fun saveTransaction(transaction: TransactionModel) {
23 | transactionsDao.insert(transaction.toTransactionCacheModel())
24 | }
25 |
26 | fun getTransaction(): Flow> {
27 | return transactionsDao.getAllTransactions().map {
28 | it.mapInto {
29 | it.toTransactionModel()
30 | }
31 | }
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/room/Converters.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.data.cache.room
17 |
18 | import androidx.room.TypeConverter
19 | import com.google.gson.Gson
20 | import com.google.gson.reflect.TypeToken
21 | import com.mnsons.offlinebank.model.BankMenuModel
22 |
23 |
24 | class Converters {
25 |
26 | @TypeConverter
27 | fun fromBankMenuModelString(value: String?): List? {
28 | val listType = object : TypeToken?>() {
29 | }.type
30 | return Gson().fromJson(value, listType)
31 | }
32 |
33 | @TypeConverter
34 | fun fromBankMenuModelList(data: List?): String {
35 | val gson = Gson()
36 | return gson.toJson(data)
37 | }
38 |
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/room/DBClass.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.data.cache.room
17 |
18 |
19 | import androidx.room.Database
20 | import androidx.room.RoomDatabase
21 | import androidx.room.TypeConverters
22 | import com.mnsons.offlinebank.data.cache.room.dao.BankMenuDao
23 | import com.mnsons.offlinebank.data.cache.room.dao.BanksDao
24 | import com.mnsons.offlinebank.data.cache.room.dao.TransactionsDao
25 | import com.mnsons.offlinebank.data.cache.room.entities.BankCacheModel
26 | import com.mnsons.offlinebank.data.cache.room.entities.BankMenuCacheModel
27 | import com.mnsons.offlinebank.data.cache.room.entities.TransactionCacheModel
28 |
29 |
30 | @Database(
31 | entities = [BankCacheModel::class, BankMenuCacheModel::class, TransactionCacheModel::class],
32 | version = 1, exportSchema = false
33 | )
34 | @TypeConverters(Converters::class)
35 | abstract class DBClass : RoomDatabase() {
36 |
37 | abstract fun banksDao(): BanksDao
38 |
39 | abstract fun bankMenuDao(): BankMenuDao
40 |
41 | abstract fun transactionsDao(): TransactionsDao
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/room/dao/BankMenuDao.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.data.cache.room.dao
17 |
18 | import androidx.room.Dao
19 | import androidx.room.Insert
20 | import androidx.room.OnConflictStrategy
21 | import androidx.room.Query
22 | import com.mnsons.offlinebank.data.cache.room.entities.BankMenuCacheModel
23 |
24 | @Dao
25 | interface BankMenuDao {
26 |
27 | @Insert(onConflict = OnConflictStrategy.REPLACE)
28 | suspend fun insert(menus: List)
29 |
30 | @Insert(onConflict = OnConflictStrategy.REPLACE)
31 | suspend fun insert(menu: BankMenuCacheModel)
32 |
33 | @Query("SELECT * FROM BANKS_MENU where id=:id")
34 | suspend fun getBankMenu(id: Int): BankMenuCacheModel?
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/room/dao/BanksDao.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.data.cache.room.dao
17 |
18 | import androidx.room.Dao
19 | import androidx.room.Insert
20 | import androidx.room.OnConflictStrategy
21 | import androidx.room.Query
22 | import com.mnsons.offlinebank.data.cache.room.entities.BankCacheModel
23 | import kotlinx.coroutines.flow.Flow
24 |
25 | @Dao
26 | interface BanksDao {
27 |
28 | @Insert(onConflict = OnConflictStrategy.REPLACE)
29 | suspend fun insert(banks: List)
30 |
31 | @Insert(onConflict = OnConflictStrategy.REPLACE)
32 | suspend fun insert(bank: BankCacheModel)
33 |
34 | @Query("SELECT * FROM BANKS")
35 | fun getAllBanks(): Flow>
36 |
37 | @Query("SELECT COUNT(*) FROM BANKS")
38 | suspend fun getAllBanksCount(): Int
39 |
40 | @Query("DELETE FROM BANKS WHERE id =:id")
41 | suspend fun deleteBank(id: Int)
42 |
43 | @Query("DELETE FROM BANKS")
44 | suspend fun deleteAllBanks()
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/room/dao/TransactionsDao.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.data.cache.room.dao
17 |
18 | import androidx.room.Dao
19 | import androidx.room.Insert
20 | import androidx.room.OnConflictStrategy
21 | import androidx.room.Query
22 | import com.mnsons.offlinebank.data.cache.room.entities.BankCacheModel
23 | import com.mnsons.offlinebank.data.cache.room.entities.TransactionCacheModel
24 | import kotlinx.coroutines.flow.Flow
25 |
26 | @Dao
27 | interface TransactionsDao {
28 |
29 | @Insert(onConflict = OnConflictStrategy.REPLACE)
30 | suspend fun insert(transactions: List)
31 |
32 | @Insert(onConflict = OnConflictStrategy.REPLACE)
33 | suspend fun insert(transaction: TransactionCacheModel)
34 |
35 | @Query("SELECT * FROM TRANSACTIONS")
36 | fun getAllTransactions(): Flow>
37 |
38 | @Query("DELETE FROM TRANSACTIONS WHERE id =:id")
39 | suspend fun deleteTransactions(id: Int)
40 |
41 | @Query("DELETE FROM TRANSACTIONS")
42 | suspend fun deleteAllTransactions()
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/room/entities/BankCacheModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.data.cache.room.entities
17 |
18 | import androidx.room.Entity
19 | import androidx.room.PrimaryKey
20 |
21 | @Entity(tableName = "BANKS")
22 | data class BankCacheModel(
23 | @PrimaryKey
24 | var id: Int,
25 | var name: Int,
26 | var lastKnownBalance: Long,
27 | var sortCode: String,
28 | var imageURL: Int
29 | ) {
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/room/entities/BankMenuCacheModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.data.cache.room.entities
17 |
18 | import androidx.room.Entity
19 | import androidx.room.PrimaryKey
20 | import com.mnsons.offlinebank.model.BankMenuModel
21 |
22 | @Entity(tableName = "BANKS_MENU")
23 | data class BankMenuCacheModel(
24 | @PrimaryKey
25 | var id: Int,
26 | var menuItems: List
27 | )
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/data/cache/room/entities/TransactionCacheModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.data.cache.room.entities
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import com.mnsons.offlinebank.model.transaction.TransactionStatus
6 | import com.mnsons.offlinebank.model.transaction.TransactionType
7 |
8 | @Entity(tableName = "TRANSACTIONS")
9 | data class TransactionCacheModel(
10 | @PrimaryKey
11 | var id: String,
12 | var amount: Double,
13 | var timestamp: Long,
14 | var type: String,
15 | var status: Int,
16 | var bank: String
17 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/di/modules/CacheModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.di.modules
17 |
18 | import android.content.Context
19 | import androidx.room.Room
20 | import com.mnsons.offlinebank.data.cache.room.DBClass
21 | import com.mnsons.offlinebank.data.cache.room.dao.BankMenuDao
22 | import com.mnsons.offlinebank.data.cache.room.dao.BanksDao
23 | import com.mnsons.offlinebank.data.cache.room.dao.TransactionsDao
24 | import dagger.Module
25 | import dagger.Provides
26 | import dagger.hilt.InstallIn
27 | import dagger.hilt.android.components.ApplicationComponent
28 | import dagger.hilt.android.qualifiers.ApplicationContext
29 | import javax.inject.Singleton
30 |
31 | @Module
32 | @InstallIn(ApplicationComponent::class)
33 | class CacheModule {
34 |
35 |
36 | @Singleton
37 | @Provides
38 | fun providesBanksDao(dBClass: DBClass): BanksDao {
39 | return dBClass.banksDao()
40 | }
41 |
42 | @Singleton
43 | @Provides
44 | fun providesTransactionsDao(dBClass: DBClass): TransactionsDao {
45 | return dBClass.transactionsDao()
46 | }
47 |
48 |
49 | @Singleton
50 | @Provides
51 | fun providesBankMenuDao(dBClass: DBClass): BankMenuDao {
52 | return dBClass.bankMenuDao()
53 | }
54 |
55 | @Singleton
56 | @Provides
57 | fun providesDB(@ApplicationContext context: Context): DBClass {
58 | return Room.databaseBuilder(
59 | context.applicationContext,
60 | DBClass::class.java, "mandsons_database"
61 | ).fallbackToDestructiveMigration().build()
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/di/modules/UtilsModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.di.modules
17 |
18 | import com.mnsons.offlinebank.utils.flow.PostExecutionThread
19 | import com.mnsons.offlinebank.utils.flow.PostExecutionThreadImpl
20 | import dagger.Binds
21 | import dagger.Module
22 | import dagger.hilt.InstallIn
23 | import dagger.hilt.android.components.ApplicationComponent
24 |
25 | @Module
26 | @InstallIn(ApplicationComponent::class)
27 | abstract class UtilsModule {
28 |
29 | @Binds
30 | abstract fun bindsPostExecutionThread(postExecutionThread: PostExecutionThreadImpl): PostExecutionThread
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/model/BankMenuModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.model
2 |
3 | data class BankMenuModel(
4 | val bankName: String,
5 | var id: Int
6 | )
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/model/Mappers.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.model
2 |
3 | import com.mnsons.offlinebank.data.cache.room.entities.BankCacheModel
4 | import com.mnsons.offlinebank.data.cache.room.entities.TransactionCacheModel
5 | import com.mnsons.offlinebank.model.bank.BankModel
6 | import com.mnsons.offlinebank.model.transaction.TransactionModel
7 | import com.mnsons.offlinebank.model.transaction.TransactionStatus
8 | import com.mnsons.offlinebank.model.transaction.TransactionType
9 | import java.util.*
10 |
11 | fun List.mapInto(function: (input: I) -> O): List {
12 | return map {
13 | function.invoke(it)
14 | }
15 | }
16 |
17 | fun TransactionModel.toTransactionCacheModel(): TransactionCacheModel {
18 | return TransactionCacheModel(
19 | UUID.randomUUID().toString(),
20 | amount,
21 | timestamp,
22 | type.value,
23 | status.value,
24 | bank
25 | )
26 | }
27 |
28 | fun TransactionCacheModel.toTransactionModel(): TransactionModel {
29 | return TransactionModel(
30 | amount,
31 | timestamp,
32 | TransactionType.fromString(type),
33 | TransactionStatus.fromString(status),
34 | bank
35 | )
36 | }
37 |
38 | fun BankModel.toBankCacheModel(): BankCacheModel {
39 | return BankCacheModel(
40 | id,
41 | bankName,
42 | lastKnownBalance,
43 | sortCode,
44 | bankLogo
45 | )
46 | }
47 |
48 | fun BankCacheModel.toBankModel(): BankModel {
49 | return BankModel(
50 | name,
51 | id,
52 | lastKnownBalance,
53 | sortCode,
54 | imageURL
55 | )
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/model/MoneyTransferModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.model
2 |
3 | data class MoneyTransferModel(
4 | val recipientBank: Int? = null,
5 | val actionId: String? = null,
6 | val amount: String? = null,
7 | val accountNumber: String? = null,
8 | val bank: String? = null
9 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/model/bank/BankModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.model.bank
2 |
3 | import android.os.Parcelable
4 | import androidx.annotation.DrawableRes
5 | import kotlinx.android.parcel.Parcelize
6 |
7 | @Parcelize
8 | data class BankModel(
9 | val bankName: Int,
10 | var id: Int,
11 | var lastKnownBalance: Long,
12 | var sortCode: String,
13 | @DrawableRes
14 | val bankLogo: Int
15 | ) : Parcelable
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/model/buyairtime/BuyAirtimeModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.model.buyairtime
2 |
3 | data class BuyAirtimeModel(
4 | val actionId: String? = null,
5 | val amount: String? = null,
6 | val phoneNumber: String? = null,
7 | val bank:String? = null
8 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/model/transaction/SectionedTransactionModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.model.transaction
2 |
3 | data class SectionedTransactionModel(
4 | val day: String,
5 | val transactions: MutableList
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/model/transaction/TransactionModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.model.transaction
2 |
3 | data class TransactionModel(
4 | val amount: Double,
5 | val timestamp: Long,
6 | val type: TransactionType,
7 | val status: TransactionStatus,
8 | val bank: String
9 | )
10 |
11 | enum class TransactionType(val value: String) {
12 | BANK_TRANSFER("Bank Transfer"),
13 | AIRTIME_PURCHASE("Airtime Purchase"),
14 | BALANCE_CHECK("Account Balance Check");
15 |
16 | companion object {
17 | @JvmStatic
18 | fun fromString(category: String): TransactionType =
19 | values().find { value -> value.value == category } ?: AIRTIME_PURCHASE
20 | }
21 | }
22 |
23 | enum class TransactionStatus(val value: Int) {
24 | SUCCESS(1),
25 | PENDING(2),
26 | FAILED(3);
27 |
28 | companion object {
29 | @JvmStatic
30 | fun fromString(status: Int): TransactionStatus =
31 | values().find { value -> value.value == status } ?: PENDING
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/model/user/UserModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.model.user
2 |
3 | import com.mnsons.offlinebank.model.bank.BankModel
4 |
5 | data class UserModel(
6 | val firstName: String,
7 | val lastName: String,
8 | val phoneNumber: String,
9 | val banks: List
10 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/adapters/AccountBalanceAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.adapters
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.mnsons.offlinebank.R
8 | import kotlinx.android.synthetic.main.item_account_balance.view.*
9 |
10 | class AccountBalanceAdapter :
11 | RecyclerView.Adapter() {
12 |
13 | var all: List = mutableListOf()
14 |
15 | override fun onCreateViewHolder(
16 | parent: ViewGroup,
17 | viewType: Int
18 | ): AccountBalanceItemViewHolder {
19 | return AccountBalanceItemViewHolder(
20 | LayoutInflater.from(parent.context)
21 | .inflate(R.layout.item_account_balance, parent, false)
22 | )
23 | }
24 |
25 | override fun getItemCount(): Int {
26 | return all.size
27 | }
28 |
29 | override fun onBindViewHolder(holderMenu: AccountBalanceItemViewHolder, position: Int) {
30 | holderMenu.bind(all[position])
31 | }
32 |
33 | inner class AccountBalanceItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
34 |
35 | fun bind(balance: String) {
36 | itemView.tvBankNameAndBalance.text = balance
37 | }
38 |
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/adapters/BankMenuAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.adapters
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.mnsons.offlinebank.R
8 | import com.mnsons.offlinebank.model.BankMenuModel
9 | import kotlinx.android.synthetic.main.item_bank.view.*
10 |
11 | class BankMenuAdapter(
12 | private val selectionListener: SelectionListener? = null
13 | ) : RecyclerView.Adapter() {
14 |
15 | var all: List = mutableListOf()
16 |
17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BankMenuItemViewHolder {
18 | return BankMenuItemViewHolder(
19 | LayoutInflater.from(parent.context).inflate(R.layout.item_bank, parent, false)
20 | )
21 | }
22 |
23 | override fun getItemCount(): Int {
24 | return all.size
25 | }
26 |
27 | override fun onBindViewHolder(holderMenu: BankMenuItemViewHolder, position: Int) {
28 | holderMenu.bind(all[position])
29 | }
30 |
31 | inner class BankMenuItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
32 |
33 | fun bind(bank: BankMenuModel) {
34 | itemView.tvBankName.text = bank.bankName
35 | itemView.ivBankLogo.setImageResource(R.drawable.ic_bank_logo)
36 | itemView.setOnClickListener {
37 | selectionListener?.select(bank)
38 | }
39 | }
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/adapters/SelectionListener.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.adapters
2 |
3 | interface SelectionListener {
4 |
5 | fun select(item: T)
6 |
7 | fun deselect(item: T)
8 |
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/adapters/bank/BankSelectionAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.adapters.bank
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.core.content.ContextCompat
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.mnsons.offlinebank.R
9 | import com.mnsons.offlinebank.model.bank.BankModel
10 | import com.mnsons.offlinebank.ui.commons.adapters.SelectionListener
11 | import kotlinx.android.synthetic.main.item_bank.view.*
12 |
13 | class BankSelectionAdapter(
14 | private val viewType: ViewType = ViewType.NORMAL,
15 | private val selectionListener: SelectionListener? = null
16 | ) : RecyclerView.Adapter() {
17 |
18 | var all: List = mutableListOf()
19 | var backUp: List = mutableListOf()
20 | var selected: MutableList = mutableListOf()
21 |
22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BankItemViewHolder {
23 | return BankItemViewHolder(
24 | LayoutInflater.from(parent.context).inflate(R.layout.item_bank, parent, false)
25 | )
26 | }
27 |
28 | override fun getItemCount(): Int {
29 | return all.size
30 | }
31 |
32 | override fun onBindViewHolder(holder: BankItemViewHolder, position: Int) {
33 | var isSelected = false
34 |
35 | if (selected.contains(all[position])) {
36 | isSelected = true
37 | }
38 |
39 | holder.bind(all[position], isSelected)
40 | }
41 |
42 | fun addToSelected(item: BankModel) {
43 | selected.add(item)
44 | notifyDataSetChanged()
45 | }
46 |
47 | fun removeFromSelected(item: BankModel) {
48 | selected.remove(item)
49 | notifyDataSetChanged()
50 | }
51 |
52 | fun filter(newText: String) {
53 |
54 | }
55 |
56 | inner class BankItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
57 |
58 | fun bind(bank: BankModel, isSelected: Boolean) {
59 | itemView.tvBankName.setText(bank.bankName)
60 | itemView.ivBankLogo.setImageResource(bank.bankLogo)
61 |
62 | if (viewType == ViewType.SELECTABLE) {
63 | itemView.ivCheckMark.isSelected = isSelected
64 |
65 | itemView.itemBankContainer.strokeColor = if (isSelected) {
66 | ContextCompat.getColor(itemView.context, R.color.blue)
67 | } else {
68 | ContextCompat.getColor(itemView.context, R.color.greyTransparent)
69 | }
70 |
71 | itemView.setOnClickListener {
72 | if (isSelected) {
73 | selectionListener?.deselect(bank)
74 | } else {
75 | selectionListener?.select(bank)
76 | }
77 | }
78 | } else {
79 | itemView.ivCheckMark.visibility = View.INVISIBLE
80 | itemView.ivEllipses.visibility = View.VISIBLE
81 | }
82 |
83 | }
84 | }
85 |
86 | enum class ViewType {
87 | NORMAL,
88 | SELECTABLE
89 | }
90 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/adapters/transaction/SectionedTransactionsAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.adapters.transaction
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.mnsons.offlinebank.R
8 | import com.mnsons.offlinebank.model.transaction.SectionedTransactionModel
9 | import kotlinx.android.synthetic.main.item_sectioned_transaction.view.*
10 |
11 | class SectionedTransactionsAdapter(
12 | private val transactionsAdapter: TransactionsAdapter
13 | ) : RecyclerView.Adapter() {
14 |
15 | private val sectionedTransactions = mutableListOf()
16 |
17 | override fun onCreateViewHolder(
18 | parent: ViewGroup,
19 | viewType: Int
20 | ): SectionedTransactionItemViewHolder {
21 | return SectionedTransactionItemViewHolder(
22 | LayoutInflater.from(parent.context)
23 | .inflate(R.layout.item_sectioned_transaction, parent, false)
24 | )
25 | }
26 |
27 | override fun getItemCount(): Int = sectionedTransactions.size
28 |
29 | override fun onBindViewHolder(holder: SectionedTransactionItemViewHolder, position: Int) {
30 | holder.bind(sectionedTransactions[position])
31 | }
32 |
33 | fun setTransactions(items: List) {
34 | sectionedTransactions.clear()
35 | sectionedTransactions.addAll(items)
36 | notifyDataSetChanged()
37 | }
38 |
39 | inner class SectionedTransactionItemViewHolder(itemView: View) :
40 | RecyclerView.ViewHolder(itemView) {
41 |
42 | fun bind(sectionedTransaction: SectionedTransactionModel) {
43 | itemView.tvDay.text = sectionedTransaction.day
44 |
45 | val totalAmount = sectionedTransaction.transactions.sumByDouble { it.amount }
46 | val transactionsCount = sectionedTransaction.transactions.size
47 |
48 | itemView.tvDescription.text = "You spent N$totalAmount on $transactionsCount Transactions"
49 |
50 | itemView.rvTransactions.adapter = transactionsAdapter
51 | transactionsAdapter.setTransactions(sectionedTransaction.transactions)
52 | }
53 |
54 | }
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/adapters/transaction/TransactionsAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.adapters.transaction
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.mnsons.offlinebank.R
8 | import com.mnsons.offlinebank.model.transaction.TransactionModel
9 | import com.mnsons.offlinebank.utils.TransactionUtil
10 | import kotlinx.android.synthetic.main.item_transaction.view.*
11 |
12 | class TransactionsAdapter : RecyclerView.Adapter() {
13 |
14 | private val transactions = mutableListOf()
15 |
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionItemViewHolder {
17 | return TransactionItemViewHolder(
18 | LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false)
19 | )
20 | }
21 |
22 | override fun getItemCount(): Int = transactions.size
23 |
24 | override fun onBindViewHolder(holder: TransactionItemViewHolder, position: Int) {
25 | holder.bind(transactions[position])
26 | }
27 |
28 | fun setTransactions(items: List) {
29 | transactions.clear()
30 | transactions.addAll(items)
31 | notifyDataSetChanged()
32 | }
33 |
34 | inner class TransactionItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
35 |
36 | fun bind(transaction: TransactionModel) {
37 | itemView.ivTypeIcon.setImageResource(TransactionUtil.getIconByType(transaction.type))
38 | itemView.tvType.text = transaction.type.value
39 | itemView.tvBankName.text = transaction.bank
40 | itemView.tvAmount.text = "N ${transaction.amount}"
41 | }
42 |
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/banks/BanksPopulator.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.banks
2 |
3 | import com.mnsons.offlinebank.R
4 | import com.mnsons.offlinebank.model.bank.BankModel
5 |
6 | object BanksPopulator {
7 |
8 | fun fetchSupportedBanks(): List {
9 | val gtBank = BankModel(
10 | R.string.gtbank,
11 | 1,
12 | 0,
13 | "111",
14 | R.drawable.ic_gtbank
15 | )
16 |
17 | val accessBank = BankModel(
18 | R.string.accessbank,
19 | 2,
20 | 0,
21 | "112",
22 | R.drawable.ic_access
23 | )
24 |
25 | val zenithBank = BankModel(
26 | R.string.zenith_bank,
27 | 3,
28 | 0,
29 | "113",
30 | R.drawable.ic_zenith
31 | )
32 |
33 | val uba = BankModel(
34 | R.string.uba,
35 | 4,
36 | 0,
37 | "114",
38 | R.drawable.ic_uba
39 | )
40 |
41 | return mutableListOf(gtBank, accessBank, zenithBank, uba)
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/base/BaseRoundedBottomSheetDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.base
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.annotation.LayoutRes
8 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
9 | import com.mnsons.offlinebank.R
10 |
11 | abstract class BaseRoundedBottomSheetDialogFragment : BottomSheetDialogFragment() {
12 |
13 | override fun getTheme(): Int = R.style.BottomSheetDialogTheme
14 |
15 | override fun onDestroyView() {
16 | if (dialog != null) {
17 | dialog?.setDismissMessage(null)
18 | }
19 | super.onDestroyView()
20 | }
21 |
22 | @LayoutRes
23 | abstract fun getLayoutRes(): Int
24 |
25 | override fun onCreateView(
26 | inflater: LayoutInflater,
27 | container: ViewGroup?,
28 | savedInstanceState: Bundle?
29 | ): View? {
30 | return inflater.inflate(getLayoutRes(), container, false)
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/dialogs/SelectBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.dialogs
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import com.mnsons.offlinebank.R
6 | import com.mnsons.offlinebank.model.bank.BankModel
7 | import com.mnsons.offlinebank.ui.commons.adapters.SelectionListener
8 | import com.mnsons.offlinebank.ui.commons.adapters.bank.BankSelectionAdapter
9 | import com.mnsons.offlinebank.ui.commons.base.BaseRoundedBottomSheetDialogFragment
10 | import com.mnsons.offlinebank.utils.ext.slightDelay
11 | import kotlinx.android.synthetic.main.layout_select_bank_bottom_sheet.*
12 |
13 | class SelectBottomSheet(
14 | private val banks: List,
15 | private val selectBankListener: (BankModel) -> Unit
16 | ) : BaseRoundedBottomSheetDialogFragment(), SelectionListener {
17 |
18 | private val bankSelectionAdapter by lazy {
19 | BankSelectionAdapter(
20 | BankSelectionAdapter.ViewType.SELECTABLE,
21 | this
22 | ).apply {
23 | all = banks
24 | }
25 | }
26 |
27 | override fun getLayoutRes(): Int = R.layout.layout_select_bank_bottom_sheet
28 |
29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
30 | super.onViewCreated(view, savedInstanceState)
31 |
32 | rvBanks.adapter = bankSelectionAdapter
33 | }
34 |
35 | override fun select(item: BankModel) {
36 | dismiss()
37 |
38 | slightDelay({
39 | selectBankListener.invoke(item)
40 | }, 200)
41 | }
42 |
43 | override fun deselect(item: BankModel) {
44 |
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/commons/dialogs/SelectFromMenuBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.commons.dialogs
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import com.mnsons.offlinebank.R
6 | import com.mnsons.offlinebank.model.BankMenuModel
7 | import com.mnsons.offlinebank.ui.commons.adapters.BankMenuAdapter
8 | import com.mnsons.offlinebank.ui.commons.adapters.SelectionListener
9 | import com.mnsons.offlinebank.ui.commons.base.BaseRoundedBottomSheetDialogFragment
10 | import com.mnsons.offlinebank.utils.ext.slightDelay
11 | import kotlinx.android.synthetic.main.layout_select_bank_bottom_sheet.*
12 |
13 | class SelectFromMenuBottomSheet(
14 | private val banks: List,
15 | private val selectBankListener: (BankMenuModel) -> Unit
16 | ) : BaseRoundedBottomSheetDialogFragment(), SelectionListener {
17 |
18 | private val bankSelectionAdapter by lazy {
19 | BankMenuAdapter(
20 | this
21 | ).apply {
22 | all = banks
23 | }
24 | }
25 |
26 | override fun getLayoutRes(): Int = R.layout.layout_select_bank_bottom_sheet
27 |
28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
29 | super.onViewCreated(view, savedInstanceState)
30 |
31 | rvBanks.adapter = bankSelectionAdapter
32 | }
33 |
34 | override fun select(item: BankMenuModel) {
35 | dismiss()
36 |
37 | slightDelay({
38 | selectBankListener.invoke(item)
39 | }, 200)
40 | }
41 |
42 | override fun deselect(item: BankMenuModel) {
43 |
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/accountbalance/AccountBalanceState.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.accountbalance
2 |
3 | sealed class AccountBalanceState(
4 | val isLoading: Boolean,
5 | val accounts: List?,
6 | val error: Throwable?
7 | ) {
8 |
9 | class Error(error: Throwable?) : AccountBalanceState(false, null, error)
10 | class Fetching : AccountBalanceState(false, null, null)
11 | class Fetched(accounts: List?) : AccountBalanceState(false, accounts, null)
12 |
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/accountbalance/AccountBalanceViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.accountbalance
2 |
3 | import androidx.hilt.lifecycle.ViewModelInject
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import com.mnsons.offlinebank.model.bank.BankModel
8 | import javax.inject.Inject
9 |
10 | class AccountBalanceViewModel @ViewModelInject constructor() : ViewModel() {
11 |
12 | lateinit var bank: BankModel
13 |
14 | private val _state = MutableLiveData()
15 | val state: LiveData = _state
16 |
17 | fun fetchAccountBalance() {
18 | _state.value = AccountBalanceState.Fetching()
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/buyairtime/BuyAirtimeState.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.buyairtime
2 |
3 | import com.mnsons.offlinebank.model.buyairtime.BuyAirtimeModel
4 |
5 | sealed class BuyAirtimeState(
6 | val isLoading: Boolean,
7 | val buyAirtimeModel: BuyAirtimeModel?,
8 | val error: Throwable?
9 | ) {
10 |
11 | class Error(error: Throwable?) : BuyAirtimeState(false, null, error)
12 | class Initialize(buyAirtimeModel: BuyAirtimeModel) :
13 | BuyAirtimeState(false, buyAirtimeModel, null)
14 |
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/buyairtime/BuyAirtimeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.buyairtime
2 |
3 | import androidx.hilt.lifecycle.ViewModelInject
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import com.mnsons.offlinebank.data.cache.impl.TransactionsCache
9 | import com.mnsons.offlinebank.model.bank.BankModel
10 | import com.mnsons.offlinebank.model.buyairtime.BuyAirtimeModel
11 | import com.mnsons.offlinebank.model.transaction.TransactionModel
12 | import com.mnsons.offlinebank.model.transaction.TransactionStatus
13 | import com.mnsons.offlinebank.model.transaction.TransactionType
14 | import kotlinx.coroutines.launch
15 | import java.util.*
16 | import javax.inject.Inject
17 |
18 | class BuyAirtimeViewModel @ViewModelInject constructor(private val transactionsCache: TransactionsCache) :
19 | ViewModel() {
20 |
21 | lateinit var bank: BankModel
22 |
23 | private var buyAirtimeModel = BuyAirtimeModel()
24 | private val _state = MutableLiveData()
25 | val state: LiveData = _state
26 |
27 |
28 | fun persistTransaction(status: TransactionStatus) {
29 | viewModelScope.launch {
30 | transactionsCache.saveTransaction(
31 | TransactionModel(buyAirtimeModel.amount!!.toDouble(), Calendar.getInstance().time.time,
32 | TransactionType.AIRTIME_PURCHASE, status, buyAirtimeModel.bank!!)
33 | )
34 | }
35 | }
36 |
37 | fun initiateBuyAirtime(actionId: String, amount: String, phoneNumber: String, originBankAccount:String) {
38 | when {
39 | amount.isEmpty() -> {
40 | _state.value = BuyAirtimeState.Error(Throwable("Please input an amount to buy"))
41 | }
42 | phoneNumber.isEmpty() -> {
43 | _state.value =
44 | BuyAirtimeState.Error(Throwable("Please input recipient's phone number"))
45 | }
46 | else -> {
47 | buyAirtimeModel = BuyAirtimeModel(
48 | actionId,
49 | amount,
50 | phoneNumber,
51 | originBankAccount
52 | )
53 | _state.value = BuyAirtimeState.Initialize(
54 | buyAirtimeModel
55 | )
56 | }
57 | }
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/dashboard/DashboardFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.dashboard
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 | import androidx.fragment.app.viewModels
11 | import com.mnsons.offlinebank.R
12 | import com.mnsons.offlinebank.databinding.FragmentDashboardBinding
13 | import com.mnsons.offlinebank.model.transaction.TransactionType
14 | import com.mnsons.offlinebank.ui.commons.adapters.transaction.SectionedTransactionsAdapter
15 | import com.mnsons.offlinebank.ui.commons.adapters.transaction.TransactionsAdapter
16 | import com.mnsons.offlinebank.ui.main.MainActivity
17 | import com.mnsons.offlinebank.utils.ext.nonNullObserve
18 | import com.mnsons.offlinebank.utils.ext.showSnackbar
19 | import com.mnsons.offlinebank.utils.ext.viewBinding
20 | import dagger.hilt.android.AndroidEntryPoint
21 | import javax.inject.Inject
22 |
23 | @AndroidEntryPoint
24 | class DashboardFragment : Fragment(R.layout.fragment_dashboard) {
25 |
26 | private val dashboardViewModel: DashboardViewModel by viewModels()
27 |
28 | private val transactionsAdapter by lazy { TransactionsAdapter() }
29 |
30 | private val sectionedTransactionsAdapter by lazy {
31 | SectionedTransactionsAdapter(transactionsAdapter)
32 | }
33 |
34 | private val binding: FragmentDashboardBinding by viewBinding(FragmentDashboardBinding::bind)
35 |
36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
37 | super.onViewCreated(view, savedInstanceState)
38 |
39 | binding.rvSectionedTransactions.adapter = sectionedTransactionsAdapter
40 |
41 | nonNullObserve(dashboardViewModel.state, ::handleStates)
42 | }
43 |
44 | @SuppressLint("SetTextI18n")
45 | private fun handleStates(dashboardState: DashboardState) {
46 | when (dashboardState) {
47 | is DashboardState.Idle, is DashboardState.Editing -> {
48 | dashboardState.transactions?.let {
49 | sectionedTransactionsAdapter.setTransactions(it)
50 |
51 | val allTransactions = it.flatMap { it.transactions }
52 |
53 | binding.tvBankTransferAmount.text = "N " + allTransactions.filter {
54 | it.type == TransactionType.BANK_TRANSFER
55 | }.sumByDouble { it.amount }.toString()
56 |
57 | binding.tvAirtimePurchaseAmount.text = "N " + allTransactions.filter {
58 | it.type == TransactionType.AIRTIME_PURCHASE
59 | }.sumByDouble { it.amount }.toString()
60 | }
61 | }
62 | is DashboardState.Error -> showSnackbar(dashboardState.error?.message.toString())
63 | }
64 | }
65 |
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/dashboard/DashboardState.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.dashboard
2 |
3 | import com.mnsons.offlinebank.model.transaction.SectionedTransactionModel
4 |
5 | sealed class DashboardState(
6 | val isLoading: Boolean,
7 | val transactions: List?,
8 | val error: Throwable?
9 | ) {
10 |
11 | class Error(error: Throwable?) : DashboardState(false, null, error)
12 | class Editing(transactions: List) :
13 | DashboardState(false, transactions, null)
14 | class Idle(transactions: List) :
15 | DashboardState(false, transactions, null)
16 |
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/dashboard/DashboardViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.dashboard
2 |
3 | import androidx.hilt.lifecycle.ViewModelInject
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import com.mnsons.offlinebank.data.cache.impl.TransactionsCache
9 | import com.mnsons.offlinebank.model.transaction.SectionedTransactionModel
10 | import com.mnsons.offlinebank.model.transaction.TransactionModel
11 | import kotlinx.coroutines.flow.launchIn
12 | import kotlinx.coroutines.flow.onEach
13 | import java.text.SimpleDateFormat
14 | import java.util.*
15 | import javax.inject.Inject
16 |
17 | class DashboardViewModel @ViewModelInject constructor(
18 | transactionsCache: TransactionsCache
19 | ) : ViewModel() {
20 |
21 | private val _state = MutableLiveData()
22 | val state: LiveData = _state
23 |
24 | init {
25 | transactionsCache.getTransaction().onEach {
26 | _state.value = DashboardState.Idle(generateSectionedTransactions(it))
27 | }.launchIn(viewModelScope)
28 | }
29 |
30 | private fun generateSectionedTransactions(
31 | transactions: List
32 | ): List {
33 | val sectionedTransactions = mutableListOf()
34 | val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
35 |
36 | val sortedTransactions = transactions.sortedByDescending { it.timestamp }
37 |
38 | sortedTransactions.forEach { transaction ->
39 | val dayString =
40 | when (val formattedDateString = dateFormat.format(Date(transaction.timestamp))) {
41 | dateFormat.format(Date()) -> "Today"
42 | dateFormat.format(Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000)) -> "Yesterday"
43 | else -> formattedDateString
44 | }
45 |
46 | val dayList = sectionedTransactions.map { it.day }
47 |
48 | if (dayString in dayList) {
49 | sectionedTransactions.find {
50 | it.day == dayString
51 | }?.transactions?.add(transaction)
52 | } else {
53 | sectionedTransactions.add(
54 | SectionedTransactionModel(dayString, mutableListOf(transaction))
55 | )
56 | }
57 | }
58 |
59 | return sectionedTransactions
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/home/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.home
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 |
7 | class HomeViewModel : ViewModel() {
8 |
9 | private val _text = MutableLiveData().apply {
10 | value = "This is home Fragment"
11 | }
12 | val text: LiveData = _text
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/home/menu/MenuAction.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.home.menu
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.annotation.StringRes
5 | import com.mnsons.offlinebank.R
6 |
7 | sealed class MenuAction(
8 | val id: Int,
9 | @StringRes val titleRes: Int,
10 | @DrawableRes val iconRes: Int,
11 | val isEnabled: Boolean = false
12 | ) {
13 | object TransferFunds : MenuAction(1, R.string.transfer_funds, R.drawable.transfer, true)
14 | object BuyAirtime : MenuAction(2, R.string.buy_airtime, R.drawable.ic_airtime, true)
15 | object BuyData : MenuAction(3, R.string.buy_internet, R.drawable.ic_internet, true)
16 | object PayBills : MenuAction(4, R.string.pay_bills, R.drawable.ic_bills, false)
17 | object CheckAccountBalance :
18 | MenuAction(5, R.string.check_balance, R.drawable.ic_profile_circle, true)
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/home/menu/MenuAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.home.menu
2 |
3 | import android.text.Html
4 | import android.view.LayoutInflater
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.DiffUtil
7 | import androidx.recyclerview.widget.ListAdapter
8 | import androidx.recyclerview.widget.RecyclerView
9 | import com.mnsons.offlinebank.databinding.MenuItemCardBinding
10 | import com.mnsons.offlinebank.utils.ext.recursivelyApplyToChildren
11 |
12 | class MenuAdapter constructor(private val menuActionClickListener: MenuActionClickListener) :
13 | ListAdapter(DiffCallback()) {
14 |
15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
16 | val binding =
17 | MenuItemCardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
18 | return MenuActionViewHolder(binding, menuActionClickListener)
19 | }
20 |
21 | fun isEmpty() = super.getItemCount() == 0
22 |
23 |
24 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
25 | (holder as MenuActionViewHolder).bind(getItem(position))
26 | }
27 |
28 |
29 | class MenuActionViewHolder(
30 | private val binding: MenuItemCardBinding,
31 | private val actionClickListener: MenuActionClickListener
32 | ) :
33 | RecyclerView.ViewHolder(binding.root) {
34 |
35 | fun bind(model: MenuAction) {
36 | itemView.setOnClickListener {
37 | actionClickListener.onMenuActionClick(model)
38 | }
39 | binding.parent.recursivelyApplyToChildren {
40 | it.isEnabled = model.isEnabled
41 | }
42 | binding.icon.setImageDrawable(itemView.context.getDrawable(model.iconRes))
43 | binding.title.text = Html.fromHtml(itemView.context.getString(model.titleRes))
44 | }
45 |
46 | }
47 |
48 | class DiffCallback : DiffUtil.ItemCallback() {
49 | override fun areItemsTheSame(oldItem: MenuAction, newItem: MenuAction): Boolean {
50 | return oldItem.id == newItem.id
51 | }
52 |
53 | override fun areContentsTheSame(
54 | oldItem: MenuAction,
55 | newItem: MenuAction
56 | ): Boolean {
57 | return oldItem == newItem
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/home/menu/MenuItemClickListener.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.home.menu
2 |
3 |
4 | interface MenuActionClickListener {
5 | fun onMenuActionClick(model: MenuAction)
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/presentation/MainState.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.presentation
2 |
3 | import com.mnsons.offlinebank.model.user.UserModel
4 |
5 | sealed class MainState(
6 | val isLoading: Boolean,
7 | val user: UserModel?,
8 | val error: Throwable?
9 | ) {
10 |
11 | object LoggedOut : MainState(false, null, null)
12 | class Idle(user: UserModel) : MainState(false, user, null)
13 | class Editing(user: UserModel) : MainState(false, user, null)
14 | class Error(error: Throwable?) : MainState(false, null, error)
15 |
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/presentation/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.presentation
2 |
3 | import androidx.hilt.lifecycle.ViewModelInject
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import com.mnsons.offlinebank.data.cache.impl.BanksCache
9 | import com.mnsons.offlinebank.data.cache.impl.SettingsCache
10 | import com.mnsons.offlinebank.model.*
11 | import com.mnsons.offlinebank.model.user.UserModel
12 | import kotlinx.coroutines.flow.launchIn
13 | import kotlinx.coroutines.flow.onEach
14 |
15 | class MainViewModel @ViewModelInject constructor(
16 | private val settingsCache: SettingsCache,
17 | private val banksCache: BanksCache
18 | ) : ViewModel() {
19 |
20 | private val _state = MutableLiveData()
21 | val state: LiveData = _state
22 |
23 | init {
24 | if (settingsCache.userDataExists()) {
25 | banksCache.getBanks()
26 | .onEach { banks ->
27 | _state.value = MainState.Idle(
28 | UserModel(
29 | settingsCache.fetchUserFirstName()!!,
30 | settingsCache.fetchUserLastName()!!,
31 | settingsCache.fetchUserPhone()!!,
32 | banks.mapInto {
33 | it.toBankModel()
34 | }
35 | )
36 | )
37 | }
38 | .launchIn(viewModelScope)
39 | } else {
40 | _state.value = MainState.LoggedOut
41 | }
42 | }
43 |
44 |
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/profile/addbank/AddBankState.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.profile.addbank
2 |
3 | import com.mnsons.offlinebank.model.user.UserModel
4 |
5 | sealed class AddBankState(
6 | val isLoading: Boolean,
7 | val user: UserModel?,
8 | val error: Throwable?
9 | ) {
10 |
11 | class Idle(user: UserModel) : AddBankState(false, user, null)
12 | class Editing(user: UserModel) : AddBankState(false, user, null)
13 | class Error(error: Throwable?) : AddBankState(false, null, error)
14 |
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/profile/addbank/AddBankViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.profile.addbank
2 |
3 | import androidx.hilt.lifecycle.ViewModelInject
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import com.mnsons.offlinebank.data.cache.impl.BanksCache
9 | import com.mnsons.offlinebank.data.cache.impl.SettingsCache
10 | import com.mnsons.offlinebank.model.*
11 | import com.mnsons.offlinebank.model.bank.BankModel
12 | import com.mnsons.offlinebank.model.user.UserModel
13 | import kotlinx.coroutines.flow.launchIn
14 | import kotlinx.coroutines.flow.onEach
15 | import kotlinx.coroutines.launch
16 | import javax.inject.Inject
17 |
18 | class AddBankViewModel @ViewModelInject constructor(
19 | val settingsCache: SettingsCache,
20 | val banksCache: BanksCache
21 | ) : ViewModel() {
22 |
23 | private val _state = MutableLiveData()
24 | val state: LiveData = _state
25 |
26 | init {
27 | if (settingsCache.userDataExists()) {
28 | banksCache.getBanks()
29 | .onEach { banks ->
30 | _state.value = AddBankState.Idle(
31 | UserModel(
32 | settingsCache.fetchUserFirstName()!!,
33 | settingsCache.fetchUserLastName()!!,
34 | settingsCache.fetchUserPhone()!!,
35 | banks.mapInto {
36 | it.toBankModel()
37 | }
38 | )
39 | )
40 | }.launchIn(viewModelScope)
41 | }
42 | }
43 |
44 | fun updateUserBanks(banks: List) {
45 | if (banks.isEmpty()) {
46 | _state.value = AddBankState.Error(Throwable("Please select at least one bank"))
47 | } else {
48 | if (settingsCache.userDataExists()) {
49 | _state.value = AddBankState.Editing(
50 | UserModel(
51 | settingsCache.fetchUserFirstName()!!,
52 | settingsCache.fetchUserLastName()!!,
53 | settingsCache.fetchUserPhone()!!,
54 | banks
55 | )
56 | )
57 |
58 | viewModelScope.launch {
59 | banksCache.clearBanks()
60 | banksCache.saveBanks(
61 | banks.mapInto {
62 | it.toBankCacheModel()
63 | }
64 | )
65 | }
66 | } else {
67 | _state.value = AddBankState.Error(Throwable("There was an error adding new banks"))
68 | }
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/profile/userdetails/UserDetailsFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.profile.userdetails
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.Fragment
6 | import androidx.fragment.app.activityViewModels
7 | import androidx.navigation.fragment.findNavController
8 | import com.mnsons.offlinebank.R
9 | import com.mnsons.offlinebank.databinding.FragmentUserDetailsBinding
10 | import com.mnsons.offlinebank.ui.commons.adapters.bank.BankSelectionAdapter
11 | import com.mnsons.offlinebank.ui.main.presentation.MainState
12 | import com.mnsons.offlinebank.ui.main.presentation.MainViewModel
13 | import com.mnsons.offlinebank.utils.ext.nonNullObserve
14 | import com.mnsons.offlinebank.utils.ext.viewBinding
15 | import dagger.hilt.android.AndroidEntryPoint
16 |
17 | @AndroidEntryPoint
18 | class UserDetailsFragment : Fragment(R.layout.fragment_user_details) {
19 |
20 | private val binding: FragmentUserDetailsBinding by viewBinding(FragmentUserDetailsBinding::bind)
21 |
22 | private val mainViewModel: MainViewModel by activityViewModels()
23 |
24 | private val bankSelectionAdapter by lazy {
25 | BankSelectionAdapter()
26 | }
27 |
28 |
29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
30 | super.onViewCreated(view, savedInstanceState)
31 |
32 | nonNullObserve(mainViewModel.state, ::handleStates)
33 |
34 | binding.btnAddBank.setOnClickListener {
35 | findNavController().navigate(UserDetailsFragmentDirections.actionNavigationProfileToNavigationAddBank())
36 | }
37 | }
38 |
39 |
40 | private fun handleStates(mainState: MainState) {
41 | if (mainState is MainState.Idle) {
42 | mainState.user?.let {
43 | binding.tvUserFullName.text = "${it.firstName} ${it.lastName}"
44 | binding.tvUserPhoneNumber.text = it.phoneNumber
45 | bankSelectionAdapter.all = it.banks
46 | binding.rvSelectedBanks.adapter = bankSelectionAdapter
47 | }
48 | }
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/transfermoney/TransferMoneyState.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.transfermoney
2 |
3 | import com.mnsons.offlinebank.model.MoneyTransferModel
4 |
5 | sealed class TransferMoneyState(
6 | val transferModel: MoneyTransferModel?,
7 | val error: Throwable?
8 | ) {
9 |
10 | class Error(error: Throwable?) : TransferMoneyState(null, error)
11 | class Initialize(moneyTransferModel: MoneyTransferModel) :
12 | TransferMoneyState(moneyTransferModel, null)
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/main/transfermoney/TransferMoneyViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.main.transfermoney
2 |
3 | import androidx.hilt.lifecycle.ViewModelInject
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import com.mnsons.offlinebank.data.cache.impl.BanksCache
9 | import com.mnsons.offlinebank.data.cache.impl.TransactionsCache
10 | import com.mnsons.offlinebank.model.BankMenuModel
11 | import com.mnsons.offlinebank.model.MoneyTransferModel
12 | import com.mnsons.offlinebank.model.transaction.TransactionModel
13 | import com.mnsons.offlinebank.model.transaction.TransactionStatus
14 | import com.mnsons.offlinebank.model.transaction.TransactionType
15 | import kotlinx.coroutines.flow.launchIn
16 | import kotlinx.coroutines.flow.onEach
17 | import kotlinx.coroutines.launch
18 | import java.util.*
19 |
20 | class TransferMoneyViewModel @ViewModelInject constructor(
21 | private val bankCache: BanksCache,
22 | private val transactionsCache: TransactionsCache
23 | ) : ViewModel() {
24 |
25 | var bankIds = emptyList()
26 |
27 | private val _state = MutableLiveData()
28 | val state: LiveData = _state
29 |
30 | private var moneyTransferModel: MoneyTransferModel = MoneyTransferModel()
31 |
32 | fun fetchBankMenu(bankId: Int) {
33 | bankCache.getBankMenu(bankId)
34 | .onEach {
35 | bankIds = it
36 | }.launchIn(viewModelScope)
37 | }
38 |
39 |
40 | fun persistTransaction(status: TransactionStatus) {
41 | viewModelScope.launch {
42 | transactionsCache.saveTransaction(TransactionModel(moneyTransferModel.amount!!.toDouble(),
43 | Calendar.getInstance().time.time, TransactionType.BANK_TRANSFER,
44 | status, moneyTransferModel.bank!!))
45 | }
46 | }
47 |
48 | fun initiateFundTransfer(actionId: String, amount: String, accountNumber: String, originatingBankName:String) {
49 | when {
50 | amount.isEmpty() -> {
51 | _state.value = TransferMoneyState.Error(Throwable("Please input an amount to buy"))
52 | }
53 | accountNumber.isEmpty() -> {
54 | _state.value =
55 | TransferMoneyState.Error(Throwable("Please input recipient's bank account"))
56 | }
57 | else -> {
58 | moneyTransferModel = moneyTransferModel.copy(
59 | actionId = actionId,
60 | amount = amount,
61 | accountNumber = accountNumber,
62 | bank = originatingBankName
63 | )
64 | _state.value = TransferMoneyState.Initialize(moneyTransferModel)
65 | }
66 | }
67 | }
68 |
69 | fun saveTransaction() {
70 |
71 | }
72 |
73 | fun setRecipientBank(id: Int) {
74 | moneyTransferModel = moneyTransferModel.copy(recipientBank = id)
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/onboarding/OnBoardingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.onboarding
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 | import com.mnsons.offlinebank.R
6 | import dagger.hilt.android.AndroidEntryPoint
7 |
8 | @AndroidEntryPoint
9 | class OnBoardingActivity : AppCompatActivity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContentView(R.layout.activity_on_boarding)
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/onboarding/collectuserdetails/CollectUserDetailsFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.onboarding.collectuserdetails
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.Fragment
9 | import androidx.fragment.app.viewModels
10 | import androidx.navigation.fragment.findNavController
11 | import com.google.android.material.snackbar.Snackbar
12 | import com.mnsons.offlinebank.R
13 | import com.mnsons.offlinebank.databinding.FragmentCollectUserDetailsBinding
14 | import com.mnsons.offlinebank.ui.onboarding.OnBoardingActivity
15 | import com.mnsons.offlinebank.ui.onboarding.presentation.OnBoardingState
16 | import com.mnsons.offlinebank.ui.onboarding.presentation.OnBoardingViewModel
17 | import com.mnsons.offlinebank.utils.ext.nonNullObserve
18 | import com.mnsons.offlinebank.utils.ext.viewBinding
19 | import dagger.hilt.android.AndroidEntryPoint
20 |
21 | @AndroidEntryPoint
22 | class CollectUserDetailsFragment : Fragment(R.layout.fragment_collect_user_details) {
23 |
24 | private val onBoardingViewModel: OnBoardingViewModel by viewModels()
25 |
26 | private val binding: FragmentCollectUserDetailsBinding by viewBinding(FragmentCollectUserDetailsBinding::bind)
27 |
28 |
29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
30 | super.onViewCreated(view, savedInstanceState)
31 |
32 | binding.btnNext.setOnClickListener {
33 | onBoardingViewModel.setUserDetails(
34 | binding.etFirstName.text.toString(),
35 | binding.etLastName.text.toString(),
36 | binding.etPhoneNumber.text.toString()
37 | )
38 | }
39 |
40 | nonNullObserve(onBoardingViewModel.state, ::handleState)
41 | }
42 |
43 |
44 | private fun handleState(onBoardingState: OnBoardingState) {
45 | when (onBoardingState) {
46 | is OnBoardingState.Loading -> {
47 |
48 | }
49 | is OnBoardingState.Finished -> {
50 |
51 | }
52 | is OnBoardingState.Editing -> {
53 | findNavController().navigate(R.id.action_navigation_collect_user_details_to_navigation_select_user_banks)
54 | }
55 | is OnBoardingState.Error -> {
56 | Snackbar.make(
57 | binding.root,
58 | onBoardingState.error?.message.toString(),
59 | Snackbar.LENGTH_LONG
60 | ).show()
61 | }
62 | }
63 | }
64 |
65 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/onboarding/done/AllDoneFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.onboarding.done
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import com.mnsons.offlinebank.R
9 |
10 | class AllDoneFragment : Fragment() {
11 |
12 | override fun onCreateView(
13 | inflater: LayoutInflater, container: ViewGroup?,
14 | savedInstanceState: Bundle?
15 | ): View? {
16 | return inflater.inflate(R.layout.fragment_all_done, container, false)
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/onboarding/presentation/OnBoardingState.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.onboarding.presentation
2 |
3 | import com.mnsons.offlinebank.model.user.UserModel
4 |
5 | sealed class OnBoardingState(
6 | val isLoading: Boolean,
7 | val user: UserModel?,
8 | val error: Throwable?
9 | ) {
10 | class Editing(user: UserModel) : OnBoardingState(false, user, null)
11 | class Error(error: Throwable?) : OnBoardingState(false, null, error)
12 | class Loading(isLoading: Boolean) : OnBoardingState(isLoading, null, null)
13 | class Finished(user: UserModel) : OnBoardingState(false, user, null)
14 |
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/onboarding/selectuserbanks/BankTransferMenuIndexer.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.onboarding.selectuserbanks
2 |
3 | import androidx.fragment.app.Fragment
4 | import com.mnsons.offlinebank.contracts.access.FetchAccessBankOtherBanksContract
5 | import com.mnsons.offlinebank.contracts.gtb.FetchGTBankOtherBanksFirstPageContract
6 | import com.mnsons.offlinebank.model.BankMenuModel
7 | import com.mnsons.offlinebank.model.bank.BankModel
8 | import javax.inject.Inject
9 |
10 | class BankTransferMenuIndexer @Inject constructor(
11 | private val successAction: (bankId: Int, List) -> Unit,
12 | private val finishedAction: () -> Unit,
13 | val fragment: Fragment
14 | ) {
15 |
16 | private var selectedBanks = listOf()
17 | private var currentIndex: Int = 0
18 |
19 | private val gtBankMenuCall =
20 | fragment.registerForActivityResult(FetchGTBankOtherBanksFirstPageContract()) { result ->
21 | result.data?.let {
22 | successAction.invoke(selectedBanks[currentIndex].id, it)
23 | }
24 | runNextMenuCall()
25 | }
26 |
27 | private val accessBankMenuCall = fragment.registerForActivityResult(
28 | FetchAccessBankOtherBanksContract()
29 | ) { result ->
30 | result.data?.let {
31 | successAction.invoke(selectedBanks[currentIndex].id, it)
32 | }
33 | runNextMenuCall()
34 | }
35 |
36 | private fun runNextMenuCall() {
37 | currentIndex += 1
38 | if (currentIndex <= selectedBanks.lastIndex) {
39 | runContractForBank(selectedBanks[currentIndex])
40 | } else {
41 | currentIndex = 0
42 | selectedBanks = emptyList()
43 | finishedAction.invoke()
44 | }
45 | }
46 |
47 | private fun runContractForBank(bankModel: BankModel) {
48 | if (bankModel.id == 1) {
49 | gtBankMenuCall.launch(Unit)
50 | } else if (bankModel.id == 2) {
51 | accessBankMenuCall.launch(Unit)
52 | }
53 | }
54 |
55 | fun indexMenuForBanks(selectedBanks: List) {
56 | this.selectedBanks = selectedBanks
57 | runContractForBank(this.selectedBanks[0])
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/ui/onboarding/setingup/SettingUpFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.ui.onboarding.setingup
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import com.mnsons.offlinebank.R
9 |
10 | class SettingUpFragment : Fragment() {
11 |
12 | override fun onCreateView(
13 | inflater: LayoutInflater, container: ViewGroup?,
14 | savedInstanceState: Bundle?
15 | ): View? {
16 | return inflater.inflate(R.layout.fragment_setting_up, container, false)
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/BuyAirtimeUtil.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.utils
2 |
3 | object BuyAirtimeUtil {
4 |
5 | fun getActionIdByBankId(id: Int): String = hashMapOfActions[id]!!
6 |
7 | private val hashMapOfActions = hashMapOf(
8 | 1 to "58f263f5",
9 | 2 to "1fb3b56e"
10 | )
11 |
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/CheckBalanceUtil.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.utils
2 |
3 | object CheckBalanceUtil {
4 |
5 | fun getActionIdByBankId(id: Int) = hashMapOfActions[id]
6 |
7 | private val hashMapOfActions = hashMapOf(
8 | 1 to "19142a42"
9 | )
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.utils
2 |
3 | object Constants {
4 |
5 | const val EXTRA_PHONE_NUMBER = "phoneNumber"
6 | const val EXTRA_AMOUNT = "amount"
7 |
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/TransferMoneyUtil.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.utils
2 |
3 | object TransferMoneyUtil {
4 |
5 | fun getActionIdByBankId(id: Int): String = hashMapOfActions[id]!!
6 |
7 | private val hashMapOfActions = hashMapOf(
8 | 1 to "75872765",
9 | 2 to "f561a10f"
10 | )
11 |
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/ext/ContextExtensions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.utils.ext
17 |
18 | import android.content.Context
19 | import android.util.DisplayMetrics
20 | import androidx.annotation.ColorRes
21 | import androidx.annotation.StringRes
22 | import androidx.core.content.ContextCompat
23 | import kotlin.math.roundToInt
24 |
25 | /**
26 | * Get resource string from optional id
27 | *
28 | * @param resId Resource string identifier.
29 | * @return The key value if exist, otherwise empty.
30 | */
31 | fun Context.getString(@StringRes resId: Int?) =
32 | resId?.let {
33 | getString(it)
34 | } ?: run {
35 | ""
36 | }
37 |
38 |
39 | fun Context.dpToPx(dp: Int): Int {
40 | var displayMetrics = resources.displayMetrics
41 | return (dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)).roundToInt()
42 | }
43 |
44 | fun Context.getColorHexString(@ColorRes resId: Int): String {
45 | val colorInt = ContextCompat.getColor(this, resId)
46 | return String.format("#%06X", 0xFFFFFF and colorInt)
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/ext/LifecycleOwnerExtensions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.utils.ext
17 |
18 | import androidx.lifecycle.LifecycleOwner
19 | import androidx.lifecycle.LiveData
20 | import androidx.lifecycle.MutableLiveData
21 | import androidx.lifecycle.Observer
22 | import com.mnsons.offlinebank.utils.livedata.NonNullObserver
23 |
24 | /**
25 | * Adds the given observer to the observers list within the lifespan of the given
26 | * owner. The events are dispatched on the main_home thread. If LiveData already has data
27 | * set, it will be delivered to the observer.
28 | *
29 | * @param liveData The liveData to observe.
30 | * @param observer The observer that will receive the events.
31 | * @see LiveData.observe
32 | */
33 | fun LifecycleOwner.observe(liveData: LiveData, observer: (T) -> Unit) {
34 | liveData.observe(this, Observer {
35 | it?.let { t -> observer(t) }
36 | })
37 | }
38 |
39 | fun > LifecycleOwner.nonNullObserve(liveData: L, body: (T) -> Unit) {
40 | liveData.observe(this, NonNullObserver(body))
41 | }
42 |
43 | /**
44 | * Adds the given observer to the observers list within the lifespan of the given
45 | * owner. The events are dispatched on the main_home thread. If LiveData already has data
46 | * set, it will be delivered to the observer.
47 | *
48 | * @param liveData The mutableLiveData to observe.
49 | * @param observer The observer that will receive the events.
50 | * @see MutableLiveData.observe
51 | */
52 | fun LifecycleOwner.observe(liveData: MutableLiveData, observer: (T) -> Unit) {
53 | liveData.observe(this, Observer {
54 | it?.let { t -> observer(t) }
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/ext/ListExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.utils.ext
17 |
18 |
19 | fun List.replaceItemInList(comparator: (item: T) -> Boolean, item: T): List {
20 | val list = this.toMutableList()
21 | list.forEachIndexed { index, each ->
22 | each.takeIf { comparator(each) }?.let {
23 | list[index] = item
24 | }
25 | }
26 | return list
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/ext/LiveDataExtension.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.utils.ext
17 |
18 | import androidx.lifecycle.LifecycleOwner
19 | import androidx.lifecycle.LiveData
20 | import androidx.lifecycle.Observer
21 | import androidx.lifecycle.Transformations
22 |
23 | fun LiveData.map(transformation: (T) -> R): LiveData {
24 | return Transformations.map(this, transformation)
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/ext/StringExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.utils.ext
17 |
18 | import android.os.Build
19 | import android.os.Build.VERSION.SDK_INT
20 | import android.text.Html
21 | import android.text.Spanned
22 |
23 |
24 | @SuppressWarnings("deprecation")
25 | fun fromHtml(source: String?): Spanned {
26 | return if (SDK_INT >= Build.VERSION_CODES.N) {
27 | Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY)
28 | } else {
29 | Html.fromHtml(source)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/flow/PostExecutionThread.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.utils.flow
17 |
18 | import kotlinx.coroutines.CoroutineDispatcher
19 |
20 |
21 | interface PostExecutionThread {
22 |
23 | val ui: CoroutineDispatcher
24 |
25 | val io: CoroutineDispatcher
26 |
27 | val default: CoroutineDispatcher
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/flow/PostExecutionThreadImpl.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Abdul-Mujeeb Aliu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.mnsons.offlinebank.utils.flow
17 |
18 | import kotlinx.coroutines.CoroutineDispatcher
19 | import kotlinx.coroutines.Dispatchers
20 | import javax.inject.Inject
21 |
22 | class PostExecutionThreadImpl @Inject constructor() : PostExecutionThread {
23 |
24 | override val ui: CoroutineDispatcher = Dispatchers.Main
25 | override val io: CoroutineDispatcher = Dispatchers.IO
26 | override val default: CoroutineDispatcher = Dispatchers.Default
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mnsons/offlinebank/utils/livedata/NonNullObserver.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank.utils.livedata
2 |
3 | import androidx.lifecycle.Observer
4 |
5 |
6 | internal class NonNullObserver(private val block: (T) -> Unit) : Observer {
7 |
8 | override fun onChanged(it: T?) {
9 | if (it != null) {
10 | block(it)
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_in_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_out_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/activity.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bottom_sheet_background_drawable.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/home.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_access.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_airtime.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_airtime_purchase_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_airtime_purchase_summary.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bank_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bank_not_selected.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bank_selected.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bank_transfer_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
14 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bank_transfer_summary.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
14 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dashboard_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dummy_bank_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/drawable/ic_dummy_bank_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dummy_user_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/drawable/ic_dummy_user_avatar.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_ellipses.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_filter.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_gtbank.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home_indicator.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_internet.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_item_bank_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_notifications_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_official_building.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_profile_circle.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_transaction_failure.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_transaction_success.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zenith.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
13 |
17 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/profile.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/transfer.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_bold_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_bold_italic.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_book.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_book.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_book_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_book_italic.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_demi.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_demi.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_demi_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_demi_italic.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_light.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_light_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_light_italic.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_medium.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/maisonneue_medium_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/font/maisonneue_medium_italic.ttf
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
28 |
29 |
30 |
42 |
43 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_on_boarding.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_account_balance.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
22 |
23 |
35 |
36 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_add_bank.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
23 |
24 |
32 |
33 |
41 |
42 |
43 |
58 |
59 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_all_done.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
30 |
31 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_buy_airtime.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
15 |
16 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_notifications.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_setting_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_transfer_money.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
15 |
16 |
19 |
20 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_account_balance.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_bank.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
18 |
19 |
28 |
29 |
42 |
43 |
52 |
53 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_sectioned_transaction.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
30 |
31 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_transaction.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
31 |
32 |
42 |
43 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_buy_airtime_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
26 |
27 |
28 |
38 |
39 |
45 |
46 |
47 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_enter_ussd_pin.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
20 |
21 |
31 |
32 |
39 |
40 |
41 |
50 |
51 |
69 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_select_bank.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
21 |
22 |
31 |
32 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_select_bank_bottom_sheet.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
23 |
24 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_successful_transaction.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
22 |
23 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_transaction_outcome.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
17 |
18 |
34 |
35 |
45 |
46 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/menu_item_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
18 |
19 |
27 |
28 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottom_nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/onboarding_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
21 |
22 |
23 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v23/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #BB86FC
5 | #6200EE
6 | #3700B3
7 | #03DAC5
8 |
9 | #3274F5
10 | #5F6578
11 | #C7C7C7
12 | #FFFFFF
13 | #48FFFFFF
14 |
15 | #E8E8E8
16 | #165F6578
17 | #000000
18 |
19 | #34C7C7C7
20 | #E8E8E8
21 |
22 | #1AE1E1E1
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 170dp
6 | 150dp
7 | 18sp
8 |
9 |
10 | 32dp
11 | 8dp
12 | 16dp
13 | 24dp
14 | 4dp
15 | 16sp
16 | 12dp
17 | 60dp
18 | 24dp
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/mnsons/offlinebank/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mnsons.offlinebank
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/app_icon.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 |
4 | //this is how we manage dependencies for now. KISS
5 | ext.kotlin_version = "1.4.10"
6 | ext.activity_version = "1.2.0-alpha04"
7 | ext.fragment_version = "1.3.0-alpha04"
8 | ext.room_version = "2.2.5"
9 | ext.coroutines = "1.3.9"
10 | ext.flowbinding_version = "1.0.0-alpha02"
11 | ext.hilt_version = "2.28-alpha"
12 | ext.coil_version = "1.0.0-rc3"
13 | ext.kohii_version = "1.1.1.2011003"
14 | ext.navigation_version = "2.3.0"
15 | ext.dagger = "2.27"
16 |
17 | repositories {
18 | google()
19 | jcenter()
20 | maven {url 'https://jitpack.io'}
21 | }
22 | dependencies {
23 | classpath 'com.android.tools.build:gradle:4.2.0-alpha15'
24 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
25 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
26 | classpath 'com.google.gms:google-services:4.3.4'
27 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
28 | classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
29 | classpath "com.github.alexfu:androidautoversion:3.3.0"
30 |
31 | // NOTE: Do not place your application dependencies here; they belong
32 | // in the individual module build.gradle files
33 | }
34 | }
35 |
36 | allprojects {
37 | repositories {
38 | google()
39 | mavenCentral()
40 | maven { url "https://jitpack.io" }
41 | maven { url "http://maven.usehover.com/releases" }
42 | jcenter()
43 | }
44 |
45 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
46 | kotlinOptions {
47 | freeCompilerArgs = ['-Xjvm-default=enable'] //enable or compatibility
48 | jvmTarget = "1.8"
49 | }
50 | }
51 | }
52 |
53 | task clean(type: Delete) {
54 | delete rootProject.buildDir
55 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliumujib/Bankable/c69f03c17575e4ae4bee1f851b32c7316fc7872b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat May 09 15:05:13 WAT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Bankable
2 |
3 |
4 | ### Introduction
5 | Android banking app uses Hover's USSD sdk to power fully offline banking and transaction analytics. The app aggregates banking operations of different app into one
6 | concise good looking application.
7 |
8 | ### Goals
9 | - Significantly improve the UX of USSD in terms of accessibility and aesthetics.
10 | - Achieve very lean APK size, currently at 6.3 MB with 2 bank integrations, can't go up much more than that even if we added more banks because we'd
11 | be adding only new contracts for the bank, and an SVG logo image for each bank. Size can be further optimized with code splitting and obfuscation.
12 | - Make intelligent use of Hover's SDK (Cut some corners though, for speed 🤣)
13 | - Aggregate USSD banking services in one place so the user doesn't have to remember them all.
14 |
15 | ### Used libraries
16 | **Hover SDK** - USSD calls
17 | **Dagger2** - Dagger2 was used for dependency injection.
18 | **Kotlin Flow** - Kotlin Flow was used for threading and data stream management.
19 | **AndroidKtx** - For cool extensions to Android classes.
20 | **Architecture Components** - For Lifecycle management etc.
21 |
22 |
23 | ### Possible Improvements
24 | We had a lot of fun building this. There are some improvements we intend to make.
25 |
26 | - Write tests.
27 | - Further explore the `Activity Result API` as a means of further abstracting Hover SDK logic from activities and fragments for a cleaner dev experience
28 | - Improve success/error parsing logic with parsers
29 | - Add more banks
30 |
31 |
32 | ### Team
33 | - [Abdul-Mujeeb Aliu](https://github.com/aliumujib).
34 | - [Quadri Anifowose](https://github.com/Quadriyanney).
35 | - [Olusesan Peter](https://sesan.design).
36 |
37 | ### Build Instructions
38 | - Clone repository.
39 | - Run with Android Studio 4.1 canary 8 and above.
40 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name = "OfflineBank"
--------------------------------------------------------------------------------