diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index 91e5999a3..a55e7a179 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index d63344830..000000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,245 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Android
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index cd5da0eba..c21d945bc 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -39,14 +39,14 @@ android {
main.java.srcDirs += 'src/main/kotlin'
}
- compileSdk 31
+ compileSdk 32
defaultConfig {
applicationId "com.hover.stax"
minSdk 21
- targetSdk 31
- versionCode 157
- versionName "1.11.17"
+ targetSdk 32
+ versionCode 174
+ versionName "1.12.16"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
@@ -66,7 +66,6 @@ android {
kotlinOptions {
jvmTarget = "1.8"
- useIR = true
}
buildTypes {
@@ -125,19 +124,18 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
- implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2'
- implementation 'androidx.navigation:navigation-ui-ktx:2.4.2'
+ implementation 'androidx.navigation:navigation-fragment-ktx:2.5.1'
+ implementation 'androidx.navigation:navigation-ui-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.biometric:biometric:1.1.0"
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.viewpager:viewpager:1.0.0'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1'
- implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
- implementation 'androidx.activity:activity-compose:1.4.0'
- kapt "androidx.lifecycle:lifecycle-common-java8:2.4.1"
-
- implementation 'androidx.core:core-splashscreen:1.0.0-rc01'
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
+ implementation 'androidx.activity:activity-compose:1.5.1'
+ kapt "androidx.lifecycle:lifecycle-common-java8:2.5.1"
+ implementation 'androidx.core:core-splashscreen:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
@@ -146,12 +144,19 @@ dependencies {
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
+ implementation "androidx.compose.ui:ui-viewbinding:$compose_version"
+ implementation 'androidx.compose.runtime:runtime-livedata:1.3.0-alpha02'
+ implementation 'com.google.accompanist:accompanist-drawablepainter:0.25.1'
//logging
implementation 'com.jakewharton.timber:timber:5.0.1'
+ implementation 'com.uxcam:uxcam:3.4.2@aar'
+ implementation 'com.amplitude:android-sdk:3.35.1'
+ implementation 'com.appsflyer:af-android-sdk:6.8.0'
+ implementation 'com.android.installreferrer:installreferrer:2.2'
// Firebase
- implementation platform('com.google.firebase:firebase-bom:28.4.0')
+ implementation platform('com.google.firebase:firebase-bom:30.3.2')
implementation 'com.google.firebase:firebase-crashlytics'
implementation 'com.google.firebase:firebase-analytics'
implementation 'com.google.firebase:firebase-messaging'
@@ -161,33 +166,31 @@ dependencies {
implementation 'com.google.android.play:core:1.10.3'
implementation 'com.google.firebase:firebase-perf'
implementation 'com.google.firebase:firebase-firestore-ktx'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4'
//auth
implementation 'com.google.android.gms:play-services-auth:20.2.0'
implementation 'com.google.firebase:firebase-auth-ktx'
- implementation 'com.amplitude:android-sdk:3.35.1'
implementation "com.squareup.okhttp3:okhttp:4.9.3"
- implementation "com.googlecode.libphonenumber:libphonenumber:8.12.50"
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53'
implementation "com.github.YarikSOffice:lingver:1.3.0"
- implementation 'com.appsflyer:af-android-sdk:6.6.1'
- implementation 'com.android.installreferrer:installreferrer:2.2'
implementation 'com.github.bumptech.glide:glide:4.13.2'
kapt 'com.github.bumptech.glide:compiler:4.13.2'
+ implementation "io.coil-kt:coil-compose:2.1.0"
- def roomVersion = "2.4.2"
- implementation "androidx.room:room-ktx:$roomVersion"
- implementation "androidx.room:room-runtime:$roomVersion"
- kapt "androidx.room:room-compiler:$roomVersion"
- androidTestImplementation "androidx.room:room-testing:$roomVersion"
+ implementation "androidx.room:room-ktx:$room_version"
+ implementation "androidx.room:room-runtime:$room_version"
+ kapt "androidx.room:room-compiler:$room_version"
+ androidTestImplementation "androidx.room:room-testing:$room_version"
implementation "androidx.core:core-ktx:1.8.0"
- implementation "io.grpc:grpc-okhttp:1.44.1"
+ implementation 'io.grpc:grpc-okhttp:1.48.1'
//di
- def koin_version= "3.1.5"
implementation "io.insert-koin:koin-android:$koin_version"
+ implementation "io.insert-koin:koin-androidx-compose:$koin_version"
// Tests
testImplementation 'junit:junit:4.13.2'
@@ -195,14 +198,9 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Hover SDK
- debugImplementation project(":hover.sdk")
- def sdk_version = "2.0.0-stax-1.11.0-pro"
-
- // UXCam
- implementation 'com.uxcam:uxcam:3.3.7@aar'
-
+ def sdk_version = "2.0.0-stax-1.12.14-pro"
releaseImplementation "com.hover:android-sdk:$sdk_version"
-
+ debugImplementation project(":hover.sdk")
debugImplementation 'com.android.volley:volley:1.2.1'
debugImplementation 'com.google.android.gms:play-services-analytics:18.0.1'
debugImplementation 'com.squareup.picasso:picasso:2.71828'
@@ -211,4 +209,5 @@ dependencies {
//compose
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
+
}
\ No newline at end of file
diff --git a/app/schemas/com.hover.stax.database.AppDatabase/32.json b/app/schemas/com.hover.stax.database.AppDatabase/32.json
index e9217f5f9..39868153d 100644
--- a/app/schemas/com.hover.stax.database.AppDatabase/32.json
+++ b/app/schemas/com.hover.stax.database.AppDatabase/32.json
@@ -2,11 +2,7 @@
"formatVersion": 1,
"database": {
"version": 32,
-<<<<<<< HEAD
- "identityHash": "7b995a5aacc2674bda5a1d44694c68b0",
-=======
"identityHash": "e9dc3dbe16f88faa4d9a646e8503ec05",
->>>>>>> development
"entities": [
{
"tableName": "channels",
@@ -130,11 +126,7 @@
},
{
"tableName": "stax_transactions",
-<<<<<<< HEAD
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT NOT NULL, `action_id` TEXT NOT NULL, `environment` INTEGER NOT NULL DEFAULT 0, `transaction_type` TEXT NOT NULL, `channel_id` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'pending', `category` TEXT, `initiated_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, `description` TEXT, `amount` REAL, `fee` REAL, `confirm_code` TEXT, `recipient_id` TEXT, `balance` TEXT, `counterparty` TEXT)",
-=======
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT NOT NULL, `action_id` TEXT NOT NULL, `environment` INTEGER NOT NULL DEFAULT 0, `transaction_type` TEXT NOT NULL, `channel_id` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'pending', `initiated_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, `description` TEXT, `amount` REAL, `fee` REAL, `confirm_code` TEXT, `recipient_id` TEXT, `category` TEXT, `counterparty` TEXT)",
->>>>>>> development
"fields": [
{
"fieldPath": "id",
@@ -181,15 +173,6 @@
"defaultValue": "'pending'"
},
{
-<<<<<<< HEAD
- "fieldPath": "category",
- "columnName": "category",
- "affinity": "TEXT",
- "notNull": false
- },
- {
-=======
->>>>>>> development
"fieldPath": "initiated_at",
"columnName": "initiated_at",
"affinity": "INTEGER",
@@ -234,13 +217,8 @@
"notNull": false
},
{
-<<<<<<< HEAD
- "fieldPath": "balance",
- "columnName": "balance",
-=======
"fieldPath": "category",
"columnName": "category",
->>>>>>> development
"affinity": "TEXT",
"notNull": false
},
@@ -505,8 +483,6 @@
},
"indices": [],
"foreignKeys": []
-<<<<<<< HEAD
-=======
},
{
"tableName": "accounts",
@@ -603,17 +579,12 @@
]
}
]
->>>>>>> development
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-<<<<<<< HEAD
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7b995a5aacc2674bda5a1d44694c68b0')"
-=======
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e9dc3dbe16f88faa4d9a646e8503ec05')"
->>>>>>> development
]
}
}
\ No newline at end of file
diff --git a/app/schemas/com.hover.stax.database.AppDatabase/33.json b/app/schemas/com.hover.stax.database.AppDatabase/33.json
index bf02ed1a7..52805a31b 100644
--- a/app/schemas/com.hover.stax.database.AppDatabase/33.json
+++ b/app/schemas/com.hover.stax.database.AppDatabase/33.json
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 33,
- "identityHash": "4ce93c73680a412f95390c6e41575c61",
+ "identityHash": "5da73f0f0303be4fe1af75bd8911fa71",
"entities": [
{
"tableName": "channels",
@@ -126,7 +126,7 @@
},
{
"tableName": "stax_transactions",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT NOT NULL, `action_id` TEXT NOT NULL, `environment` INTEGER NOT NULL DEFAULT 0, `transaction_type` TEXT NOT NULL, `channel_id` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'pending', `initiated_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, `description` TEXT, `amount` REAL, `fee` REAL, `confirm_code` TEXT, `recipient_id` TEXT, `category` TEXT, `account_id` INTEGER, `counterparty` TEXT)",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT NOT NULL, `action_id` TEXT NOT NULL, `environment` INTEGER NOT NULL DEFAULT 0, `transaction_type` TEXT NOT NULL, `channel_id` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'pending', `initiated_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, `description` TEXT, `amount` REAL, `fee` REAL, `confirm_code` TEXT, `recipient_id` TEXT, `category` TEXT, `counterparty` TEXT)",
"fields": [
{
"fieldPath": "id",
@@ -222,12 +222,6 @@
"affinity": "TEXT",
"notNull": false
},
- {
- "fieldPath": "accountId",
- "columnName": "account_id",
- "affinity": "INTEGER",
- "notNull": false
- },
{
"fieldPath": "counterparty",
"columnName": "counterparty",
@@ -492,7 +486,7 @@
},
{
"tableName": "accounts",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `alias` TEXT, `logo_url` TEXT NOT NULL, `account_no` TEXT, `channelId` INTEGER NOT NULL, `primary_color_hex` TEXT NOT NULL, `secondary_color_hex` TEXT NOT NULL, `isDefault` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `latestBalance` TEXT, `latestBalanceTimestamp` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(`channelId`) REFERENCES `channels`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `alias` TEXT NOT NULL, `logo_url` TEXT NOT NULL, `account_no` TEXT, `channelId` INTEGER NOT NULL, `primary_color_hex` TEXT NOT NULL, `secondary_color_hex` TEXT NOT NULL, `isDefault` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `latestBalance` TEXT, `latestBalanceTimestamp` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(`channelId`) REFERENCES `channels`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "name",
@@ -504,7 +498,7 @@
"fieldPath": "alias",
"columnName": "alias",
"affinity": "TEXT",
- "notNull": false
+ "notNull": true
},
{
"fieldPath": "logoUrl",
@@ -596,7 +590,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4ce93c73680a412f95390c6e41575c61')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5da73f0f0303be4fe1af75bd8911fa71')"
]
}
}
\ No newline at end of file
diff --git a/app/src/main/assets/channels-prod.json b/app/src/main/assets/channels-prod.json
index 4d7eee016..559e111cf 100644
--- a/app/src/main/assets/channels-prod.json
+++ b/app/src/main/assets/channels-prod.json
@@ -1212,7 +1212,7 @@
"menus_count": 2,
"custom_actions_count": 249,
"channel_actions_count": 5,
- "transactions_count": 5138,
+ "transactions_count": 5146,
"pending_percent": "unknown",
"bounties_count": 5,
"open_bounties_count": 5,
@@ -4594,7 +4594,7 @@
"menus_count": 29,
"custom_actions_count": 101,
"channel_actions_count": 4,
- "transactions_count": 5634,
+ "transactions_count": 5635,
"pending_percent": "unknown",
"bounties_count": 0,
"open_bounties_count": 0,
@@ -8419,7 +8419,7 @@
"menus_count": 17,
"custom_actions_count": 248,
"channel_actions_count": 6,
- "transactions_count": 2666,
+ "transactions_count": 2668,
"pending_percent": "unknown",
"bounties_count": 6,
"open_bounties_count": 3,
@@ -9866,7 +9866,7 @@
"menus_count": 51,
"custom_actions_count": 1007,
"channel_actions_count": 18,
- "transactions_count": 8898,
+ "transactions_count": 8900,
"pending_percent": "unknown",
"bounties_count": 12,
"open_bounties_count": 11,
@@ -13419,7 +13419,7 @@
"menus_count": 25,
"custom_actions_count": 218,
"channel_actions_count": 8,
- "transactions_count": 474029,
+ "transactions_count": 479689,
"pending_percent": "unknown",
"bounties_count": 10,
"open_bounties_count": 7,
@@ -14036,7 +14036,7 @@
"menus_count": 46,
"custom_actions_count": 191,
"channel_actions_count": 27,
- "transactions_count": 4260,
+ "transactions_count": 4265,
"pending_percent": "unknown",
"bounties_count": 22,
"open_bounties_count": 5,
@@ -14701,7 +14701,7 @@
"menus_count": 34,
"custom_actions_count": 243,
"channel_actions_count": 11,
- "transactions_count": 559965,
+ "transactions_count": 565849,
"pending_percent": "unknown",
"bounties_count": 6,
"open_bounties_count": 6,
@@ -17817,7 +17817,7 @@
"menus_count": 38,
"custom_actions_count": 6,
"channel_actions_count": 21,
- "transactions_count": 1399,
+ "transactions_count": 1404,
"pending_percent": "unknown",
"bounties_count": 19,
"open_bounties_count": 10,
@@ -28586,7 +28586,7 @@
"menus_count": 70,
"custom_actions_count": 212,
"channel_actions_count": 39,
- "transactions_count": 531333,
+ "transactions_count": 532331,
"pending_percent": "unknown",
"bounties_count": 27,
"open_bounties_count": 6,
@@ -35644,7 +35644,7 @@
"menus_count": 33,
"custom_actions_count": 0,
"channel_actions_count": 22,
- "transactions_count": 556,
+ "transactions_count": 557,
"pending_percent": "unknown",
"bounties_count": 9,
"open_bounties_count": 5,
@@ -39938,7 +39938,7 @@
"menus_count": 21,
"custom_actions_count": 0,
"channel_actions_count": 10,
- "transactions_count": 687,
+ "transactions_count": 689,
"pending_percent": "unknown",
"bounties_count": 4,
"open_bounties_count": 1,
@@ -40124,7 +40124,7 @@
"menus_count": 7,
"custom_actions_count": 405,
"channel_actions_count": 1,
- "transactions_count": 14697,
+ "transactions_count": 14713,
"pending_percent": "unknown",
"bounties_count": 0,
"open_bounties_count": 0,
@@ -48915,7 +48915,7 @@
"menus_count": 57,
"custom_actions_count": 27,
"channel_actions_count": 22,
- "transactions_count": 5717,
+ "transactions_count": 5718,
"pending_percent": "unknown",
"bounties_count": 17,
"open_bounties_count": 6,
@@ -50097,7 +50097,7 @@
"menus_count": 0,
"custom_actions_count": 0,
"channel_actions_count": 3,
- "transactions_count": 7,
+ "transactions_count": 11,
"pending_percent": "unknown",
"bounties_count": 3,
"open_bounties_count": 3,
@@ -53992,7 +53992,7 @@
"menus_count": 38,
"custom_actions_count": 160,
"channel_actions_count": 18,
- "transactions_count": 26367,
+ "transactions_count": 26369,
"pending_percent": "unknown",
"bounties_count": 17,
"open_bounties_count": 9,
@@ -54818,7 +54818,7 @@
"menus_count": 26,
"custom_actions_count": 3,
"channel_actions_count": 18,
- "transactions_count": 249,
+ "transactions_count": 251,
"pending_percent": "unknown",
"bounties_count": 4,
"open_bounties_count": 1,
@@ -59045,7 +59045,7 @@
"menus_count": 22,
"custom_actions_count": 0,
"channel_actions_count": 5,
- "transactions_count": 600,
+ "transactions_count": 601,
"pending_percent": "unknown",
"bounties_count": 4,
"open_bounties_count": 1,
@@ -59538,7 +59538,7 @@
"menus_count": 35,
"custom_actions_count": 13,
"channel_actions_count": 12,
- "transactions_count": 6795,
+ "transactions_count": 6798,
"pending_percent": "unknown",
"bounties_count": 9,
"open_bounties_count": 1,
@@ -60567,7 +60567,7 @@
"menus_count": 26,
"custom_actions_count": 29,
"channel_actions_count": 11,
- "transactions_count": 4954,
+ "transactions_count": 4959,
"pending_percent": "unknown",
"bounties_count": 11,
"open_bounties_count": 8,
@@ -65066,7 +65066,7 @@
"menus_count": 28,
"custom_actions_count": 0,
"channel_actions_count": 8,
- "transactions_count": 637,
+ "transactions_count": 638,
"pending_percent": "unknown",
"bounties_count": 6,
"open_bounties_count": 1,
@@ -67849,7 +67849,7 @@
"menus_count": 19,
"custom_actions_count": 0,
"channel_actions_count": 9,
- "transactions_count": 11369,
+ "transactions_count": 11371,
"pending_percent": "unknown",
"bounties_count": 9,
"open_bounties_count": 1,
@@ -67923,7 +67923,7 @@
"menus_count": 25,
"custom_actions_count": 0,
"channel_actions_count": 6,
- "transactions_count": 736,
+ "transactions_count": 738,
"pending_percent": "unknown",
"bounties_count": 2,
"open_bounties_count": 0,
diff --git a/app/src/main/assets/channels-staging.json b/app/src/main/assets/channels-staging.json
index a7bd5ea6c..07d2698e2 100644
--- a/app/src/main/assets/channels-staging.json
+++ b/app/src/main/assets/channels-staging.json
@@ -28531,7 +28531,7 @@
"menus_count": 70,
"custom_actions_count": 212,
"channel_actions_count": 39,
- "transactions_count": 519505,
+ "transactions_count": 519506,
"pending_percent": "unknown",
"bounties_count": 27,
"open_bounties_count": 6,
diff --git a/app/src/main/java/com/hover/stax/ApplicationInstance.kt b/app/src/main/java/com/hover/stax/ApplicationInstance.kt
index 6b9477c4d..be1207da2 100644
--- a/app/src/main/java/com/hover/stax/ApplicationInstance.kt
+++ b/app/src/main/java/com/hover/stax/ApplicationInstance.kt
@@ -8,9 +8,7 @@ import com.appsflyer.AppsFlyerProperties
import com.google.firebase.FirebaseApp
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.hover.sdk.api.Hover
-import com.hover.stax.database.appModule
-import com.hover.stax.database.dataModule
-import com.hover.stax.database.networkModule
+import com.hover.stax.di.*
import com.hover.stax.utils.network.NetworkMonitor
import com.yariksoffice.lingver.Lingver
import org.koin.android.ext.koin.androidContext
@@ -45,7 +43,7 @@ class ApplicationInstance : Application() {
private fun initDI() {
startKoin {
androidContext(this@ApplicationInstance)
- modules(listOf(appModule, dataModule, networkModule))
+ modules(appModule + dataModule + networkModule + useCases + repositories)
}
}
@@ -75,7 +73,7 @@ class ApplicationInstance : Application() {
AppsFlyerLib.getInstance().apply {
init(getString(R.string.appsflyer_key), conversionListener, this@ApplicationInstance)
- if(AppsFlyerProperties.getInstance().getString(AppsFlyerProperties.APP_USER_ID) == null)
+ if (AppsFlyerProperties.getInstance().getString(AppsFlyerProperties.APP_USER_ID) == null)
setCustomerUserId(Hover.getDeviceId(this@ApplicationInstance))
start(this@ApplicationInstance)
diff --git a/app/src/main/java/com/hover/stax/RoutingActivity.kt b/app/src/main/java/com/hover/stax/RoutingActivity.kt
index 0d45b4a15..411d2561c 100644
--- a/app/src/main/java/com/hover/stax/RoutingActivity.kt
+++ b/app/src/main/java/com/hover/stax/RoutingActivity.kt
@@ -23,20 +23,22 @@ import com.hover.sdk.api.Hover
import com.hover.stax.addChannels.ChannelsViewModel
import com.hover.stax.channels.ImportChannelsWorker
import com.hover.stax.channels.UpdateChannelsWorker
-import com.hover.stax.financialTips.FinancialTipsFragment
import com.hover.stax.home.MainActivity
import com.hover.stax.hover.PERM_ACTIVITY
import com.hover.stax.inapp_banner.BannerUtils
import com.hover.stax.notifications.PushNotificationTopicsInterface
import com.hover.stax.onboarding.OnBoardingActivity
+import com.hover.stax.presentation.financial_tips.FinancialTipsFragment
import com.hover.stax.requests.REQUEST_LINK
import com.hover.stax.schedules.ScheduleWorker
import com.hover.stax.settings.BiometricChecker
+import com.hover.stax.transfers.STAX_PREFIX
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.UIHelper
import com.hover.stax.utils.Utils
import com.uxcam.OnVerificationListener
import com.uxcam.UXCam
+import com.uxcam.datamodel.UXConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.json.JSONException
@@ -46,7 +48,6 @@ import timber.log.Timber
const val FRAGMENT_DIRECT = "fragment_direct"
const val FROM_FCM = "from_notification"
-const val VARIANT = "variant"
class RoutingActivity : AppCompatActivity(), BiometricChecker.AuthListener, PushNotificationTopicsInterface {
@@ -129,17 +130,26 @@ class RoutingActivity : AppCompatActivity(), BiometricChecker.AuthListener, Push
setConfigSettingsAsync(configSettings)
setDefaultsAsync(R.xml.remote_config_default)
fetchAndActivate().addOnCompleteListener {
- val variant = remoteConfig.getString("onboarding_variant")
- Utils.saveString(VARIANT, variant, this@RoutingActivity)
-
+ fetchConfigs(remoteConfig)
+ validateUser()
+ }.addOnFailureListener {
validateUser()
}
}
}
+ private fun fetchConfigs(remoteConfig: FirebaseRemoteConfig) {
+ val staxPrefix = remoteConfig.getString(STAX_PREFIX)
+ Utils.saveString(STAX_PREFIX, staxPrefix, this)
+ }
+
private fun initUxCam() {
if (!BuildConfig.DEBUG) {
- UXCam.startWithKey(getString(R.string.uxcam_key))
+ val config = UXConfig.Builder(getString(R.string.uxcam_key))
+ .enableAutomaticScreenNameTagging(false)
+ .enableImprovedScreenCapture(true)
+ .build()
+ UXCam.startWithConfiguration(config)
UXCam.addVerificationListener(object : OnVerificationListener {
override fun onVerificationSuccess() {
@@ -164,7 +174,7 @@ class RoutingActivity : AppCompatActivity(), BiometricChecker.AuthListener, Push
}
- private fun registerUXCamPushNotification(){
+ private fun registerUXCamPushNotification() {
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
return@OnCompleteListener
@@ -260,7 +270,7 @@ class RoutingActivity : AppCompatActivity(), BiometricChecker.AuthListener, Push
finish()
}
- override fun onAuthError(error: String) = runOnUiThread { UIHelper.flashMessage(this, getString(R.string.toast_error_auth)) }
+ override fun onAuthError(error: String) = runOnUiThread { UIHelper.flashAndReportMessage(this, getString(R.string.toast_error_auth)) }
override fun onAuthSuccess(action: HoverAction?) = chooseNavigation(intent)
diff --git a/app/src/main/java/com/hover/stax/accounts/AccountDetailFragment.kt b/app/src/main/java/com/hover/stax/accounts/AccountDetailFragment.kt
index 57def3f3f..a4e8446eb 100644
--- a/app/src/main/java/com/hover/stax/accounts/AccountDetailFragment.kt
+++ b/app/src/main/java/com/hover/stax/accounts/AccountDetailFragment.kt
@@ -15,9 +15,10 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.hover.sdk.actions.HoverAction
import com.hover.stax.R
-import com.hover.stax.balances.BalanceAdapter
-import com.hover.stax.balances.BalancesViewModel
+import com.hover.stax.presentation.home.BalancesViewModel
import com.hover.stax.databinding.FragmentAccountBinding
+import com.hover.stax.domain.model.Account
+import com.hover.stax.domain.model.PLACEHOLDER
import com.hover.stax.futureTransactions.FutureViewModel
import com.hover.stax.futureTransactions.RequestsAdapter
import com.hover.stax.futureTransactions.ScheduledAdapter
@@ -34,7 +35,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
class AccountDetailFragment : Fragment(), TransactionHistoryAdapter.SelectListener, ScheduledAdapter.SelectListener,
- RequestsAdapter.SelectListener, BalanceAdapter.BalanceListener {
+ RequestsAdapter.SelectListener {
private val viewModel: AccountDetailViewModel by sharedViewModel()
private val balancesViewModel: BalancesViewModel by sharedViewModel()
@@ -72,7 +73,7 @@ class AccountDetailFragment : Fragment(), TransactionHistoryAdapter.SelectListen
binding.balanceCard.root.cardElevation = 0F
binding.balanceCard.balanceChannelName.setTextColor(ContextCompat.getColor(requireActivity(), R.color.offWhite))
binding.balanceCard.balanceAmount.setTextColor(ContextCompat.getColor(requireActivity(), R.color.offWhite))
- binding.balanceCard.balanceRefreshIcon.setOnClickListener { onTapRefresh(viewModel.account.value) }
+ binding.balanceCard.balanceRefreshIcon.setOnClickListener { onTapBalanceRefresh(viewModel.account.value) }
}
private fun setUpManage() {
@@ -144,7 +145,7 @@ class AccountDetailFragment : Fragment(), TransactionHistoryAdapter.SelectListen
} else binding.balanceCard.balanceSubtitle.text = getString(R.string.refresh_balance_desc)
binding.feesDescription.text = getString(R.string.fees_label, acct.name)
- binding.detailsCard.officialName.text = if(acct.name == PLACEHOLDER) acct.alias else acct.name
+ binding.detailsCard.officialName.text = if(acct.name.contains(PLACEHOLDER)) acct.alias else acct.name
binding.manageCard.nicknameInput.setText(acct.alias, false)
binding.manageCard.accountNumberInput.setText(acct.accountNo, false)
@@ -177,20 +178,18 @@ class AccountDetailFragment : Fragment(), TransactionHistoryAdapter.SelectListen
}
private fun observeBalanceCheck() {
- collectLatestLifecycleFlow(balancesViewModel.balanceAction) {
+ collectLifecycleFlow(balancesViewModel.balanceAction) {
attemptCallHover(viewModel.account.value, it)
}
}
- override fun onTapRefresh(account: Account?) {
+ private fun onTapBalanceRefresh(account: Account?) {
account?.let {
AnalyticsUtil.logAnalyticsEvent(getString(R.string.refresh_balance_single), requireContext())
balancesViewModel.requestBalance(account)
}
}
- override fun onTapDetail(accountId: Int) { }
-
private fun attemptCallHover(account: Account?, action: HoverAction?) {
action?.let { account?.let { callHover(account, action) } }
}
@@ -212,7 +211,7 @@ class AccountDetailFragment : Fragment(), TransactionHistoryAdapter.SelectListen
private fun removeAccount(account: Account) {
viewModel.removeAccount(account)
NavHostFragment.findNavController(this).popBackStack()
- UIHelper.flashMessage(requireActivity(), resources.getString(R.string.toast_confirm_acctremoved))
+ UIHelper.flashAndReportMessage(requireActivity(), resources.getString(R.string.toast_confirm_acctremoved))
}
private fun initRecyclerViews() {
@@ -273,4 +272,6 @@ class AccountDetailFragment : Fragment(), TransactionHistoryAdapter.SelectListen
_binding = null
}
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/accounts/AccountDetailViewModel.kt b/app/src/main/java/com/hover/stax/accounts/AccountDetailViewModel.kt
index 8c741dc8c..cc86ede6c 100644
--- a/app/src/main/java/com/hover/stax/accounts/AccountDetailViewModel.kt
+++ b/app/src/main/java/com/hover/stax/accounts/AccountDetailViewModel.kt
@@ -3,9 +3,11 @@ package com.hover.stax.accounts
import android.app.Application
import androidx.lifecycle.*
import com.hover.sdk.actions.HoverAction
-import com.hover.stax.actions.ActionRepo
+import com.hover.stax.data.local.actions.ActionRepo
import com.hover.stax.channels.Channel
-import com.hover.stax.channels.ChannelRepo
+import com.hover.stax.data.local.channels.ChannelRepo
+import com.hover.stax.data.local.accounts.AccountRepo
+import com.hover.stax.domain.model.Account
import com.hover.stax.transactions.StaxTransaction
import com.hover.stax.transactions.TransactionHistory
import com.hover.stax.transactions.TransactionRepo
@@ -15,7 +17,8 @@ import java.util.*
class AccountDetailViewModel(val application: Application, val repo: AccountRepo, private val transactionRepo: TransactionRepo,
- private val channelRepo: ChannelRepo, val actionRepo: ActionRepo) : ViewModel() {
+ private val channelRepo: ChannelRepo, val actionRepo: ActionRepo
+) : ViewModel() {
private val id = MutableLiveData()
var account: LiveData = MutableLiveData()
@@ -45,13 +48,13 @@ class AccountDetailViewModel(val application: Application, val repo: AccountRepo
private fun loadFeesThisYear(id: Int): LiveData? = transactionRepo.getFees(id, calendar.get(Calendar.YEAR))
- fun updateAccountName(newName: String) = viewModelScope.launch {
+ fun updateAccountName(newName: String) = viewModelScope.launch(Dispatchers.IO) {
val a = account.value!!
a.alias = newName
repo.update(a)
}
- fun updateAccountNumber(newNumber: String) = viewModelScope.launch {
+ fun updateAccountNumber(newNumber: String) = viewModelScope.launch(Dispatchers.IO) {
val a = account.value!!
a.accountNo = newNumber
repo.update(a)
@@ -77,6 +80,7 @@ class AccountDetailViewModel(val application: Application, val repo: AccountRepo
}
repo.delete(account)
+ transactionRepo.deleteAccountTransactions(account.id)
val accounts = repo.getAllAccounts()
val changeDefault = account.isDefault
@@ -92,5 +96,6 @@ class AccountDetailViewModel(val application: Application, val repo: AccountRepo
}
channelRepo.update(channelsToUpdate.toList())
+
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/accounts/AccountDropdown.kt b/app/src/main/java/com/hover/stax/accounts/AccountDropdown.kt
index 367c4dc10..5316d12bd 100644
--- a/app/src/main/java/com/hover/stax/accounts/AccountDropdown.kt
+++ b/app/src/main/java/com/hover/stax/accounts/AccountDropdown.kt
@@ -12,12 +12,10 @@ import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.hover.sdk.actions.HoverAction
import com.hover.stax.R
-import com.hover.stax.actions.ActionSelect
+import com.hover.stax.domain.model.Account
import com.hover.stax.utils.UIHelper
import com.hover.stax.views.StaxDropdownLayout
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
-import timber.log.Timber
class AccountDropdown(context: Context, attributeSet: AttributeSet) : StaxDropdownLayout(context, attributeSet) {
@@ -74,15 +72,16 @@ class AccountDropdown(context: Context, attributeSet: AttributeSet) : StaxDropdo
}
}
- private fun updateChoices(accounts: MutableList) {
+ private fun updateChoices(accounts: List) {
if (highlightedAccount == null) setDropdownValue(null)
- accounts.add(Account("Add account"))
val adapter = AccountDropdownAdapter(accounts, context)
autoCompleteTextView.apply {
setAdapter(adapter)
setOnItemClickListener { parent, _, position, _ -> onSelect(parent.getItemAtPosition(position) as Account) }
}
- onSelect(accounts.firstOrNull { it.isDefault })
+
+ if (accounts.firstOrNull()?.id != 0)
+ onSelect(accounts.firstOrNull { it.isDefault })
}
@@ -100,8 +99,8 @@ class AccountDropdown(context: Context, attributeSet: AttributeSet) : StaxDropdo
with(viewModel) {
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- accounts.collect {
- accountUpdate(it)
+ accountList.collect {
+ accountUpdate(it.accounts.plus(Account("Add account")))
}
}
}
@@ -130,7 +129,7 @@ class AccountDropdown(context: Context, attributeSet: AttributeSet) : StaxDropdo
} else if (actions.isNotEmpty() && actions.size == 1)
addInfoMessage(actions.first())
else if (viewModel.activeAccount.value != null && showSelected)
- setState(helperText, SUCCESS)
+ setState(null, SUCCESS)
}
private fun addInfoMessage(action: HoverAction) {
diff --git a/app/src/main/java/com/hover/stax/accounts/AccountDropdownAdapter.kt b/app/src/main/java/com/hover/stax/accounts/AccountDropdownAdapter.kt
index fd1ad54b7..b8f596750 100644
--- a/app/src/main/java/com/hover/stax/accounts/AccountDropdownAdapter.kt
+++ b/app/src/main/java/com/hover/stax/accounts/AccountDropdownAdapter.kt
@@ -7,7 +7,7 @@ import android.view.ViewGroup
import android.widget.ArrayAdapter
import com.hover.stax.R
import com.hover.stax.databinding.StaxSpinnerItemWithLogoBinding
-import com.hover.stax.utils.UIHelper
+import com.hover.stax.domain.model.Account
import com.hover.stax.utils.UIHelper.loadImage
class AccountDropdownAdapter(val accounts: List, context: Context) : ArrayAdapter(context, 0, accounts) {
@@ -41,7 +41,7 @@ class AccountDropdownAdapter(val accounts: List, context: Context) : Ar
fun setAccount(account: Account) {
binding.serviceItemNameId.text = account.alias
- if(account.logoUrl.isEmpty())
+ if (account.logoUrl.isEmpty())
binding.serviceItemImageId.loadImage(binding.root.context, R.drawable.ic_add)
else
binding.serviceItemImageId.loadImage(binding.root.context, account.logoUrl)
diff --git a/app/src/main/java/com/hover/stax/accounts/AccountsAdapter.kt b/app/src/main/java/com/hover/stax/accounts/AccountsAdapter.kt
index 515c20188..4476d10ec 100644
--- a/app/src/main/java/com/hover/stax/accounts/AccountsAdapter.kt
+++ b/app/src/main/java/com/hover/stax/accounts/AccountsAdapter.kt
@@ -5,6 +5,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hover.stax.databinding.StaxSpinnerItemWithLogoBinding
import com.hover.stax.R
+import com.hover.stax.domain.model.Account
import com.hover.stax.utils.GlideApp
class AccountsAdapter(var accounts: List) : RecyclerView.Adapter() {
diff --git a/app/src/main/java/com/hover/stax/accounts/AccountsViewModel.kt b/app/src/main/java/com/hover/stax/accounts/AccountsViewModel.kt
index e7c1175d6..f4c968bc1 100644
--- a/app/src/main/java/com/hover/stax/accounts/AccountsViewModel.kt
+++ b/app/src/main/java/com/hover/stax/accounts/AccountsViewModel.kt
@@ -8,22 +8,25 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.hover.sdk.actions.HoverAction
import com.hover.stax.R
-import com.hover.stax.actions.ActionRepo
-import com.hover.stax.bonus.BonusRepo
+import com.hover.stax.data.local.accounts.AccountRepo
+import com.hover.stax.data.local.actions.ActionRepo
+import com.hover.stax.domain.model.Account
+import com.hover.stax.domain.model.PLACEHOLDER
import com.hover.stax.schedules.Schedule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-class AccountsViewModel(application: Application, val repo: AccountRepo, val actionRepo: ActionRepo, private val bonusRepo: BonusRepo) : AndroidViewModel(application),
+class AccountsViewModel(application: Application, val repo: AccountRepo, val actionRepo: ActionRepo) : AndroidViewModel(application),
AccountDropdown.HighlightListener {
- private val _accounts = MutableStateFlow>(emptyList())
- val accounts: StateFlow> = _accounts
+ private val _accounts = MutableStateFlow(AccountList())
+ val accountList = _accounts.asStateFlow()
+
val activeAccount = MutableLiveData()
private var type = MutableLiveData()
@@ -42,10 +45,10 @@ class AccountsViewModel(application: Application, val repo: AccountRepo, val act
}
private fun fetchAccounts() = viewModelScope.launch {
- repo.getAccounts().collect {
- _accounts.value = it
+ repo.getAccounts().collect { a ->
+ _accounts.update { it.copy(accounts = a) }
- setActiveAccountIfNull(it)
+ setActiveAccountIfNull(a)
}
}
@@ -63,17 +66,15 @@ class AccountsViewModel(application: Application, val repo: AccountRepo, val act
private fun loadActions(type: String?) {
if (type == null || activeAccount.value == null) return
- if (accounts.value.isEmpty()) return
+ if (accountList.value.accounts.isEmpty()) return
+
loadActions(activeAccount.value!!, type)
}
private fun loadActions(account: Account?) {
if (account == null || type.value.isNullOrEmpty()) return
- if (type.value == HoverAction.AIRTIME)
- checkForBonus(account)
- else
- loadActions(account, type.value!!)
+ loadActions(account, type.value!!)
}
private fun loadActions(account: Account, t: String) = viewModelScope.launch(Dispatchers.IO) {
@@ -83,21 +84,7 @@ class AccountsViewModel(application: Application, val repo: AccountRepo, val act
)
}
- private fun loadActions(channelId: Int, t: String = HoverAction.AIRTIME) = viewModelScope.launch(Dispatchers.IO) {
- val actions = actionRepo.getActions(channelId, t)
- channelActions.postValue(actions)
- }
-
- private fun checkForBonus(account: Account) = viewModelScope.launch(Dispatchers.IO) {
- val bonus = bonusRepo.getBonusByUserChannel(account.channelId)
-
- if (bonus != null)
- loadActions(bonus.purchaseChannel)
- else
- loadActions(account, type.value!!)
- }
-
- fun setActiveAccount(accountId: Int?) = accountId?.let { activeAccount.postValue(accounts.value.find { it.id == accountId }) }
+ fun setActiveAccount(accountId: Int?) = accountId?.let { activeAccount.postValue(accountList.value.accounts.find { it.id == accountId }) }
fun setActiveAccountFromChannel(userChannelId: Int) = viewModelScope.launch {
repo.getAccounts().collect { accounts ->
@@ -117,19 +104,19 @@ class AccountsViewModel(application: Application, val repo: AccountRepo, val act
}
}
- fun isValidAccount(): Boolean = activeAccount.value!!.name != PLACEHOLDER
+ fun isValidAccount(): Boolean = !activeAccount.value!!.name.contains(PLACEHOLDER)
fun view(s: Schedule) {
setType(s.type)
}
fun reset() {
- activeAccount.value = accounts.value.firstOrNull { it.isDefault }
+ activeAccount.value = accountList.value.accounts.firstOrNull { it.isDefault }
}
fun setDefaultAccount(account: Account) = viewModelScope.launch(Dispatchers.IO) {
- if (accounts.value.isNotEmpty()) {
- val accts = accounts.value
+ if (accountList.value.accounts.isNotEmpty()) {
+ val accts = accountList.value.accounts
//remove current default account
val current: Account? = accts.firstOrNull { it.isDefault }
@@ -150,4 +137,6 @@ class AccountsViewModel(application: Application, val repo: AccountRepo, val act
activeAccount.postValue(account)
}
-}
\ No newline at end of file
+}
+
+data class AccountList(val accounts: List = emptyList())
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/accounts/ChannelWithAccounts.kt b/app/src/main/java/com/hover/stax/accounts/ChannelWithAccounts.kt
index da85c4758..fe40325e3 100644
--- a/app/src/main/java/com/hover/stax/accounts/ChannelWithAccounts.kt
+++ b/app/src/main/java/com/hover/stax/accounts/ChannelWithAccounts.kt
@@ -3,6 +3,7 @@ package com.hover.stax.accounts
import androidx.room.Embedded
import androidx.room.Relation
import com.hover.stax.channels.Channel
+import com.hover.stax.domain.model.Account
data class ChannelWithAccounts(
@Embedded
diff --git a/app/src/main/java/com/hover/stax/actions/ActionSelectViewModel.kt b/app/src/main/java/com/hover/stax/actions/ActionSelectViewModel.kt
index 03779542c..de84e9fdf 100644
--- a/app/src/main/java/com/hover/stax/actions/ActionSelectViewModel.kt
+++ b/app/src/main/java/com/hover/stax/actions/ActionSelectViewModel.kt
@@ -5,10 +5,11 @@ import android.content.Context
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
-import com.hover.stax.accounts.ACCOUNT_NAME
+
import com.hover.sdk.actions.HoverAction
import com.hover.sdk.actions.HoverAction.*
import com.hover.stax.R
+import com.hover.stax.domain.model.ACCOUNT_NAME
import java.util.LinkedHashMap
const val RECIPIENT_INSTITUTION = "recipientInstitution"
diff --git a/app/src/main/java/com/hover/stax/addChannels/AddChannelsFragment.kt b/app/src/main/java/com/hover/stax/addChannels/AddChannelsFragment.kt
index 961a61a68..d51150b17 100644
--- a/app/src/main/java/com/hover/stax/addChannels/AddChannelsFragment.kt
+++ b/app/src/main/java/com/hover/stax/addChannels/AddChannelsFragment.kt
@@ -22,7 +22,7 @@ import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager
import com.hover.sdk.sims.SimInfo
import com.hover.stax.R
-import com.hover.stax.accounts.Account
+import com.hover.stax.domain.model.Account
import com.hover.stax.accounts.AccountsAdapter
import com.hover.stax.bonus.BonusViewModel
import com.hover.stax.channels.Channel
@@ -120,10 +120,6 @@ class AddChannelsFragment : Fragment(), ChannelsAdapter.SelectListener, CountryA
}
private fun fillUpChannelLists() {
- binding.selectedList.apply {
- layoutManager = UIHelper.setMainLinearManagers(requireContext())
- }
-
binding.channelsList.apply {
layoutManager = UIHelper.setMainLinearManagers(requireContext())
adapter = selectAdapter
@@ -175,12 +171,12 @@ class AddChannelsFragment : Fragment(), ChannelsAdapter.SelectListener, CountryA
binding.channelsListCard.hideProgressIndicator()
showSelected(accounts.isNotEmpty())
- if (accounts.isNotEmpty())
- binding.selectedList.adapter = AccountsAdapter(accounts)
+// if (accounts.isNotEmpty())
+// binding.selectedList.adapter = AccountsAdapter(accounts)
}
private fun showSelected(visible: Boolean) {
- binding.selectedChannelsCard.visibility = if (visible) VISIBLE else GONE
+// binding.selectedChannelsCard.visibility = if (visible) VISIBLE else GONE
binding.channelsListCard.setBackButtonVisibility(if (visible) GONE else VISIBLE)
}
diff --git a/app/src/main/java/com/hover/stax/addChannels/ChannelsViewModel.kt b/app/src/main/java/com/hover/stax/addChannels/ChannelsViewModel.kt
index 124b99e79..8284a4ff5 100644
--- a/app/src/main/java/com/hover/stax/addChannels/ChannelsViewModel.kt
+++ b/app/src/main/java/com/hover/stax/addChannels/ChannelsViewModel.kt
@@ -13,14 +13,15 @@ import com.hover.sdk.api.ActionApi
import com.hover.sdk.api.Hover
import com.hover.sdk.sims.SimInfo
import com.hover.stax.R
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.AccountRepo
-import com.hover.stax.accounts.PLACEHOLDER
-import com.hover.stax.actions.ActionRepo
-import com.hover.stax.bonus.BonusRepo
+import com.hover.stax.domain.model.Account
+import com.hover.stax.data.local.accounts.AccountRepo
+import com.hover.stax.data.local.actions.ActionRepo
+import com.hover.stax.data.local.bonus.BonusRepo
import com.hover.stax.channels.Channel
-import com.hover.stax.channels.ChannelRepo
+import com.hover.stax.channels.ChannelUtil.updateChannels
+import com.hover.stax.data.local.channels.ChannelRepo
import com.hover.stax.countries.CountryAdapter
+import com.hover.stax.domain.model.PLACEHOLDER
import com.hover.stax.notifications.PushNotificationTopicsInterface
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.Utils
@@ -28,7 +29,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel as KChannel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import org.json.JSONObject
@@ -182,7 +182,7 @@ class ChannelsViewModel(application: Application, val repo: ChannelRepo, val acc
val defaultAccount = accountRepo.getDefaultAccount()
val accounts = channels.mapIndexed { index, channel ->
- val accountName: String = if (getFetchAccountAction(channel.id) == null) channel.name else PLACEHOLDER //placeholder alias for easier identification later
+ val accountName: String = if (getFetchAccountAction(channel.id) == null) channel.name else channel.name.plus(PLACEHOLDER) //ensures uniqueness of name due to db constraints
Account(
accountName, channel.name, channel.logoUrl, channel.accountNo, channel.id, channel.countryAlpha2,
channel.id, channel.primaryColorHex, channel.secondaryColorHex, defaultAccount == null && index == 0
@@ -192,8 +192,8 @@ class ChannelsViewModel(application: Application, val repo: ChannelRepo, val acc
ActionApi.scheduleActionConfigUpdate(it.countryAlpha2, 24, getApplication())
}
- channels.onEach { it.selected = true }.also { repo.update(it) }
val accountIds = accountRepo.insert(accounts)
+ channels.onEach { it.selected = true }.also { repo.update(it) }
promptBalanceCheck(accountIds.first().toInt())
}
diff --git a/app/src/main/java/com/hover/stax/balances/BalanceAdapter.kt b/app/src/main/java/com/hover/stax/balances/BalanceAdapter.kt
deleted file mode 100644
index b8376c00b..000000000
--- a/app/src/main/java/com/hover/stax/balances/BalanceAdapter.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.hover.stax.balances
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.core.content.ContextCompat
-import androidx.core.graphics.drawable.DrawableCompat
-import androidx.recyclerview.widget.RecyclerView
-import com.hover.stax.R
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.DUMMY
-import com.hover.stax.databinding.BalanceItemBinding
-import com.hover.stax.utils.DateUtils
-import com.hover.stax.utils.UIHelper
-import com.hover.stax.utils.Utils
-import timber.log.Timber
-
-
-class BalanceAdapter(val accounts: List, val balanceListener: BalanceListener?) : RecyclerView.Adapter() {
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BalancesViewHolder {
- val binding = BalanceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- return BalancesViewHolder(binding)
- }
-
- override fun onBindViewHolder(holder: BalancesViewHolder, position: Int) {
- val account = accounts[holder.adapterPosition]
- holder.bindItems(account, holder)
- }
-
- override fun getItemCount(): Int = accounts.size
-
- private fun setColors(holder: BalancesViewHolder, primary: Int, secondary: Int) {
- holder.binding.root.setCardBackgroundColor(primary)
- holder.binding.balanceSubtitle.setTextColor(secondary)
- holder.binding.balanceAmount.setTextColor(secondary)
- holder.binding.balanceChannelName.setTextColor(secondary)
- holder.binding.balanceRefreshIcon.setColorFilter(secondary)
- }
-
- private fun setColorForEmptyAmount(holder: BalancesViewHolder, secondary: Int) {
- var drawable = ContextCompat.getDrawable(holder.itemView.context, R.drawable.ic_remove)
-
- if (drawable != null) {
- drawable = DrawableCompat.wrap(drawable)
- DrawableCompat.setTint(drawable.mutate(), secondary)
- holder.binding.balanceAmount.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null)
- }
- }
-
- inner class BalancesViewHolder(val binding: BalanceItemBinding) : RecyclerView.ViewHolder(binding.root) {
-
- fun bindItems(account: Account, holder: BalancesViewHolder) {
- UIHelper.setTextUnderline(binding.balanceChannelName, account.alias)
-
- binding.balanceSubtitle.visibility = View.GONE
-
- when {
- account.latestBalance != null -> {
- binding.balanceSubtitle.visibility = View.VISIBLE
- binding.balanceSubtitle.text = DateUtils.humanFriendlyDateTime(account.latestBalanceTimestamp)
- binding.balanceAmount.text = Utils.formatAmount(account.latestBalance!!)
- }
- account.latestBalance == null -> {
- binding.balanceAmount.text = "-"
- binding.balanceSubtitle.visibility = View.VISIBLE
- binding.balanceSubtitle.text = itemView.context.getString(R.string.refresh_balance_desc)
- }
- else -> {
- binding.balanceAmount.text = ""
- setColorForEmptyAmount(holder, UIHelper.getColor(account.secondaryColorHex, false, binding.root.context))
- }
- }
-
- setColors(
- holder, UIHelper.getColor(account.primaryColorHex, true, holder.itemView.context),
- UIHelper.getColor(account.secondaryColorHex, false, holder.itemView.context)
- )
-
- if (account.id == DUMMY) {
- holder.binding.balanceSubtitle.visibility = View.GONE
- holder.binding.balanceRefreshIcon.setImageResource(R.drawable.ic_add_icon_24)
- }
-
- binding.root.setOnClickListener {
- balanceListener?.onTapDetail(account.id)
- }
-
- binding.balanceRefreshIcon.setOnClickListener {
- balanceListener?.onTapRefresh(account)
- }
- }
- }
-
- interface BalanceListener {
- fun onTapRefresh(account: Account?)
-
- fun onTapDetail(accountId: Int)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/balances/BalanceCardStackAdapter.kt b/app/src/main/java/com/hover/stax/balances/BalanceCardStackAdapter.kt
deleted file mode 100644
index 6edb92d0e..000000000
--- a/app/src/main/java/com/hover/stax/balances/BalanceCardStackAdapter.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.hover.stax.balances
-
-import android.content.Context
-import android.view.ViewGroup
-import com.hover.stax.accounts.Account
-import com.hover.stax.databinding.StackBalanceCardBinding
-import com.hover.stax.utils.UIHelper
-import com.hover.stax.views.staxcardstack.StaxCardStackAdapter
-import com.hover.stax.views.staxcardstack.StaxCardStackView
-
-class BalanceCardStackAdapter(private val ctx: Context) : StaxCardStackAdapter(ctx) {
-
- override fun onCreateView(parent: ViewGroup?, viewType: Int): StaxCardStackView.ViewHolder {
- return MyViewHolder(StackBalanceCardBinding.inflate(layoutInflater, parent, false))
- }
-
- override fun bindView(account: Account, position: Int, holder: StaxCardStackView.ViewHolder?) {
- if (holder is MyViewHolder) holder.bind(account.primaryColorHex)
- }
-
- inner class MyViewHolder(val binding: StackBalanceCardBinding) : StaxCardStackView.ViewHolder(binding.root) {
-
- fun bind(hex: String) {
- binding.root.setCardBackgroundColor(UIHelper.getColor(hex, false, ctx))
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/balances/BalancesFragment.kt b/app/src/main/java/com/hover/stax/balances/BalancesFragment.kt
deleted file mode 100644
index 27b65fa20..000000000
--- a/app/src/main/java/com/hover/stax/balances/BalancesFragment.kt
+++ /dev/null
@@ -1,223 +0,0 @@
-package com.hover.stax.balances
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.cardview.widget.CardView
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.navigation.fragment.findNavController
-import androidx.recyclerview.widget.RecyclerView
-import com.hover.sdk.actions.HoverAction
-import com.hover.stax.MainNavigationDirections
-import com.hover.stax.R
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.AccountsViewModel
-import com.hover.stax.accounts.DUMMY
-import com.hover.stax.addChannels.ChannelsViewModel
-import com.hover.stax.databinding.FragmentBalanceBinding
-import com.hover.stax.home.HomeFragmentDirections
-import com.hover.stax.home.MainActivity
-import com.hover.stax.hover.AbstractHoverCallerActivity
-import com.hover.stax.utils.AnalyticsUtil
-import com.hover.stax.utils.UIHelper
-import com.hover.stax.utils.collectLatestLifecycleFlow
-import com.hover.stax.views.StaxDialog
-import com.hover.stax.views.staxcardstack.StaxCardStackView
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-import org.koin.androidx.viewmodel.ext.android.sharedViewModel
-
-
-class BalancesFragment : Fragment(), BalanceAdapter.BalanceListener {
-
- private lateinit var addAccountBtn: CardView
- private lateinit var balanceTitle: TextView
- private lateinit var balanceStack: StaxCardStackView
- private lateinit var balancesRecyclerView: RecyclerView
-
- private var _binding: FragmentBalanceBinding? = null
- private val binding get() = _binding!!
-
- private val accountsViewModel: AccountsViewModel by sharedViewModel()
- private val balancesViewModel: BalancesViewModel by sharedViewModel()
- private val channelsViewModel: ChannelsViewModel by sharedViewModel()
- private lateinit var cardStackAdapter: BalanceCardStackAdapter
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- _binding = FragmentBalanceBinding.inflate(inflater, container, false)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- setUpBalances()
- setUpLinkNewAccount()
- }
-
- private fun setUpBalances() {
- setUpBalanceHeader()
- setUpBalanceList()
- setUpHiddenStack()
-
- balancesViewModel.showBalances.observe(viewLifecycleOwner) { showBalanceCards(it) }
-
- balancesViewModel.accounts.observe(viewLifecycleOwner) {
- updateAccounts(ArrayList(it))
- }
-
- collectLatestLifecycleFlow(balancesViewModel.balanceAction) {
- attemptCallHover(balancesViewModel.userRequestedBalanceAccount.value, it)
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- channelsViewModel.accountCallback.collect {
- askToCheckBalance(it)
- }
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- balancesViewModel.actionRunError.collect {
- UIHelper.flashMessage(requireActivity(), it)
- }
- }
- }
- }
-
- private fun attemptCallHover(account: Account?, action: HoverAction?) {
- action?.let { account?.let { callHover(account, action) } }
- }
-
- private fun callHover(account: Account, action: HoverAction) {
- (requireActivity() as AbstractHoverCallerActivity).runSession(account, action)
- }
-
- private fun setUpLinkNewAccount() {
- addAccountBtn = binding.newAccountLink
- addAccountBtn.setOnClickListener {
- (requireActivity() as MainActivity).checkPermissionsAndNavigate(MainNavigationDirections.actionGlobalAddChannelsFragment())
- }
- }
-
- private fun setUpBalanceHeader() {
- balanceTitle = binding.homeCardBalances.balanceHeaderTitleId.also {
- it.setCompoundDrawablesRelativeWithIntrinsicBounds(
- if (balancesViewModel.showBalances.value == true) R.drawable.ic_visibility_on else R.drawable.ic_visibility_off,
- 0,
- 0,
- 0
- )
- it.setOnClickListener { balancesViewModel.setBalanceState(!balancesViewModel.showBalances.value!!) }
- }
- }
-
- private fun setUpBalanceList() {
- balancesRecyclerView = binding.homeCardBalances.balancesRecyclerView.apply {
- layoutManager = UIHelper.setMainLinearManagers(context)
- setHasFixedSize(true)
- visibility = View.GONE
- }
- }
-
- private fun setUpHiddenStack() {
- cardStackAdapter = BalanceCardStackAdapter(requireActivity())
- balanceStack = binding.stackBalanceCards
- balanceStack.apply {
- setAdapter(cardStackAdapter)
- setOverlapGaps(STACK_OVERLAY_GAP)
- rotationX = ROTATE_UPSIDE_DOWN
- }
- }
-
- private fun showBalanceCards(show: Boolean) {
- AnalyticsUtil.logAnalyticsEvent(getString(if (balancesViewModel.showBalances.value != true) R.string.show_balances else R.string.hide_balances), requireActivity())
- balanceTitle.setCompoundDrawablesRelativeWithIntrinsicBounds(
- if (show) R.drawable.ic_visibility_on else R.drawable.ic_visibility_off, 0, 0, 0
- )
-
- showAddAccount(accountsViewModel.accounts.value, show)
- if (show) binding.homeCardBalances.balancesMl.transitionToEnd() else binding.homeCardBalances.balancesMl.transitionToStart()
-
- balanceStack.visibility = if (show) View.GONE else View.VISIBLE
- }
-
- private fun updateAccounts(accounts: ArrayList) {
- accounts.let {
- addDummyAccountsIfRequired(accounts)
- cardStackAdapter.updateData(accounts.reversed())
- updateBalanceCardStackHeight(accounts.size)
- showAddAccount(accounts, balancesViewModel.showBalances.value!!)
- }
-
- val balancesAdapter = BalanceAdapter(accounts, this)
- balancesRecyclerView.adapter = balancesAdapter
- showBalanceCards(balancesViewModel.showBalances.value!!)
- }
-
- private fun askToCheckBalance(account: Account) {
- val dialog = StaxDialog(requireActivity())
- .setDialogTitle(R.string.check_balance_title)
- .setDialogMessage(R.string.check_balance_desc)
- .setNegButton(R.string.later, null)
- .setPosButton(R.string.check_balance_title) { onTapRefresh(account) }
- dialog.showIt()
- }
-
- private fun updateBalanceCardStackHeight(numOfItems: Int) {
- val params = balanceStack.layoutParams
- params.height = 20 * numOfItems
- balanceStack.layoutParams = params
- }
-
- private fun showAddAccount(accounts: List?, show: Boolean) {
- addAccountBtn.visibility = if (!accounts.isNullOrEmpty() && accounts.size > 1 && show) View.VISIBLE else View.GONE
- }
-
- private fun addDummyAccountsIfRequired(accounts: ArrayList?) {
- accounts?.let {
- if (it.isEmpty()) {
- accounts.add(Account(getString(R.string.your_main_account), GREEN_BG).dummy())
- accounts.add(Account(getString(R.string.your_other_account), BLUE_BG).dummy())
- }
- if (it.size == 1)
- accounts.add(Account(getString(R.string.your_other_account), BLUE_BG).dummy())
- }
- }
-
- override fun onTapRefresh(account: Account?) {
- if (account == null || account.id == DUMMY)
- (requireActivity() as MainActivity).checkPermissionsAndNavigate(HomeFragmentDirections.actionNavigationHomeToNavigationLinkAccount())
- else {
- AnalyticsUtil.logAnalyticsEvent(getString(R.string.refresh_balance_single), requireContext())
- balancesViewModel.requestBalance(account)
- }
- }
-
- override fun onTapDetail(accountId: Int) {
- if (accountId == DUMMY)
- (requireActivity() as MainActivity).checkPermissionsAndNavigate(HomeFragmentDirections.actionNavigationHomeToNavigationLinkAccount())
- else
- findNavController().navigate(HomeFragmentDirections.actionNavigationHomeToAccountDetailsFragment(accountId))
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-
- companion object {
- const val GREEN_BG = "#46E6CC"
- const val BLUE_BG = "#04CCFC"
-
- const val STACK_OVERLAY_GAP = 10
- const val ROTATE_UPSIDE_DOWN = 180f
- const val BALANCE_VISIBILITY_KEY: String = "BALANCE_VISIBLE"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/bonus/BonusViewModel.kt b/app/src/main/java/com/hover/stax/bonus/BonusViewModel.kt
index 389197a7c..03d564743 100644
--- a/app/src/main/java/com/hover/stax/bonus/BonusViewModel.kt
+++ b/app/src/main/java/com/hover/stax/bonus/BonusViewModel.kt
@@ -6,12 +6,13 @@ import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.firestore.ktx.firestoreSettings
import com.google.firebase.ktx.Firebase
import com.hover.stax.channels.Channel
-import com.hover.stax.channels.ChannelRepo
+import com.hover.stax.data.local.channels.ChannelRepo
+import com.hover.stax.data.local.bonus.BonusRepo
+import com.hover.stax.domain.model.Bonus
import com.hover.stax.utils.toHni
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
diff --git a/app/src/main/java/com/hover/stax/bounties/BountyAdapter.kt b/app/src/main/java/com/hover/stax/bounties/BountyAdapter.kt
deleted file mode 100644
index 0686426ec..000000000
--- a/app/src/main/java/com/hover/stax/bounties/BountyAdapter.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.hover.stax.bounties
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
-import com.hover.stax.databinding.BountyCardChannelBinding
-
-class BountyAdapter(private val selectListener: BountyListItem.SelectListener) : ListAdapter(diffUtil) {
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BountyViewHolder {
- return BountyViewHolder(BountyCardChannelBinding.inflate(LayoutInflater.from(parent.context), parent, false))
- }
-
- override fun onBindViewHolder(holder: BountyViewHolder, position: Int) {
- getItem(holder.adapterPosition)?.let {
- holder.bindItems(it, selectListener)
- }
- }
-
- class BountyViewHolder(var binding: BountyCardChannelBinding) : RecyclerView.ViewHolder(binding.root) {
-
- fun bindItems(channelBounties: ChannelBounties, listener: BountyListItem.SelectListener) {
- binding.bountyChannelCard.setTitle(channelBounties.channel.ussdName)
- binding.bountyList.removeAllViews()
-
- for (b in channelBounties.bounties) {
- val bountyLi = BountyListItem(binding.bountyChannelCard.context, null)
- bountyLi.setBounty(b, listener)
- binding.bountyList.addView(bountyLi)
- }
- }
-
- }
-
- companion object {
- private val diffUtil = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: ChannelBounties, newItem: ChannelBounties): Boolean {
- return oldItem.channel.id == newItem.channel.id
- }
-
- override fun areContentsTheSame(oldItem: ChannelBounties, newItem: ChannelBounties): Boolean {
- return oldItem.channel == newItem.channel
- }
- }
- }
-
-}
diff --git a/app/src/main/java/com/hover/stax/bounties/BountyListItem.kt b/app/src/main/java/com/hover/stax/bounties/BountyListItem.kt
deleted file mode 100644
index 9a0d1fb26..000000000
--- a/app/src/main/java/com/hover/stax/bounties/BountyListItem.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-package com.hover.stax.bounties
-
-import android.content.Context
-import android.text.Spannable
-import android.text.Spanned
-import android.text.method.LinkMovementMethod
-import android.text.style.StrikethroughSpan
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.OnClickListener
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import androidx.core.text.HtmlCompat
-import com.hover.stax.R
-import com.hover.stax.databinding.BountyListItemBinding
-
-class BountyListItem(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
-
- private val binding: BountyListItemBinding
- private var bounty: Bounty? = null
- private var selectListener: SelectListener? = null
-
- fun setBounty(b: Bounty?, listener: SelectListener?) {
- bounty = b
- setContent()
- chooseState()
- selectListener = listener
- }
-
- private fun setContent() {
- binding.liTitle.text = bounty!!.generateDescription(context)
- binding.liAmount.text = context.getString(R.string.bounty_amount_with_currency, bounty!!.action.bounty_amount)
- }
-
- private fun chooseState() {
- when {
- bounty!!.hasASuccessfulTransaction() -> {
- setState(R.color.muted_green, R.string.done, R.drawable.ic_check, false, null)
- }
- bounty!!.isLastTransactionFailed() && !bounty!!.action.bounty_is_open -> {
- setState(
- R.color.stax_bounty_red_bg, R.string.bounty_transaction_failed, R.drawable.ic_error,
- false,
- navTransactionDetail()
- )
- }
- bounty!!.isLastTransactionFailed() && bounty!!.action.bounty_is_open -> {
- setState(
- R.color.stax_bounty_red_bg, R.string.bounty_transaction_failed_try_again, R.drawable.ic_error,
- true,
- showBountyDetail()
- )
- }
- !bounty!!.action.bounty_is_open -> { // This bounty is closed and done by another user
- setState(R.color.lighter_grey, 0, 0, false, null)
- }
- bounty!!.transactionCount > 0 -> { // Bounty is open and with a transaction by current user
- setState(
- R.color.pending_brown, R.string.bounty_pending_short_desc, R.drawable.ic_warning,
- true,
- navTransactionDetail()
- )
- }
- else -> setState(R.color.cardViewColor, 0, 0, true, showBountyDetail())
- }
- }
-
- private fun navTransactionDetail(): OnClickListener {
- return OnClickListener {
- selectListener!!.viewTransactionDetail(
- bounty!!.transactions[bounty!!.lastTransactionIndex()].uuid
- )
- }
- }
-
- private fun showBountyDetail(): OnClickListener {
- return OnClickListener { bounty?.let { selectListener!!.viewBountyDetail(bounty!!) } }
- }
-
- private fun setState(
- color: Int,
- noticeString: Int,
- noticeIcon: Int,
- isOpen: Boolean,
- listener: OnClickListener?
- ) {
- setBackgroundColor(ContextCompat.getColor(context, color))
-
- if (noticeString != 0) {
- binding.liDetail.text = HtmlCompat.fromHtml(context.getString(noticeString), HtmlCompat.FROM_HTML_MODE_LEGACY)
- binding.liDetail.movementMethod = LinkMovementMethod.getInstance()
- }
-
- binding.liDetail.setCompoundDrawablesWithIntrinsicBounds(noticeIcon, 0, 0, 0)
- binding.liDetail.visibility = if (noticeString != 0) View.VISIBLE else View.GONE
-
- if (!isOpen) strikeThrough(binding.liAmount)
- if (!isOpen) strikeThrough(binding.liTitle)
-
- setOnClickListener(listener)
- }
-
- private fun strikeThrough(textView: TextView) {
- textView.setText(textView.text, TextView.BufferType.SPANNABLE)
- val spannable: Spannable = textView.text as Spannable
- spannable.setSpan(
- StrikethroughSpan(),
- 0,
- textView.text.length,
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- }
-
- interface SelectListener {
- fun viewTransactionDetail(uuid: String?)
- fun viewBountyDetail(b: Bounty)
- }
-
- init {
- binding = BountyListItemBinding.inflate(LayoutInflater.from(context), this, true)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/bounties/BountyViewModel.kt b/app/src/main/java/com/hover/stax/bounties/BountyViewModel.kt
deleted file mode 100644
index 3818de388..000000000
--- a/app/src/main/java/com/hover/stax/bounties/BountyViewModel.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-package com.hover.stax.bounties
-
-import android.app.Application
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import androidx.lifecycle.*
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
-import com.hover.sdk.actions.HoverAction
-import com.hover.sdk.api.Hover
-import com.hover.sdk.sims.SimInfo
-import com.hover.stax.actions.ActionRepo
-import com.hover.stax.channels.Channel
-import com.hover.stax.channels.ChannelRepo
-import com.hover.stax.countries.CountryAdapter
-import com.hover.stax.transactions.StaxTransaction
-import com.hover.stax.transactions.TransactionRepo
-import com.hover.stax.utils.Utils.getPackage
-import kotlinx.coroutines.*
-
-private const val MAX_LOOKUP_COUNT = 40
-
-class BountyViewModel(application: Application, val repo: ChannelRepo, val actionRepo: ActionRepo, transactionRepo: TransactionRepo) : AndroidViewModel(application) {
-
- @JvmField
- var country: String = CountryAdapter.CODE_ALL_COUNTRIES
-
- val actions: LiveData>
- val channels: LiveData>
- val transactions: LiveData>
- private val bountyList = MediatorLiveData>()
-
- private var _channelCountryList = MediatorLiveData>()
- val channelCountryList: LiveData> = _channelCountryList
-
- var sims: MutableLiveData> = MutableLiveData()
- private lateinit var bountyListAsync: Deferred>
-
- private var simReceiver: BroadcastReceiver? = null
-
- init {
- simReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- viewModelScope.launch {
- sims.postValue(repo.presentSims)
- }
- }
- }
-
- loadSims()
- actions = actionRepo.bountyActions
- channels = Transformations.switchMap(actions, this::loadChannels)
- _channelCountryList.addSource(channels, this::loadCountryList)
- transactions = transactionRepo.bountyTransactions!!
- bountyList.apply {
- addSource(actions, this@BountyViewModel::makeBounties)
- addSource(transactions, this@BountyViewModel::makeBountiesIfActions)
- }
- }
-
- private fun loadSims() {
- viewModelScope.launch {
- sims.postValue(repo.presentSims)
- }
-
- simReceiver?.let {
- LocalBroadcastManager.getInstance(getApplication())
- .registerReceiver(it, IntentFilter(getPackage(getApplication()) + ".NEW_SIM_INFO_ACTION"))
- }
- Hover.updateSimInfo(getApplication())
- }
-
- fun isSimPresent(b: Bounty): Boolean {
- if (sims.value.isNullOrEmpty()) return false
- for (sim in sims.value!!) {
- for (i in 0 until b.action.hni_list.length()) if (b.action.hni_list.optString(i) == sim.osReportedHni) return true
- }
- return false
- }
-
- private fun loadChannels(actions: List?): LiveData> {
- if (actions == null) return MutableLiveData()
- val ids = getChannelIdArray(actions.distinctBy { it.id }).toList()
-
- val channelList = runBlocking {
- getChannelsAsync(ids).await()
- }
-
- return MutableLiveData(channelList)
- }
-
- private fun loadCountryList(channels: List) = viewModelScope.launch {
- val countryCodes = mutableListOf(country)
- countryCodes.addAll(channels.map { it.countryAlpha2 }.distinct())
- _channelCountryList.postValue(countryCodes)
- }
-
- private fun getChannelsAsync(ids: List): Deferred> = viewModelScope.async(Dispatchers.IO) {
- val channels = ArrayList()
-
- ids.chunked(MAX_LOOKUP_COUNT).forEach { idList ->
- val results = repo.getChannelsByIds(idList)
- channels.addAll(results)
- }
-
- channels
- }
-
- val bounties: LiveData>
- get() = bountyList
-
- fun filterChannels(countryCode: String): LiveData> {
- country = countryCode
- val actions = actions.value ?: return MutableLiveData(ArrayList())
-
- return if (countryCode == CountryAdapter.CODE_ALL_COUNTRIES)
- loadChannels(actions)
- else
- repo.getChannelsByCountry(getChannelIdArray(actions), countryCode)
- }
-
- private fun getChannelIdArray(actions: List): IntArray = actions.distinctBy { it.channel_id }.map { it.channel_id }.toIntArray()
-
- private fun makeBountiesIfActions(transactions: List?) {
- if (actions.value != null && transactions != null) makeBounties(actions.value, transactions)
- }
-
- private fun makeBounties(actions: List?) {
- if (actions != null) makeBounties(actions, transactions.value)
- }
-
- private fun makeBounties(actions: List?, transactions: List?) {
- viewModelScope.launch(Dispatchers.Main) {
- bountyList.value = getBounties(actions, transactions)
- }
- }
-
- private suspend fun getBounties(actions: List?, transactions: List?): MutableList {
- coroutineScope {
- bountyListAsync = async(Dispatchers.IO) {
- val bounties: MutableList = ArrayList()
- val transactionsCopy: MutableList = if (transactions == null) ArrayList() else ArrayList(transactions)
- for (action in actions!!) {
- val filterTransactions: MutableList = ArrayList()
- val iter = transactionsCopy.listIterator()
- while (iter.hasNext()) {
- val t = iter.next()
- if (t.action_id == action.public_id) {
- filterTransactions.add(t)
- iter.remove()
- }
- }
- bounties.add(Bounty(action, filterTransactions))
- }
- bounties
- }
- }
- return bountyListAsync.await()
- }
-
- override fun onCleared() {
- try {
- simReceiver?.let {
- LocalBroadcastManager.getInstance(getApplication()).unregisterReceiver(it)
- }
- } catch (ignored: Exception) {
- }
- super.onCleared()
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/channels/ImportChannelsWorker.kt b/app/src/main/java/com/hover/stax/channels/ImportChannelsWorker.kt
index c32788b16..0b09133c7 100644
--- a/app/src/main/java/com/hover/stax/channels/ImportChannelsWorker.kt
+++ b/app/src/main/java/com/hover/stax/channels/ImportChannelsWorker.kt
@@ -9,6 +9,7 @@ import androidx.core.app.NotificationCompat
import androidx.work.*
import com.hover.stax.BuildConfig
import com.hover.stax.R
+import com.hover.stax.data.local.channels.ChannelDao
import com.hover.stax.database.AppDatabase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
diff --git a/app/src/main/java/com/hover/stax/accounts/AccountDao.kt b/app/src/main/java/com/hover/stax/data/local/accounts/AccountDao.kt
similarity index 85%
rename from app/src/main/java/com/hover/stax/accounts/AccountDao.kt
rename to app/src/main/java/com/hover/stax/data/local/accounts/AccountDao.kt
index 425aba355..a485db63d 100644
--- a/app/src/main/java/com/hover/stax/accounts/AccountDao.kt
+++ b/app/src/main/java/com/hover/stax/data/local/accounts/AccountDao.kt
@@ -1,7 +1,8 @@
-package com.hover.stax.accounts
+package com.hover.stax.data.local.accounts
import androidx.lifecycle.LiveData
import androidx.room.*
+import com.hover.stax.domain.model.Account
import kotlinx.coroutines.flow.Flow
@Dao
@@ -37,11 +38,14 @@ interface AccountDao {
@Query("SELECT * FROM accounts where isDefault = 1")
fun getDefaultAccount(): Account?
+ @Query("SELECT * FROM accounts where isDefault = 1")
+ suspend fun getDefaultAccountAsync(): Account?
+
@Query("SELECT COUNT(id) FROM accounts")
fun getDataCount(): Int
@Insert(onConflict = OnConflictStrategy.IGNORE)
- fun insertAll(accounts: List): List
+ suspend fun insertAll(accounts: List): List
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(account: Account)
@@ -50,7 +54,7 @@ interface AccountDao {
fun update(account: Account?)
@Update
- fun updateAll(accounts: List)
+ suspend fun updateAll(accounts: List)
@Delete
fun delete(account: Account)
diff --git a/app/src/main/java/com/hover/stax/accounts/AccountRepo.kt b/app/src/main/java/com/hover/stax/data/local/accounts/AccountRepo.kt
similarity index 84%
rename from app/src/main/java/com/hover/stax/accounts/AccountRepo.kt
rename to app/src/main/java/com/hover/stax/data/local/accounts/AccountRepo.kt
index 1621abe3f..4ca0b91bd 100644
--- a/app/src/main/java/com/hover/stax/accounts/AccountRepo.kt
+++ b/app/src/main/java/com/hover/stax/data/local/accounts/AccountRepo.kt
@@ -1,7 +1,8 @@
-package com.hover.stax.accounts
+package com.hover.stax.data.local.accounts
import androidx.lifecycle.LiveData
import com.hover.stax.database.AppDatabase
+import com.hover.stax.domain.model.Account
import com.hover.stax.utils.AnalyticsUtil
import kotlinx.coroutines.flow.Flow
@@ -18,6 +19,8 @@ class AccountRepo(db: AppDatabase) {
fun getDefaultAccount(): Account? = accountDao.getDefaultAccount()
+ suspend fun getDefaultAccountAsync(): Account? = accountDao.getDefaultAccountAsync()
+
fun getAccount(id: Int): Account? = accountDao.getAccount(id)
fun getLiveAccount(id: Int?): LiveData = accountDao.getLiveAccount(id)
@@ -44,10 +47,12 @@ class AccountRepo(db: AppDatabase) {
fun insert(account: Account) = accountDao.insert(account)
- fun insert(accounts: List): List = accountDao.insertAll(accounts)
+ suspend fun insert(accounts: List): List = accountDao.insertAll(accounts)
fun update(account: Account?) = account?.let { accountDao.update(it) }
+ suspend fun update(accounts: List) = accountDao.updateAll(accounts)
+
fun delete(account: Account) = accountDao.delete(account)
fun deleteAccount(channelId: Int, name: String) {
diff --git a/app/src/main/java/com/hover/stax/actions/ActionRepo.kt b/app/src/main/java/com/hover/stax/data/local/actions/ActionRepo.kt
similarity index 92%
rename from app/src/main/java/com/hover/stax/actions/ActionRepo.kt
rename to app/src/main/java/com/hover/stax/data/local/actions/ActionRepo.kt
index a02627d1c..a80dddedd 100644
--- a/app/src/main/java/com/hover/stax/actions/ActionRepo.kt
+++ b/app/src/main/java/com/hover/stax/data/local/actions/ActionRepo.kt
@@ -1,10 +1,9 @@
-package com.hover.stax.actions
+package com.hover.stax.data.local.actions
import androidx.lifecycle.LiveData
import com.hover.sdk.actions.HoverAction
import com.hover.sdk.actions.HoverActionDao
import com.hover.sdk.database.HoverRoomDatabase
-import com.hover.stax.database.AppDatabase
class ActionRepo(sdkDb: HoverRoomDatabase) {
@@ -44,4 +43,7 @@ class ActionRepo(sdkDb: HoverRoomDatabase) {
val bountyActions: LiveData>
get() = actionDao.bountyActions
+
+ val bounties: List
+ get() = actionDao.bounties
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/bonus/BonusDao.kt b/app/src/main/java/com/hover/stax/data/local/bonus/BonusDao.kt
similarity index 93%
rename from app/src/main/java/com/hover/stax/bonus/BonusDao.kt
rename to app/src/main/java/com/hover/stax/data/local/bonus/BonusDao.kt
index f8c63891b..f015716d3 100644
--- a/app/src/main/java/com/hover/stax/bonus/BonusDao.kt
+++ b/app/src/main/java/com/hover/stax/data/local/bonus/BonusDao.kt
@@ -1,6 +1,7 @@
-package com.hover.stax.bonus
+package com.hover.stax.data.local.bonus
import androidx.room.*
+import com.hover.stax.domain.model.Bonus
import kotlinx.coroutines.flow.Flow
@Dao
diff --git a/app/src/main/java/com/hover/stax/bonus/BonusRepo.kt b/app/src/main/java/com/hover/stax/data/local/bonus/BonusRepo.kt
similarity index 88%
rename from app/src/main/java/com/hover/stax/bonus/BonusRepo.kt
rename to app/src/main/java/com/hover/stax/data/local/bonus/BonusRepo.kt
index d2e01ff5e..f0396a2e7 100644
--- a/app/src/main/java/com/hover/stax/bonus/BonusRepo.kt
+++ b/app/src/main/java/com/hover/stax/data/local/bonus/BonusRepo.kt
@@ -1,6 +1,7 @@
-package com.hover.stax.bonus
+package com.hover.stax.data.local.bonus
import com.hover.stax.database.AppDatabase
+import com.hover.stax.domain.model.Bonus
class BonusRepo(val db: AppDatabase) {
diff --git a/app/src/main/java/com/hover/stax/channels/ChannelDao.kt b/app/src/main/java/com/hover/stax/data/local/channels/ChannelDao.kt
similarity index 87%
rename from app/src/main/java/com/hover/stax/channels/ChannelDao.kt
rename to app/src/main/java/com/hover/stax/data/local/channels/ChannelDao.kt
index 4dc408904..5f840f0f9 100644
--- a/app/src/main/java/com/hover/stax/channels/ChannelDao.kt
+++ b/app/src/main/java/com/hover/stax/data/local/channels/ChannelDao.kt
@@ -1,8 +1,9 @@
-package com.hover.stax.channels
+package com.hover.stax.data.local.channels
import androidx.lifecycle.LiveData
import androidx.room.*
import com.hover.stax.accounts.ChannelWithAccounts
+import com.hover.stax.channels.Channel
@Dao
interface ChannelDao {
@@ -25,8 +26,11 @@ interface ChannelDao {
@Query("SELECT * FROM channels WHERE country_alpha2 = :countryCode ORDER BY name ASC")
fun getChannels(countryCode: String): List
+// @Query("SELECT * FROM channels WHERE country_alpha2 = :countryCode AND id IN (:channel_ids) ORDER BY name ASC")
+// fun getChannels(countryCode: String, channel_ids: IntArray): LiveData>
+
@Query("SELECT * FROM channels WHERE country_alpha2 = :countryCode AND id IN (:channel_ids) ORDER BY name ASC")
- fun getChannels(countryCode: String, channel_ids: IntArray): LiveData>
+ fun getChannels(countryCode: String, channel_ids: IntArray): List
@Query("SELECT * FROM channels WHERE id = :id LIMIT 1")
fun getChannel(id: Int): Channel?
@@ -65,4 +69,5 @@ interface ChannelDao {
@Query("DELETE FROM channels")
fun deleteAll()
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/channels/ChannelRepo.kt b/app/src/main/java/com/hover/stax/data/local/channels/ChannelRepo.kt
similarity index 83%
rename from app/src/main/java/com/hover/stax/channels/ChannelRepo.kt
rename to app/src/main/java/com/hover/stax/data/local/channels/ChannelRepo.kt
index f1a410f9e..473df8467 100644
--- a/app/src/main/java/com/hover/stax/channels/ChannelRepo.kt
+++ b/app/src/main/java/com/hover/stax/data/local/channels/ChannelRepo.kt
@@ -1,11 +1,10 @@
-package com.hover.stax.channels
+package com.hover.stax.data.local.channels
import androidx.lifecycle.LiveData
-import com.hover.sdk.actions.HoverAction
import com.hover.sdk.database.HoverRoomDatabase
import com.hover.sdk.sims.SimInfo
import com.hover.sdk.sims.SimInfoDao
-import com.hover.stax.accounts.ChannelWithAccounts
+import com.hover.stax.channels.Channel
import com.hover.stax.database.AppDatabase
class ChannelRepo(db: AppDatabase, sdkDb: HoverRoomDatabase) {
@@ -31,9 +30,9 @@ class ChannelRepo(db: AppDatabase, sdkDb: HoverRoomDatabase) {
fun getChannelsByIds(ids: List): List = channelDao.getChannelsByIds(ids)
- fun getChannelsByCountry(channelIds: IntArray, countryCode: String): LiveData> {
- return channelDao.getChannels(countryCode.uppercase(), channelIds)
- }
+ fun getChannelsByIdsAsync(ids: List): List = channelDao.getChannelsByIds(ids)
+
+ fun getChannelsByCountry(channelIds: IntArray, countryCode: String): List = channelDao.getChannels(countryCode, channelIds)
fun getChannelsByCountry(countryCode: String): List {
return channelDao.getChannels(countryCode.uppercase())
diff --git a/app/src/main/java/com/hover/stax/database/ParserRepo.kt b/app/src/main/java/com/hover/stax/data/local/parser/ParserRepo.kt
similarity index 87%
rename from app/src/main/java/com/hover/stax/database/ParserRepo.kt
rename to app/src/main/java/com/hover/stax/data/local/parser/ParserRepo.kt
index 757e05d26..2c3ac6d6c 100644
--- a/app/src/main/java/com/hover/stax/database/ParserRepo.kt
+++ b/app/src/main/java/com/hover/stax/data/local/parser/ParserRepo.kt
@@ -1,4 +1,4 @@
-package com.hover.stax.database
+package com.hover.stax.data.local.parser
import com.hover.sdk.database.HoverRoomDatabase
import com.hover.sdk.parsers.ParserDao
diff --git a/app/src/main/java/com/hover/stax/bounties/UpdateBountyTransactionsWorker.kt b/app/src/main/java/com/hover/stax/data/remote/workers/UpdateBountyTransactionsWorker.kt
similarity index 95%
rename from app/src/main/java/com/hover/stax/bounties/UpdateBountyTransactionsWorker.kt
rename to app/src/main/java/com/hover/stax/data/remote/workers/UpdateBountyTransactionsWorker.kt
index 04cf33a7e..1ba2bb3af 100644
--- a/app/src/main/java/com/hover/stax/bounties/UpdateBountyTransactionsWorker.kt
+++ b/app/src/main/java/com/hover/stax/data/remote/workers/UpdateBountyTransactionsWorker.kt
@@ -1,4 +1,4 @@
-package com.hover.stax.bounties
+package com.hover.stax.data.remote.workers
import android.content.Context
import androidx.work.*
@@ -24,8 +24,8 @@ import java.util.concurrent.TimeUnit
class UpdateBountyTransactionsWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
companion object {
- val TAG = "BountyTransactionWorker"
- val BOUNTY_TRANSACTION_WORK_ID = "BOUNTY_TRANSACTION"
+ const val TAG = "BountyTransactionWorker"
+ const val BOUNTY_TRANSACTION_WORK_ID = "BOUNTY_TRANSACTION"
fun makeToil(): PeriodicWorkRequest {
return PeriodicWorkRequest.Builder(UpdateChannelsWorker::class.java, 24, TimeUnit.HOURS)
diff --git a/app/src/main/java/com/hover/stax/data/repository/AccountRepositoryImpl.kt b/app/src/main/java/com/hover/stax/data/repository/AccountRepositoryImpl.kt
new file mode 100644
index 000000000..df5b728eb
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/data/repository/AccountRepositoryImpl.kt
@@ -0,0 +1,78 @@
+package com.hover.stax.data.repository
+
+import android.content.Context
+import com.hover.sdk.actions.HoverAction
+import com.hover.sdk.api.ActionApi
+import com.hover.stax.R
+import com.hover.stax.data.local.actions.ActionRepo
+import com.hover.stax.channels.Channel
+import com.hover.stax.data.local.channels.ChannelRepo
+import com.hover.stax.data.local.accounts.AccountRepo
+import com.hover.stax.domain.model.Account
+import com.hover.stax.domain.model.PLACEHOLDER
+import com.hover.stax.domain.repository.AccountRepository
+import com.hover.stax.notifications.PushNotificationTopicsInterface
+import com.hover.stax.utils.AnalyticsUtil
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.json.JSONObject
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+
+class AccountRepositoryImpl(val accountRepo: AccountRepo, val channelRepo: ChannelRepo, val actionRepo: ActionRepo, private val coroutineDispatcher: CoroutineDispatcher) : AccountRepository, PushNotificationTopicsInterface, KoinComponent {
+
+ private val context: Context by inject()
+
+ override val fetchAccounts: Flow>
+ get() = accountRepo.getAccounts()
+
+ override suspend fun createAccounts(channels: List): List {
+ val defaultAccount = accountRepo.getDefaultAccountAsync()
+
+ val accounts = channels.mapIndexed { index, channel ->
+ val accountName: String = if (getFetchAccountAction(channel.id) == null) channel.name else channel.name.plus(PLACEHOLDER )//placeholder alias for easier identification later
+ Account(
+ accountName, channel.name, channel.logoUrl, channel.accountNo, channel.id, channel.countryAlpha2,
+ channel.id, channel.primaryColorHex, channel.secondaryColorHex, defaultAccount == null && index == 0
+ )
+ }.onEach {
+ logChoice(it)
+ ActionApi.scheduleActionConfigUpdate(it.countryAlpha2, 24, context)
+ }
+
+ channels.onEach { it.selected = true }.also { channelRepo.update(it) }
+ return accountRepo.insert(accounts)
+ }
+
+ override suspend fun setDefaultAccount(account: Account) {
+ fetchAccounts.collect { accounts ->
+ val current = accounts.firstOrNull { it.isDefault }?.also {
+ it.isDefault = false
+ }
+
+ val defaultAccount = accounts.first { it.id == account.id }.also { it.isDefault = true }
+
+ withContext(coroutineDispatcher) {
+ launch {
+ accountRepo.update(listOf(current!!, defaultAccount))
+ }
+ }
+ }
+ }
+
+ private fun getFetchAccountAction(channelId: Int): HoverAction? = actionRepo.getActions(channelId, HoverAction.FETCH_ACCOUNTS).firstOrNull()
+
+ private fun logChoice(account: Account) {
+ joinChannelGroup(account.channelId, context)
+ val args = JSONObject()
+
+ try {
+ args.put(context.getString(R.string.added_channel_id), account.channelId)
+ } catch (ignored: Exception) {
+ }
+
+ AnalyticsUtil.logAnalyticsEvent(context.getString(R.string.new_channel_selected), args, context)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/data/repository/BonusRepositoryImpl.kt b/app/src/main/java/com/hover/stax/data/repository/BonusRepositoryImpl.kt
new file mode 100644
index 000000000..ac2a519c0
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/data/repository/BonusRepositoryImpl.kt
@@ -0,0 +1,94 @@
+package com.hover.stax.data.repository
+
+import com.google.firebase.firestore.ktx.firestore
+import com.google.firebase.firestore.ktx.firestoreSettings
+import com.google.firebase.ktx.Firebase
+import com.hover.stax.channels.Channel
+import com.hover.stax.data.local.channels.ChannelRepo
+import com.hover.stax.data.local.bonus.BonusRepo
+import com.hover.stax.domain.model.Bonus
+import com.hover.stax.domain.repository.BonusRepository
+import com.hover.stax.utils.toHni
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.tasks.await
+import kotlinx.coroutines.withContext
+
+class BonusRepositoryImpl(private val bonusRepo: BonusRepo, private val channelRepo: ChannelRepo, private val coroutineDispatcher: CoroutineDispatcher) : BonusRepository {
+
+ private val settings = firestoreSettings { isPersistenceEnabled = true }
+ private val db = Firebase.firestore.also { it.firestoreSettings = settings }
+
+ override suspend fun fetchBonuses() {
+ val bonuses = db.collection("bonuses")
+ .get()
+ .await()
+ .documents
+ .mapNotNull { document ->
+ document.data?.let {
+ Bonus(
+ it["user_channel"].toString().toInt(), it["purchase_channel"].toString().toInt(),
+ it["bonus_percent"].toString().toDouble(), it["message"].toString()
+ )
+ }
+ }
+
+ filterResults(bonuses)
+ }
+
+ override val bonusList: Flow>
+ get() = channelFlow {
+ val simHnis = channelRepo.presentSims.map { it.osReportedHni }
+
+ bonusRepo.bonuses.collect {
+ withContext(coroutineDispatcher) {
+ launch {
+ val bonusChannels = getBonusChannels(it)
+ val showBonuses = hasValidSim(simHnis, bonusChannels)
+
+ if (showBonuses)
+ send(it)
+ else
+ send(emptyList())
+ }
+ }
+ }
+ }
+
+ override suspend fun saveBonuses(bonusList: List) {
+ return bonusRepo.save(bonusList)
+ }
+
+ override suspend fun getBonusByPurchaseChannel(channelId: Int): Bonus? {
+ return bonusRepo.getBonusByPurchaseChannel(channelId)
+ }
+
+ override suspend fun getBonusByUserChannel(channelId: Int): Bonus? {
+ return bonusRepo.getBonusByUserChannel(channelId)
+ }
+
+ private suspend fun filterResults(bonuses: List) = withContext(coroutineDispatcher) {
+ launch {
+ val bonusChannels = getBonusChannels(bonuses)
+
+ val toSave = bonuses.filter { bonusChannels.map { channel -> channel.id }.contains(it.purchaseChannel) }
+ bonusRepo.updateBonuses(toSave)
+ }
+ }
+
+ private fun hasValidSim(simHnis: List, bonusChannels: List): Boolean {
+ val hniList = mutableSetOf()
+ bonusChannels.forEach { channel ->
+ channel.hniList.split(",").forEach {
+ if (simHnis.contains(it.toHni()))
+ hniList.add(it.toHni())
+ }
+ }
+
+ return hniList.isNotEmpty()
+ }
+
+ override suspend fun getBonusChannels(bonusList: List): List = channelRepo.getChannelsByIdsAsync(bonusList.map { it.purchaseChannel })
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/data/repository/BountyRepositoryImpl.kt b/app/src/main/java/com/hover/stax/data/repository/BountyRepositoryImpl.kt
new file mode 100644
index 000000000..f5ca56ca9
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/data/repository/BountyRepositoryImpl.kt
@@ -0,0 +1,76 @@
+package com.hover.stax.data.repository
+
+import androidx.lifecycle.LiveData
+import com.hover.sdk.actions.HoverAction
+import com.hover.sdk.sims.SimInfo
+import com.hover.stax.channels.Channel
+import com.hover.stax.countries.CountryAdapter
+import com.hover.stax.data.local.actions.ActionRepo
+import com.hover.stax.domain.model.Bounty
+import com.hover.stax.domain.model.ChannelBounties
+import com.hover.stax.domain.repository.BountyRepository
+import com.hover.stax.transactions.StaxTransaction
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.launch
+
+class BountyRepositoryImpl(val actionRepo: ActionRepo, private val coroutineDispatcher: CoroutineDispatcher) : BountyRepository {
+
+ override val bountyActions: List
+ get() = actionRepo.bounties
+
+ override fun isSimPresent(bounty: Bounty, sims: List): Boolean {
+ if (sims.isEmpty()) return false
+
+ sims.forEach { simInfo ->
+ for (i in 0 until bounty.action.hni_list.length()) if (bounty.action.hni_list.optString(i) == simInfo.osReportedHni) return true
+ }
+
+ return false
+ }
+
+ override fun getCountryList(): Flow> = channelFlow {
+ launch(coroutineDispatcher) {
+ val actions = bountyActions
+ val countryCodes = mutableListOf(CountryAdapter.CODE_ALL_COUNTRIES)
+ actions.asSequence().map { it.country_alpha2.uppercase() }.distinct().sorted().toCollection(countryCodes)
+ send(countryCodes)
+ }
+ }
+
+ override suspend fun makeBounties(actions: List, transactions: List?, channels: List): List {
+ if (actions.isEmpty()) return emptyList()
+
+ val bounties = getBounties(actions, transactions)
+
+ return generateChannelBounties(channels, bounties)
+ }
+
+ private fun getBounties(actions: List, transactions: List?): List {
+ val bounties: MutableList = ArrayList()
+ val transactionList = transactions?.toMutableList() ?: mutableListOf()
+
+ for (action in actions) {
+ val filteredTransactions = transactionList.filter { it.action_id == action.public_id }
+ bounties.add(Bounty(action, filteredTransactions))
+ }
+
+ return bounties
+ }
+
+ private fun generateChannelBounties(channels: List, bounties: List): List {
+ if (channels.isEmpty() || bounties.isEmpty()) return emptyList()
+
+ val openBounties = bounties.filter { it.action.bounty_is_open || it.transactionCount != 0 }
+
+ val channelBounties = channels.filter { c ->
+ openBounties.any { it.action.channel_id == c.id }
+ }.map { channel ->
+ ChannelBounties(channel, openBounties.filter { it.action.channel_id == channel.id })
+ }
+
+ return channelBounties
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/data/repository/ChannelRepositoryImpl.kt b/app/src/main/java/com/hover/stax/data/repository/ChannelRepositoryImpl.kt
new file mode 100644
index 000000000..430c7fbfd
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/data/repository/ChannelRepositoryImpl.kt
@@ -0,0 +1,41 @@
+package com.hover.stax.data.repository
+
+import com.hover.sdk.actions.HoverAction
+import com.hover.sdk.sims.SimInfo
+import com.hover.stax.channels.Channel
+import com.hover.stax.countries.CountryAdapter
+import com.hover.stax.data.local.channels.ChannelRepo
+import com.hover.stax.domain.repository.ChannelRepository
+
+private const val MAX_LOOKUP_COUNT = 40
+
+class ChannelRepositoryImpl(val channelRepo: ChannelRepo) : ChannelRepository {
+
+ override val presentSims: List
+ get() = channelRepo.presentSims
+
+ override suspend fun getChannelsByIds(ids: List): List = channelRepo.getChannelsByIds(ids)
+
+ override suspend fun getChannelsByCountryCode(ids: IntArray, countryCode: String): List = channelRepo.getChannelsByCountry(ids, countryCode)
+
+ override suspend fun filterChannels(countryCode: String, actions: List): List {
+ val ids = actions.asSequence().distinctBy { it.channel_id }.map { it.channel_id }.toList()
+
+ return if (countryCode == CountryAdapter.CODE_ALL_COUNTRIES)
+ getChunkedChannelsByIds(ids)
+ else
+ getChannelsByCountryCode(ids.toIntArray(), countryCode)
+ }
+
+ private fun getChunkedChannelsByIds(ids: List): List {
+ val channels = mutableListOf()
+
+ ids.chunked(MAX_LOOKUP_COUNT).forEach { idList ->
+ val results = channelRepo.getChannelsByIds(idList)
+ channels.addAll(results)
+ }
+
+ return channels
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/data/repository/FinancialTipsRepositoryImpl.kt b/app/src/main/java/com/hover/stax/data/repository/FinancialTipsRepositoryImpl.kt
new file mode 100644
index 000000000..61d8d426d
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/data/repository/FinancialTipsRepositoryImpl.kt
@@ -0,0 +1,48 @@
+package com.hover.stax.data.repository
+
+import android.content.Context
+import com.google.firebase.firestore.Query
+import com.google.firebase.firestore.ktx.firestore
+import com.google.firebase.firestore.ktx.firestoreSettings
+import com.google.firebase.ktx.Firebase
+import com.hover.stax.R
+import com.hover.stax.domain.model.FINANCIAL_TIP_ID
+import com.hover.stax.domain.model.FinancialTip
+import com.hover.stax.domain.repository.FinancialTipsRepository
+import com.hover.stax.utils.Utils
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.tasks.await
+
+class FinancialTipsRepositoryImpl(val context: Context) : FinancialTipsRepository {
+
+ val db = Firebase.firestore
+ val settings = firestoreSettings { isPersistenceEnabled = true }
+
+ override suspend fun getTips(): List {
+ val today = System.currentTimeMillis()
+
+ return db.collection(context.getString(R.string.tips_table))
+ .orderBy("date", Query.Direction.DESCENDING)
+ .whereLessThanOrEqualTo("date", today / 1000)
+ .limit(20)
+ .get()
+ .await()
+ .documents
+ .mapNotNull { document ->
+ FinancialTip(
+ document.id, document.data!!["title"].toString(), document.data!!["content"].toString(),
+ document.data!!["snippet"].toString(), (document.data!!["date"].toString().toLong() * 1000), document.data!!["share copy"].toString(),
+ document.data!!["deep link"].toString()
+ )
+ }
+ }
+
+ override fun getDismissedTipId(): String? {
+ return Utils.getString(FINANCIAL_TIP_ID, context)
+ }
+
+ override fun dismissTip(id: String) {
+ Utils.saveString(FINANCIAL_TIP_ID, id, context)
+ }
+}
diff --git a/app/src/main/java/com/hover/stax/database/AppDatabase.kt b/app/src/main/java/com/hover/stax/database/AppDatabase.kt
index 5223c5a5e..fa425606a 100644
--- a/app/src/main/java/com/hover/stax/database/AppDatabase.kt
+++ b/app/src/main/java/com/hover/stax/database/AppDatabase.kt
@@ -6,12 +6,12 @@ import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.AccountDao
-import com.hover.stax.bonus.Bonus
-import com.hover.stax.bonus.BonusDao
+import com.hover.stax.domain.model.Account
+import com.hover.stax.data.local.accounts.AccountDao
+import com.hover.stax.domain.model.Bonus
+import com.hover.stax.data.local.bonus.BonusDao
import com.hover.stax.channels.Channel
-import com.hover.stax.channels.ChannelDao
+import com.hover.stax.data.local.channels.ChannelDao
import com.hover.stax.contacts.ContactDao
import com.hover.stax.contacts.StaxContact
import com.hover.stax.merchants.Merchant
diff --git a/app/src/main/java/com/hover/stax/database/Modules.kt b/app/src/main/java/com/hover/stax/database/Modules.kt
deleted file mode 100644
index 21e0daa9a..000000000
--- a/app/src/main/java/com/hover/stax/database/Modules.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.hover.stax.database
-
-import com.hover.sdk.database.HoverRoomDatabase
-import com.hover.stax.accounts.AccountDetailViewModel
-import com.hover.stax.accounts.AccountRepo
-import com.hover.stax.actions.ActionRepo
-import com.hover.stax.actions.ActionSelectViewModel
-import com.hover.stax.addChannels.ChannelsViewModel
-import com.hover.stax.balances.BalancesViewModel
-import com.hover.stax.bonus.BonusRepo
-import com.hover.stax.bonus.BonusViewModel
-import com.hover.stax.bounties.BountyViewModel
-import com.hover.stax.channels.ChannelRepo
-import com.hover.stax.accounts.AccountsViewModel
-import com.hover.stax.contacts.ContactRepo
-import com.hover.stax.faq.FaqViewModel
-import com.hover.stax.financialTips.FinancialTipsViewModel
-import com.hover.stax.futureTransactions.FutureViewModel
-import com.hover.stax.inapp_banner.BannerViewModel
-import com.hover.stax.languages.LanguageViewModel
-import com.hover.stax.login.LoginNetworking
-import com.hover.stax.login.LoginViewModel
-import com.hover.stax.merchants.MerchantRepo
-import com.hover.stax.merchants.MerchantViewModel
-import com.hover.stax.paybill.PaybillRepo
-import com.hover.stax.paybill.PaybillViewModel
-import com.hover.stax.requests.NewRequestViewModel
-import com.hover.stax.requests.RequestDetailViewModel
-import com.hover.stax.requests.RequestRepo
-import com.hover.stax.schedules.ScheduleDetailViewModel
-import com.hover.stax.schedules.ScheduleRepo
-import com.hover.stax.transactionDetails.TransactionDetailsViewModel
-import com.hover.stax.transactions.TransactionHistoryViewModel
-import com.hover.stax.transactions.TransactionRepo
-import com.hover.stax.transfers.TransferViewModel
-import com.hover.stax.user.UserRepo
-import org.koin.androidx.viewmodel.dsl.viewModel
-import org.koin.dsl.module
-
-val appModule = module {
- viewModel { FaqViewModel() }
- viewModel { ActionSelectViewModel(get()) }
- viewModel { ChannelsViewModel(get(), get(), get(), get(), get()) }
- viewModel { AccountsViewModel(get(), get(), get(), get()) }
- viewModel { AccountDetailViewModel(get(), get(), get(), get(), get()) }
- viewModel { NewRequestViewModel(get(), get(), get(), get(), get()) }
- viewModel { TransferViewModel(get(), get(), get(), get()) }
- viewModel { ScheduleDetailViewModel(get(), get(), get()) }
- viewModel { BalancesViewModel(get(), get(), get()) }
- viewModel { TransactionHistoryViewModel(get(), get()) }
- viewModel { BannerViewModel(get(), get()) }
- viewModel { FutureViewModel(get(), get(), get()) }
- viewModel { LoginViewModel(get(), get(), get())}
- viewModel { TransactionDetailsViewModel(get(), get(), get(), get(), get(), get(), get(), get()) }
- viewModel { LanguageViewModel(get()) }
- viewModel { BountyViewModel(get(), get(), get(), get()) }
- viewModel { FinancialTipsViewModel(get()) }
- viewModel { PaybillViewModel(get(), get(), get(), get(), get(), get()) }
- viewModel { MerchantViewModel(get(), get(), get(), get()) }
- viewModel { RequestDetailViewModel(get(), get(), get()) }
- viewModel { BonusViewModel(get(), get()) }
-}
-
-val dataModule = module(createdAtStart = true) {
- single { AppDatabase.getInstance(get()) }
- single { HoverRoomDatabase.getInstance(get()) }
-
- single { TransactionRepo(get()) }
- single { ChannelRepo(get(), get()) }
- single { ActionRepo(get()) }
- single { ContactRepo(get()) }
- single { AccountRepo(get()) }
- single { RequestRepo(get()) }
- single { ScheduleRepo(get()) }
- single { PaybillRepo(get()) }
- single { MerchantRepo(get()) }
- single { UserRepo(get()) }
- single { BonusRepo(get()) }
- single { ParserRepo(get()) }
-}
-
-val networkModule = module {
- single { LoginNetworking(get()) }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/di/Modules.kt b/app/src/main/java/com/hover/stax/di/Modules.kt
new file mode 100644
index 000000000..ea940a0ed
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/di/Modules.kt
@@ -0,0 +1,131 @@
+package com.hover.stax.di
+
+import com.hover.sdk.database.HoverRoomDatabase
+import com.hover.stax.accounts.AccountDetailViewModel
+import com.hover.stax.accounts.AccountsViewModel
+import com.hover.stax.actions.ActionSelectViewModel
+import com.hover.stax.addChannels.ChannelsViewModel
+import com.hover.stax.bonus.BonusViewModel
+import com.hover.stax.contacts.ContactRepo
+import com.hover.stax.data.local.accounts.AccountRepo
+import com.hover.stax.data.local.actions.ActionRepo
+import com.hover.stax.data.local.bonus.BonusRepo
+import com.hover.stax.data.local.channels.ChannelRepo
+import com.hover.stax.data.local.parser.ParserRepo
+import com.hover.stax.data.repository.*
+import com.hover.stax.database.AppDatabase
+import com.hover.stax.domain.repository.*
+import com.hover.stax.domain.use_case.accounts.CreateAccountsUseCase
+import com.hover.stax.domain.use_case.accounts.GetAccountsUseCase
+import com.hover.stax.domain.use_case.accounts.SetDefaultAccountUseCase
+import com.hover.stax.domain.use_case.bonus.FetchBonusUseCase
+import com.hover.stax.domain.use_case.bonus.GetBonusesUseCase
+import com.hover.stax.domain.use_case.bounties.GetChannelBountiesUseCase
+import com.hover.stax.domain.use_case.channels.GetPresentSimsUseCase
+import com.hover.stax.domain.use_case.financial_tips.TipsUseCase
+import com.hover.stax.faq.FaqViewModel
+import com.hover.stax.futureTransactions.FutureViewModel
+import com.hover.stax.inapp_banner.BannerViewModel
+import com.hover.stax.languages.LanguageViewModel
+import com.hover.stax.login.LoginNetworking
+import com.hover.stax.login.LoginViewModel
+import com.hover.stax.merchants.MerchantRepo
+import com.hover.stax.merchants.MerchantViewModel
+import com.hover.stax.paybill.PaybillRepo
+import com.hover.stax.paybill.PaybillViewModel
+import com.hover.stax.presentation.bounties.BountyViewModel
+import com.hover.stax.presentation.financial_tips.FinancialTipsViewModel
+import com.hover.stax.presentation.home.BalancesViewModel
+import com.hover.stax.presentation.home.HomeViewModel
+import com.hover.stax.requests.NewRequestViewModel
+import com.hover.stax.requests.RequestDetailViewModel
+import com.hover.stax.requests.RequestRepo
+import com.hover.stax.schedules.ScheduleDetailViewModel
+import com.hover.stax.schedules.ScheduleRepo
+import com.hover.stax.transactionDetails.TransactionDetailsViewModel
+import com.hover.stax.transactions.TransactionHistoryViewModel
+import com.hover.stax.transactions.TransactionRepo
+import com.hover.stax.transfers.TransferViewModel
+import com.hover.stax.user.UserRepo
+import kotlinx.coroutines.Dispatchers
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.core.module.dsl.bind
+import org.koin.core.module.dsl.factoryOf
+import org.koin.core.module.dsl.singleOf
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+
+val appModule = module {
+ viewModelOf(::FaqViewModel)
+ viewModelOf(::ActionSelectViewModel)
+ viewModelOf(::ChannelsViewModel)
+ viewModelOf(::AccountsViewModel)
+ viewModelOf(::AccountDetailViewModel)
+ viewModelOf(::NewRequestViewModel)
+ viewModelOf(::TransferViewModel)
+ viewModelOf(::ScheduleDetailViewModel)
+ viewModelOf(::BalancesViewModel)
+ viewModelOf(::TransactionHistoryViewModel)
+ viewModelOf(::BannerViewModel)
+ viewModelOf(::FutureViewModel)
+ viewModelOf(::LoginViewModel)
+ viewModelOf(::TransactionDetailsViewModel)
+ viewModelOf(::LanguageViewModel)
+ viewModelOf(::BountyViewModel)
+ viewModelOf(::FinancialTipsViewModel)
+ viewModelOf(::PaybillViewModel)
+ viewModelOf(::MerchantViewModel)
+ viewModelOf(::RequestDetailViewModel)
+ viewModelOf(::BonusViewModel)
+
+ viewModelOf(::HomeViewModel)
+}
+
+val dataModule = module(createdAtStart = true) {
+ single { AppDatabase.getInstance(get()) }
+ single { HoverRoomDatabase.getInstance(get()) }
+
+ singleOf(::TransactionRepo)
+ singleOf(::ChannelRepo)
+ singleOf(::ActionRepo)
+ singleOf(::ContactRepo)
+ singleOf(::AccountRepo)
+ singleOf(::RequestRepo)
+ singleOf(::ScheduleRepo)
+ singleOf(::PaybillRepo)
+ singleOf(::MerchantRepo)
+ singleOf(::UserRepo)
+ singleOf(::BonusRepo)
+ singleOf(::ParserRepo)
+}
+
+val networkModule = module {
+ singleOf(::LoginNetworking)
+}
+
+val repositories = module {
+ single(named("CoroutineDispatcher")) {
+ Dispatchers.IO
+ }
+
+ single { BonusRepositoryImpl(get(), get(), get(named("CoroutineDispatcher"))) }
+ single { AccountRepositoryImpl(get(), get(), get(), get(named("CoroutineDispatcher"))) }
+ single { BountyRepositoryImpl(get(), get(named("CoroutineDispatcher"))) }
+
+ singleOf(::FinancialTipsRepositoryImpl) { bind() }
+ singleOf(::ChannelRepositoryImpl) { bind() }
+}
+
+val useCases = module {
+ factoryOf(::GetBonusesUseCase)
+ factoryOf(::FetchBonusUseCase)
+
+ factoryOf(::GetAccountsUseCase)
+ factoryOf(::SetDefaultAccountUseCase)
+ factoryOf(::CreateAccountsUseCase)
+
+ factoryOf(::TipsUseCase)
+
+ factoryOf(::GetChannelBountiesUseCase)
+ factoryOf(::GetPresentSimsUseCase)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/accounts/Account.kt b/app/src/main/java/com/hover/stax/domain/model/Account.kt
similarity index 92%
rename from app/src/main/java/com/hover/stax/accounts/Account.kt
rename to app/src/main/java/com/hover/stax/domain/model/Account.kt
index 9c6670637..f385fa61d 100644
--- a/app/src/main/java/com/hover/stax/accounts/Account.kt
+++ b/app/src/main/java/com/hover/stax/domain/model/Account.kt
@@ -1,12 +1,11 @@
-package com.hover.stax.accounts
+package com.hover.stax.domain.model
import androidx.room.*
import com.hover.stax.channels.Channel
import com.hover.stax.utils.DateUtils.now
import timber.log.Timber
-const val DUMMY = -1
-const val PLACEHOLDER = "placeholder"
+const val PLACEHOLDER = " placeholder"
const val ACCOUNT_NAME: String = "account_name"
const val ACCOUNT_ID: String = "account_id"
@@ -76,13 +75,6 @@ data class Account(
}
}
- fun dummy(): Account {
- id = DUMMY
- latestBalanceTimestamp = -1L
- latestBalance = "0"
- return this
- }
-
override fun toString() = buildString {
append(alias)
diff --git a/app/src/main/java/com/hover/stax/bonus/Bonus.kt b/app/src/main/java/com/hover/stax/domain/model/Bonus.kt
similarity index 93%
rename from app/src/main/java/com/hover/stax/bonus/Bonus.kt
rename to app/src/main/java/com/hover/stax/domain/model/Bonus.kt
index adef25845..5f0837080 100644
--- a/app/src/main/java/com/hover/stax/bonus/Bonus.kt
+++ b/app/src/main/java/com/hover/stax/domain/model/Bonus.kt
@@ -1,4 +1,4 @@
-package com.hover.stax.bonus
+package com.hover.stax.domain.model
import androidx.room.ColumnInfo
import androidx.room.Entity
diff --git a/app/src/main/java/com/hover/stax/bounties/Bounty.kt b/app/src/main/java/com/hover/stax/domain/model/Bounty.kt
similarity index 94%
rename from app/src/main/java/com/hover/stax/bounties/Bounty.kt
rename to app/src/main/java/com/hover/stax/domain/model/Bounty.kt
index 53bb4201f..30a5a5a11 100644
--- a/app/src/main/java/com/hover/stax/bounties/Bounty.kt
+++ b/app/src/main/java/com/hover/stax/domain/model/Bounty.kt
@@ -1,4 +1,4 @@
-package com.hover.stax.bounties
+package com.hover.stax.domain.model
import android.content.Context
import com.hover.sdk.actions.HoverAction
@@ -9,13 +9,11 @@ import com.hover.stax.transactions.StaxTransaction
import com.yariksoffice.lingver.Lingver
import java.util.*
-
class Bounty(val action: HoverAction, val transactions: List) {
val transactionCount get(): Int = transactions.size
-
- fun lastTransactionIndex(): Int = if (transactionCount == 0) 0 else transactionCount - 1
- fun hasASuccessfulTransaction(): Boolean = transactions.any { it.status == Transaction.SUCCEEDED }
+
+ fun hasSuccessfulTransactions(): Boolean = transactions.any { it.status == Transaction.SUCCEEDED }
fun isLastTransactionFailed(): Boolean = if (transactionCount == 0) false else transactions.last().status == Transaction.FAILED
fun generateDescription(c: Context): String = when (action.transaction_type) {
diff --git a/app/src/main/java/com/hover/stax/domain/model/FinancialTip.kt b/app/src/main/java/com/hover/stax/domain/model/FinancialTip.kt
new file mode 100644
index 000000000..acaf7a485
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/model/FinancialTip.kt
@@ -0,0 +1,12 @@
+package com.hover.stax.domain.model
+
+data class FinancialTip(
+ val id: String,
+ val title: String,
+ val content: String,
+ val snippet: String,
+ val date: Long?,
+ val shareCopy: String?,
+ val deepLink: String?
+)
+val FINANCIAL_TIP_ID = "id"
diff --git a/app/src/main/java/com/hover/stax/domain/model/Resource.kt b/app/src/main/java/com/hover/stax/domain/model/Resource.kt
new file mode 100644
index 000000000..6d10e9be7
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/model/Resource.kt
@@ -0,0 +1,7 @@
+package com.hover.stax.domain.model
+
+sealed class Resource(val data: T? = null, val message: String? = null) {
+ class Success(data: T) : Resource(data)
+ class Error(message: String, data: T? = null) : Resource(data, message)
+ class Loading(data: T? = null) : Resource(data)
+}
diff --git a/app/src/main/java/com/hover/stax/domain/repository/AccountRepository.kt b/app/src/main/java/com/hover/stax/domain/repository/AccountRepository.kt
new file mode 100644
index 000000000..bac9cbfdf
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/repository/AccountRepository.kt
@@ -0,0 +1,14 @@
+package com.hover.stax.domain.repository
+
+import com.hover.stax.domain.model.Account
+import com.hover.stax.channels.Channel
+import kotlinx.coroutines.flow.Flow
+
+interface AccountRepository {
+
+ val fetchAccounts: Flow>
+
+ suspend fun createAccounts(channels: List): List
+
+ suspend fun setDefaultAccount(account: Account)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/repository/BonusRepository.kt b/app/src/main/java/com/hover/stax/domain/repository/BonusRepository.kt
new file mode 100644
index 000000000..f5b21cd61
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/repository/BonusRepository.kt
@@ -0,0 +1,20 @@
+package com.hover.stax.domain.repository
+
+import com.hover.stax.domain.model.Bonus
+import com.hover.stax.channels.Channel
+import kotlinx.coroutines.flow.Flow
+
+interface BonusRepository {
+
+ suspend fun fetchBonuses()
+
+ val bonusList: Flow>
+
+ suspend fun saveBonuses(bonusList: List)
+
+ suspend fun getBonusChannels(bonusList: List): List
+
+ suspend fun getBonusByPurchaseChannel(channelId: Int): Bonus?
+
+ suspend fun getBonusByUserChannel(channelId: Int): Bonus?
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/repository/BountyRepository.kt b/app/src/main/java/com/hover/stax/domain/repository/BountyRepository.kt
new file mode 100644
index 000000000..d6ccafc6e
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/repository/BountyRepository.kt
@@ -0,0 +1,21 @@
+package com.hover.stax.domain.repository
+
+import androidx.lifecycle.LiveData
+import com.hover.sdk.actions.HoverAction
+import com.hover.sdk.sims.SimInfo
+import com.hover.stax.domain.model.Bounty
+import com.hover.stax.domain.model.ChannelBounties
+import com.hover.stax.channels.Channel
+import com.hover.stax.transactions.StaxTransaction
+import kotlinx.coroutines.flow.Flow
+
+interface BountyRepository {
+
+ val bountyActions: List
+
+ fun isSimPresent(bounty: Bounty, sims: List): Boolean
+
+ fun getCountryList(): Flow>
+
+ suspend fun makeBounties(actions: List, transactions: List?, channels: List): List
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/repository/ChannelRepository.kt b/app/src/main/java/com/hover/stax/domain/repository/ChannelRepository.kt
new file mode 100644
index 000000000..0429cd5f4
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/repository/ChannelRepository.kt
@@ -0,0 +1,17 @@
+package com.hover.stax.domain.repository
+
+import com.hover.sdk.actions.HoverAction
+import com.hover.sdk.sims.SimInfo
+import com.hover.stax.channels.Channel
+import kotlinx.coroutines.flow.Flow
+
+interface ChannelRepository {
+
+ val presentSims: List
+
+ suspend fun getChannelsByIds(ids: List): List
+
+ suspend fun getChannelsByCountryCode(ids: IntArray, countryCode: String): List
+
+ suspend fun filterChannels(countryCode: String, actions: List): List
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/repository/FinancialTipsRepository.kt b/app/src/main/java/com/hover/stax/domain/repository/FinancialTipsRepository.kt
new file mode 100644
index 000000000..70990c007
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/repository/FinancialTipsRepository.kt
@@ -0,0 +1,11 @@
+package com.hover.stax.domain.repository
+
+import com.hover.stax.domain.model.FinancialTip
+import kotlinx.coroutines.flow.Flow
+
+interface FinancialTipsRepository {
+
+ suspend fun getTips(): List
+ fun getDismissedTipId() : String?
+ fun dismissTip(id: String)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/use_case/accounts/CreateAccountsUseCase.kt b/app/src/main/java/com/hover/stax/domain/use_case/accounts/CreateAccountsUseCase.kt
new file mode 100644
index 000000000..ba435cdd7
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/use_case/accounts/CreateAccountsUseCase.kt
@@ -0,0 +1,11 @@
+package com.hover.stax.domain.use_case.accounts
+
+import com.hover.stax.channels.Channel
+import com.hover.stax.domain.repository.AccountRepository
+
+class CreateAccountsUseCase(private val accountsRepository: AccountRepository) {
+
+ suspend operator fun invoke(channels: List): List {
+ return accountsRepository.createAccounts(channels)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/use_case/accounts/GetAccountsUseCase.kt b/app/src/main/java/com/hover/stax/domain/use_case/accounts/GetAccountsUseCase.kt
new file mode 100644
index 000000000..48ee55ba4
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/use_case/accounts/GetAccountsUseCase.kt
@@ -0,0 +1,11 @@
+package com.hover.stax.domain.use_case.accounts
+
+import com.hover.stax.domain.model.Account
+import com.hover.stax.domain.repository.AccountRepository
+import kotlinx.coroutines.flow.Flow
+
+class GetAccountsUseCase(accountsRepository: AccountRepository) {
+
+ val accounts: Flow> = accountsRepository.fetchAccounts
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/use_case/accounts/SetDefaultAccountUseCase.kt b/app/src/main/java/com/hover/stax/domain/use_case/accounts/SetDefaultAccountUseCase.kt
new file mode 100644
index 000000000..1c2b91651
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/use_case/accounts/SetDefaultAccountUseCase.kt
@@ -0,0 +1,11 @@
+package com.hover.stax.domain.use_case.accounts
+
+import com.hover.stax.domain.model.Account
+import com.hover.stax.domain.repository.AccountRepository
+
+class SetDefaultAccountUseCase(private val accountsRepository: AccountRepository) {
+
+ suspend operator fun invoke(account: Account) {
+ accountsRepository.setDefaultAccount(account)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/use_case/bonus/FetchBonusUseCase.kt b/app/src/main/java/com/hover/stax/domain/use_case/bonus/FetchBonusUseCase.kt
new file mode 100644
index 000000000..7c2e58cc0
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/use_case/bonus/FetchBonusUseCase.kt
@@ -0,0 +1,9 @@
+package com.hover.stax.domain.use_case.bonus
+
+import com.hover.stax.domain.repository.BonusRepository
+
+class FetchBonusUseCase(private val repository: BonusRepository) {
+
+ suspend operator fun invoke() = repository.fetchBonuses()
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/use_case/bonus/GetBonusesUseCase.kt b/app/src/main/java/com/hover/stax/domain/use_case/bonus/GetBonusesUseCase.kt
new file mode 100644
index 000000000..a3262669a
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/use_case/bonus/GetBonusesUseCase.kt
@@ -0,0 +1,11 @@
+package com.hover.stax.domain.use_case.bonus
+
+import com.hover.stax.domain.model.Bonus
+import com.hover.stax.domain.repository.BonusRepository
+import kotlinx.coroutines.flow.Flow
+
+class GetBonusesUseCase(repository: BonusRepository) {
+
+ val bonusList: Flow> = repository.bonusList
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/use_case/bounties/GetChannelBountiesUseCase.kt b/app/src/main/java/com/hover/stax/domain/use_case/bounties/GetChannelBountiesUseCase.kt
new file mode 100644
index 000000000..201e6bfe6
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/use_case/bounties/GetChannelBountiesUseCase.kt
@@ -0,0 +1,45 @@
+package com.hover.stax.domain.use_case.bounties
+
+import com.hover.stax.countries.CountryAdapter
+import com.hover.stax.domain.model.ChannelBounties
+import com.hover.stax.domain.model.Resource
+import com.hover.stax.domain.repository.BountyRepository
+import com.hover.stax.domain.repository.ChannelRepository
+import com.hover.stax.transactions.TransactionRepo
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+class GetChannelBountiesUseCase(private val channelRepository: ChannelRepository, private val bountyRepository: BountyRepository, private val transactionRepo: TransactionRepo) {
+
+ fun getBounties(countryCode: String = CountryAdapter.CODE_ALL_COUNTRIES): Flow>> = flow {
+ try {
+ emit(Resource.Loading())
+
+ emit(Resource.Success(fetchBounties(countryCode)))
+ } catch (e: Exception) {
+ emit(Resource.Error("Error loading bounties"))
+ }
+ }
+
+ private suspend fun fetchBounties(countryCode: String): List {
+ val channelBounties: Deferred>
+
+ coroutineScope {
+ channelBounties = async(Dispatchers.IO) {
+ val bountyActions = bountyRepository.bountyActions
+ val bountyTransactionList = transactionRepo.bountyTransactionList
+ val bountyChannels = channelRepository.filterChannels(countryCode, bountyActions)
+
+ bountyRepository.makeBounties(bountyActions, bountyTransactionList, bountyChannels)
+ }
+ }
+
+ return channelBounties.await()
+ }
+
+ fun getChannelList(): Flow> = bountyRepository.getCountryList()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/use_case/channels/GetPresentSimsUseCase.kt b/app/src/main/java/com/hover/stax/domain/use_case/channels/GetPresentSimsUseCase.kt
new file mode 100644
index 000000000..8fdb87096
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/use_case/channels/GetPresentSimsUseCase.kt
@@ -0,0 +1,14 @@
+package com.hover.stax.domain.use_case.channels
+
+import com.hover.sdk.sims.SimInfo
+import com.hover.stax.domain.model.Bounty
+import com.hover.stax.domain.repository.BountyRepository
+import com.hover.stax.domain.repository.ChannelRepository
+
+class GetPresentSimsUseCase(channelRepository: ChannelRepository, private val bountyRepository: BountyRepository) {
+
+ val presentSims: List = channelRepository.presentSims
+
+ fun simPresent(bounty: Bounty, sims: List): Boolean = bountyRepository.isSimPresent(bounty, sims)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/domain/use_case/financial_tips/TipsUseCase.kt b/app/src/main/java/com/hover/stax/domain/use_case/financial_tips/TipsUseCase.kt
new file mode 100644
index 000000000..cc1601d2f
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/domain/use_case/financial_tips/TipsUseCase.kt
@@ -0,0 +1,27 @@
+package com.hover.stax.domain.use_case.financial_tips
+
+import androidx.compose.runtime.mutableStateOf
+import com.hover.stax.domain.model.FinancialTip
+import com.hover.stax.domain.model.Resource
+import com.hover.stax.domain.repository.FinancialTipsRepository
+import kotlinx.coroutines.flow.*
+import timber.log.Timber
+
+class TipsUseCase(private val financialTipsRepository: FinancialTipsRepository) {
+
+ operator fun invoke(): Flow>> = flow {
+ try {
+ emit(Resource.Loading())
+
+ val financialTips = financialTipsRepository.getTips()
+ emit(Resource.Success(financialTips))
+ } catch (e: Exception) {
+ emit(Resource.Error("Error fetching tips"))
+ }
+ }
+
+ fun getDismissedTipId() : String? = financialTipsRepository.getDismissedTipId()
+ fun dismissTip(id: String) {
+ financialTipsRepository.dismissTip(id)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/financialTips/FinancialTipsViewModel.kt b/app/src/main/java/com/hover/stax/financialTips/FinancialTipsViewModel.kt
deleted file mode 100644
index e9e573bc1..000000000
--- a/app/src/main/java/com/hover/stax/financialTips/FinancialTipsViewModel.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.hover.stax.financialTips
-
-import android.app.Application
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.google.firebase.firestore.Query
-import com.google.firebase.firestore.ktx.firestore
-import com.google.firebase.firestore.ktx.firestoreSettings
-import com.google.firebase.ktx.Firebase
-import com.hover.stax.R
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
-import timber.log.Timber
-
-data class FinancialTip(val id: String, val title: String, val content: String, val snippet: String, val date: Long?, val shareCopy: String?, val deepLink: String?)
-
-data class FinancialTipsState(val tips: List = emptyList())
-
-class FinancialTipsViewModel(val application: Application) : ViewModel() {
-
- val db = Firebase.firestore
- val settings = firestoreSettings { isPersistenceEnabled = true }
-
- private val _tips = MutableStateFlow(FinancialTipsState())
- val tipState: StateFlow = _tips
-
- init {
- db.firestoreSettings = settings
- }
-
- fun getTips() = viewModelScope.launch {
- val today = System.currentTimeMillis()
-
- db.collection(application.getString(R.string.tips_table))
- .orderBy("date", Query.Direction.DESCENDING)
- .whereLessThanOrEqualTo("date", today / 1000)
- .limit(20)
- .get()
- .addOnSuccessListener { snapshot ->
- val financialTip = snapshot.map { document ->
- FinancialTip(
- document.id, document.data["title"].toString(), document.data["content"].toString(),
- document.data["snippet"].toString(), (document.data["date"].toString().toLong() * 1000), document.data["share copy"].toString(),
- document.data["deep link"].toString()
- )
- }
-
- _tips.value = tipState.value.copy(tips = financialTip.filterNot { it.date == null }.sortedByDescending { it.date })
- }
- .addOnFailureListener {
- Timber.e("Error fetching wellness tips: ${it.localizedMessage}")
- _tips.value = tipState.value.copy(tips = emptyList())
- }
- }
-}
diff --git a/app/src/main/java/com/hover/stax/futureTransactions/FutureViewModel.kt b/app/src/main/java/com/hover/stax/futureTransactions/FutureViewModel.kt
index d48ab4379..13db61178 100644
--- a/app/src/main/java/com/hover/stax/futureTransactions/FutureViewModel.kt
+++ b/app/src/main/java/com/hover/stax/futureTransactions/FutureViewModel.kt
@@ -5,12 +5,11 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.hover.stax.channels.Channel
-import com.hover.stax.channels.ChannelRepo
+import com.hover.stax.data.local.channels.ChannelRepo
import com.hover.stax.schedules.ScheduleRepo
import com.hover.stax.requests.Request
import com.hover.stax.requests.RequestRepo
import com.hover.stax.schedules.Schedule
-import com.hover.stax.transactions.TransactionRepo
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
diff --git a/app/src/main/java/com/hover/stax/home/HomeFragment.kt b/app/src/main/java/com/hover/stax/home/HomeFragment.kt
deleted file mode 100644
index f55dd2c77..000000000
--- a/app/src/main/java/com/hover/stax/home/HomeFragment.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-package com.hover.stax.home
-
-import android.os.Bundle
-import android.text.method.LinkMovementMethod
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.navigation.NavDirections
-import androidx.navigation.fragment.findNavController
-import com.hover.sdk.actions.HoverAction
-import com.hover.stax.R
-import com.hover.stax.addChannels.ChannelsViewModel
-import com.hover.stax.bonus.Bonus
-import com.hover.stax.bonus.BonusViewModel
-import com.hover.stax.databinding.FragmentHomeBinding
-import com.hover.stax.financialTips.FinancialTip
-import com.hover.stax.financialTips.FinancialTipsViewModel
-import com.hover.stax.utils.AnalyticsUtil
-import com.hover.stax.utils.NavUtil
-import com.hover.stax.utils.collectLatestLifecycleFlow
-import com.hover.stax.utils.network.NetworkMonitor
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-import org.koin.androidx.viewmodel.ext.android.sharedViewModel
-import org.koin.androidx.viewmodel.ext.android.viewModel
-import timber.log.Timber
-import java.util.*
-
-
-class HomeFragment : Fragment() {
-
- private var _binding: FragmentHomeBinding? = null
- private val binding get() = _binding!!
-
- private val wellnessViewModel: FinancialTipsViewModel by viewModel()
- private val bonusViewModel: BonusViewModel by sharedViewModel()
- private val channelsViewModel: ChannelsViewModel by sharedViewModel()
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- AnalyticsUtil.logAnalyticsEvent(getString(R.string.visit_screen, getString(R.string.visit_home)), requireContext())
- _binding = FragmentHomeBinding.inflate(inflater, container, false)
-
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- bonusViewModel.fetchBonuses()
- wellnessViewModel.getTips()
- setupBanner()
-
- binding.airtime.setOnClickListener { navigateTo(getTransferDirection(HoverAction.AIRTIME)) }
- binding.transfer.setOnClickListener { navigateTo(getTransferDirection(HoverAction.P2P)) }
- binding.merchant.setOnClickListener { navigateTo(HomeFragmentDirections.actionNavigationHomeToMerchantFragment()) }
- binding.paybill.setOnClickListener { navigateTo(HomeFragmentDirections.actionNavigationHomeToPaybillFragment()) }
- binding.requestMoney.setOnClickListener { navigateTo(HomeFragmentDirections.actionNavigationHomeToNavigationRequest()) }
-
- NetworkMonitor.StateLiveData.get().observe(viewLifecycleOwner) {
- updateOfflineIndicator(it)
- }
-
- setUpWellnessTips()
- setKeVisibility()
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- channelsViewModel.accountEventFlow.collect {
- navigateTo(getTransferDirection(HoverAction.AIRTIME, bonusViewModel.bonusList.value.bonuses.first().userChannel.toString()))
- }
- }
- }
- }
-
- private fun getTransferDirection(type: String, channelId: String? = null): NavDirections {
- return HomeFragmentDirections.actionNavigationHomeToNavigationTransfer(type).also {
- if (channelId != null)
- it.channelId = channelId
- }
- }
-
- private fun setupBanner() {
- bonusViewModel.getBonusList()
-
- collectLatestLifecycleFlow(bonusViewModel.bonusList) { bonusList ->
- if (bonusList.bonuses.isNotEmpty()) {
- with(binding.bonusCard) {
- message.text = bonusList.bonuses.first().message
- learnMore.movementMethod = LinkMovementMethod.getInstance()
- }
- binding.bonusCard.apply {
- cardBonus.visibility = View.VISIBLE
- cta.setOnClickListener {
- AnalyticsUtil.logAnalyticsEvent(getString(R.string.clicked_bonus_airtime_banner), requireActivity())
- validateAccounts(bonusList.bonuses.first())
- }
- }
- } else binding.bonusCard.cardBonus.visibility = View.GONE
- }
- }
-
- private fun setKeVisibility() {
- channelsViewModel.simCountryList.observe(viewLifecycleOwner) {
- binding.merchant.visibility = if (showMpesaActions(it)) View.VISIBLE else View.GONE
- binding.paybill.visibility = if (showMpesaActions(it)) View.VISIBLE else View.GONE
- }
- }
-
- private fun showMpesaActions(countryIsos: List): Boolean = countryIsos.any { it.contentEquals("KE", ignoreCase = true) }
-
- private fun navigateTo(navDirections: NavDirections) = (requireActivity() as MainActivity).checkPermissionsAndNavigate(navDirections)
-
- private fun updateOfflineIndicator(isConnected: Boolean) {
- binding.offlineBadge.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_internet_off, 0, 0, 0)
- binding.offlineBadge.visibility = if (isConnected) View.GONE else View.VISIBLE
- }
-
- private fun setUpWellnessTips() = collectLatestLifecycleFlow(wellnessViewModel.tipState) {
- if (it.tips.isNotEmpty())
- showTip(it.tips.first())
- else
- binding.wellnessCard.tipsCard.visibility = View.GONE
- }
-
- private fun showTip(tip: FinancialTip) {
- tip.date?.let {
- if (android.text.format.DateUtils.isToday(it)) {
- with(binding.wellnessCard) {
- tipsCard.visibility = View.VISIBLE
-
- title.text = tip.title
- snippet.text = tip.snippet
-
- contentLayout.setOnClickListener {
- NavUtil.navigate(findNavController(), HomeFragmentDirections.actionNavigationHomeToWellnessFragment(tip.id))
- }
-
- readMoreLayout.setOnClickListener {
- NavUtil.navigate(findNavController(), HomeFragmentDirections.actionNavigationHomeToWellnessFragment(null))
- }
- }
- } else
- Timber.i("No tips available today")
- }
- }
-
- private fun validateAccounts(bonus: Bonus) {
- channelsViewModel.validateAccounts(bonus.userChannel)
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/home/MainActivity.kt b/app/src/main/java/com/hover/stax/home/MainActivity.kt
index 05b5848dc..91aef6355 100644
--- a/app/src/main/java/com/hover/stax/home/MainActivity.kt
+++ b/app/src/main/java/com/hover/stax/home/MainActivity.kt
@@ -11,7 +11,7 @@ import com.hover.stax.MainNavigationDirections
import com.hover.stax.R
import com.hover.stax.bonus.BonusViewModel
import com.hover.stax.databinding.ActivityMainBinding
-import com.hover.stax.financialTips.FinancialTipsFragment
+import com.hover.stax.presentation.financial_tips.FinancialTipsFragment
import com.hover.stax.login.AbstractGoogleAuthActivity
import com.hover.stax.notifications.PushNotificationTopicsInterface
import com.hover.stax.requests.NewRequestViewModel
@@ -117,7 +117,7 @@ class MainActivity : AbstractGoogleAuthActivity(), BiometricChecker.AuthListener
sendSms(requestViewModel, this)
} else if (requestCode == SMS) {
AnalyticsUtil.logAnalyticsEvent(getString(R.string.perms_sms_denied), this)
- UIHelper.flashMessage(this, getString(R.string.toast_error_smsperm))
+ UIHelper.flashAndReportMessage(this, getString(R.string.toast_error_smsperm))
}
}
diff --git a/app/src/main/java/com/hover/stax/home/NavHelper.kt b/app/src/main/java/com/hover/stax/home/NavHelper.kt
index 84f89d8d2..48d583583 100644
--- a/app/src/main/java/com/hover/stax/home/NavHelper.kt
+++ b/app/src/main/java/com/hover/stax/home/NavHelper.kt
@@ -42,7 +42,7 @@ class NavHelper(val activity: AppCompatActivity) {
navController?.let {
NavigationUI.setupWithNavController(nav, navController!!)
appBarConfiguration = AppBarConfiguration.Builder(
- R.id.navigation_home, R.id.navigation_balance, R.id.navigation_history, R.id.libraryFragment, R.id.navigation_settings
+ R.id.navigation_home, R.id.navigation_history, R.id.libraryFragment
).build()
}
@@ -50,8 +50,6 @@ class NavHelper(val activity: AppCompatActivity) {
setDestinationChangeListener(nav)
}
- fun showTxnDetails(uuid: String, isNewTransaction: Boolean? = false) = navController?.let { NavUtil.showTransactionDetailsFragment(it, uuid, isNewTransaction!!) }
-
fun navigateWellness(tipId: String?) = navController?.let {
NavUtil.navigate(it, MainNavigationDirections.actionGlobalWellnessFragment(tipId))
}
@@ -79,9 +77,6 @@ class NavHelper(val activity: AppCompatActivity) {
private fun setDestinationChangeListener(nav: BottomNavigationView) = navController?.let {
it.addOnDestinationChangedListener { _, destination, _ ->
nav.visibility = if (destination.id == R.id.navigation_linkAccount) View.GONE else View.VISIBLE
-
- if (destination.id == R.id.bountyEmailFragment || destination.id == R.id.bountyListFragment)
- nav.menu.findItem(R.id.navigation_settings).isChecked = true
}
}
@@ -111,7 +106,6 @@ class NavHelper(val activity: AppCompatActivity) {
R.id.navigation_settings, NAV_SETTINGS -> MainNavigationDirections.actionGlobalNavigationSettings()
R.id.navigation_home, NAV_HOME -> MainNavigationDirections.actionGlobalNavigationHome()
R.id.libraryFragment, NAV_USSD_LIB -> MainNavigationDirections.actionGlobalLibraryFragment()
- R.id.navigation_balance, NAV_BALANCE -> MainNavigationDirections.actionGlobalNavigationBalance()
NAV_TRANSFER -> MainNavigationDirections.actionGlobalTransferFragment(HoverAction.P2P)
NAV_AIRTIME -> MainNavigationDirections.actionGlobalTransferFragment(HoverAction.AIRTIME)
NAV_LINK_ACCOUNT -> MainNavigationDirections.actionGlobalAddChannelsFragment()
diff --git a/app/src/main/java/com/hover/stax/hover/AbstractHoverCallerActivity.kt b/app/src/main/java/com/hover/stax/hover/AbstractHoverCallerActivity.kt
index d38e99c42..14ab6c711 100644
--- a/app/src/main/java/com/hover/stax/hover/AbstractHoverCallerActivity.kt
+++ b/app/src/main/java/com/hover/stax/hover/AbstractHoverCallerActivity.kt
@@ -7,8 +7,8 @@ import com.hover.sdk.actions.HoverAction
import com.hover.sdk.api.HoverParameters
import com.hover.sdk.transactions.TransactionContract
import com.hover.stax.R
-import com.hover.stax.accounts.Account
-import com.hover.stax.balances.BalancesViewModel
+import com.hover.stax.domain.model.Account
+import com.hover.stax.presentation.home.BalancesViewModel
import com.hover.stax.home.NavHelper
import com.hover.stax.notifications.PushNotificationTopicsInterface
import com.hover.stax.schedules.Schedule
@@ -41,7 +41,7 @@ abstract class AbstractHoverCallerActivity : AppCompatActivity(), PushNotificati
hsb.run()
updatePushNotifGroupStatus()
} catch (e: Exception) {
- runOnUiThread { UIHelper.flashMessage(this, getString(R.string.error_running_action)) }
+ runOnUiThread { UIHelper.flashAndReportMessage(this, getString(R.string.error_running_action)) }
createLog(hsb, "Failed Actions")
}
@@ -93,7 +93,6 @@ abstract class AbstractHoverCallerActivity : AppCompatActivity(), PushNotificati
BOUNTY_REQUEST -> showBountyDetails(data)
FEE_REQUEST -> showFeeDetails(data)
else -> {
- balancesViewModel.setBalanceState(true)
navToTransactionDetail(data)
}
}
@@ -105,7 +104,7 @@ abstract class AbstractHoverCallerActivity : AppCompatActivity(), PushNotificati
else ""
}
- private fun showMessage(str: String) = UIHelper.flashMessage(this, findViewById(R.id.fab), str)
+ private fun showMessage(str: String) = UIHelper.showAndReportSnackBar(this, findViewById(R.id.fab), str)
private fun showBountyDetails(data: Intent?) {
Timber.i("Request code is bounty")
diff --git a/app/src/main/java/com/hover/stax/hover/HoverSession.kt b/app/src/main/java/com/hover/stax/hover/HoverSession.kt
index b8529eed2..dc945d474 100644
--- a/app/src/main/java/com/hover/stax/hover/HoverSession.kt
+++ b/app/src/main/java/com/hover/stax/hover/HoverSession.kt
@@ -7,9 +7,9 @@ import com.hover.sdk.actions.HoverAction
import com.hover.sdk.api.Hover
import com.hover.sdk.api.HoverParameters
import com.hover.stax.R
-import com.hover.stax.accounts.ACCOUNT_ID
-import com.hover.stax.accounts.ACCOUNT_NAME
-import com.hover.stax.accounts.Account
+import com.hover.stax.domain.model.ACCOUNT_ID
+import com.hover.stax.domain.model.ACCOUNT_NAME
+import com.hover.stax.domain.model.Account
import com.hover.stax.contacts.PhoneHelper
import com.hover.stax.settings.TEST_MODE
import com.hover.stax.utils.AnalyticsUtil
@@ -113,7 +113,6 @@ class HoverSession private constructor(b: Builder) {
init {
requireNotNull(a) { "Action must not be null" }
- requireNotNull(c) { "Account must not be null" }
this.activity = activity
account = c
action = a
diff --git a/app/src/main/java/com/hover/stax/hover/TransactionReceiver.kt b/app/src/main/java/com/hover/stax/hover/TransactionReceiver.kt
index bba16efb0..0d0f1214a 100644
--- a/app/src/main/java/com/hover/stax/hover/TransactionReceiver.kt
+++ b/app/src/main/java/com/hover/stax/hover/TransactionReceiver.kt
@@ -5,15 +5,15 @@ import android.content.Context
import android.content.Intent
import com.hover.sdk.actions.HoverAction
import com.hover.sdk.transactions.TransactionContract
-import com.hover.stax.accounts.ACCOUNT_ID
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.AccountRepo
-import com.hover.stax.accounts.PLACEHOLDER
-import com.hover.stax.actions.ActionRepo
+import com.hover.stax.domain.model.ACCOUNT_ID
+import com.hover.stax.domain.model.Account
+import com.hover.stax.data.local.accounts.AccountRepo
+import com.hover.stax.data.local.actions.ActionRepo
import com.hover.stax.channels.Channel
-import com.hover.stax.channels.ChannelRepo
+import com.hover.stax.data.local.channels.ChannelRepo
import com.hover.stax.contacts.ContactRepo
import com.hover.stax.contacts.StaxContact
+import com.hover.stax.domain.model.PLACEHOLDER
import com.hover.stax.merchants.MerchantRepo
import com.hover.stax.paybill.BUSINESS_NAME
import com.hover.stax.paybill.BUSINESS_NO
@@ -196,7 +196,7 @@ class TransactionReceiver : BroadcastReceiver(), KoinComponent {
parsedAccounts.forEach {
if (savedAccounts.contains(it.channelId)) {
Timber.e("Removing ${it.channelId} from ${it.name}")
- accountRepo.deleteAccount(it.channelId, PLACEHOLDER)
+ accountRepo.deleteAccount(it.channelId, it.name.plus(PLACEHOLDER))
}
}
}
diff --git a/app/src/main/java/com/hover/stax/inapp_banner/BannerUtils.kt b/app/src/main/java/com/hover/stax/inapp_banner/BannerUtils.kt
index 3e429ae22..01fed00a6 100644
--- a/app/src/main/java/com/hover/stax/inapp_banner/BannerUtils.kt
+++ b/app/src/main/java/com/hover/stax/inapp_banner/BannerUtils.kt
@@ -2,8 +2,7 @@ package com.hover.stax.inapp_banner
import android.content.Context
import com.hover.sdk.permissions.PermissionHelper
-import com.hover.stax.accounts.AccountRepo
-import com.hover.stax.schedules.ScheduleRepo
+import com.hover.stax.data.local.accounts.AccountRepo
import com.hover.stax.utils.DateUtils
import com.hover.stax.utils.Utils
import kotlinx.coroutines.*
diff --git a/app/src/main/java/com/hover/stax/login/AbstractGoogleAuthActivity.kt b/app/src/main/java/com/hover/stax/login/AbstractGoogleAuthActivity.kt
index 29fb1525a..4dfde6a16 100644
--- a/app/src/main/java/com/hover/stax/login/AbstractGoogleAuthActivity.kt
+++ b/app/src/main/java/com/hover/stax/login/AbstractGoogleAuthActivity.kt
@@ -15,8 +15,8 @@ import com.google.android.play.core.install.model.InstallStatus
import com.google.android.play.core.install.model.UpdateAvailability
import com.hover.stax.BuildConfig
import com.hover.stax.R
-import com.hover.stax.bounties.BountyEmailFragmentDirections
import com.hover.stax.hover.AbstractHoverCallerActivity
+import com.hover.stax.presentation.bounties.BountyEmailFragmentDirections
import com.hover.stax.settings.SettingsFragment
import com.hover.stax.utils.UIHelper
import org.koin.androidx.viewmodel.ext.android.viewModel
@@ -74,7 +74,7 @@ abstract class AbstractGoogleAuthActivity : AbstractHoverCallerActivity(), StaxG
it?.let { staxGoogleLoginInterface.googleLoginFailed() }
}
- user.observe(this@AbstractGoogleAuthActivity) {
+ googleUser.observe(this@AbstractGoogleAuthActivity) {
it?.let { staxGoogleLoginInterface.googleLoginSuccessful() }
}
}
@@ -137,7 +137,7 @@ abstract class AbstractGoogleAuthActivity : AbstractHoverCallerActivity(), StaxG
}
override fun googleLoginFailed() {
- UIHelper.flashMessage(this, R.string.login_google_err)
+ UIHelper.flashAndReportMessage(this, R.string.login_google_err)
}
companion object {
diff --git a/app/src/main/java/com/hover/stax/login/LoginViewModel.kt b/app/src/main/java/com/hover/stax/login/LoginViewModel.kt
index a878eae32..d52f08721 100644
--- a/app/src/main/java/com/hover/stax/login/LoginViewModel.kt
+++ b/app/src/main/java/com/hover/stax/login/LoginViewModel.kt
@@ -3,7 +3,9 @@ package com.hover.stax.login
import android.app.Application
import android.content.Context
import android.content.Intent
-import androidx.lifecycle.*
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
@@ -14,7 +16,6 @@ import com.hover.stax.user.StaxUser
import com.hover.stax.user.UserRepo
import com.hover.stax.utils.AnalyticsUtil
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.json.JSONObject
import timber.log.Timber
@@ -24,8 +25,9 @@ class LoginViewModel(application: Application, private val userRepo: UserRepo, p
lateinit var signInClient: GoogleSignInClient
- val user = MutableLiveData()
- val staxUser = MutableLiveData()
+ val googleUser = MutableLiveData()
+ var staxUser = MutableLiveData()
+ private set
var progress = MutableLiveData(-1)
var error = MutableLiveData()
@@ -127,7 +129,7 @@ class LoginViewModel(application: Application, private val userRepo: UserRepo, p
private fun setUser(signInAccount: GoogleSignInAccount, idToken: String) {
Timber.e("setting user: %s", signInAccount.email)
- user.postValue(signInAccount)
+ googleUser.postValue(signInAccount)
progress.value = 33
uploadUserToStax(signInAccount.email, signInAccount.displayName, idToken)
diff --git a/app/src/main/java/com/hover/stax/merchants/Merchant.kt b/app/src/main/java/com/hover/stax/merchants/Merchant.kt
index 6ecb117d2..839a7733b 100644
--- a/app/src/main/java/com/hover/stax/merchants/Merchant.kt
+++ b/app/src/main/java/com/hover/stax/merchants/Merchant.kt
@@ -1,8 +1,8 @@
package com.hover.stax.merchants
import androidx.room.*
-import com.hover.stax.accounts.Account
import com.hover.stax.channels.Channel
+import com.hover.stax.domain.model.Account
import javax.annotation.Nullable
@Entity(tableName = "merchants",
@@ -56,6 +56,6 @@ data class Merchant(
}
fun hasName(): Boolean {
- return businessName != null && !businessName!!.isEmpty()
+ return businessName != null && businessName!!.isNotEmpty()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/merchants/MerchantArrayAdapter.kt b/app/src/main/java/com/hover/stax/merchants/MerchantArrayAdapter.kt
index 4cc3d7728..ed8d44cbe 100644
--- a/app/src/main/java/com/hover/stax/merchants/MerchantArrayAdapter.kt
+++ b/app/src/main/java/com/hover/stax/merchants/MerchantArrayAdapter.kt
@@ -8,6 +8,8 @@ import android.widget.ArrayAdapter
import android.widget.Filter
import android.widget.TextView
import com.hover.stax.databinding.StaxSpinner2lineBinding
+import java.util.*
+import kotlin.collections.ArrayList
class MerchantArrayAdapter(context: Context, val allMerchants: List): ArrayAdapter(context, 0, allMerchants) {
@@ -39,8 +41,8 @@ class MerchantArrayAdapter(context: Context, val allMerchants: List):
val filtered: MutableList = ArrayList()
if (constraint != null) {
for (merchant in allMerchants) {
- if (merchant.toString().replace(" ".toRegex(), "").lowercase()
- .contains(constraint.toString().lowercase())
+ if (merchant.toString().replace(" ".toRegex(), "").lowercase(Locale.getDefault())
+ .contains(constraint.toString().lowercase(Locale.getDefault()))
) {
filtered.add(merchant)
}
diff --git a/app/src/main/java/com/hover/stax/merchants/MerchantFragment.kt b/app/src/main/java/com/hover/stax/merchants/MerchantFragment.kt
index b83a7cc69..49db6a946 100644
--- a/app/src/main/java/com/hover/stax/merchants/MerchantFragment.kt
+++ b/app/src/main/java/com/hover/stax/merchants/MerchantFragment.kt
@@ -14,10 +14,9 @@ import com.hover.stax.contacts.StaxContact
import com.hover.stax.databinding.FragmentMerchantBinding
import com.hover.stax.hover.AbstractHoverCallerActivity
import com.hover.stax.transfers.AbstractFormFragment
-import com.hover.stax.transfers.TransferFragmentDirections
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.Utils
-import com.hover.stax.utils.collectLatestLifecycleFlow
+import com.hover.stax.utils.collectLifecycleFlow
import com.hover.stax.views.AbstractStatefulInput
import org.koin.androidx.viewmodel.ext.android.getSharedViewModel
import timber.log.Timber
@@ -52,7 +51,6 @@ class MerchantFragment : AbstractFormFragment() {
override fun startObservers(root: View) {
super.startObservers(root)
- observeAccountList()
observeActiveAccount()
observeActions()
observeActionSelection()
@@ -61,13 +59,6 @@ class MerchantFragment : AbstractFormFragment() {
observeRecentMerchants()
}
- private fun observeAccountList() {
- collectLatestLifecycleFlow(accountsViewModel.accounts) {
- if (it.isEmpty())
- setDropdownTouchListener(MerchantFragmentDirections.actionMerchantFragmentToAccountsFragment())
- }
- }
-
private fun observeActiveAccount() {
accountsViewModel.activeAccount.observe(viewLifecycleOwner) { account ->
account?.let { binding.summaryCard.accountValue.setTitle(it.toString()) }
diff --git a/app/src/main/java/com/hover/stax/merchants/MerchantViewModel.kt b/app/src/main/java/com/hover/stax/merchants/MerchantViewModel.kt
index b8649e8e2..7443f25cd 100644
--- a/app/src/main/java/com/hover/stax/merchants/MerchantViewModel.kt
+++ b/app/src/main/java/com/hover/stax/merchants/MerchantViewModel.kt
@@ -6,7 +6,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.hover.sdk.actions.HoverAction
import com.hover.stax.R
-import com.hover.stax.accounts.Account
+import com.hover.stax.domain.model.Account
import com.hover.stax.contacts.ContactRepo
import com.hover.stax.paybill.BUSINESS_NO
import com.hover.stax.schedules.ScheduleRepo
@@ -15,7 +15,7 @@ import com.hover.stax.utils.DateUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-class MerchantViewModel(application: Application, contactRepo: ContactRepo, val merchantRepo: MerchantRepo, scheduleRepo: ScheduleRepo) : AbstractFormViewModel(application, contactRepo, scheduleRepo) {
+class MerchantViewModel(application: Application, contactRepo: ContactRepo, private val merchantRepo: MerchantRepo, scheduleRepo: ScheduleRepo) : AbstractFormViewModel(application, contactRepo, scheduleRepo) {
val amount = MutableLiveData()
val merchant = MutableLiveData()
diff --git a/app/src/main/java/com/hover/stax/notifications/MessagingService.kt b/app/src/main/java/com/hover/stax/notifications/MessagingService.kt
index 3d475e2d8..a8b143ef5 100644
--- a/app/src/main/java/com/hover/stax/notifications/MessagingService.kt
+++ b/app/src/main/java/com/hover/stax/notifications/MessagingService.kt
@@ -16,7 +16,7 @@ import com.google.firebase.messaging.RemoteMessage
import com.hover.stax.FRAGMENT_DIRECT
import com.hover.stax.FROM_FCM
import com.hover.stax.R
-import com.hover.stax.financialTips.FinancialTipsFragment
+import com.hover.stax.presentation.financial_tips.FinancialTipsFragment
import com.hover.stax.home.MainActivity
import timber.log.Timber
import kotlin.random.Random
diff --git a/app/src/main/java/com/hover/stax/onboarding/OnBoardingActivity.kt b/app/src/main/java/com/hover/stax/onboarding/OnBoardingActivity.kt
index bc9201efc..4b1c5c447 100644
--- a/app/src/main/java/com/hover/stax/onboarding/OnBoardingActivity.kt
+++ b/app/src/main/java/com/hover/stax/onboarding/OnBoardingActivity.kt
@@ -8,19 +8,18 @@ import com.hover.sdk.permissions.PermissionHelper
import com.hover.stax.FRAGMENT_DIRECT
import com.hover.stax.OnboardingNavigationDirections
import com.hover.stax.R
-import com.hover.stax.VARIANT
import com.hover.stax.databinding.OnboardingLayoutBinding
import com.hover.stax.home.MainActivity
import com.hover.stax.home.NAV_HOME
import com.hover.stax.home.NAV_LINK_ACCOUNT
import com.hover.stax.login.AbstractGoogleAuthActivity
-import com.hover.stax.login.StaxGoogleLoginInterface
-import com.hover.stax.onboarding.signInVariant.SignInVariantFragmentDirections
import com.hover.stax.permissions.PermissionUtils
-import com.hover.stax.utils.*
-import timber.log.Timber
+import com.hover.stax.utils.AnalyticsUtil
+import com.hover.stax.utils.NavUtil
+import com.hover.stax.utils.UIHelper
+import com.hover.stax.utils.Utils
-class OnBoardingActivity : AbstractGoogleAuthActivity(), StaxGoogleLoginInterface {
+class OnBoardingActivity : AbstractGoogleAuthActivity() {
private lateinit var binding: OnboardingLayoutBinding
private lateinit var navController: NavController
@@ -35,7 +34,6 @@ class OnBoardingActivity : AbstractGoogleAuthActivity(), StaxGoogleLoginInterfac
setupNavigation()
navigateNextScreen()
- setGoogleLoginInterface(this)
}
private fun setupNavigation() {
@@ -50,14 +48,6 @@ class OnBoardingActivity : AbstractGoogleAuthActivity(), StaxGoogleLoginInterfac
else NavUtil.navigate(navController, OnboardingNavigationDirections.actionGlobalInteractiveOnboardingVariant())
}
- override fun googleLoginSuccessful() {
- NavUtil.navigate(navController, SignInVariantFragmentDirections.actionSignInVariantFragmentToWelcomeFragment(1))
- }
-
- override fun googleLoginFailed() {
- UIHelper.flashMessage(this, R.string.login_google_err)
- }
-
fun checkPermissionsAndNavigate() {
val permissionHelper = PermissionHelper(this)
if (permissionHelper.hasBasicPerms()) navigateToMainActivity()
diff --git a/app/src/main/java/com/hover/stax/onboarding/defaultVariant/DefaultVariantFragment.kt b/app/src/main/java/com/hover/stax/onboarding/defaultVariant/DefaultVariantFragment.kt
deleted file mode 100644
index eb8a19bae..000000000
--- a/app/src/main/java/com/hover/stax/onboarding/defaultVariant/DefaultVariantFragment.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.hover.stax.onboarding.defaultVariant
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import com.hover.stax.R
-import com.hover.stax.databinding.FragmentDefaultVariantBinding
-import com.hover.stax.onboarding.OnBoardingActivity
-import com.hover.stax.utils.AnalyticsUtil
-
-class DefaultVariantFragment : Fragment() {
-
- private var _binding: FragmentDefaultVariantBinding? = null
- private val binding get() = _binding!!
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = FragmentDefaultVariantBinding.inflate(inflater, container, false)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- AnalyticsUtil.logAnalyticsEvent(getString(R.string.visit_screen, getString(R.string.visit_onboarding)), requireActivity())
- initContinueButton()
- }
-
- private fun initContinueButton() = binding.onboardingContinueBtn.setOnClickListener {
- AnalyticsUtil.logAnalyticsEvent(getString(R.string.clicked_getstarted), requireContext())
- (requireActivity() as OnBoardingActivity).checkPermissionsAndNavigate()
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/onboarding/signInVariant/SignInVariantFragment.kt b/app/src/main/java/com/hover/stax/onboarding/signInVariant/SignInVariantFragment.kt
deleted file mode 100644
index 4ecdfc8c8..000000000
--- a/app/src/main/java/com/hover/stax/onboarding/signInVariant/SignInVariantFragment.kt
+++ /dev/null
@@ -1,214 +0,0 @@
-package com.hover.stax.onboarding.signInVariant
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
-import android.os.Bundle
-import android.text.method.LinkMovementMethod
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.activity.OnBackPressedCallback
-import androidx.core.content.ContextCompat
-import androidx.core.text.HtmlCompat
-import androidx.fragment.app.Fragment
-import androidx.navigation.fragment.findNavController
-import androidx.viewpager.widget.ViewPager
-import com.google.android.material.progressindicator.LinearProgressIndicator
-import com.hover.stax.R
-import com.hover.stax.databinding.FragmentSigninVariantBinding
-import com.hover.stax.onboarding.OnBoardingActivity
-import com.hover.stax.utils.AnalyticsUtil
-import com.hover.stax.utils.NavUtil
-import timber.log.Timber
-
-
-class SignInVariantFragment : Fragment(), ViewPager.OnPageChangeListener {
-
- private var _binding: FragmentSigninVariantBinding? = null
- private val binding get() = _binding!!
-
- private lateinit var progressBar1: LinearProgressIndicator
- private lateinit var progressBar2: LinearProgressIndicator
- private lateinit var progressBar3: LinearProgressIndicator
- private lateinit var progressBar4: LinearProgressIndicator
-
- private lateinit var animator1: ValueAnimator
- private lateinit var animator2: ValueAnimator
- private lateinit var animator3: ValueAnimator
- private lateinit var animator4: ValueAnimator
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- _binding = FragmentSigninVariantBinding.inflate(inflater, container, false)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- AnalyticsUtil.logAnalyticsEvent(getString(R.string.visit_screen, getString(R.string.visit_sign_in)), requireActivity())
-
- initProgressBarView()
- initAnimators()
-
- setUpSlides()
-
- setupPrivacyPolicy()
- setupTermsOfService()
-
- setupSignInWithGoogle()
- setupContinueNoSignIn()
-
- requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedCallback)
- }
-
- private fun setupSignInWithGoogle() = binding.continueWithGoogle.setOnClickListener {
- AnalyticsUtil.logAnalyticsEvent(getString(R.string.clicked_google_sign_in), requireActivity())
- (requireActivity() as OnBoardingActivity).signIn()
- }
-
- private fun setupContinueNoSignIn() = binding.continueNoSignIn.setOnClickListener {
- AnalyticsUtil.logAnalyticsEvent(getString(R.string.clicked_skip_sign_in), requireActivity())
- NavUtil.navigate(findNavController(), SignInVariantFragmentDirections.actionSignInVariantFragmentToWelcomeFragment(1))
- }
-
- private fun initProgressBarView() {
- progressBar1 = binding.pb1
- progressBar2 = binding.pb2
- progressBar3 = binding.pb3
- progressBar4 = binding.pb4
-
- val brightBlue = ContextCompat.getColor(requireActivity(), R.color.brightBlue)
- progressBar1.trackColor = brightBlue
- progressBar2.trackColor = brightBlue
- progressBar3.trackColor = brightBlue
- progressBar4.trackColor = brightBlue
-
- val deepBlue = ContextCompat.getColor(requireActivity(), R.color.stax_state_blue)
- progressBar1.setIndicatorColor(deepBlue)
- progressBar2.setIndicatorColor(deepBlue)
- progressBar3.setIndicatorColor(deepBlue)
- progressBar4.setIndicatorColor(deepBlue)
- }
-
- private fun initAnimators() {
- animator1 = ValueAnimator.ofInt(0, progressBar1.max)
- animator2 = ValueAnimator.ofInt(0, progressBar2.max)
- animator3 = ValueAnimator.ofInt(0, progressBar3.max)
- animator4 = ValueAnimator.ofInt(0, progressBar4.max)
- }
-
- private fun setUpSlides() {
- val viewPagerAdapter = SlidesPagerAdapter(requireContext())
- binding.vpPager.apply {
- startAutoScroll(FIRST_SCROLL_DELAY)
- setInterval(SCROLL_INTERVAL)
- setCycle(true)
- setAutoScrollDurationFactor(AUTO_SCROLL_EASE_DURATION_FACTOR)
- setSwipeScrollDurationFactor(SWIPE_DURATION_FACTOR)
- setStopScrollWhenTouch(true)
- addOnPageChangeListener(this@SignInVariantFragment)
- adapter = viewPagerAdapter
- }
- }
-
- private fun setupPrivacyPolicy() {
- binding.onboardingV1Tos.text = HtmlCompat.fromHtml(requireContext().getString(R.string.privacyPolicyFullLabel), HtmlCompat.FROM_HTML_MODE_LEGACY)
- binding.onboardingV1Tos.movementMethod = LinkMovementMethod.getInstance()
- }
-
- private fun setupTermsOfService() {
- binding.onboardingV1PrivacyPolicy.text = HtmlCompat.fromHtml(requireContext().getString(R.string.termsOfServiceFullLabel), HtmlCompat.FROM_HTML_MODE_LEGACY)
- binding.onboardingV1PrivacyPolicy.movementMethod = LinkMovementMethod.getInstance()
- }
-
- private fun updateProgressAnimation(animator: ValueAnimator, progressBar: LinearProgressIndicator) = animator.apply {
- duration = 3500
- addUpdateListener { animation ->
- progressBar.progress = animation.animatedValue as Int
- if (progressBar.progress > 90) {
- fillUpProgress(progressBar)
- }
- }
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- super.onAnimationEnd(animation)
- }
- })
- start()
- }
-
- override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
- showProgress(position)
- }
-
- override fun onPageSelected(position: Int) {}
-
- override fun onPageScrollStateChanged(state: Int) {}
-
- private fun showProgress(currentPos: Int) {
- when (currentPos) {
- 0 -> {
- updateProgressAnimation(animator1, progressBar1)
- resetFilledProgress(animator2, progressBar2)
- resetFilledProgress(animator3, progressBar3)
- resetFilledProgress(animator4, progressBar4)
- }
-
- 1 -> {
- fillUpProgress(progressBar1)
- updateProgressAnimation(animator2, progressBar2)
- resetFilledProgress(animator3, progressBar3)
- resetFilledProgress(animator4, progressBar4)
- }
-
- 2 -> {
- fillUpProgress(progressBar1)
- fillUpProgress(progressBar2)
- updateProgressAnimation(animator3, progressBar3)
- resetFilledProgress(animator4, progressBar4)
- }
-
- 3 -> {
- fillUpProgress(progressBar1)
- fillUpProgress(progressBar2)
- fillUpProgress(progressBar3)
- updateProgressAnimation(animator4, progressBar4)
- }
- }
- }
-
- private fun fillUpProgress(progressBar: LinearProgressIndicator) {
- try {
- val deepBlue = ContextCompat.getColor(requireActivity(), R.color.stax_state_blue)
- progressBar.progress = 100
- progressBar.trackColor = deepBlue
- } catch (e: IllegalStateException) {
- Timber.i("animation needed to complete")
- }
- }
-
- private fun resetFilledProgress(animator: ValueAnimator, progressBar: LinearProgressIndicator) {
- animator.cancel()
- val brightBlue = ContextCompat.getColor(requireActivity(), R.color.brightBlue)
- progressBar.progress = 0
- progressBar.trackColor = brightBlue
- }
-
- private val backPressedCallback = object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- Timber.i("Back navigation disabled") //do nothing to prevent navigation back to the home fragment (default variant)
- }
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-
- companion object {
- const val FIRST_SCROLL_DELAY = 3500
- const val SCROLL_INTERVAL = 3500L
- const val SWIPE_DURATION_FACTOR = 2.0
- const val AUTO_SCROLL_EASE_DURATION_FACTOR = 5.0
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/onboarding/signInVariant/SlidesPagerAdapter.kt b/app/src/main/java/com/hover/stax/onboarding/signInVariant/SlidesPagerAdapter.kt
deleted file mode 100644
index bfc081e9c..000000000
--- a/app/src/main/java/com/hover/stax/onboarding/signInVariant/SlidesPagerAdapter.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.hover.stax.onboarding.signInVariant
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.viewpager.widget.PagerAdapter
-import androidx.viewpager.widget.ViewPager
-import com.hover.stax.R
-import com.hover.stax.databinding.ItemSiginViewpagerBinding
-
-private data class SlideData(val imgRes: Int, val titleRes: Int, val descRes: Int)
-
-class SlidesPagerAdapter(private val context: Context) : PagerAdapter() {
-
- override fun destroyItem(container: ViewGroup, position: Int, arg1: Any) {
- (container as ViewPager).removeView(arg1 as View?)
- }
-
- override fun instantiateItem(container: ViewGroup, position: Int): Any {
- val binding = ItemSiginViewpagerBinding.inflate(LayoutInflater.from(context), container, false)
-
- val slideData = getSlideData(position)
- binding.onboardingV1Title.setText(slideData.titleRes)
- binding.onboardingV1Desc.setText(slideData.descRes)
- binding.onboardingV1Image.setImageResource(slideData.imgRes)
-
- container.addView(binding.root)
- return binding.root
- }
-
- override fun getCount(): Int {
- return 4
- }
-
- override fun isViewFromObject(view: View, arg1: Any): Boolean {
- return view == arg1 as View?
- }
-
- private fun getSlideData(position: Int): SlideData {
- return when (position) {
- 0 -> SlideData(R.drawable.send_illustration, R.string.onboarding_v1_slide1_title, R.string.slide1_desc)
- 1 -> SlideData(R.drawable.send_illustration, R.string.onboarding_v1_slide2_title, R.string.slide2_desc)
- 2 -> SlideData(R.drawable.request_illustration, R.string.onboarding_v1_slide3_title, R.string.slide3_desc)
- else -> SlideData(R.drawable.airtime_illustration, R.string.onboarding_v1_slide4_title, R.string.slide4_desc)
- }
- }
-
-}
-
diff --git a/app/src/main/java/com/hover/stax/onboarding/signInVariant/StaxAutoScrollViewPager.kt b/app/src/main/java/com/hover/stax/onboarding/signInVariant/StaxAutoScrollViewPager.kt
deleted file mode 100644
index c631fc5d5..000000000
--- a/app/src/main/java/com/hover/stax/onboarding/signInVariant/StaxAutoScrollViewPager.kt
+++ /dev/null
@@ -1,200 +0,0 @@
-package com.hover.stax.onboarding.signInVariant
-
-import android.content.Context
-import android.os.Handler
-import android.os.Message
-import android.util.AttributeSet
-import android.view.MotionEvent
-import android.view.animation.Interpolator
-import android.widget.Scroller
-import androidx.viewpager.widget.ViewPager
-import java.lang.ref.WeakReference
-
-class StaxAutoScrollViewPager : ViewPager {
-
- private var interval = DEFAULT_INTERVAL.toLong()
- private var direction = RIGHT
- private var isCycle = true
- private var stopScrollWhenTouch = true
- private val slideBorderMode = SLIDE_BORDER_MODE_NONE
- private val isBorderAnimation = true
- private var autoScrollFactor = 1.0
- private var swipeScrollFactor = 1.0
- private lateinit var handler: MyHandler
- private var isAutoScroll = false
- private var isStopByTouch = false
- private var touchX = 0f
- private var downX = 0f
- private var scroller: CustomDurationScroller? = null
-
- constructor(paramContext: Context?) : super(paramContext!!) {
- init()
- }
-
- constructor(paramContext: Context?, paramAttributeSet: AttributeSet?) : super(paramContext!!,
- paramAttributeSet) {
- init()
- }
-
- private fun init() {
- handler = MyHandler(this)
- setViewPagerScroller()
- }
-
- private fun startAutoScroll() {
- isAutoScroll = true
- sendScrollMessage((interval + scroller!!.duration / autoScrollFactor * swipeScrollFactor).toLong())
- }
-
- fun startAutoScroll(delayTimeInMills: Int) {
- isAutoScroll = true
- sendScrollMessage(delayTimeInMills.toLong())
- }
-
- private fun stopAutoScroll() {
- isAutoScroll = false
- handler.removeMessages(SCROLL_WHAT)
- }
-
- fun setSwipeScrollDurationFactor(scrollFactor: Double) {
- swipeScrollFactor = scrollFactor
- }
-
- fun setAutoScrollDurationFactor(scrollFactor: Double) {
- autoScrollFactor = scrollFactor
- }
-
- private fun sendScrollMessage(delayTimeInMills: Long) {
- handler.removeMessages(SCROLL_WHAT)
- handler.sendEmptyMessageDelayed(SCROLL_WHAT, delayTimeInMills)
- }
-
- private fun setViewPagerScroller() {
- try {
- val scrollerField = ViewPager::class.java.getDeclaredField("mScroller")
- scrollerField.isAccessible = true
- val interpolatorField = ViewPager::class.java.getDeclaredField("sInterpolator")
- interpolatorField.isAccessible = true
- scroller = CustomDurationScroller(context, interpolatorField[null] as Interpolator)
- scrollerField[this] = scroller
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
-
- fun scrollOnce() {
- val adapter = adapter
- var currentItem = currentItem
- var totalCount = 0
- if (adapter == null || adapter.count.also { totalCount = it } <= 1) {
- return
- }
- val nextItem = if (direction == LEFT) --currentItem else ++currentItem
- if (nextItem < 0) {
- if (isCycle) {
- setCurrentItem(totalCount - 1, isBorderAnimation)
- }
- } else if (nextItem == totalCount) {
- if (isCycle) {
- setCurrentItem(0, isBorderAnimation)
- }
- } else {
- setCurrentItem(nextItem, true)
- }
- }
-
- private fun pauseScrolling(ev: MotionEvent) {
- if (ev.actionMasked == MotionEvent.ACTION_DOWN && isAutoScroll) {
- isStopByTouch = true
- stopAutoScroll()
- } else if (ev.action == MotionEvent.ACTION_UP && isStopByTouch) {
- startAutoScroll()
- }
- }
-
- override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
- if (stopScrollWhenTouch) pauseScrolling(ev)
-
- if (slideBorderMode == SLIDE_BORDER_MODE_TO_PARENT || slideBorderMode == SLIDE_BORDER_MODE_CYCLE) {
- touchX = ev.x
- if (ev.action == MotionEvent.ACTION_DOWN) {
- downX = touchX
- }
- val currentItem = currentItem
- val adapter = adapter
- val pageCount = adapter?.count ?: 0
- if (currentItem == 0 && downX <= touchX || currentItem == pageCount - 1 && downX >= touchX) {
- if (slideBorderMode == SLIDE_BORDER_MODE_TO_PARENT) {
- parent.requestDisallowInterceptTouchEvent(false)
- } else {
- if (pageCount > 1) {
- setCurrentItem(pageCount - currentItem - 1, isBorderAnimation)
- }
- parent.requestDisallowInterceptTouchEvent(true)
- }
- return super.dispatchTouchEvent(ev)
- }
- }
- parent.requestDisallowInterceptTouchEvent(true)
- return super.dispatchTouchEvent(ev)
- }
-
- private class MyHandler(staxAutoScrollViewPager: StaxAutoScrollViewPager) : Handler() {
- private val autoScrollViewPager: WeakReference = WeakReference(staxAutoScrollViewPager)
- override fun handleMessage(msg: Message) {
- super.handleMessage(msg)
- if (msg.what == SCROLL_WHAT) {
- val pager = autoScrollViewPager.get()
- if (pager != null) {
- pager.scroller!!.setScrollDurationFactor(pager.autoScrollFactor)
- pager.scrollOnce()
- pager.scroller!!.setScrollDurationFactor(pager.swipeScrollFactor)
- pager.sendScrollMessage(pager.interval + pager.scroller!!.duration)
- }
- }
- }
-
- }
-
- fun setStopScrollWhenTouch(stopScrollWhenTouch: Boolean) {
- this.stopScrollWhenTouch = stopScrollWhenTouch
- }
-
- fun setCycle(isCycle: Boolean) {
- this.isCycle = isCycle
- }
-
- fun setDirection(direction: Int) {
- this.direction = direction
- }
-
- fun setInterval(interval: Long) {
- this.interval = interval
- }
-
- fun getDirection(): Int {
- return if (direction == LEFT) LEFT else RIGHT
- }
-
- private class CustomDurationScroller(context: Context?, interpolator: Interpolator?) :
- Scroller(context, interpolator) {
- private var scrollFactor = 1.0
- fun setScrollDurationFactor(scrollFactor: Double) {
- this.scrollFactor = scrollFactor
- }
-
- override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
- super.startScroll(startX, startY, dx, dy, (duration * scrollFactor).toInt())
- }
- }
-
- companion object {
- const val DEFAULT_INTERVAL = 1500
- const val LEFT = 0
- const val RIGHT = 1
- const val SLIDE_BORDER_MODE_NONE = 0
- const val SLIDE_BORDER_MODE_CYCLE = 1
- const val SLIDE_BORDER_MODE_TO_PARENT = 2
- const val SCROLL_WHAT = 0
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/paybill/PayBill.kt b/app/src/main/java/com/hover/stax/paybill/PayBill.kt
index 2bec40dcc..efbf47cb6 100644
--- a/app/src/main/java/com/hover/stax/paybill/PayBill.kt
+++ b/app/src/main/java/com/hover/stax/paybill/PayBill.kt
@@ -2,8 +2,9 @@ package com.hover.stax.paybill
import androidx.room.*
import com.hover.sdk.actions.HoverAction
-import com.hover.stax.accounts.Account
+
import com.hover.stax.channels.Channel
+import com.hover.stax.domain.model.Account
import javax.annotation.Nullable
const val BUSINESS_NO = "businessNo"
diff --git a/app/src/main/java/com/hover/stax/paybill/PaybillFragment.kt b/app/src/main/java/com/hover/stax/paybill/PaybillFragment.kt
index 68f3641bd..e8ff06c44 100644
--- a/app/src/main/java/com/hover/stax/paybill/PaybillFragment.kt
+++ b/app/src/main/java/com/hover/stax/paybill/PaybillFragment.kt
@@ -17,7 +17,6 @@ import com.hover.stax.transfers.AbstractFormFragment
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.UIHelper
import com.hover.stax.utils.Utils
-import com.hover.stax.utils.collectLatestLifecycleFlow
import com.hover.stax.views.AbstractStatefulInput
import com.hover.stax.views.StaxDialog
import com.hover.stax.views.StaxTextInput
@@ -120,7 +119,7 @@ class PaybillFragment : AbstractFormFragment(), PaybillIconsAdapter.IconSelectLi
viewModel.selectedPaybill.value?.isSaved == true -> viewModel.setEditing(false)
else -> {
viewModel.savePaybill(accountsViewModel.activeAccount.value, actionSelectViewModel.activeAction.value)
- UIHelper.flashMessage(requireActivity(), R.string.paybill_save_success)
+ UIHelper.flashAndReportMessage(requireActivity(), R.string.paybill_save_success)
}
}
}
@@ -143,11 +142,6 @@ class PaybillFragment : AbstractFormFragment(), PaybillIconsAdapter.IconSelectLi
viewModel.getSavedPaybills(account.id)
}
}
-
- collectLatestLifecycleFlow(accountsViewModel.accounts) {
- if(it.isEmpty())
- setDropdownTouchListener(PaybillFragmentDirections.actionGlobalAddChannelsFragment())
- }
}
private fun observeActions() {
@@ -180,7 +174,7 @@ class PaybillFragment : AbstractFormFragment(), PaybillIconsAdapter.IconSelectLi
}
private fun updateBiz(name: String?, no: String?) {
- binding.editCard.businessNoInput.setMutlipartText(name, no)
+ binding.editCard.businessNoInput.setMultipartText(name, no)
binding.summaryCard.recipient.setContent(name, no)
}
@@ -295,7 +289,7 @@ class PaybillFragment : AbstractFormFragment(), PaybillIconsAdapter.IconSelectLi
.setPosButton(R.string.btn_update) { _ ->
if (activity != null) {
viewModel.updatePaybill(it)
- UIHelper.flashMessage(requireActivity(), R.string.paybill_update_success)
+ UIHelper.flashAndReportMessage(requireActivity(), R.string.paybill_update_success)
viewModel.setEditing(false)
}
}
diff --git a/app/src/main/java/com/hover/stax/paybill/PaybillListFragment.kt b/app/src/main/java/com/hover/stax/paybill/PaybillListFragment.kt
index c1e6bf064..09f6b0781 100644
--- a/app/src/main/java/com/hover/stax/paybill/PaybillListFragment.kt
+++ b/app/src/main/java/com/hover/stax/paybill/PaybillListFragment.kt
@@ -108,7 +108,7 @@ class PaybillListFragment : Fragment(), PaybillAdapter.ClickListener, PaybillAct
.setPosButton(R.string.btn_delete) {
if (activity != null) {
paybillViewModel.deletePaybill(paybill)
- UIHelper.flashMessage(requireActivity(), R.string.paybill_delete_success)
+ UIHelper.flashAndReportMessage(requireActivity(), R.string.paybill_delete_success)
}
}
dialog!!.showIt()
diff --git a/app/src/main/java/com/hover/stax/paybill/PaybillViewModel.kt b/app/src/main/java/com/hover/stax/paybill/PaybillViewModel.kt
index 1d139d81b..8551e38ae 100644
--- a/app/src/main/java/com/hover/stax/paybill/PaybillViewModel.kt
+++ b/app/src/main/java/com/hover/stax/paybill/PaybillViewModel.kt
@@ -5,15 +5,15 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.hover.sdk.actions.HoverAction
import com.hover.stax.R
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.AccountRepo
-import com.hover.stax.actions.ActionRepo
+
+import com.hover.stax.data.local.accounts.AccountRepo
+import com.hover.stax.data.local.actions.ActionRepo
import com.hover.stax.contacts.ContactRepo
+import com.hover.stax.domain.model.Account
import com.hover.stax.schedules.ScheduleRepo
import com.hover.stax.transfers.AbstractFormViewModel
import com.hover.stax.utils.AnalyticsUtil
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.json.JSONObject
import timber.log.Timber
@@ -42,10 +42,6 @@ class PaybillViewModel(
}
fun selectPaybill(paybill: Paybill) {
- Timber.e("selecting paybill by paybill: %s", paybill.businessNo)
- Timber.e("current amount: %s", amount.value)
- Timber.e("isSaved: %s", paybill.isSaved)
-
selectedPaybill.value = paybill
businessName.value = paybill.businessName
diff --git a/app/src/main/java/com/hover/stax/presentation/bounties/BountiesState.kt b/app/src/main/java/com/hover/stax/presentation/bounties/BountiesState.kt
new file mode 100644
index 000000000..2904f372f
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/bounties/BountiesState.kt
@@ -0,0 +1,9 @@
+package com.hover.stax.presentation.bounties
+
+import com.hover.stax.domain.model.ChannelBounties
+
+data class BountiesState(
+ var loading: Boolean = false,
+ var error: String = "",
+ var bounties: List = emptyList()
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/presentation/bounties/BountiesViewModel.kt b/app/src/main/java/com/hover/stax/presentation/bounties/BountiesViewModel.kt
new file mode 100644
index 000000000..64332f867
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/bounties/BountiesViewModel.kt
@@ -0,0 +1,103 @@
+package com.hover.stax.presentation.bounties
+
+import android.app.Application
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import com.hover.sdk.api.Hover
+import com.hover.sdk.sims.SimInfo
+import com.hover.stax.countries.CountryAdapter
+import com.hover.stax.domain.model.Bounty
+import com.hover.stax.domain.model.Resource
+import com.hover.stax.domain.use_case.bounties.GetChannelBountiesUseCase
+import com.hover.stax.domain.use_case.channels.GetPresentSimsUseCase
+import com.hover.stax.utils.Utils.getPackage
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.launch
+
+class BountyViewModel(private val simsUseCase: GetPresentSimsUseCase, private val bountiesUseCase: GetChannelBountiesUseCase, val application: Application) : ViewModel() {
+
+ private val _countryList = MutableStateFlow>(emptyList())
+ val countryList = _countryList.asStateFlow()
+
+ private val _sims = MutableStateFlow>(emptyList())
+ val sims = _sims.asStateFlow()
+
+ private val _bountiesState = MutableStateFlow(BountiesState())
+ val bountiesState = _bountiesState.asStateFlow()
+
+ private val _country = MutableStateFlow(CountryAdapter.CODE_ALL_COUNTRIES)
+ val country = _country.asStateFlow()
+
+ private val onBountySelectEvent = Channel()
+ val bountySelectEvent = onBountySelectEvent.receiveAsFlow()
+
+ private var simReceiver: BroadcastReceiver? = null
+
+ init {
+ simReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ fetchSims()
+ }
+ }
+
+ loadBountyData()
+ }
+
+ private fun loadBountyData() {
+ loadSims()
+ loadCountryList()
+ loadBounties()
+ }
+
+ private fun loadCountryList() = viewModelScope.launch {
+ bountiesUseCase.getChannelList().collect { codes ->
+ _countryList.update { codes }
+ }
+ }
+
+ private fun loadSims() {
+ fetchSims()
+
+ simReceiver?.let {
+ LocalBroadcastManager.getInstance(application)
+ .registerReceiver(it, IntentFilter(getPackage(application) + ".NEW_SIM_INFO_ACTION"))
+ }
+ Hover.updateSimInfo(application)
+ }
+
+ private fun fetchSims() = viewModelScope.launch(Dispatchers.IO) {
+ _sims.update { simsUseCase.presentSims }
+ }
+
+ fun loadBounties(countryCode: String = CountryAdapter.CODE_ALL_COUNTRIES) {
+ _country.value = countryCode
+ bountiesUseCase.getBounties(countryCode).onEach { result ->
+ when (result) {
+ is Resource.Loading -> _bountiesState.update { it.copy(loading = true) }
+ is Resource.Error -> _bountiesState.update { it.copy(loading = false, error = result.message!!) }
+ is Resource.Success -> _bountiesState.update { it.copy(loading = false, bounties = result.data!!) }
+ }
+ }.launchIn(viewModelScope)
+ }
+
+ fun isSimPresent(bounty: Bounty): Boolean = simsUseCase.simPresent(bounty, sims.value)
+
+ fun handleBountyEvent(bountySelectEvent: BountySelectEvent) = viewModelScope.launch {
+ onBountySelectEvent.send(bountySelectEvent)
+ }
+
+ override fun onCleared() {
+ try {
+ simReceiver?.let { LocalBroadcastManager.getInstance(application).unregisterReceiver(it) }
+ } catch (ignored: Exception) {
+ }
+ super.onCleared()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/bounties/BountyEmailFragment.kt b/app/src/main/java/com/hover/stax/presentation/bounties/BountyEmailFragment.kt
similarity index 99%
rename from app/src/main/java/com/hover/stax/bounties/BountyEmailFragment.kt
rename to app/src/main/java/com/hover/stax/presentation/bounties/BountyEmailFragment.kt
index 8a3574d5c..9abac9f46 100644
--- a/app/src/main/java/com/hover/stax/bounties/BountyEmailFragment.kt
+++ b/app/src/main/java/com/hover/stax/presentation/bounties/BountyEmailFragment.kt
@@ -1,4 +1,4 @@
-package com.hover.stax.bounties
+package com.hover.stax.presentation.bounties
import android.os.Bundle
import android.text.method.LinkMovementMethod
diff --git a/app/src/main/java/com/hover/stax/bounties/BountyListFragment.kt b/app/src/main/java/com/hover/stax/presentation/bounties/BountyListFragment.kt
similarity index 51%
rename from app/src/main/java/com/hover/stax/bounties/BountyListFragment.kt
rename to app/src/main/java/com/hover/stax/presentation/bounties/BountyListFragment.kt
index 5e399d5fc..3ccd48b70 100644
--- a/app/src/main/java/com/hover/stax/bounties/BountyListFragment.kt
+++ b/app/src/main/java/com/hover/stax/presentation/bounties/BountyListFragment.kt
@@ -1,11 +1,11 @@
-package com.hover.stax.bounties
+package com.hover.stax.presentation.bounties
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
-import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.work.ExistingPeriodicWorkPolicy
@@ -13,38 +13,34 @@ import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager
import com.hover.sdk.actions.HoverAction
import com.hover.sdk.api.Hover
-import com.hover.sdk.sims.SimInfo
import com.hover.stax.R
-import com.hover.stax.channels.Channel
import com.hover.stax.channels.UpdateChannelsWorker
-import com.hover.stax.countries.CountryAdapter
+import com.hover.stax.data.remote.workers.UpdateBountyTransactionsWorker
import com.hover.stax.databinding.FragmentBountyListBinding
+import com.hover.stax.domain.model.Bounty
import com.hover.stax.hover.AbstractHoverCallerActivity
-import com.hover.stax.transactions.StaxTransaction
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.NavUtil
-import com.hover.stax.utils.UIHelper
import com.hover.stax.utils.Utils
+import com.hover.stax.utils.collectLifecycleFlow
import com.hover.stax.utils.network.NetworkMonitor
-import com.hover.stax.views.AbstractStatefulInput
import com.hover.stax.views.StaxDialog
import kotlinx.coroutines.launch
-import org.koin.androidx.viewmodel.ext.android.sharedViewModel
+import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
-class BountyListFragment : Fragment(), BountyListItem.SelectListener, CountryAdapter.SelectListener {
+class BountyListFragment : Fragment() {
private lateinit var networkMonitor: NetworkMonitor
- private val bountyViewModel: BountyViewModel by sharedViewModel()
+ private val bountiesViewModel: BountyViewModel by viewModel()
+
private var _binding: FragmentBountyListBinding? = null
private val binding get() = _binding!!
private var dialog: StaxDialog? = null
- private val bountyAdapter = BountyAdapter(this)
-
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
AnalyticsUtil.logAnalyticsEvent(getString(R.string.visit_screen, getString(R.string.visit_bounty_list)), requireActivity())
@@ -56,14 +52,10 @@ class BountyListFragment : Fragment(), BountyListItem.SelectListener, CountryAda
super.onViewCreated(view, savedInstanceState)
networkMonitor = NetworkMonitor(requireActivity())
- initRecyclerView()
-
- startObservers()
+ initBountyList()
+ observeBountyEvents()
- binding.bountiesRecyclerView.adapter = bountyAdapter
- binding.bountyCountryDropdown.isEnabled = false
binding.countryFilter.apply {
- showProgressIndicator()
setOnClickIcon {
NavUtil.navigate(findNavController(), BountyListFragmentDirections.actionBountyListFragmentToNavigationSettings())
}
@@ -75,6 +67,13 @@ class BountyListFragment : Fragment(), BountyListItem.SelectListener, CountryAda
forceUserToBeOnline()
}
+ private fun observeBountyEvents() = collectLifecycleFlow(bountiesViewModel.bountySelectEvent) {
+ when (it) {
+ is BountySelectEvent.ViewBountyDetail -> viewBountyDetail(it.bounty)
+ is BountySelectEvent.ViewTransactionDetail -> NavUtil.showTransactionDetailsFragment(findNavController(), it.uuid)
+ }
+ }
+
private fun forceUserToBeOnline() {
if (isAdded && networkMonitor.isNetworkConnected) {
updateActionConfig()
@@ -83,15 +82,17 @@ class BountyListFragment : Fragment(), BountyListItem.SelectListener, CountryAda
} else showOfflineDialog()
}
- private fun updateActionConfig() = Hover.initialize(requireActivity(), object : Hover.DownloadListener {
- override fun onError(p0: String?) {
- AnalyticsUtil.logErrorAndReportToFirebase(BountyListFragment::class.java.simpleName, "Failed to update action configs: $p0", null)
- }
+ private fun updateActionConfig() = lifecycleScope.launch {
+ Hover.initialize(requireActivity(), object : Hover.DownloadListener {
+ override fun onError(p0: String?) {
+ AnalyticsUtil.logErrorAndReportToFirebase(BountyListFragment::class.java.simpleName, "Failed to update action configs: $p0", null)
+ }
- override fun onSuccess(p0: ArrayList?) {
- Timber.i("Action configs initialized successfully $p0")
- }
- })
+ override fun onSuccess(p0: ArrayList?) {
+ Timber.i("Action configs initialized successfully $p0")
+ }
+ })
+ }
private fun updateChannelsWorker() = with(WorkManager.getInstance(requireActivity())) {
beginUniqueWork(UpdateChannelsWorker.CHANNELS_WORK_ID, ExistingWorkPolicy.REPLACE, UpdateChannelsWorker.makeWork()).enqueue()
@@ -115,85 +116,17 @@ class BountyListFragment : Fragment(), BountyListItem.SelectListener, CountryAda
dialog!!.showIt()
}
- private fun initCountryDropdown(countryCodes: List) = binding.bountyCountryDropdown.apply {
- setListener(this@BountyListFragment)
- updateChoices(countryCodes, bountyViewModel.country)
- isEnabled = true
- }
-
- private fun initRecyclerView() {
- binding.bountiesRecyclerView.layoutManager = UIHelper.setMainLinearManagers(context)
- }
-
- private fun startObservers() = with(bountyViewModel) {
- val actionsObserver = object : Observer> {
- override fun onChanged(t: List?) {
- Timber.v("Actions update: ${t?.size}")
- }
- }
-
- val txnObserver = object : Observer> {
- override fun onChanged(t: List?) {
- Timber.v("Transactions update ${t?.size}")
+ private fun initBountyList() {
+ binding.bountyList.apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent {
+ BountyList(bountyViewModel = bountiesViewModel)
}
}
-
- val simsObserver = object: Observer> {
- override fun onChanged(t: List?) {
- Timber.v("Sims update ${t?.size}")
- }
- }
-
- actions.observe(viewLifecycleOwner, actionsObserver)
- transactions.observe(viewLifecycleOwner, txnObserver)
- sims.observe(viewLifecycleOwner, simsObserver)
- bounties.observe(viewLifecycleOwner) { updateChannelList(channels.value, it) }
- channels.observe(viewLifecycleOwner) { updateChannelList(it, bounties.value)}
- channelCountryList.observe(viewLifecycleOwner) { initCountryDropdown(it) }
}
- private fun updateChannelList(channels: List?, bounties: List?) {
- binding.countryFilter.hideProgressIndicator()
-
- if (!channels.isNullOrEmpty() && !bounties.isNullOrEmpty() &&
- bountyViewModel.country == CountryAdapter.CODE_ALL_COUNTRIES || channels?.firstOrNull()?.countryAlpha2 == bountyViewModel.country
- ) {
- hideLoadingState()
-
- binding.msgNoBounties.visibility = View.GONE
- binding.bountiesRecyclerView.visibility = View.VISIBLE
-
- showBounties(channels, bounties!!)
- } else {
- binding.msgNoBounties.visibility = View.VISIBLE
- binding.bountiesRecyclerView.visibility = View.GONE
- }
- }
-
- private fun showBounties(channels: List, bounties: List) = lifecycleScope.launch {
- val openBounties = bounties.filter { it.action.bounty_is_open || it.transactionCount != 0 }
-
- val channelBounties = channels.filter { c ->
- openBounties.any { it.action.channel_id == c.id }
- }.map { channel ->
- ChannelBounties(channel, openBounties.filter { it.action.channel_id == channel.id })
- }
-
- bountyAdapter.submitList(channelBounties)
- }
-
- override fun viewTransactionDetail(uuid: String?) {
- uuid?.let { NavUtil.showTransactionDetailsFragment(findNavController(), uuid) }
- }
-
- override fun viewBountyDetail(b: Bounty) {
- if (bountyViewModel.isSimPresent(b)) showBountyDescDialog(b) else showSimErrorDialog(b)
- }
-
- override fun countrySelect(countryCode: String) {
- showLoadingState()
-
- bountyViewModel.filterChannels(countryCode).observe(viewLifecycleOwner) { updateChannelList(it, bountyViewModel.bounties.value) }
+ private fun viewBountyDetail(b: Bounty) {
+ if (bountiesViewModel.isSimPresent(b)) showBountyDescDialog(b) else showSimErrorDialog(b)
}
private fun showSimErrorDialog(b: Bounty) {
@@ -223,22 +156,8 @@ class BountyListFragment : Fragment(), BountyListItem.SelectListener, CountryAda
(requireActivity() as AbstractHoverCallerActivity).makeRegularCall(b.action, R.string.clicked_start_bounty)
}
- private fun showLoadingState() {
- binding.bountyCountryDropdown.setState(getString(R.string.filtering_in_progress), AbstractStatefulInput.INFO)
- binding.bountiesRecyclerView.visibility = View.GONE
- }
-
- private fun hideLoadingState() {
- binding.bountyCountryDropdown.setState(null, AbstractStatefulInput.NONE)
- binding.bountiesRecyclerView.visibility = View.VISIBLE
- }
-
private fun retrySimMatch(b: Bounty?) {
- with(bountyViewModel.sims) {
- removeObservers(viewLifecycleOwner)
- observe(viewLifecycleOwner) { b?.let { viewBountyDetail(b) } }
- }
-
+ b?.let { viewBountyDetail(b) }
Hover.updateSimInfo(requireActivity())
}
diff --git a/app/src/main/java/com/hover/stax/presentation/bounties/BountyScreen.kt b/app/src/main/java/com/hover/stax/presentation/bounties/BountyScreen.kt
new file mode 100644
index 000000000..b6c8d9b46
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/bounties/BountyScreen.kt
@@ -0,0 +1,477 @@
+package com.hover.stax.presentation.bounties
+
+import android.content.Context
+import android.text.Html
+import android.text.method.LinkMovementMethod
+import android.view.Gravity
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsPressedAsState
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.text.HtmlCompat
+import androidx.core.widget.TextViewCompat
+import com.hover.stax.R
+import com.hover.stax.countries.CountryAdapter
+import com.hover.stax.domain.model.Bounty
+import com.hover.stax.domain.model.ChannelBounties
+import com.hover.stax.ui.theme.Brutalista
+import com.hover.stax.ui.theme.StaxTheme
+import com.yariksoffice.lingver.Lingver
+import java.util.*
+
+const val CODE_ALL_COUNTRIES = "00"
+
+@Composable
+fun BountyList(bountyViewModel: BountyViewModel) {
+ val bountiesState by bountyViewModel.bountiesState.collectAsState()
+ val countries by bountyViewModel.countryList.collectAsState(initial = listOf(CODE_ALL_COUNTRIES))
+ val country by bountyViewModel.country.collectAsState()
+
+ StaxTheme {
+ Surface(
+ modifier = Modifier
+ .fillMaxSize(), color = MaterialTheme.colors.background
+ ) {
+ LazyColumn {
+ item {
+ CountryDropdown(countries, country, bountiesState.loading, bountyViewModel)
+ }
+
+ items(bountiesState.bounties) {
+ ChannelBountyCard(channelBounty = it, bountyViewModel)
+ }
+
+ if (bountiesState.bounties.isEmpty() && !bountiesState.loading) {
+ item {
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(dimensionResource(id = R.dimen.margin_13)),
+ text = stringResource(id = R.string.bounty_error_none),
+ style = MaterialTheme.typography.body2,
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun ChannelBountyCard(channelBounty: ChannelBounties, bountyViewModel: BountyViewModel) {
+ if (channelBounty.bounties.isNotEmpty())
+ Column {
+ Text(
+ text = channelBounty.channel.ussdName.uppercase(),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(dimensionResource(id = R.dimen.margin_13)),
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.End
+ )
+
+ channelBounty.bounties.forEach {
+ BountyCard(bounty = it, bountyViewModel)
+ }
+ }
+}
+
+@Composable
+fun BountyCard(bounty: Bounty, bountyViewModel: BountyViewModel) {
+ val context = LocalContext.current
+ val margin8 = dimensionResource(id = R.dimen.margin_8)
+ val margin13 = dimensionResource(id = R.dimen.margin_13)
+ val margin5 = dimensionResource(id = R.dimen.margin_5)
+
+ val bountyState = getBountyState(bounty)
+
+ val strikeThrough = TextStyle(
+ fontFamily = Brutalista,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ textDecoration = TextDecoration.LineThrough
+ )
+
+ Column(
+ modifier = Modifier
+ .background(color = colorResource(id = bountyState.color))
+ .padding(vertical = margin8)
+ .clickable { bountyState.bountySelectEvent?.let { bountyViewModel.handleBountyEvent(it) } }
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = margin13),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = bounty.generateDescription(context).replaceFirstChar { it.uppercase() },
+ modifier = Modifier
+ .padding(top = margin8, bottom = margin8, end = margin13)
+ .weight(1f),
+ style = if (bountyState.isOpen) MaterialTheme.typography.body1 else strikeThrough
+ )
+
+ Text(
+ text = stringResource(R.string.bounty_amount_with_currency, bounty.action.bounty_amount),
+ modifier = Modifier
+ .padding(top = margin8, bottom = margin8),
+ style = if (bountyState.isOpen) MaterialTheme.typography.body1 else strikeThrough,
+ fontWeight = FontWeight.Medium
+ )
+ }
+
+ if (bountyState.msg != 0)
+ SpannableImageTextView(
+ drawable = bountyState.icon,
+ stringRes = bountyState.msg,
+ modifier = Modifier
+ .padding(start = margin13, end = margin13, top = margin5, bottom = margin5),
+ )
+ }
+}
+
+@Composable
+internal fun SpannableImageTextView(
+ @DrawableRes drawable: Int,
+ @StringRes stringRes: Int,
+ modifier: Modifier = Modifier
+) {
+ val text = HtmlCompat.fromHtml(stringResource(id = stringRes), HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
+
+ Row(horizontalArrangement = Arrangement.Start, modifier = modifier) {
+ Image(
+ painter = painterResource(id = drawable),
+ contentDescription = null,
+ modifier = Modifier.align(Alignment.CenterVertically),
+ )
+ //only workaround available for handling html text in compose textviews
+ AndroidView(
+ factory = { context ->
+ TextView(context).apply {
+ setText(text)
+ setTextColor(R.color.offWhite)
+ TextViewCompat.setTextAppearance(this, android.R.style.TextAppearance_Material_Caption)
+ layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+ movementMethod = LinkMovementMethod.getInstance()
+ gravity = Gravity.CENTER_VERTICAL
+ typeface = context.resources.getFont(R.font.brutalista_regular)
+ }
+ }, modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .padding(start = dimensionResource(id = R.dimen.margin_8))
+ )
+ }
+}
+
+@Composable
+fun CountryDropdown(countries: List, country: String, isLoading: Boolean, bountyViewModel: BountyViewModel) {
+ var expanded by remember { mutableStateOf(false) }
+ var selected by remember { mutableStateOf(country) }
+ var textFieldSize by remember { mutableStateOf(Size.Zero) }
+ val interactionSource = remember { MutableInteractionSource() }
+
+ val icon = if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown
+ val borderColor = if (isLoading) colorResource(id = R.color.stax_state_blue) else Color.White
+
+ val context = LocalContext.current
+
+ if (interactionSource.collectIsPressedAsState().value)
+ expanded = !expanded
+
+ Column(
+ Modifier
+ .padding(10.dp)
+ ) {
+ OutlinedTextField(
+ value = getCountryString(selected, context),
+ onValueChange = { selected = it },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = dimensionResource(id = R.dimen.margin_10))
+ .onGloballyPositioned { coordinates ->
+ textFieldSize = coordinates.size.toSize()
+ },
+ label = {
+ Text(stringResource(id = R.string.select_country), style = MaterialTheme.typography.body1)
+ },
+ trailingIcon = {
+ Icon(
+ icon,
+ "Dropdown",
+ Modifier.clickable { expanded = !expanded },
+ tint = if (isLoading) colorResource(id = R.color.stax_state_blue) else Color.White
+ )
+ },
+ colors = TextFieldDefaults.outlinedTextFieldColors(
+ focusedLabelColor = borderColor,
+ unfocusedBorderColor = borderColor,
+ focusedBorderColor = borderColor,
+ unfocusedLabelColor = borderColor
+ ),
+ readOnly = true,
+ interactionSource = interactionSource
+ )
+
+ DropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false },
+ modifier = Modifier
+ .width(with(LocalDensity.current) { textFieldSize.width.toDp() })
+ ) {
+ countries.forEach { countryCode ->
+ DropdownMenuItem(onClick = {
+ selected = countryCode
+ expanded = false
+ bountyViewModel.loadBounties(countryCode)
+ }) {
+ Text(
+ modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.margin_10)),
+ text = getCountryString(countryCode, context),
+ style = MaterialTheme.typography.button
+ )
+ }
+ }
+ }
+
+ if (isLoading)
+ Text(
+ modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.margin_10)),
+ text = stringResource(id = R.string.filtering_in_progress),
+ style = MaterialTheme.typography.body2,
+ color = colorResource(id = R.color.stax_state_blue)
+ )
+ }
+}
+
+@Preview
+@Composable
+fun ChannelBountiesCardPreview() {
+ Column {
+ Text(
+ text = "ACS Microfinance - *614*435# - NG".uppercase(),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(dimensionResource(id = R.dimen.margin_13)),
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.End
+ )
+
+ repeat(3) {
+ BountyCardPreview()
+ }
+ }
+}
+
+@Preview
+@Composable
+fun BountyCardPreview() {
+ val margin13 = dimensionResource(id = R.dimen.margin_13)
+ val margin8 = dimensionResource(id = R.dimen.margin_8)
+
+ Column(
+ modifier = Modifier
+ .background(color = colorResource(id = R.color.colorSurface))
+ .padding(vertical = margin8)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = margin13),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = "Check Balance",
+ modifier = Modifier
+ .padding(top = margin8, bottom = margin8, end = margin13),
+ style = MaterialTheme.typography.body1
+ )
+
+ Text(
+ text = "USD $1",
+ modifier = Modifier
+ .padding(top = margin8, bottom = margin8),
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Medium
+ )
+ }
+
+ SpannableImageTextView(
+ drawable = R.drawable.ic_error,
+ stringRes = R.string.bounty_transaction_failed,
+ modifier = Modifier.padding(start = margin8, end = margin13, top = 5.dp, bottom = dimensionResource(id = R.dimen.margin_10)),
+ )
+ }
+}
+
+@Preview
+@Composable
+fun CountryDropdownPreview() {
+ val countryCodes = listOf("KE, UG, TZ, ET, ZA")
+ var expanded by remember { mutableStateOf(false) }
+ var selected by remember { mutableStateOf(CODE_ALL_COUNTRIES) }
+ var textFieldSize by remember { mutableStateOf(Size.Zero) }
+
+ val icon = if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown
+
+ Column(Modifier.padding(10.dp)) {
+ OutlinedTextField(
+ value = selected,
+ onValueChange = { selected = it },
+ modifier = Modifier
+ .fillMaxWidth()
+ .onGloballyPositioned { coordinates ->
+ textFieldSize = coordinates.size.toSize()
+ },
+ label = {
+ Text(
+ text = stringResource(id = R.string.select_country),
+ style = MaterialTheme.typography.body2,
+ )
+ },
+ trailingIcon = {
+ Icon(
+ icon,
+ "contentDescription",
+ Modifier.clickable { expanded = !expanded },
+ tint = Color.White
+ )
+ },
+ readOnly = true,
+ colors = TextFieldDefaults.outlinedTextFieldColors(
+ focusedLabelColor = colorResource(id = R.color.stax_state_blue),
+ unfocusedBorderColor = Color.White,
+ focusedBorderColor = colorResource(id = R.color.stax_state_blue),
+ unfocusedLabelColor = Color.White
+ )
+ )
+
+ DropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false },
+ modifier = Modifier
+ .width(with(LocalDensity.current) { textFieldSize.width.toDp() })
+ ) {
+ countryCodes.forEach { countryCode ->
+ DropdownMenuItem(onClick = {
+ selected = countryCode
+ expanded = false
+ }) {
+ Text(text = getCountryString(countryCode, LocalContext.current))
+ }
+ }
+ }
+
+ Text(
+ modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.margin_10)),
+ text = stringResource(id = R.string.filtering_in_progress),
+ style = MaterialTheme.typography.body2,
+ color = colorResource(id = R.color.stax_state_blue)
+ )
+ }
+}
+
+@Preview
+@Composable
+fun BountiesPreview() {
+ StaxTheme {
+ Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
+ LazyColumn {
+ item {
+ CountryDropdownPreview()
+ }
+ items(5) {
+ ChannelBountiesCardPreview()
+ }
+ }
+ }
+ }
+}
+
+private fun getBountyState(bounty: Bounty): BountyItemState {
+ return when {
+ bounty.hasSuccessfulTransactions() ->
+ BountyItemState(color = R.color.muted_green, msg = R.string.done, icon = R.drawable.ic_check, isOpen = false, bountySelectEvent = null)
+ bounty.isLastTransactionFailed() && !bounty.action.bounty_is_open ->
+ BountyItemState(color = R.color.stax_bounty_red_bg, msg = R.string.bounty_transaction_failed, icon = R.drawable.ic_error, isOpen = false, bountySelectEvent = BountySelectEvent.ViewTransactionDetail(bounty.transactions.last().uuid))
+ bounty.isLastTransactionFailed() && bounty.action.bounty_is_open ->
+ BountyItemState(color = R.color.stax_bounty_red_bg, msg = R.string.bounty_transaction_failed_try_again, icon = R.drawable.ic_error, isOpen = true, bountySelectEvent = BountySelectEvent.ViewBountyDetail(bounty))
+ !bounty.action.bounty_is_open ->
+ BountyItemState(color = R.color.lighter_grey, isOpen = false, bountySelectEvent = null)
+ bounty.transactionCount > 0 ->
+ BountyItemState(color = R.color.pending_brown, msg = R.string.bounty_pending_short_desc, icon = R.drawable.ic_warning, isOpen = true, bountySelectEvent = BountySelectEvent.ViewTransactionDetail(bounty.transactions.last().uuid))
+ else ->
+ BountyItemState(color = R.color.colorSurface, isOpen = true, bountySelectEvent = BountySelectEvent.ViewBountyDetail(bounty))
+ }
+}
+
+fun getCountryString(code: String, context: Context): String = if (code.isEmpty() || code == CountryAdapter.CODE_ALL_COUNTRIES)
+ context.getString(R.string.all_countries_with_emoji)
+else
+ context.getString(R.string.country_with_emoji, countryCodeToEmoji(code), getFullCountryName(code))
+
+private fun getFullCountryName(code: String): String {
+ val locale = Locale(Lingver.getInstance().getLanguage(), code)
+ return locale.displayCountry
+}
+
+private fun countryCodeToEmoji(countryCode: String): String {
+ return try {
+ val firstLetter = Character.codePointAt(countryCode.uppercase(), 0) - 0x41 + 0x1F1E6
+ val secondLetter = Character.codePointAt(countryCode.uppercase(), 1) - 0x41 + 0x1F1E6
+ String(Character.toChars(firstLetter)) + String(Character.toChars(secondLetter))
+ } catch (e: Exception) {
+ ""
+ }
+}
+
+data class BountyItemState(
+ @ColorRes val color: Int = 0,
+ @StringRes val msg: Int = 0,
+ @DrawableRes val icon: Int = 0,
+ val isOpen: Boolean = true,
+ val bountySelectEvent: BountySelectEvent? = null
+)
+
+sealed class BountySelectEvent {
+ data class ViewTransactionDetail(val uuid: String) : BountySelectEvent()
+ data class ViewBountyDetail(val bounty: Bounty) : BountySelectEvent()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/financialTips/FinancialTipsAdapter.kt b/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsAdapter.kt
similarity index 93%
rename from app/src/main/java/com/hover/stax/financialTips/FinancialTipsAdapter.kt
rename to app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsAdapter.kt
index 841346323..55858bd96 100644
--- a/app/src/main/java/com/hover/stax/financialTips/FinancialTipsAdapter.kt
+++ b/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsAdapter.kt
@@ -1,9 +1,10 @@
-package com.hover.stax.financialTips
+package com.hover.stax.presentation.financial_tips
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hover.stax.databinding.ItemWellnessTipsBinding
+import com.hover.stax.domain.model.FinancialTip
import com.hover.stax.utils.DateUtils
import timber.log.Timber
import java.util.*
diff --git a/app/src/main/java/com/hover/stax/financialTips/FinancialTipsFragment.kt b/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsFragment.kt
similarity index 77%
rename from app/src/main/java/com/hover/stax/financialTips/FinancialTipsFragment.kt
rename to app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsFragment.kt
index f9e16e9e8..61c4d05f4 100644
--- a/app/src/main/java/com/hover/stax/financialTips/FinancialTipsFragment.kt
+++ b/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsFragment.kt
@@ -1,4 +1,4 @@
-package com.hover.stax.financialTips
+package com.hover.stax.presentation.financial_tips
import android.content.Intent
import android.os.Bundle
@@ -13,9 +13,10 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.hover.stax.R
import com.hover.stax.databinding.FragmentWellnessBinding
+import com.hover.stax.domain.model.FinancialTip
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.UIHelper
-import com.hover.stax.utils.collectLatestLifecycleFlow
+import com.hover.stax.utils.collectLifecycleFlow
import org.json.JSONObject
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
@@ -42,8 +43,7 @@ class FinancialTipsFragment : Fragment(), FinancialTipsAdapter.SelectListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- binding.title.text = getString(R.string.financial_wellness_tips)
- binding.backButton.setOnClickListener { findNavController().popBackStack() }
+ initViews()
viewModel.getTips()
@@ -51,11 +51,41 @@ class FinancialTipsFragment : Fragment(), FinancialTipsAdapter.SelectListener {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedCallback)
}
- private fun startObserver() = collectLatestLifecycleFlow(viewModel.tipState) {
- showFinancialTips(it.tips, args.tipId)
+ private fun initViews(){
+ binding.title.text = getString(R.string.financial_wellness_tips)
+ binding.backButton.setOnClickListener { findNavController().popBackStack() }
+ binding.progressIndicator.setVisibilityAfterHide(View.GONE)
+ }
+
+ private fun startObserver() = collectLifecycleFlow(viewModel.tipsState) {
+ when {
+ it.isLoading -> {
+ binding.progressIndicator.show()
+ binding.empty.visibility = View.GONE
+ }
+ it.tips.isEmpty() && !it.isLoading-> {
+ binding.progressIndicator.hide()
+ binding.empty.visibility = View.VISIBLE
+ binding.financialTips.visibility = View.GONE
+ binding.financialTipsDetail.visibility = View.GONE
+ }
+ it.tips.isNotEmpty() -> {
+ binding.progressIndicator.hide()
+ showFinancialTips(it.tips, args.tipId)
+ }
+ it.error.isNotEmpty() -> {
+ binding.progressIndicator.hide()
+ binding.empty.visibility = View.VISIBLE
+ binding.financialTips.visibility = View.GONE
+ binding.financialTipsDetail.visibility = View.GONE
+ }
+ }
}
private fun showFinancialTips(tips: List, id: String? = null) {
+ binding.empty.visibility = View.GONE
+ binding.financialTips.visibility = View.VISIBLE
+
if (id != null) {
tips.firstOrNull { it.id == id }?.let { onTipSelected(it, true) }
} else {
@@ -154,8 +184,8 @@ class FinancialTipsFragment : Fragment(), FinancialTipsAdapter.SelectListener {
binding.financialTipsDetail.visibility = View.GONE
binding.tipsCard.visibility = View.VISIBLE
- if (viewModel.tipState.value.tips.isNotEmpty())
- showFinancialTips(viewModel.tipState.value.tips, null)
+ if (viewModel.tipsState.value.tips.isNotEmpty())
+ showFinancialTips(viewModel.tipsState.value.tips, null)
}
override fun onDestroyView() {
diff --git a/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsState.kt b/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsState.kt
new file mode 100644
index 000000000..405acf538
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsState.kt
@@ -0,0 +1,9 @@
+package com.hover.stax.presentation.financial_tips
+
+import com.hover.stax.domain.model.FinancialTip
+
+data class FinancialTipsState(
+ val isLoading: Boolean = false,
+ val tips: List = emptyList(),
+ val error: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsViewModel.kt b/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsViewModel.kt
new file mode 100644
index 000000000..2ad9ff9ef
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/financial_tips/FinancialTipsViewModel.kt
@@ -0,0 +1,29 @@
+package com.hover.stax.presentation.financial_tips
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.hover.stax.domain.model.Resource
+import com.hover.stax.domain.use_case.financial_tips.TipsUseCase
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+class FinancialTipsViewModel(private val tipsUseCase: TipsUseCase) : ViewModel() {
+
+
+ private val _tipsState = MutableStateFlow(FinancialTipsState())
+ val tipsState = _tipsState.asStateFlow()
+
+ init {
+ getTips()
+ }
+
+ fun getTips() = tipsUseCase().onEach { result ->
+ when (result) {
+ is Resource.Loading -> _tipsState.value = FinancialTipsState(isLoading = true)
+ is Resource.Error -> _tipsState.value = FinancialTipsState(error = result.message ?: "An unexpected error occurred", isLoading = false)
+ is Resource.Success -> _tipsState.value = FinancialTipsState(tips = result.data ?: emptyList(), isLoading = false)
+ }
+ }.launchIn(viewModelScope)
+}
diff --git a/app/src/main/java/com/hover/stax/presentation/home/BalanceScreen.kt b/app/src/main/java/com/hover/stax/presentation/home/BalanceScreen.kt
new file mode 100644
index 000000000..14a4f732b
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/home/BalanceScreen.kt
@@ -0,0 +1,238 @@
+package com.hover.stax.presentation.home
+
+import android.content.Context
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.*
+import androidx.compose.material.MaterialTheme.colors
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import coil.request.CachePolicy
+import coil.request.ImageRequest
+import com.hover.stax.R
+import com.hover.stax.domain.model.Account
+import com.hover.stax.ui.theme.ColorSurface
+import com.hover.stax.ui.theme.DarkGray
+import com.hover.stax.ui.theme.OffWhite
+import com.hover.stax.ui.theme.StaxTheme
+import com.hover.stax.utils.DateUtils
+
+
+interface BalanceTapListener {
+ fun onTapBalanceRefresh(account: Account?)
+ fun onTapBalanceDetail(accountId: Int)
+}
+
+@Composable
+fun BalanceHeader(onClickedAddAccount: () -> Unit, accountExists: Boolean) {
+ val size13 = dimensionResource(id = R.dimen.margin_13)
+
+ Row(
+ modifier = Modifier
+ .padding(all = size13)
+ .fillMaxWidth()
+ ) {
+ Text(
+ text = stringResource(id = R.string.your_accounts),
+ modifier = Modifier.weight(1f),
+ style = MaterialTheme.typography.h4
+ )
+
+ if (accountExists) {
+ Text(
+ text = stringResource(id = R.string.add_an_account),
+ style = MaterialTheme.typography.body2,
+ modifier = Modifier
+ .clickable(onClick = onClickedAddAccount)
+ .padding(end = 5.dp)
+ )
+
+ Spacer(modifier = Modifier.width(10.dp))
+
+ Icon(
+ painter = painterResource(id = R.drawable.ic_add_white_16),
+ contentDescription = null,
+ modifier = Modifier
+ .clip(CircleShape)
+ .clickable(onClick = onClickedAddAccount)
+ .background(color = colorResource(id = R.color.brightBlue))
+ )
+ }
+ }
+}
+
+@Composable
+fun EmptyBalance(onClickedAddAccount: () -> Unit) {
+ val size34 = dimensionResource(id = R.dimen.margin_34)
+ val size16 = dimensionResource(id = R.dimen.margin_16)
+ Column(modifier = Modifier.padding(vertical = size16)) {
+ val modifier = Modifier.padding(horizontal = size34)
+
+ Text(
+ text = stringResource(id = R.string.your_accounts),
+ style = MaterialTheme.typography.h4,
+ modifier = Modifier.padding(horizontal = size16)
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = stringResource(id = R.string.empty_balance_desc),
+ style = MaterialTheme.typography.body1,
+ color = colorResource(id = R.color.offWhite),
+ textAlign = TextAlign.Center,
+ modifier = modifier
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ OutlinedButton(
+ onClick = onClickedAddAccount,
+ modifier = Modifier
+ .fillMaxWidth()
+ .shadow(elevation = 0.dp)
+ .then(modifier),
+ shape = MaterialTheme.shapes.medium,
+ border = BorderStroke(width = 0.5.dp, color = DarkGray),
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = ColorSurface,
+ contentColor = OffWhite
+ )
+ ) {
+ Text(
+ text = stringResource(id = R.string.add_account),
+ style = MaterialTheme.typography.button,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 5.dp, bottom = 5.dp),
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+}
+
+@Composable
+fun BalanceItem(staxAccount: Account, balanceTapListener: BalanceTapListener?, context: Context) {
+ val size34 = dimensionResource(id = R.dimen.margin_34)
+ val size13 = dimensionResource(id = R.dimen.margin_13)
+ Column {
+ Row(modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 13.dp)
+ .heightIn(min = 70.dp)
+ .clickable { balanceTapListener?.onTapBalanceDetail(accountId = staxAccount.id) }) {
+
+ AsyncImage(
+ model = ImageRequest.Builder(LocalContext.current)
+ .data(staxAccount.logoUrl)
+ .crossfade(true)
+ .diskCachePolicy(CachePolicy.ENABLED)
+ .build(),
+ contentDescription = "",
+ placeholder = painterResource(id = R.drawable.img_placeholder),
+ error = painterResource(id = R.drawable.img_placeholder),
+ modifier = Modifier
+ .size(size34)
+ .clip(CircleShape)
+ .align(Alignment.CenterVertically),
+ contentScale = ContentScale.Crop
+ )
+
+ Text(
+ text = staxAccount.alias,
+ style = MaterialTheme.typography.body2,
+ modifier = Modifier
+ .weight(1f)
+ .padding(start = size13)
+ .align(Alignment.CenterVertically),
+ color = colorResource(id = R.color.white)
+ )
+
+ Column(modifier = Modifier.align(Alignment.CenterVertically)) {
+ Text(
+ text = staxAccount.latestBalance ?: " - ",
+ modifier = Modifier.align(Alignment.End),
+ style = MaterialTheme.typography.subtitle2,
+ color = colorResource(id = R.color.offWhite)
+ )
+
+ Spacer(modifier = Modifier.height(2.dp))
+
+ if (staxAccount.latestBalance != null)
+ Text(
+ text = DateUtils.timeAgo(context, staxAccount.latestBalanceTimestamp),
+ modifier = Modifier.align(Alignment.End),
+ color = colorResource(id = R.color.offWhite),
+ style = MaterialTheme.typography.caption
+ )
+ }
+
+ Image(painter = painterResource(id = R.drawable.ic_refresh_white_24dp),
+ contentDescription = null,
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .padding(start = size13)
+ .clickable { balanceTapListener?.onTapBalanceRefresh(staxAccount) }
+ .size(32.dp)
+ )
+
+ }
+
+ Divider(
+ color = colorResource(id = R.color.nav_grey),
+ modifier = Modifier.padding(horizontal = 13.dp)
+ )
+ }
+}
+
+@Preview
+@Composable
+fun BalanceScreenPreview() {
+ StaxTheme {
+ Surface {
+ BalanceListForPreview(accountList = emptyList())
+ }
+ }
+}
+
+@Composable
+private fun BalanceListForPreview(accountList: List) {
+
+ if (accountList.isEmpty()) {
+ EmptyBalance {}
+ } else {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(13.dp)
+ ) {
+ item {
+ BalanceHeader(onClickedAddAccount = {}, accountExists = false)
+ }
+ items(accountList) { account ->
+ val context = LocalContext.current
+ BalanceItem(staxAccount = account, context = context, balanceTapListener = null)
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/hover/stax/balances/BalancesViewModel.kt b/app/src/main/java/com/hover/stax/presentation/home/BalancesViewModel.kt
similarity index 67%
rename from app/src/main/java/com/hover/stax/balances/BalancesViewModel.kt
rename to app/src/main/java/com/hover/stax/presentation/home/BalancesViewModel.kt
index efd949587..ed2697a7d 100644
--- a/app/src/main/java/com/hover/stax/balances/BalancesViewModel.kt
+++ b/app/src/main/java/com/hover/stax/presentation/home/BalancesViewModel.kt
@@ -1,4 +1,4 @@
-package com.hover.stax.balances
+package com.hover.stax.presentation.home
import android.app.Application
import android.content.Context
@@ -8,24 +8,21 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.hover.sdk.actions.HoverAction
import com.hover.stax.R
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.AccountRepo
-import com.hover.stax.accounts.PLACEHOLDER
-import com.hover.stax.actions.ActionRepo
-import com.hover.stax.utils.Utils
+import com.hover.stax.data.local.accounts.AccountRepo
+import com.hover.stax.data.local.actions.ActionRepo
+
+import com.hover.stax.domain.model.Account
+import com.hover.stax.domain.model.PLACEHOLDER
+
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
class BalancesViewModel(application: Application, val actionRepo: ActionRepo, val accountRepo: AccountRepo) : AndroidViewModel(application) {
- private var _showBalances = MutableLiveData(false)
- val showBalances: LiveData = _showBalances
-
var userRequestedBalanceAccount = MutableLiveData()
private var _balanceAction = MutableSharedFlow()
@@ -38,24 +35,19 @@ class BalancesViewModel(application: Application, val actionRepo: ActionRepo, va
val actionRunError = _actionRunError.receiveAsFlow()
init {
- _showBalances.value = Utils.getBoolean(BalancesFragment.BALANCE_VISIBILITY_KEY, getApplication(), true)
-
getAccounts()
}
- fun setBalanceState(show: Boolean) = viewModelScope.launch {
- Utils.saveBoolean(BalancesFragment.BALANCE_VISIBILITY_KEY, show, getApplication())
- _showBalances.postValue(show)
- }
-
fun requestBalance(account: Account) {
userRequestedBalanceAccount.value = account
startBalanceActionFor(userRequestedBalanceAccount.value)
}
private fun startBalanceActionFor(account: Account?) = viewModelScope.launch(Dispatchers.IO) {
- val channelId = account?.channelId ?: -1
- val action = actionRepo.getActions(channelId, if (account?.name == PLACEHOLDER) HoverAction.FETCH_ACCOUNTS else HoverAction.BALANCE).firstOrNull()
+ if(account == null) return@launch
+
+ val channelId = account.channelId
+ val action = actionRepo.getActions(channelId, if (account.name.contains(PLACEHOLDER)) HoverAction.FETCH_ACCOUNTS else HoverAction.BALANCE).firstOrNull()
action?.let { _balanceAction.emit(action) } ?: run { _actionRunError.send((getApplication() as Context).getString(R.string.error_running_action)) }
}
diff --git a/app/src/main/java/com/hover/stax/presentation/home/HomeFragment.kt b/app/src/main/java/com/hover/stax/presentation/home/HomeFragment.kt
new file mode 100644
index 000000000..c2fe8a3d5
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/home/HomeFragment.kt
@@ -0,0 +1,149 @@
+package com.hover.stax.presentation.home
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.fragment.app.Fragment
+import androidx.navigation.NavDirections
+import androidx.navigation.fragment.findNavController
+import com.hover.sdk.actions.HoverAction
+import com.hover.stax.MainNavigationDirections
+import com.hover.stax.R
+import com.hover.stax.addChannels.ChannelsViewModel
+import com.hover.stax.bonus.BonusViewModel
+import com.hover.stax.databinding.FragmentHomeBinding
+import com.hover.stax.domain.model.Account
+import com.hover.stax.home.MainActivity
+import com.hover.stax.hover.AbstractHoverCallerActivity
+import com.hover.stax.utils.*
+import com.hover.stax.views.StaxDialog
+import org.koin.androidx.viewmodel.ext.android.sharedViewModel
+import org.koin.androidx.viewmodel.ext.android.viewModel
+import timber.log.Timber
+
+
+class HomeFragment : Fragment(), FinancialTipClickInterface, BalanceTapListener {
+
+ private var _binding: FragmentHomeBinding? = null
+ private val binding get() = _binding!!
+
+ private val bonusViewModel: BonusViewModel by sharedViewModel()
+ private val channelsViewModel: ChannelsViewModel by sharedViewModel()
+ private val balancesViewModel: BalancesViewModel by sharedViewModel()
+ private val homeViewModel: HomeViewModel by viewModel()
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ AnalyticsUtil.logAnalyticsEvent(getString(R.string.visit_screen, getString(R.string.visit_home)), requireContext())
+ _binding = FragmentHomeBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setComposeView()
+
+ observeForBalances()
+ observeForBonus()
+ }
+
+ private fun getHomeClickFunctions(): HomeClickFunctions {
+ fun onSendMoneyClicked() = navigateTo(getTransferDirection(HoverAction.P2P))
+ fun onBuyAirtimeClicked() = navigateTo(getTransferDirection(HoverAction.AIRTIME))
+ fun onBuyGoodsClicked() = navigateTo(HomeFragmentDirections.actionNavigationHomeToMerchantFragment())
+ fun onPayBillClicked() = navigateTo(HomeFragmentDirections.actionNavigationHomeToPaybillFragment())
+ fun onRequestMoneyClicked() = navigateTo(HomeFragmentDirections.actionNavigationHomeToNavigationRequest())
+ fun onClickedAddNewAccount() = (requireActivity() as MainActivity).checkPermissionsAndNavigate(MainNavigationDirections.actionGlobalAddChannelsFragment())
+ fun onClickedTermsAndConditions() = Utils.openUrl(getString(R.string.terms_and_condition_url), requireContext())
+ fun onClickedSettingsIcon() = navigateTo(HomeFragmentDirections.toSettingsFragment())
+
+ return HomeClickFunctions(
+ onSendMoneyClicked = { onSendMoneyClicked() },
+ onBuyAirtimeClicked = { onBuyAirtimeClicked() },
+ onBuyGoodsClicked = { onBuyGoodsClicked() },
+ onPayBillClicked = { onPayBillClicked() },
+ onRequestMoneyClicked = { onRequestMoneyClicked() },
+ onClickedAddNewAccount = { onClickedAddNewAccount() },
+ onClickedTC = { onClickedTermsAndConditions() },
+ onClickedSettingsIcon = { onClickedSettingsIcon() }
+ )
+ }
+
+ private fun setComposeView() {
+ binding.root.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ binding.root.setContent {
+ HomeScreen(
+ channelsViewModel,
+ homeClickFunctions = getHomeClickFunctions(),
+ tipInterface = this@HomeFragment,
+ balanceTapListener = this@HomeFragment,
+ homeViewModel = homeViewModel
+ )
+ }
+ }
+
+ private fun observeForBonus() {
+ collectLifecycleFlow(channelsViewModel.accountEventFlow) {
+ navigateTo(getTransferDirection(HoverAction.AIRTIME, bonusViewModel.bonusList.value.bonuses.first().userChannel.toString()))
+ }
+ }
+
+ private fun observeForBalances() {
+ collectLifecycleFlow(balancesViewModel.balanceAction) {
+ attemptCallHover(balancesViewModel.userRequestedBalanceAccount.value, it)
+ }
+
+ collectLifecycleFlow(channelsViewModel.accountCallback) {
+ askToCheckBalance(it)
+ }
+
+ collectLifecycleFlow(balancesViewModel.actionRunError) {
+ UIHelper.flashAndReportError(requireActivity(), it)
+ }
+ }
+
+ private fun getTransferDirection(type: String, channelId: String? = null): NavDirections {
+ return HomeFragmentDirections.actionNavigationHomeToNavigationTransfer(type).also {
+ if (channelId != null) it.channelId = channelId
+ }
+ }
+
+ private fun attemptCallHover(account: Account?, action: HoverAction?) {
+ action?.let { account?.let { callHover(account, action) } }
+ }
+
+ private fun callHover(account: Account, action: HoverAction) {
+ (requireActivity() as AbstractHoverCallerActivity).runSession(account, action)
+ }
+
+ private fun askToCheckBalance(account: Account) {
+ val dialog = StaxDialog(requireActivity()).setDialogTitle(R.string.check_balance_title)
+ .setDialogMessage(R.string.check_balance_desc).setNegButton(R.string.later, null)
+ .setPosButton(R.string.check_balance_title) { onTapBalanceRefresh(account) }
+ dialog.showIt()
+ }
+
+ private fun navigateTo(navDirections: NavDirections) = (requireActivity() as MainActivity).checkPermissionsAndNavigate(navDirections)
+
+ override fun onTipClicked(tipId: String?) {
+ NavUtil.navigate(findNavController(), HomeFragmentDirections.actionNavigationHomeToWellnessFragment(tipId))
+ }
+
+ override fun onTapBalanceRefresh(account: Account?) {
+ if (account != null) {
+ AnalyticsUtil.logAnalyticsEvent(getString(R.string.refresh_balance_single), requireContext())
+ balancesViewModel.requestBalance(account)
+ }
+ }
+
+ override fun onTapBalanceDetail(accountId: Int) {
+ findNavController().navigate(HomeFragmentDirections.actionNavigationHomeToAccountDetailsFragment(accountId))
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/presentation/home/HomeScreen.kt b/app/src/main/java/com/hover/stax/presentation/home/HomeScreen.kt
new file mode 100644
index 000000000..410a2a969
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/home/HomeScreen.kt
@@ -0,0 +1,463 @@
+package com.hover.stax.presentation.home
+
+import android.annotation.SuppressLint
+import android.content.Context
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.hover.stax.R
+import com.hover.stax.addChannels.ChannelsViewModel
+import com.hover.stax.domain.model.Bonus
+import com.hover.stax.domain.model.FinancialTip
+import com.hover.stax.ui.theme.StaxTheme
+import com.hover.stax.utils.AnalyticsUtil
+import com.hover.stax.utils.network.NetworkMonitor
+
+data class HomeClickFunctions(
+ val onSendMoneyClicked: () -> Unit,
+ val onBuyAirtimeClicked: () -> Unit,
+ val onBuyGoodsClicked: () -> Unit,
+ val onPayBillClicked: () -> Unit,
+ val onRequestMoneyClicked: () -> Unit,
+ val onClickedTC: () -> Unit,
+ val onClickedAddNewAccount: () -> Unit,
+ val onClickedSettingsIcon: () -> Unit
+)
+
+interface FinancialTipClickInterface {
+ fun onTipClicked(tipId: String?)
+}
+
+@Composable
+fun TopBar(@StringRes title: Int = R.string.app_name, isInternetConnected: Boolean, onClickedSettingsIcon: () -> Unit) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(all = dimensionResource(id = R.dimen.margin_13)),
+ ) {
+ HorizontalImageTextView(
+ drawable = R.drawable.stax_logo,
+ stringRes = title,
+ modifier = Modifier.weight(1f),
+ MaterialTheme.typography.button
+ )
+
+ if (!isInternetConnected) {
+ HorizontalImageTextView(
+ drawable = R.drawable.ic_internet_off,
+ stringRes = R.string.working_offline,
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .padding(horizontal = 16.dp),
+ MaterialTheme.typography.button
+ )
+ }
+
+ Image(
+ painter = painterResource(id = R.drawable.ic_settings),
+ contentDescription = null,
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .clickable(onClick = onClickedSettingsIcon)
+ .size(30.dp),
+ )
+ }
+}
+
+@Composable
+fun BonusCard(message: String, onClickedTC: () -> Unit, onClickedTopUp: () -> Unit) {
+ val size13 = dimensionResource(id = R.dimen.margin_13)
+ val size10 = dimensionResource(id = R.dimen.margin_10)
+
+ Card(modifier = Modifier.padding(all = size13), elevation = 2.dp) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(all = size13)
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = stringResource(id = R.string.get_rewarded),
+ style = MaterialTheme.typography.h3
+ )
+ Text(
+ text = message,
+ modifier = Modifier.padding(vertical = size10),
+ style = MaterialTheme.typography.body1
+ )
+ Text(
+ text = stringResource(id = R.string.tc_apply),
+ textDecoration = TextDecoration.Underline,
+ color = colorResource(id = R.color.brightBlue),
+ style = MaterialTheme.typography.body2,
+ modifier = Modifier.clickable(onClick = onClickedTC)
+ )
+ Text(
+ text = stringResource(id = R.string.top_up),
+ color = colorResource(id = R.color.brightBlue),
+ style = MaterialTheme.typography.h4,
+ modifier = Modifier
+ .padding(top = size13)
+ .clickable(onClick = onClickedTopUp)
+ )
+ }
+ Image(
+ painter = painterResource(id = R.drawable.ic_bonus),
+ contentDescription = stringResource(id = R.string.get_rewarded),
+ modifier = Modifier
+ .size(70.dp)
+ .padding(start = size13)
+ .align(Alignment.CenterVertically)
+ )
+ }
+ }
+}
+
+@Composable
+fun PrimaryFeatures(
+ onSendMoneyClicked: () -> Unit,
+ onBuyAirtimeClicked: () -> Unit,
+ onBuyGoodsClicked: () -> Unit,
+ onPayBillClicked: () -> Unit,
+ onRequestMoneyClicked: () -> Unit,
+ showKenyaFeatures: Boolean
+) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ modifier = Modifier
+ .padding(horizontal = 13.dp, vertical = 26.dp)
+ .fillMaxWidth()
+ ) {
+ VerticalImageTextView(
+ onItemClick = onSendMoneyClicked,
+ drawable = R.drawable.ic_send_money,
+ stringRes = R.string.cta_transfer
+ )
+ VerticalImageTextView(
+ onItemClick = onBuyAirtimeClicked,
+ drawable = R.drawable.ic_system_upate_24,
+ stringRes = R.string.cta_airtime
+ )
+ if (showKenyaFeatures) {
+ VerticalImageTextView(
+ onItemClick = onBuyGoodsClicked,
+ drawable = R.drawable.ic_shopping_cart,
+ stringRes = R.string.cta_merchant
+ )
+ VerticalImageTextView(
+ onItemClick = onPayBillClicked,
+ drawable = R.drawable.ic_utility,
+ stringRes = R.string.cta_paybill_linebreak
+ )
+ }
+ VerticalImageTextView(
+ onItemClick = onRequestMoneyClicked,
+ drawable = R.drawable.ic_baseline_people_24,
+ stringRes = R.string.cta_request
+ )
+ }
+}
+
+@Composable
+private fun FinancialTipCard(
+ tipInterface: FinancialTipClickInterface?,
+ financialTip: FinancialTip,
+ homeViewModel: HomeViewModel?
+) {
+ val size13 = dimensionResource(id = R.dimen.margin_13)
+
+ Card(elevation = 0.dp, modifier = Modifier.padding(all = size13)) {
+ Column {
+ Row(modifier = Modifier
+ .fillMaxWidth()
+ .padding(all = size13)) {
+ HorizontalImageTextView(
+ drawable = R.drawable.ic_tip_of_day,
+ stringRes = R.string.tip_of_the_day,
+ Modifier.weight(1f),
+ MaterialTheme.typography.button
+ )
+
+ Image(painter = painterResource(id = R.drawable.ic_close_white),
+ contentDescription = null,
+ alignment = Alignment.CenterEnd,
+ modifier = Modifier.clickable { homeViewModel?.dismissTip(financialTip.id) })
+ }
+
+ Row(modifier = Modifier
+ .padding(horizontal = size13)
+ .clickable { tipInterface?.onTipClicked(null) }) {
+
+ Column(modifier = Modifier.weight(1f)) {
+ Spacer(modifier = Modifier.height(10.dp))
+
+ Text(
+ text = financialTip.title,
+ style = MaterialTheme.typography.body2,
+ textDecoration = TextDecoration.Underline
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = financialTip.snippet,
+ style = MaterialTheme.typography.body2,
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier.padding(bottom = size13, top = 3.dp)
+ )
+
+ Text(text = stringResource(id = R.string.read_more),
+ color = colorResource(id = R.color.brightBlue),
+ modifier = Modifier
+ .padding(bottom = size13)
+ .clickable { tipInterface?.onTipClicked(financialTip.id) }
+ )
+ }
+
+ Image(
+ painter = painterResource(id = R.drawable.tips_fancy_icon),
+ contentDescription = null,
+ modifier = Modifier
+ .size(60.dp)
+ .padding(start = size13)
+ .align(Alignment.CenterVertically),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun VerticalImageTextView(
+ @DrawableRes drawable: Int,
+ @StringRes stringRes: Int,
+ onItemClick: () -> Unit
+) {
+ val size24 = dimensionResource(id = R.dimen.margin_24)
+ val blue = colorResource(id = R.color.stax_state_blue)
+
+ Column(
+ modifier = Modifier
+ .clickable(onClick = onItemClick)
+ .padding(horizontal = 2.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box(
+ modifier = Modifier
+ .clip(CircleShape)
+ .size(48.dp)
+ .background(blue),
+ contentAlignment = Alignment.Center
+ ) {
+ Image(
+ painter = painterResource(id = drawable),
+ contentDescription = "",
+ modifier = Modifier
+ .size(size24)
+ )
+ }
+
+ Text(
+ text = stringResource(id = stringRes),
+ color = colorResource(id = R.color.offWhite),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.caption,
+ modifier = Modifier
+ .padding(top = dimensionResource(id = R.dimen.margin_16))
+ .widthIn(min = 50.dp, max = 65.dp)
+ )
+ }
+}
+
+@Composable
+internal fun HorizontalImageTextView(
+ @DrawableRes drawable: Int,
+ @StringRes stringRes: Int,
+ modifier: Modifier = Modifier, textStyle: TextStyle
+) {
+ Row(horizontalArrangement = Arrangement.Start, modifier = modifier) {
+ Image(
+ painter = painterResource(id = drawable),
+ contentDescription = null,
+ modifier = Modifier.align(Alignment.CenterVertically),
+ )
+ Text(
+ text = stringResource(id = stringRes),
+ style = textStyle,
+ modifier = Modifier
+ .padding(start = dimensionResource(id = R.dimen.margin_13))
+ .align(Alignment.CenterVertically),
+ color = colorResource(id = R.color.offWhite)
+ )
+ }
+}
+
+@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
+@Composable
+fun HomeScreen(
+ channelsViewModel: ChannelsViewModel,
+ homeClickFunctions: HomeClickFunctions,
+ balanceTapListener: BalanceTapListener,
+ tipInterface: FinancialTipClickInterface,
+ homeViewModel: HomeViewModel
+) {
+ val homeState by homeViewModel.homeState.collectAsState()
+ val hasNetwork by NetworkMonitor.StateLiveData.get().observeAsState(initial = false)
+ val simCountryList by channelsViewModel.simCountryList.observeAsState(initial = emptyList())
+ val accounts by homeViewModel.accounts.observeAsState(initial = emptyList())
+ val context = LocalContext.current
+
+ StaxTheme {
+ Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
+ Scaffold(
+ topBar = { TopBar(title = R.string.nav_home, isInternetConnected = hasNetwork, homeClickFunctions.onClickedSettingsIcon) },
+ content = {
+ LazyColumn {
+ if (homeState.bonuses.isNotEmpty() && accounts.isNotEmpty())
+ item {
+ BonusCard(message = homeState.bonuses.first().message,
+ onClickedTC = homeClickFunctions.onClickedTC,
+ onClickedTopUp = {
+ clickedOnBonus(
+ context,
+ channelsViewModel,
+ homeState.bonuses.first()
+ )
+ })
+
+ }
+
+ if (accounts.isEmpty())
+ item {
+ EmptyBalance(onClickedAddAccount = homeClickFunctions.onClickedAddNewAccount)
+ }
+
+ item {
+ PrimaryFeatures(
+ onSendMoneyClicked = homeClickFunctions.onSendMoneyClicked,
+ onBuyAirtimeClicked = homeClickFunctions.onBuyAirtimeClicked,
+ onBuyGoodsClicked = homeClickFunctions.onBuyGoodsClicked,
+ onPayBillClicked = homeClickFunctions.onPayBillClicked,
+ onRequestMoneyClicked = homeClickFunctions.onRequestMoneyClicked,
+ showKEFeatures(simCountryList)
+ )
+ }
+
+ if (accounts.isNotEmpty())
+ item {
+ BalanceHeader(
+ onClickedAddAccount = homeClickFunctions.onClickedAddNewAccount, homeState.accounts.isNotEmpty()
+ )
+ }
+
+ items(accounts) { account ->
+ BalanceItem(
+ staxAccount = account,
+ context = context,
+ balanceTapListener = balanceTapListener
+ )
+ }
+
+ item {
+ homeState.financialTips.firstOrNull {
+ android.text.format.DateUtils.isToday(it.date!!)
+ }?.let {
+ if (homeState.dismissedTipId != it.id)
+ FinancialTipCard(
+ tipInterface = tipInterface,
+ financialTip = homeState.financialTips.first(),
+ homeViewModel
+ )
+ }
+ }
+ }
+ }
+ )
+ }
+ }
+}
+
+private fun clickedOnBonus(context: Context, channelsViewModel: ChannelsViewModel, bonus: Bonus) {
+ AnalyticsUtil.logAnalyticsEvent(
+ context.getString(R.string.clicked_bonus_airtime_banner),
+ context
+ )
+ channelsViewModel.validateAccounts(bonus.userChannel)
+}
+
+private fun showKEFeatures(countryIsos: List): Boolean = countryIsos.any { it.contentEquals("KE", ignoreCase = true) }
+
+@Preview
+@Composable
+fun HomeScreenPreview() {
+ val financialTip = FinancialTip(
+ id = "1234",
+ title = "Do you want to save money",
+ content = "This is a test content here so lets see if its going to use ellipse overflow",
+ snippet = "This is a test content here so lets see if its going to use ellipse overflow, with an example here",
+ date = System.currentTimeMillis(),
+ shareCopy = null,
+ deepLink = null
+ )
+
+ StaxTheme {
+ Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
+ Scaffold(
+ topBar = {
+ TopBar(title = R.string.nav_home, isInternetConnected = false) {}
+ },
+ content = { padding ->
+ LazyColumn(modifier = Modifier.padding(padding), content = {
+ item {
+ BonusCard(message = "Buy at least Ksh 50 airtime on Stax to get 3% or more bonus airtime",
+ onClickedTC = {},
+ onClickedTopUp = {})
+ }
+ item {
+ PrimaryFeatures(
+ onSendMoneyClicked = { },
+ onBuyAirtimeClicked = { },
+ onBuyGoodsClicked = { },
+ onPayBillClicked = { },
+ onRequestMoneyClicked = {},
+ true
+ )
+ }
+ item {
+ BalanceScreenPreview()
+ }
+ item {
+ FinancialTipCard(tipInterface = null, financialTip = financialTip, null)
+ }
+ })
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/presentation/home/HomeState.kt b/app/src/main/java/com/hover/stax/presentation/home/HomeState.kt
new file mode 100644
index 000000000..50b1c01ef
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/home/HomeState.kt
@@ -0,0 +1,12 @@
+package com.hover.stax.presentation.home
+
+import com.hover.stax.domain.model.Bonus
+import com.hover.stax.domain.model.Account
+import com.hover.stax.domain.model.FinancialTip
+
+data class HomeState (
+ val bonuses: List = emptyList(),
+ val accounts: List = emptyList(),
+ val financialTips: List = emptyList(),
+ val dismissedTipId: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/presentation/home/HomeViewModel.kt b/app/src/main/java/com/hover/stax/presentation/home/HomeViewModel.kt
new file mode 100644
index 000000000..aabb3fde1
--- /dev/null
+++ b/app/src/main/java/com/hover/stax/presentation/home/HomeViewModel.kt
@@ -0,0 +1,73 @@
+package com.hover.stax.presentation.home
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.hover.stax.domain.model.Account
+import com.hover.stax.domain.model.Resource
+import com.hover.stax.domain.use_case.accounts.GetAccountsUseCase
+import com.hover.stax.domain.use_case.bonus.FetchBonusUseCase
+import com.hover.stax.domain.use_case.bonus.GetBonusesUseCase
+import com.hover.stax.domain.use_case.financial_tips.TipsUseCase
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.launch
+
+class HomeViewModel(
+ private val getBonusesUseCase: GetBonusesUseCase,
+ private val fetchBonusUseCase: FetchBonusUseCase,
+ private val getAccountsUseCase: GetAccountsUseCase,
+ private val tipsUseCase: TipsUseCase
+) : ViewModel() {
+
+ private val _homeState = MutableStateFlow(HomeState())
+ val homeState = _homeState.asStateFlow()
+
+ private val _accounts = MutableLiveData>()
+ val accounts: LiveData> = _accounts
+
+ init {
+ fetchBonuses()
+ fetchData()
+ }
+
+ private fun fetchData() {
+ getBonusList()
+ getAccounts()
+ getFinancialTips()
+ getDismissedFinancialTips()
+ }
+
+ private fun fetchBonuses() = viewModelScope.launch {
+ fetchBonusUseCase()
+ }
+
+ private fun getBonusList() = viewModelScope.launch {
+ getBonusesUseCase.bonusList.collect { bonusList ->
+ _homeState.update { it.copy(bonuses = bonusList) }
+ }
+ }
+
+ private fun getAccounts() = viewModelScope.launch {
+ getAccountsUseCase.accounts.collect { accounts ->
+ _homeState.update { it.copy(accounts = accounts) }
+ _accounts.postValue(accounts)
+ }
+ }
+
+ private fun getFinancialTips() = tipsUseCase().onEach { result ->
+ if (result is Resource.Success)
+ _homeState.update { it.copy(financialTips = result.data ?: emptyList()) }
+ }.launchIn(viewModelScope)
+
+ private fun getDismissedFinancialTips() = _homeState.update {
+ it.copy(dismissedTipId = tipsUseCase.getDismissedTipId() ?: "")
+ }
+
+ fun dismissTip(id: String) {
+ viewModelScope.launch {
+ tipsUseCase.dismissTip(id)
+ _homeState.update { it.copy(dismissedTipId = id) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/requests/NewRequestFragment.kt b/app/src/main/java/com/hover/stax/requests/NewRequestFragment.kt
index b0405385c..c0f3ebbb1 100644
--- a/app/src/main/java/com/hover/stax/requests/NewRequestFragment.kt
+++ b/app/src/main/java/com/hover/stax/requests/NewRequestFragment.kt
@@ -13,15 +13,14 @@ import androidx.annotation.CallSuper
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.hover.stax.R
-import com.hover.stax.accounts.Account
import com.hover.stax.contacts.ContactInput
import com.hover.stax.contacts.StaxContact
import com.hover.stax.databinding.FragmentRequestBinding
+import com.hover.stax.domain.model.Account
import com.hover.stax.notifications.PushNotificationTopicsInterface
import com.hover.stax.transfers.AbstractFormFragment
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.Utils
-import com.hover.stax.utils.collectLatestLifecycleFlow
import com.hover.stax.views.*
import org.koin.androidx.viewmodel.ext.android.getSharedViewModel
import timber.log.Timber
@@ -101,14 +100,7 @@ class NewRequestFragment : AbstractFormFragment(), PushNotificationTopicsInterfa
}
}
- with(accountsViewModel) {
- collectLatestLifecycleFlow(accounts){
- if (it.isEmpty())
- setDropdownTouchListener(NewRequestFragmentDirections.actionNavigationRequestToAccountsFragment())
- }
-
- activeAccount.observe(viewLifecycleOwner, accountsObserver)
- }
+ accountsViewModel.activeAccount.observe(viewLifecycleOwner, accountsObserver)
with(requestViewModel) {
amount.observe(viewLifecycleOwner) {
@@ -216,7 +208,7 @@ class NewRequestFragment : AbstractFormFragment(), PushNotificationTopicsInterfa
requestViewModel.setEditing(false)
}
- override fun onSubmitForm() { }
+ override fun onSubmitForm() {}
private fun updatePushNotifGroupStatus() {
joinRequestMoneyGroup(requireContext())
diff --git a/app/src/main/java/com/hover/stax/requests/NewRequestViewModel.kt b/app/src/main/java/com/hover/stax/requests/NewRequestViewModel.kt
index 62ae02a22..526d96fd3 100644
--- a/app/src/main/java/com/hover/stax/requests/NewRequestViewModel.kt
+++ b/app/src/main/java/com/hover/stax/requests/NewRequestViewModel.kt
@@ -5,11 +5,11 @@ import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.hover.stax.R
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.AccountRepo
-import com.hover.stax.accounts.PLACEHOLDER
+import com.hover.stax.domain.model.Account
+import com.hover.stax.data.local.accounts.AccountRepo
import com.hover.stax.contacts.ContactRepo
import com.hover.stax.contacts.StaxContact
+import com.hover.stax.domain.model.PLACEHOLDER
import com.hover.stax.schedules.ScheduleRepo
import com.hover.stax.schedules.Schedule
import com.hover.stax.transfers.AbstractFormViewModel
@@ -69,7 +69,7 @@ class NewRequestViewModel(application: Application, val repo: RequestRepo, val a
fun accountError(): String? = if (activeAccount.value != null) null else getString(R.string.accounts_error_noselect)
- fun isValidAccount(): Boolean = activeAccount.value!!.name != PLACEHOLDER
+ fun isValidAccount(): Boolean = !activeAccount.value!!.name.contains(PLACEHOLDER)
fun requesterAcctNoError(): String? = if (!requesterNumber.value.isNullOrEmpty()) null else getString(R.string.requester_number_fielderror)
diff --git a/app/src/main/java/com/hover/stax/requests/Request.kt b/app/src/main/java/com/hover/stax/requests/Request.kt
index 6f6363b34..6e4738c8e 100644
--- a/app/src/main/java/com/hover/stax/requests/Request.kt
+++ b/app/src/main/java/com/hover/stax/requests/Request.kt
@@ -8,7 +8,7 @@ import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.hover.stax.R
-import com.hover.stax.accounts.Account
+import com.hover.stax.domain.model.Account
import com.hover.stax.channels.Channel
import com.hover.stax.contacts.PhoneHelper
import com.hover.stax.contacts.StaxContact
diff --git a/app/src/main/java/com/hover/stax/requests/RequestDetailFragment.kt b/app/src/main/java/com/hover/stax/requests/RequestDetailFragment.kt
index 17b8aca4a..9170db330 100644
--- a/app/src/main/java/com/hover/stax/requests/RequestDetailFragment.kt
+++ b/app/src/main/java/com/hover/stax/requests/RequestDetailFragment.kt
@@ -13,7 +13,7 @@ import com.hover.stax.contacts.StaxContact
import com.hover.stax.databinding.FragmentRequestDetailBinding
import com.hover.stax.utils.AnalyticsUtil.logAnalyticsEvent
import com.hover.stax.utils.DateUtils
-import com.hover.stax.utils.UIHelper.flashMessage
+import com.hover.stax.utils.UIHelper.flashAndReportMessage
import com.hover.stax.utils.Utils
import com.hover.stax.views.Stax2LineItem
import com.hover.stax.views.StaxDialog
@@ -98,7 +98,7 @@ class RequestDetailFragment: Fragment(), RequestSenderInterface {
.setNegButton(R.string.btn_back) {}
.setPosButton(R.string.btn_cancelreq) {
viewModel.deleteRequest()
- flashMessage(requireActivity(), getString(R.string.toast_confirm_cancelreq))
+ flashAndReportMessage(requireActivity(), getString(R.string.toast_confirm_cancelreq))
NavHostFragment.findNavController(this@RequestDetailFragment).popBackStack()
}
.isDestructive
diff --git a/app/src/main/java/com/hover/stax/requests/RequestDetailViewModel.kt b/app/src/main/java/com/hover/stax/requests/RequestDetailViewModel.kt
index 2ec7c7c1d..431bf1d03 100644
--- a/app/src/main/java/com/hover/stax/requests/RequestDetailViewModel.kt
+++ b/app/src/main/java/com/hover/stax/requests/RequestDetailViewModel.kt
@@ -1,13 +1,10 @@
package com.hover.stax.requests
import androidx.lifecycle.*
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.AccountRepo
-import com.hover.stax.channels.Channel
-import com.hover.stax.channels.ChannelRepo
+import com.hover.stax.domain.model.Account
+import com.hover.stax.data.local.accounts.AccountRepo
import com.hover.stax.contacts.ContactRepo
import com.hover.stax.contacts.StaxContact
-import com.hover.stax.schedules.ScheduleRepo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
diff --git a/app/src/main/java/com/hover/stax/requests/RequestSenderInterface.kt b/app/src/main/java/com/hover/stax/requests/RequestSenderInterface.kt
index a6f5fe25e..9958863f9 100644
--- a/app/src/main/java/com/hover/stax/requests/RequestSenderInterface.kt
+++ b/app/src/main/java/com/hover/stax/requests/RequestSenderInterface.kt
@@ -11,10 +11,10 @@ import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.hover.stax.R
-import com.hover.stax.accounts.Account
+import com.hover.stax.domain.model.Account
import com.hover.stax.contacts.StaxContact
import com.hover.stax.utils.AnalyticsUtil.logAnalyticsEvent
-import com.hover.stax.utils.UIHelper.flashMessage
+import com.hover.stax.utils.UIHelper.flashAndReportMessage
import com.hover.stax.utils.Utils.copyToClipboard
const val REQUEST_LINK = "request_link"
@@ -112,6 +112,6 @@ interface RequestSenderInterface : SmsSentObserver.SmsSentListener {
}
fun showError(c: Context) {
- flashMessage(c, c.getString(R.string.loading_link_dialoghead))
+ flashAndReportMessage(c, c.getString(R.string.loading_link_dialoghead))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/schedules/ScheduleDetailFragment.kt b/app/src/main/java/com/hover/stax/schedules/ScheduleDetailFragment.kt
index deea4ad32..22ec63006 100644
--- a/app/src/main/java/com/hover/stax/schedules/ScheduleDetailFragment.kt
+++ b/app/src/main/java/com/hover/stax/schedules/ScheduleDetailFragment.kt
@@ -104,7 +104,7 @@ class ScheduleDetailFragment : Fragment() {
.setNegButton(R.string.btn_back) {}
.setPosButton(R.string.btn_canceltrans) {
viewModel.deleteSchedule()
- UIHelper.flashMessage(requireActivity(), getString(R.string.toast_confirm_cancelfuture))
+ UIHelper.flashAndReportMessage(requireActivity(), getString(R.string.toast_confirm_cancelfuture))
findNavController().popBackStack()
}
.isDestructive
@@ -119,7 +119,7 @@ class ScheduleDetailFragment : Fragment() {
ScheduleWorker.makeWork()).enqueue()
if (!schedule.isScheduledForToday)
- UIHelper.flashMessage(requireActivity(), "Shouldn't show notification; not scheduled for today")
+ UIHelper.flashAndReportMessage(requireActivity(), "Shouldn't show notification; not scheduled for today")
}
}
}
diff --git a/app/src/main/java/com/hover/stax/schedules/ScheduleDetailViewModel.kt b/app/src/main/java/com/hover/stax/schedules/ScheduleDetailViewModel.kt
index e2e8a207e..092f05dbc 100644
--- a/app/src/main/java/com/hover/stax/schedules/ScheduleDetailViewModel.kt
+++ b/app/src/main/java/com/hover/stax/schedules/ScheduleDetailViewModel.kt
@@ -2,7 +2,7 @@ package com.hover.stax.schedules
import androidx.lifecycle.*
import com.hover.sdk.actions.HoverAction
-import com.hover.stax.actions.ActionRepo
+import com.hover.stax.data.local.actions.ActionRepo
import com.hover.stax.contacts.ContactRepo
import com.hover.stax.contacts.StaxContact
import kotlinx.coroutines.launch
diff --git a/app/src/main/java/com/hover/stax/settings/SettingsFragment.kt b/app/src/main/java/com/hover/stax/settings/SettingsFragment.kt
index a205796b0..7391735c2 100644
--- a/app/src/main/java/com/hover/stax/settings/SettingsFragment.kt
+++ b/app/src/main/java/com/hover/stax/settings/SettingsFragment.kt
@@ -16,7 +16,7 @@ import androidx.navigation.fragment.findNavController
import com.hover.sdk.api.Hover
import com.hover.stax.BuildConfig
import com.hover.stax.R
-import com.hover.stax.accounts.Account
+import com.hover.stax.domain.model.Account
import com.hover.stax.accounts.AccountsViewModel
import com.hover.stax.databinding.FragmentSettingsBinding
import com.hover.stax.languages.LanguageViewModel
@@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.getViewModel
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
+import timber.log.Timber
const val TEST_MODE = "test_mode"
@@ -68,10 +69,8 @@ class SettingsFragment : Fragment() {
binding.bountyCard.getStartedWithBountyButton.setOnClickListener { startBounties() }
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- accountsViewModel.accountUpdateMsg.collect { UIHelper.flashMessage(requireActivity(), it) }
- }
+ collectLifecycleFlow(accountsViewModel.accountUpdateMsg) {
+ UIHelper.flashAndReportMessage(requireActivity(), it)
}
}
@@ -91,12 +90,12 @@ class SettingsFragment : Fragment() {
NavUtil.navigate(findNavController(), SettingsFragmentDirections.actionNavigationSettingsToNavigationLinkAccount())
}
- collectLatestLifecycleFlow(accountsViewModel.accounts) {
- if (it.isEmpty()) {
+ collectLifecycleFlow(accountsViewModel.accountList) {
+ if (it.accounts.isEmpty()) {
binding.settingsCard.defaultAccountEntry.visibility = GONE
binding.settingsCard.connectAccounts.visibility = VISIBLE
} else
- createDefaultSelector(it)
+ createDefaultSelector(it.accounts)
}
}
@@ -166,6 +165,7 @@ class SettingsFragment : Fragment() {
}
private fun createDefaultSelector(accounts: List) {
+ binding.settingsCard.connectAccounts.visibility = GONE
val spinner = binding.settingsCard.defaultAccountSpinner
binding.settingsCard.defaultAccountEntry.visibility = VISIBLE
accountAdapter = ArrayAdapter(requireActivity(), R.layout.stax_spinner_item, accounts)
@@ -187,23 +187,24 @@ class SettingsFragment : Fragment() {
private fun setUpEnableTestMode() {
binding.settingsCard.testMode.setOnCheckedChangeListener { _, isChecked ->
Utils.saveBoolean(TEST_MODE, isChecked, requireContext())
- UIHelper.flashMessage(requireContext(), if (isChecked) R.string.test_mode_toast else R.string.test_mode_disabled)
+ UIHelper.flashAndReportMessage(requireContext(), if (isChecked) R.string.test_mode_toast else R.string.test_mode_disabled)
}
binding.settingsCard.testMode.visibility = if (Utils.getBoolean(TEST_MODE, requireContext())) VISIBLE else GONE
binding.disclaimer.setOnClickListener {
clickCounter++
- if (clickCounter == 5) UIHelper.flashMessage(requireContext(), R.string.test_mode_almost_toast) else if (clickCounter == 7) enableTestMode()
+ if (clickCounter == 5) UIHelper.flashAndReportMessage(requireContext(), R.string.test_mode_almost_toast) else if (clickCounter == 7) enableTestMode()
}
}
private fun enableTestMode() {
Utils.saveBoolean(TEST_MODE, true, requireActivity())
binding.settingsCard.testMode.visibility = VISIBLE
- UIHelper.flashMessage(requireContext(), R.string.test_mode_toast)
+ UIHelper.flashAndReportMessage(requireContext(), R.string.test_mode_toast)
}
private fun startBounties() {
val staxUser = loginViewModel.staxUser.value
+
val navDirection = if (staxUser == null || !staxUser.isMapper)
SettingsFragmentDirections.actionNavigationSettingsToBountyEmailFragment()
else
@@ -224,7 +225,7 @@ class SettingsFragment : Fragment() {
private fun logoutUser() {
loginViewModel.silentSignOut()
binding.staxSupport.marketingOptIn.isChecked = false
- UIHelper.flashMessage(requireActivity(), getString(R.string.logout_out_success))
+ UIHelper.flashAndReportMessage(requireActivity(), getString(R.string.logout_out_success))
}
private fun showLoginDialog() {
diff --git a/app/src/main/java/com/hover/stax/transactionDetails/TransactionDetailsFragment.kt b/app/src/main/java/com/hover/stax/transactionDetails/TransactionDetailsFragment.kt
index fbdb4f2f0..ccfc01af2 100644
--- a/app/src/main/java/com/hover/stax/transactionDetails/TransactionDetailsFragment.kt
+++ b/app/src/main/java/com/hover/stax/transactionDetails/TransactionDetailsFragment.kt
@@ -9,6 +9,7 @@ import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.widget.RelativeLayout
import android.widget.TextView
+import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
@@ -20,9 +21,9 @@ import com.hover.sdk.api.Hover
import com.hover.sdk.transactions.Transaction
import com.hover.stax.ApplicationInstance
import com.hover.stax.R
-import com.hover.stax.accounts.Account
import com.hover.stax.contacts.StaxContact
import com.hover.stax.databinding.FragmentTransactionBinding
+import com.hover.stax.domain.model.Account
import com.hover.stax.home.MainActivity
import com.hover.stax.hover.AbstractHoverCallerActivity
import com.hover.stax.merchants.Merchant
@@ -87,18 +88,28 @@ class TransactionDetailsFragment : Fragment() {
}
private fun startObservers() = with(viewModel) {
+ val txnObserver = object : Observer {
+ override fun onChanged(t: Transaction?) {
+ t?.let { Timber.e("Updating transaction messages ${t.uuid}") }
+ }
+ }
+
transaction.observe(viewLifecycleOwner) { showTransaction(it) }
action.observe(viewLifecycleOwner) { it?.let { updateAction(it) } }
contact.observe(viewLifecycleOwner) { updateRecipient(it) }
merchant.observe(viewLifecycleOwner) { updateRecipient(it) }
account.observe(viewLifecycleOwner) { it?.let { updateAccount(it) } }
- hoverTransaction.observe(viewLifecycleOwner) { it?.let { Timber.e("Updating transaction messages ${it.uuid}") } }
+ hoverTransaction.observe(viewLifecycleOwner, txnObserver)
messages.observe(viewLifecycleOwner) { it?.let { updateMessages(it) } }
bonusAmt.observe(viewLifecycleOwner) { showBonusAmount(it) }
- val observer = Observer {
- Timber.i("Expecting sms $it")
- action.value?.let { a -> updateAction(a) }
+
+ val observer = object: Observer {
+ override fun onChanged(t: Boolean?) {
+ Timber.i("Expecting sms $t")
+ action.value?.let { a -> updateAction(a) }
+ }
+
}
isExpectingSMS.observe(viewLifecycleOwner, observer)
}
@@ -134,7 +145,8 @@ class TransactionDetailsFragment : Fragment() {
detailsDate.text = humanFriendlyDateTime(transaction.updated_at)
typeValue.text = transaction.toString(requireContext())
viewModel.action.value?.let {
- categoryValue.text = transaction.shortStatusExplain(viewModel.action.value, requireContext()) }
+ categoryValue.text = transaction.shortStatusExplain(viewModel.action.value, requireContext())
+ }
statusValue.apply {
text = transaction.humanStatus(requireContext())
@@ -171,7 +183,8 @@ class TransactionDetailsFragment : Fragment() {
if (action.isOnNetwork) binding.details.recipInstitutionRow.visibility = GONE
else binding.details.institutionValue.setTitle(action.to_institution_name)
viewModel.transaction.value?.let {
- binding.statusInfo.longDescription.text = it.longStatus(action, viewModel.messages.value?.last(), viewModel.sms.value, viewModel.isExpectingSMS.value ?: false, requireContext())
+ val msg = it.longStatus(action, viewModel.messages.value?.last(), viewModel.sms.value, viewModel.isExpectingSMS.value ?: false, requireContext())
+ binding.statusInfo.longDescription.text = HtmlCompat.fromHtml(msg, HtmlCompat.FROM_HTML_MODE_LEGACY)
binding.details.categoryValue.text = it.shortStatusExplain(action, requireContext())
if (action.transaction_type == HoverAction.BILL)
binding.details.institutionValue.setSubtitle(Paybill.extractBizNumber(action))
@@ -187,13 +200,14 @@ class TransactionDetailsFragment : Fragment() {
private fun updateMessages(ussdCallResponses: List?) {
viewModel.action.value?.let {
viewModel.transaction.value?.let { t ->
- binding.statusInfo.longDescription.text = t.longStatus(
+ val msg = t.longStatus(
it,
ussdCallResponses?.last(),
viewModel.sms.value,
viewModel.isExpectingSMS.value ?: false,
requireContext()
)
+ binding.statusInfo.longDescription.text = HtmlCompat.fromHtml(msg, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
}
}
@@ -218,13 +232,13 @@ class TransactionDetailsFragment : Fragment() {
private fun addRetryOrSupportButton(transaction: StaxTransaction) {
if (transaction.isRecorded)
- binding.statusInfo.btnRetry.setOnClickListener{ retryBounty() }
+ binding.statusInfo.btnRetry.setOnClickListener { retryBounty() }
else if (transaction.status == Transaction.FAILED) {
if (shouldContactSupport(transaction.action_id))
setupContactSupportButton(transaction.action_id, binding.statusInfo.btnRetry)
- else binding.statusInfo.btnRetry.setOnClickListener{ maybeRetry(transaction) }
+ else binding.statusInfo.btnRetry.setOnClickListener { maybeRetry(transaction) }
}
- binding.statusInfo.btnRetry.visibility = if (transaction.isRetryable) VISIBLE else GONE
+ binding.statusInfo.btnRetry.visibility = if (transaction.canRetry) VISIBLE else GONE
}
private fun shouldContactSupport(id: String): Boolean = if (retryCounter[id] != null) retryCounter[id]!! >= 3 else false
@@ -241,7 +255,7 @@ class TransactionDetailsFragment : Fragment() {
private fun maybeRetry(transaction: StaxTransaction) {
if (viewModel.account.value == null || viewModel.action.value == null || viewModel.transaction.value == null)
- UIHelper.flashMessage(requireContext(), getString(R.string.error_still_loading))
+ UIHelper.flashAndReportError(requireContext(), R.string.error_still_loading)
else {
retry(transaction)
}
@@ -285,7 +299,7 @@ class TransactionDetailsFragment : Fragment() {
private fun showShareExcitement(transaction: StaxTransaction) {
val isTransactionSuccessful = !transaction.isRecorded && transaction.isSuccessful
- val shareMessage = when(transaction.transaction_type) {
+ val shareMessage = when (transaction.transaction_type) {
HoverAction.AIRTIME -> getString(R.string.airtime_purchase_message, getString(R.string.share_link))
HoverAction.BALANCE -> getString(R.string.check_balance_message, getString(R.string.share_link))
HoverAction.P2P -> getString(R.string.send_money_message, getString(R.string.share_link))
@@ -300,7 +314,7 @@ class TransactionDetailsFragment : Fragment() {
private fun setBottomSheetVisibility(isVisible: Boolean, shareMessage: String) {
var updatedState = BottomSheetBehavior.STATE_HIDDEN
- if(isVisible) {
+ if (isVisible) {
updatedState = BottomSheetBehavior.STATE_EXPANDED
val animation = AnimationUtils.loadAnimation(requireContext(), R.anim.slide_down)
@@ -316,7 +330,7 @@ class TransactionDetailsFragment : Fragment() {
private fun showBonusAmount(amount: Int) = with(binding.details) {
val txn = viewModel.transaction.value
- if(amount > 0 && (txn != null && txn.isSuccessful)){
+ if (amount > 0 && (txn != null && txn.isSuccessful)) {
bonusRow.visibility = VISIBLE
bonusAmount.text = amount.toString()
} else {
diff --git a/app/src/main/java/com/hover/stax/transactionDetails/TransactionDetailsViewModel.kt b/app/src/main/java/com/hover/stax/transactionDetails/TransactionDetailsViewModel.kt
index a80628085..fceddcf3c 100644
--- a/app/src/main/java/com/hover/stax/transactionDetails/TransactionDetailsViewModel.kt
+++ b/app/src/main/java/com/hover/stax/transactionDetails/TransactionDetailsViewModel.kt
@@ -6,19 +6,18 @@ import com.hover.sdk.actions.HoverAction
import com.hover.sdk.api.Hover
import com.hover.sdk.api.Hover.getSMSMessageByUUID
import com.hover.sdk.transactions.Transaction
-import com.hover.stax.accounts.Account
-import com.hover.stax.accounts.AccountRepo
-import com.hover.stax.actions.ActionRepo
-import com.hover.stax.bonus.BonusRepo
+import com.hover.stax.domain.model.Account
+import com.hover.stax.data.local.accounts.AccountRepo
+import com.hover.stax.data.local.actions.ActionRepo
+import com.hover.stax.data.local.bonus.BonusRepo
import com.hover.stax.contacts.ContactRepo
import com.hover.stax.contacts.StaxContact
-import com.hover.stax.database.ParserRepo
+import com.hover.stax.data.local.parser.ParserRepo
import com.hover.stax.merchants.Merchant
import com.hover.stax.merchants.MerchantRepo
import com.hover.stax.transactions.StaxTransaction
import com.hover.stax.transactions.TransactionRepo
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.json.JSONArray
import timber.log.Timber
diff --git a/app/src/main/java/com/hover/stax/transactions/StaxTransaction.kt b/app/src/main/java/com/hover/stax/transactions/StaxTransaction.kt
index eda249b7f..00da4e7c1 100644
--- a/app/src/main/java/com/hover/stax/transactions/StaxTransaction.kt
+++ b/app/src/main/java/com/hover/stax/transactions/StaxTransaction.kt
@@ -15,7 +15,7 @@ import timber.log.Timber
import com.hover.stax.R
import com.hover.sdk.api.HoverParameters
import com.hover.sdk.transactions.Transaction
-import com.hover.stax.accounts.ACCOUNT_ID
+import com.hover.stax.domain.model.ACCOUNT_ID
import com.hover.stax.paybill.BUSINESS_NO
import com.hover.stax.utils.Utils
import java.util.HashMap
@@ -106,13 +106,12 @@ data class StaxTransaction(
}
private fun getCounterPartyNo(intent: Intent, contact: StaxContact?): String? {
- if (contact != null)
- return contact.accountNumber
- else if (intent.hasExtra(HoverAction.ACCOUNT_KEY))
- return intent.getStringExtra(HoverAction.ACCOUNT_KEY)
- else if (intent.hasExtra(BUSINESS_NO))
- return intent.getStringExtra(BUSINESS_NO)
- else return null
+ return when {
+ contact != null -> contact.accountNumber
+ intent.hasExtra(HoverAction.ACCOUNT_KEY) -> intent.getStringExtra(HoverAction.ACCOUNT_KEY)
+ intent.hasExtra(BUSINESS_NO) -> intent.getStringExtra(BUSINESS_NO)
+ else -> null
+ }
}
fun update(data: Intent, action: HoverAction, contact: StaxContact, context: Context) {
@@ -154,7 +153,7 @@ data class StaxTransaction(
}
}
- val isRetryable: Boolean
+ val canRetry: Boolean
get() = isRecorded || ((transaction_type == HoverAction.P2P || transaction_type == HoverAction.AIRTIME
|| transaction_type == HoverAction.BALANCE) && isFailed)
diff --git a/app/src/main/java/com/hover/stax/transactions/TransactionDao.kt b/app/src/main/java/com/hover/stax/transactions/TransactionDao.kt
index 289441b54..fe08e33e6 100644
--- a/app/src/main/java/com/hover/stax/transactions/TransactionDao.kt
+++ b/app/src/main/java/com/hover/stax/transactions/TransactionDao.kt
@@ -7,6 +7,7 @@ import com.hover.sdk.transactions.Transaction as Txn
@Dao
interface TransactionDao {
+
@Query("SELECT * FROM stax_transactions WHERE channel_id = :channelId AND transaction_type != 'balance' AND status != 'failed' AND environment != 3 ORDER BY initiated_at DESC")
fun getCompleteAndPendingTransfers(channelId: Int): LiveData>?
@@ -23,6 +24,9 @@ interface TransactionDao {
@get:Query("SELECT * FROM stax_transactions WHERE environment = 3 ORDER BY initiated_at DESC")
val bountyTransactions: LiveData>?
+ @get:Query("SELECT * FROM stax_transactions WHERE environment = 3 ORDER BY initiated_at DESC")
+ val bountyTransactionList: List
+
@get:Query("SELECT * FROM stax_transactions WHERE environment != 3 AND account_id IS NOT NULL ORDER BY initiated_at DESC")
val nonBountyTransactions: LiveData>
@@ -49,4 +53,8 @@ interface TransactionDao {
@Update
fun update(transaction: StaxTransaction?)
+
+ @Query("DELETE FROM stax_transactions WHERE account_id = :accountId")
+ fun deleteAccountTransactions(accountId: Int)
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/transactions/TransactionHistoryFragment.kt b/app/src/main/java/com/hover/stax/transactions/TransactionHistoryFragment.kt
index 4a5a7ddf0..fb7a40d9d 100644
--- a/app/src/main/java/com/hover/stax/transactions/TransactionHistoryFragment.kt
+++ b/app/src/main/java/com/hover/stax/transactions/TransactionHistoryFragment.kt
@@ -4,57 +4,83 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.hover.stax.R
import com.hover.stax.databinding.TransactionCardHistoryBinding
+import com.hover.stax.presentation.home.TopBar
+import com.hover.stax.ui.theme.StaxTheme
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.NavUtil
import com.hover.stax.utils.UIHelper
+import com.hover.stax.utils.network.NetworkMonitor
import org.koin.androidx.viewmodel.ext.android.viewModel
class TransactionHistoryFragment : Fragment(), TransactionHistoryAdapter.SelectListener {
- private var _binding: TransactionCardHistoryBinding? = null
- private val binding get() = _binding!!
-
- private val viewModel: TransactionHistoryViewModel by viewModel()
- private var transactionsAdapter: TransactionHistoryAdapter? = null
-
- override fun onCreateView(inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?): View {
- AnalyticsUtil.logAnalyticsEvent(getString(R.string.visit_screen, getString(R.string.visit_transaction_history)), requireActivity())
- _binding = TransactionCardHistoryBinding.inflate(inflater, container, false)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- initRecyclerView()
- observeTransactionActionPair()
- }
-
- private fun initRecyclerView() {
- binding.transactionsRecycler.apply {
- layoutManager = UIHelper.setMainLinearManagers(context)
- transactionsAdapter = TransactionHistoryAdapter(this@TransactionHistoryFragment)
- adapter = transactionsAdapter
- }
- }
-
- private fun observeTransactionActionPair() {
- viewModel.transactionHistory.observe(viewLifecycleOwner) {
- binding.noHistory.visibility = if (it.isNullOrEmpty()) View.VISIBLE else View.GONE
- transactionsAdapter!!.submitList(it)
- }
- }
-
- override fun viewTransactionDetail(uuid: String?) {
- uuid?.let { NavUtil.showTransactionDetailsFragment(findNavController(), it) }
- }
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
+ private var _binding: TransactionCardHistoryBinding? = null
+ private val binding get() = _binding!!
+
+ private val viewModel: TransactionHistoryViewModel by viewModel()
+ private var transactionsAdapter: TransactionHistoryAdapter? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ AnalyticsUtil.logAnalyticsEvent(getString(R.string.visit_screen, getString(R.string.visit_transaction_history)), requireActivity())
+ _binding = TransactionCardHistoryBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ initToolbar()
+
+ initRecyclerView()
+ observeTransactionActionPair()
+ }
+
+ private fun initToolbar() {
+ binding.toolbar.setContent {
+ StaxTheme { Toolbar() }
+ }
+ }
+
+ @Composable
+ private fun Toolbar() {
+ val hasNetwork by NetworkMonitor.StateLiveData.get().observeAsState(initial = false)
+ TopBar(title = R.string.nav_history, isInternetConnected = hasNetwork) {
+ findNavController().navigate(TransactionHistoryFragmentDirections.actionGlobalNavigationSettings())
+ }
+ }
+
+ private fun initRecyclerView() {
+ binding.transactionsRecycler.apply {
+ layoutManager = UIHelper.setMainLinearManagers(context)
+ transactionsAdapter = TransactionHistoryAdapter(this@TransactionHistoryFragment)
+ adapter = transactionsAdapter
+ }
+ }
+
+ private fun observeTransactionActionPair() {
+ viewModel.transactionHistory.observe(viewLifecycleOwner) {
+ binding.noHistory.visibility = if (it.isNullOrEmpty()) View.VISIBLE else View.GONE
+ transactionsAdapter!!.submitList(it)
+ }
+ }
+
+ override fun viewTransactionDetail(uuid: String?) {
+ uuid?.let { NavUtil.showTransactionDetailsFragment(findNavController(), it) }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/transactions/TransactionHistoryViewModel.kt b/app/src/main/java/com/hover/stax/transactions/TransactionHistoryViewModel.kt
index 89c632d20..bb825c3f2 100644
--- a/app/src/main/java/com/hover/stax/transactions/TransactionHistoryViewModel.kt
+++ b/app/src/main/java/com/hover/stax/transactions/TransactionHistoryViewModel.kt
@@ -2,19 +2,21 @@ package com.hover.stax.transactions
import androidx.lifecycle.*
import com.hover.sdk.actions.HoverAction
-import com.hover.stax.actions.ActionRepo
+import com.hover.stax.data.local.actions.ActionRepo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TransactionHistoryViewModel(val repo: TransactionRepo, val actionRepo: ActionRepo) : ViewModel() {
- private val allNonBountyTransaction : LiveData> = repo.allNonBountyTransactions
- var transactionHistory : MediatorLiveData> = MediatorLiveData()
+ private val allNonBountyTransaction: LiveData> = repo.allNonBountyTransactions
+ var transactionHistory: MediatorLiveData> = MediatorLiveData()
private var staxTransactions: LiveData> = MutableLiveData()
private val appReviewLiveData: LiveData
init {
transactionHistory.addSource(allNonBountyTransaction, this::getTransactionHistory)
+ staxTransactions = repo.completeAndPendingTransferTransactions!!
+ appReviewLiveData = Transformations.map(repo.transactionsForAppReview!!) { showAppReview(it) }
}
private fun getTransactionHistory(transactions: List) {
@@ -40,11 +42,6 @@ class TransactionHistoryViewModel(val repo: TransactionRepo, val actionRepo: Act
}
return if (balancesTransactions >= 4) true else transfersAndAirtime >= 2
}
-
- init {
- staxTransactions = repo.completeAndPendingTransferTransactions!!
- appReviewLiveData = Transformations.map(repo.transactionsForAppReview!!) { showAppReview(it) }
- }
}
- data class TransactionHistory(val staxTransaction: StaxTransaction, val action: HoverAction?)
\ No newline at end of file
+data class TransactionHistory(val staxTransaction: StaxTransaction, val action: HoverAction?)
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/transactions/TransactionRepo.kt b/app/src/main/java/com/hover/stax/transactions/TransactionRepo.kt
index d636ba2c7..87a159c2a 100644
--- a/app/src/main/java/com/hover/stax/transactions/TransactionRepo.kt
+++ b/app/src/main/java/com/hover/stax/transactions/TransactionRepo.kt
@@ -5,12 +5,11 @@ import android.content.Context
import android.content.Intent
import androidx.lifecycle.LiveData
import com.hover.sdk.actions.HoverAction
-import com.hover.sdk.database.HoverRoomDatabase
import com.hover.sdk.transactions.TransactionContract
import com.hover.stax.R
-import com.hover.stax.accounts.Account
import com.hover.stax.contacts.StaxContact
import com.hover.stax.database.AppDatabase
+import com.hover.stax.domain.model.Account
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.DateUtils
import kotlinx.coroutines.flow.Flow
@@ -28,9 +27,12 @@ class TransactionRepo(db: AppDatabase) {
val transactionsForAppReview: LiveData>?
get() = transactionDao.transactionsForAppReview
- val allNonBountyTransactions : LiveData>
+ val allNonBountyTransactions: LiveData>
get() = transactionDao.nonBountyTransactions
+ val bountyTransactionList: List
+ get() = transactionDao.bountyTransactionList
+
@SuppressLint("DefaultLocale")
suspend fun hasTransactionLastMonth(): Boolean {
return transactionDao.getTransactionCount(String.format("%02d", DateUtils.lastMonth().first), DateUtils.lastMonth().second.toString())!! > 0
@@ -56,6 +58,8 @@ class TransactionRepo(db: AppDatabase) {
fun getTransactionAsync(uuid: String): Flow = transactionDao.getTransactionAsync(uuid)
+ fun deleteAccountTransactions(accountId: Int) = transactionDao.deleteAccountTransactions(accountId)
+
fun insertOrUpdateTransaction(intent: Intent, action: HoverAction, contact: StaxContact, c: Context) {
AppDatabase.databaseWriteExecutor.execute {
try {
diff --git a/app/src/main/java/com/hover/stax/transfers/AbstractFormFragment.kt b/app/src/main/java/com/hover/stax/transfers/AbstractFormFragment.kt
index cd6bafabb..7672fb77f 100644
--- a/app/src/main/java/com/hover/stax/transfers/AbstractFormFragment.kt
+++ b/app/src/main/java/com/hover/stax/transfers/AbstractFormFragment.kt
@@ -17,18 +17,18 @@ import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
import com.hover.sdk.actions.HoverAction
import com.hover.stax.R
-import com.hover.stax.accounts.Account
import com.hover.stax.accounts.AccountDropdown
import com.hover.stax.accounts.AccountsViewModel
import com.hover.stax.actions.ActionSelectViewModel
-import com.hover.stax.balances.BalancesViewModel
import com.hover.stax.contacts.StaxContact
+import com.hover.stax.domain.model.Account
import com.hover.stax.hover.AbstractHoverCallerActivity
import com.hover.stax.permissions.PermissionUtils
+import com.hover.stax.presentation.home.BalancesViewModel
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.NavUtil
import com.hover.stax.utils.UIHelper
-import com.hover.stax.utils.collectLatestLifecycleFlow
+import com.hover.stax.utils.collectLifecycleFlow
import com.hover.stax.views.AbstractStatefulInput
import com.hover.stax.views.StaxCardView
import com.hover.stax.views.StaxDialog
@@ -76,7 +76,7 @@ abstract class AbstractFormFragment : Fragment() {
payWithDropdown.setObservers(accountsViewModel, viewLifecycleOwner)
abstractFormViewModel.isEditing.observe(viewLifecycleOwner, Observer(this::showEdit))
- collectLatestLifecycleFlow(balancesViewModel.balanceAction) {
+ collectLifecycleFlow(balancesViewModel.balanceAction) {
callHover(accountsViewModel.activeAccount.value, it)
}
}
@@ -104,7 +104,7 @@ abstract class AbstractFormFragment : Fragment() {
} else {
onSubmitForm()
}
- } else UIHelper.flashMessage(requireActivity(), getString(R.string.toast_pleasefix))
+ } else UIHelper.flashAndReportMessage(requireActivity(), getString(R.string.toast_pleasefix))
}
abstract fun validates(): Boolean
@@ -174,7 +174,7 @@ abstract class AbstractFormFragment : Fragment() {
private fun showError(userMsg: Int, logMsg: Int) {
log(getString(logMsg))
- UIHelper.flashMessage(requireContext(), getString(userMsg))
+ UIHelper.flashAndReportMessage(requireContext(), getString(userMsg))
}
abstract fun onContactSelected(contact: StaxContact)
@@ -195,7 +195,7 @@ abstract class AbstractFormFragment : Fragment() {
private val backPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
- Timber.e("Caught back press. isediting: %s", abstractFormViewModel.isEditing.value)
+ Timber.e("Caught back press. is editing: %s", abstractFormViewModel.isEditing.value)
if (abstractFormViewModel.isEditing.value == false)
abstractFormViewModel.setEditing(true)
else
diff --git a/app/src/main/java/com/hover/stax/transfers/AbstractFormViewModel.kt b/app/src/main/java/com/hover/stax/transfers/AbstractFormViewModel.kt
index 41aae8159..6ba1e3a45 100644
--- a/app/src/main/java/com/hover/stax/transfers/AbstractFormViewModel.kt
+++ b/app/src/main/java/com/hover/stax/transfers/AbstractFormViewModel.kt
@@ -15,7 +15,7 @@ import com.hover.stax.schedules.ScheduleRepo
import com.hover.stax.schedules.Schedule
import com.hover.stax.utils.AnalyticsUtil
-abstract class AbstractFormViewModel(application: Application, val contactRepo: ContactRepo, val scheduleRepo: ScheduleRepo) : AndroidViewModel(application) {
+abstract class AbstractFormViewModel(application: Application, val contactRepo: ContactRepo, private val scheduleRepo: ScheduleRepo) : AndroidViewModel(application) {
var recentContacts: LiveData> = MutableLiveData()
val schedule = MutableLiveData()
diff --git a/app/src/main/java/com/hover/stax/transfers/TransferFragment.kt b/app/src/main/java/com/hover/stax/transfers/TransferFragment.kt
index cce5e9d89..e14215803 100644
--- a/app/src/main/java/com/hover/stax/transfers/TransferFragment.kt
+++ b/app/src/main/java/com/hover/stax/transfers/TransferFragment.kt
@@ -10,7 +10,6 @@ import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.annotation.CallSuper
import androidx.core.content.ContextCompat.getColor
-import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.hover.sdk.actions.HoverAction
@@ -25,9 +24,8 @@ import com.hover.stax.hover.FEE_REQUEST
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.UIHelper
import com.hover.stax.utils.Utils
-import com.hover.stax.utils.collectLatestLifecycleFlow
+import com.hover.stax.utils.collectLifecycleFlow
import com.hover.stax.views.AbstractStatefulInput
-import kotlinx.coroutines.flow.collect
import org.koin.androidx.viewmodel.ext.android.getSharedViewModel
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
@@ -48,6 +46,8 @@ class TransferFragment : AbstractFormFragment(), ActionSelect.HighlightListener,
private var nonStandardVariableAdapter: NonStandardVariableAdapter? = null
private lateinit var nonStandardSummaryAdapter: NonStandardSummaryAdapter
+ private var hasBonus = false
+
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
abstractFormViewModel = getSharedViewModel()
@@ -120,7 +120,7 @@ class TransferFragment : AbstractFormFragment(), ActionSelect.HighlightListener,
override fun startObservers(root: View) {
super.startObservers(root)
- observeAccountList()
+
observeActiveAccount()
observeActions()
observeActionSelection()
@@ -153,7 +153,7 @@ class TransferFragment : AbstractFormFragment(), ActionSelect.HighlightListener,
private fun observeActions() {
accountsViewModel.channelActions.observe(viewLifecycleOwner) {
actionSelectViewModel.setActions(it)
- showBonusBanner(it.firstOrNull())
+ showBonusBanner(it)
}
actionSelectViewModel.filteredActions.observe(viewLifecycleOwner) {
@@ -169,11 +169,6 @@ class TransferFragment : AbstractFormFragment(), ActionSelect.HighlightListener,
}
}
- private fun observeAccountList() = collectLatestLifecycleFlow(accountsViewModel.accounts) {
- if (it.isEmpty())
- setDropdownTouchListener(TransferFragmentDirections.actionNavigationTransferToAccountsFragment())
- }
-
private fun observeAmount() {
transferViewModel.amount.observe(viewLifecycleOwner) {
it?.let {
@@ -212,11 +207,9 @@ class TransferFragment : AbstractFormFragment(), ActionSelect.HighlightListener,
}
private fun observeAccountsEvent() {
- lifecycleScope.launchWhenStarted {
- channelsViewModel.accountEventFlow.collect {
- val bonus = bonusViewModel.bonusList.value.bonuses.firstOrNull() ?: return@collect
- accountsViewModel.setActiveAccountFromChannel(bonus.userChannel)
- }
+ collectLifecycleFlow(channelsViewModel.accountEventFlow) {
+ val bonus = bonusViewModel.bonusList.value.bonuses.firstOrNull() ?: return@collectLifecycleFlow
+ accountsViewModel.setActiveAccountFromChannel(bonus.userChannel)
}
}
@@ -265,7 +258,7 @@ class TransferFragment : AbstractFormFragment(), ActionSelect.HighlightListener,
}
private fun getExtras(): HashMap {
- val extras = transferViewModel.wrapExtras()
+ val extras = transferViewModel.wrapExtras(hasBonus)
extras.putAll(actionSelectViewModel.wrapExtras())
return extras
}
@@ -344,7 +337,7 @@ class TransferFragment : AbstractFormFragment(), ActionSelect.HighlightListener,
}
}
- private fun showBonusBanner(activeAction: HoverAction?) {
+ private fun showBonusBanner(actions: List) {
if (args.transactionType == HoverAction.AIRTIME) {
val bonus = bonusViewModel.bonusList.value.bonuses.firstOrNull() ?: return
@@ -352,7 +345,9 @@ class TransferFragment : AbstractFormFragment(), ActionSelect.HighlightListener,
cardBonus.visibility = View.VISIBLE
learnMore.movementMethod = LinkMovementMethod.getInstance()
- if (activeAction?.channel_id == bonus.purchaseChannel) {
+ hasBonus = actions.map { it.channel_id }.contains(bonus.userChannel)
+
+ if (hasBonus) {
title.text = getString(R.string.congratulations)
message.text = getString(R.string.valid_account_bonus_msg)
cta.visibility = View.GONE
@@ -379,7 +374,7 @@ class TransferFragment : AbstractFormFragment(), ActionSelect.HighlightListener,
if (!isEditing)
binding.bonusLayout.cardBonus.visibility = View.GONE
else
- showBonusBanner(actionSelectViewModel.activeAction.value)
+ showBonusBanner(actionSelectViewModel.filteredActions.value ?: emptyList())
}
override fun onDestroyView() {
diff --git a/app/src/main/java/com/hover/stax/transfers/TransferViewModel.kt b/app/src/main/java/com/hover/stax/transfers/TransferViewModel.kt
index 85a6cbf1c..8f7a8c0f4 100644
--- a/app/src/main/java/com/hover/stax/transfers/TransferViewModel.kt
+++ b/app/src/main/java/com/hover/stax/transfers/TransferViewModel.kt
@@ -1,7 +1,6 @@
package com.hover.stax.transfers
import android.app.Application
-import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.hover.sdk.actions.HoverAction
@@ -9,16 +8,20 @@ import com.hover.stax.R
import com.hover.stax.contacts.ContactRepo
import com.hover.stax.contacts.PhoneHelper
import com.hover.stax.contacts.StaxContact
-import com.hover.stax.schedules.ScheduleRepo
import com.hover.stax.requests.Request
import com.hover.stax.requests.RequestRepo
+import com.hover.stax.schedules.ScheduleRepo
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.DateUtils
+import com.hover.stax.utils.Utils
import com.yariksoffice.lingver.Lingver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
+const val STAX_PREFIX = "stax_airtime_prefix"
+private const val KE_PREFIX = "0"
+
class TransferViewModel(application: Application, private val requestRepo: RequestRepo, contactRepo: ContactRepo, scheduleRepo: ScheduleRepo) : AbstractFormViewModel(application, contactRepo, scheduleRepo) {
val amount = MutableLiveData()
@@ -48,16 +51,18 @@ class TransferViewModel(application: Application, private val requestRepo: Reque
try {
val formattedPhone = PhoneHelper.getNationalSignificantNumber(
it.requester_number!!,
- countryAlpha2 ?: Lingver.getInstance().getLocale().country)
+ countryAlpha2 ?: Lingver.getInstance().getLocale().country
+ )
val sc = contactRepo.getContactByPhone(formattedPhone)
- contact.postValue( sc ?: StaxContact(r.requester_number))
+ contact.postValue(sc ?: StaxContact(r.requester_number))
isLoading.postValue(false)
} catch (e: NumberFormatException) {
AnalyticsUtil.logErrorAndReportToFirebase(
- TransferViewModel::class.java.simpleName, e.message!!, e)
+ TransferViewModel::class.java.simpleName, e.message!!, e
+ )
}
}
- }
+ }
private fun setNote(n: String?) = note.postValue(n)
@@ -73,18 +78,21 @@ class TransferViewModel(application: Application, private val requestRepo: Reque
}
}
- fun wrapExtras(): HashMap {
+ fun wrapExtras(isBonusAirtime: Boolean = false): HashMap {
val extras: HashMap = hashMapOf()
if (amount.value != null) extras[HoverAction.AMOUNT_KEY] = amount.value!!
if (contact.value != null && contact.value?.accountNumber != null) {
extras[StaxContact.ID_KEY] = contact.value!!.id
extras[HoverAction.PHONE_KEY] = contact.value!!.accountNumber
- extras[HoverAction.ACCOUNT_KEY] = contact.value!!.accountNumber
+ extras[HoverAction.ACCOUNT_KEY] = if (isBonusAirtime) staxPrefix.plus(KE_PREFIX).plus(PhoneHelper.getNationalSignificantNumber(contact.value!!.accountNumber, "KE")) else
+ contact.value!!.accountNumber
}
if (note.value != null) extras[HoverAction.NOTE_KEY] = note.value!!
return extras
}
+ private val staxPrefix get() = Utils.getString(STAX_PREFIX, getApplication())
+
fun load(encryptedString: String) = viewModelScope.launch {
isLoading.postValue(true)
val r: Request? = requestRepo.decrypt(encryptedString, getApplication())
diff --git a/app/src/main/java/com/hover/stax/ui/theme/Color.kt b/app/src/main/java/com/hover/stax/ui/theme/Color.kt
index 7b00f4a9f..6eb724afc 100644
--- a/app/src/main/java/com/hover/stax/ui/theme/Color.kt
+++ b/app/src/main/java/com/hover/stax/ui/theme/Color.kt
@@ -2,11 +2,14 @@ package com.hover.stax.ui.theme
import androidx.compose.ui.graphics.Color
-val ColorPrimary = Color(0xFF292E35)
-val ColorPrimaryDark = Color(0xFF1E232A)
-val BrightBlue = Color(0xFF39CBFC)
-val BrightBluePressed = Color(0xFF2AAFDC)
+val ColorPrimary = Color(0xFF292E34)
+val ColorPrimaryDark = Color(0xFF1E2329)
+val BrightBlue = Color(0xFF0091E3)
+val ColorSurface = Color(0x292E34)
+val BrightBluePressed = Color(0xFF01659E)
val OffWhite = Color(0xFFF1F1F4)
-val CardViewColor = Color(0xFF292E35)
-val StaxStateRed = Color(0xFFFF0028)
+val CardViewColor = Color(0xFF292E34)
+val StaxStateRed = Color(0xFFF32345)
val DarkGray = Color(0xFF777777)
+val mainBackground = Color(0xFF1E2329)
+
diff --git a/app/src/main/java/com/hover/stax/ui/theme/Theme.kt b/app/src/main/java/com/hover/stax/ui/theme/Theme.kt
index f3ca24b80..c09f97667 100644
--- a/app/src/main/java/com/hover/stax/ui/theme/Theme.kt
+++ b/app/src/main/java/com/hover/stax/ui/theme/Theme.kt
@@ -14,7 +14,7 @@ private val DarkColorPalette = darkColors(
onSecondary= CardViewColor,
surface = CardViewColor,
onSurface = OffWhite,
- background = ColorPrimaryDark,
+ background = mainBackground,
onBackground = OffWhite,
error = StaxStateRed,
onError = OffWhite
@@ -28,7 +28,7 @@ private val LightColorPalette = lightColors(
onSecondary= CardViewColor,
surface = CardViewColor,
onSurface = OffWhite,
- background = ColorPrimaryDark,
+ background = mainBackground,
onBackground = OffWhite,
error = StaxStateRed,
onError = OffWhite
diff --git a/app/src/main/java/com/hover/stax/ui/theme/Type.kt b/app/src/main/java/com/hover/stax/ui/theme/Type.kt
index 7e9c51ba0..a6e32dc0e 100644
--- a/app/src/main/java/com/hover/stax/ui/theme/Type.kt
+++ b/app/src/main/java/com/hover/stax/ui/theme/Type.kt
@@ -25,6 +25,11 @@ val Typography = Typography(
fontWeight = FontWeight.Normal,
fontSize = 15.sp
),
+ subtitle2 = TextStyle(
+ fontFamily = Brutalista,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp
+ ),
h1 = TextStyle(
fontFamily = Brutalista,
fontWeight = FontWeight.Medium,
@@ -40,9 +45,18 @@ val Typography = Typography(
fontWeight = FontWeight.Medium,
fontSize = 19.sp
),
+ h4 = TextStyle(
+ fontFamily = Brutalista,
+ fontWeight = FontWeight.Medium,
+ fontSize = 18.sp
+ ),
button = TextStyle(
fontFamily = Brutalista,
fontWeight = FontWeight.Medium,
fontSize = 17.sp
+ ),
+ caption = TextStyle(
+ fontFamily = Brutalista,
+ fontWeight = FontWeight.Normal
)
)
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/ussd_library/LibraryFragment.kt b/app/src/main/java/com/hover/stax/ussd_library/LibraryFragment.kt
index 4a76da429..5e0a0daaa 100644
--- a/app/src/main/java/com/hover/stax/ussd_library/LibraryFragment.kt
+++ b/app/src/main/java/com/hover/stax/ussd_library/LibraryFragment.kt
@@ -8,15 +8,23 @@ import android.view.LayoutInflater
import android.view.View
import android.view.View.VISIBLE
import android.view.ViewGroup
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
import com.hover.stax.R
import com.hover.stax.addChannels.ChannelsViewModel
import com.hover.stax.channels.Channel
import com.hover.stax.countries.CountryAdapter
import com.hover.stax.databinding.FragmentLibraryBinding
+import com.hover.stax.presentation.home.TopBar
+import com.hover.stax.transactions.TransactionHistoryFragmentDirections
+import com.hover.stax.ui.theme.StaxTheme
import com.hover.stax.utils.AnalyticsUtil
import com.hover.stax.utils.UIHelper
+import com.hover.stax.utils.network.NetworkMonitor
import com.hover.stax.views.RequestServiceDialog
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
@@ -39,6 +47,8 @@ class LibraryFragment : Fragment(), CountryAdapter.SelectListener, LibraryChanne
super.onViewCreated(view, savedInstanceState)
AnalyticsUtil.logAnalyticsEvent(getString(R.string.visit_screen, LibraryFragment::class.java.simpleName), requireActivity())
+ initToolbar()
+
binding.countryCard.showProgressIndicator()
binding.countryDropdown.setListener(this)
@@ -52,6 +62,20 @@ class LibraryFragment : Fragment(), CountryAdapter.SelectListener, LibraryChanne
setObservers()
}
+ private fun initToolbar() {
+ binding.toolbar.setContent {
+ StaxTheme { Toolbar() }
+ }
+ }
+
+ @Composable
+ private fun Toolbar() {
+ val hasNetwork by NetworkMonitor.StateLiveData.get().observeAsState(initial = false)
+ TopBar(title = R.string.library_cardhead, isInternetConnected = hasNetwork) {
+ findNavController().navigate(TransactionHistoryFragmentDirections.actionGlobalNavigationSettings())
+ }
+ }
+
private fun setObservers() {
with(viewModel) {
channelCountryList.observe(viewLifecycleOwner) { it?.let { binding.countryDropdown.updateChoices(it, countryChoice.value) } }
@@ -94,7 +118,7 @@ class LibraryFragment : Fragment(), CountryAdapter.SelectListener, LibraryChanne
}
private fun showEmptyState() {
- val content = resources.getString(R.string.no_accounts_found_desc, viewModel.filterQuery.value!!)
+ val content = resources.getString(R.string.no_accounts_found_desc, viewModel.filterQuery.value ?: getString(R.string.empty_channel_placeholder))
binding.emptyState.noAccountFoundDesc.apply {
text = HtmlCompat.fromHtml(content, HtmlCompat.FROM_HTML_MODE_LEGACY)
movementMethod = LinkMovementMethod.getInstance()
diff --git a/app/src/main/java/com/hover/stax/utils/UIHelper.kt b/app/src/main/java/com/hover/stax/utils/UIHelper.kt
index 7103f6226..d85bfc12c 100644
--- a/app/src/main/java/com/hover/stax/utils/UIHelper.kt
+++ b/app/src/main/java/com/hover/stax/utils/UIHelper.kt
@@ -24,10 +24,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.request.target.CustomTarget
import com.google.android.material.snackbar.Snackbar
import com.hover.stax.R
-import com.hover.stax.accounts.Account
+import com.hover.stax.domain.model.Account
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.launch
import timber.log.Timber
@@ -35,22 +34,35 @@ object UIHelper {
private const val INITIAL_ITEMS_FETCH = 30
- fun flashMessage(context: Context, view: View?, message: String?) {
- if (view == null) flashMessage(context, message) else showSnack(view, message)
+ fun showAndReportSnackBar(context: Context, view: View?, message: String) {
+ if (view == null) flashAndReportMessage(context, message) else showSnack(view, message)
}
private fun showSnack(view: View, message: String?) {
val s = Snackbar.make(view, message!!, Snackbar.LENGTH_LONG)
s.anchorView = view
s.show()
+ AnalyticsUtil.logAnalyticsEvent(message, view.context)
}
- fun flashMessage(context: Context, message: String?) {
+ fun flashAndReportMessage(context: Context, messageRes: Int) {
+ flashAndReportMessage(context, context.getString(messageRes))
+ }
+
+ fun flashAndReportMessage(context: Context, message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
+ AnalyticsUtil.logAnalyticsEvent(message, context)
}
- fun flashMessage(context: Context, messageRes: Int) {
- Toast.makeText(context, context.getString(messageRes), Toast.LENGTH_SHORT).show()
+ fun flashAndReportError(context: Context, messageRes: Int) {
+ val message = context.getString(messageRes)
+ flashAndReportError(context, message)
+ }
+
+ fun flashAndReportError(context: Context, message: String) {
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
+ AnalyticsUtil.logAnalyticsEvent(message, context)
+ AnalyticsUtil.logErrorAndReportToFirebase(context.getString(R.string.toast_err_tag), message, null)
}
fun setMainLinearManagers(context: Context?): LinearLayoutManager {
@@ -121,16 +133,11 @@ object UIHelper {
}
-fun Fragment.collectLatestLifecycleFlow(flow: Flow, collect: suspend (T) -> Unit) {
+fun Fragment.collectLifecycleFlow(flow: Flow, collector: FlowCollector) {
viewLifecycleOwner.lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- flow.collect(collect)
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ flow.collect(collector)
}
}
}
-//fun Fragment.collectLatestSharedFlow(flow: SharedFlow, collect: suspend (T) -> Unit) {
-// lifecycleScope.launchWhenStarted {
-// flow.collect { collect }
-// }
-//}
diff --git a/app/src/main/java/com/hover/stax/utils/Utils.kt b/app/src/main/java/com/hover/stax/utils/Utils.kt
index 464e5c059..dc56ab219 100644
--- a/app/src/main/java/com/hover/stax/utils/Utils.kt
+++ b/app/src/main/java/com/hover/stax/utils/Utils.kt
@@ -137,7 +137,7 @@ object Utils {
return getBuildConfigValue(c, "DEBUG") as Boolean
}
- private fun getBuildConfigValue(context: Context, fieldName: String?): Any? {
+ private fun getBuildConfigValue(context: Context, fieldName: String): Any? {
try {
val clazz = Class.forName(getPackage(context) + ".BuildConfig")
val field = clazz.getField(fieldName)
@@ -154,7 +154,7 @@ object Utils {
val clip = ClipData.newPlainText("Stax content", content)
if (clipboard != null) {
clipboard.setPrimaryClip(clip)
- UIHelper.flashMessage(c, c.getString(R.string.copied))
+ UIHelper.flashAndReportMessage(c, c.getString(R.string.copied))
return true
}
return false
@@ -199,7 +199,7 @@ object Utils {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Timber.e("Activity not found")
- UIHelper.flashMessage(context, context.getString(R.string.email_client_not_found))
+ UIHelper.flashAndReportMessage(context, context.getString(R.string.email_client_not_found))
}
}
@@ -241,6 +241,6 @@ object Utils {
if (PermissionUtils.has(arrayOf(Manifest.permission.CALL_PHONE), c))
c.startActivity(dialIntent)
else
- UIHelper.flashMessage(c, c.getString(R.string.enable_call_permission))
+ UIHelper.flashAndReportMessage(c, c.getString(R.string.enable_call_permission))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/stax/views/StaxCardView.kt b/app/src/main/java/com/hover/stax/views/StaxCardView.kt
index 8175604d3..063b5df06 100644
--- a/app/src/main/java/com/hover/stax/views/StaxCardView.kt
+++ b/app/src/main/java/com/hover/stax/views/StaxCardView.kt
@@ -34,16 +34,15 @@ open class StaxCardView(context: Context, attrs: AttributeSet) : FrameLayout(con
useContextBackPress = a.getBoolean(R.styleable.StaxCardView_defaultBackPress, true)
backDrawable = a.getResourceId(R.styleable.StaxCardView_backRes, 0)
bgColor = a.getColor(R.styleable.StaxCardView_staxCardColor, ContextCompat.getColor(context, R.color.colorPrimary))
- isFlatView = a.getBoolean(R.styleable.StaxCardView_isFlatView, false)
+ isFlatView = a.getBoolean(R.styleable.StaxCardView_isFlatView, true)
} finally {
a.recycle()
}
}
- fun makeFlatView() {
+ private fun makeFlatView() {
val zero = 0
- binding.cardViewHeader.cardElevation = zero.toFloat()
- binding.cardViewHeader.radius = zero.toFloat()
+ binding.cardView.cardElevation = zero.toFloat()
removeCardMargin()
}
@@ -115,7 +114,7 @@ open class StaxCardView(context: Context, attrs: AttributeSet) : FrameLayout(con
private fun removeCardMargin() {
val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
params.setMargins(0, 0, 0, 0)
- binding.cardViewHeader.layoutParams = params
+ binding.cardView.layoutParams = params
}
override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
@@ -135,7 +134,7 @@ open class StaxCardView(context: Context, attrs: AttributeSet) : FrameLayout(con
}
fun updateState(icon: Int, backgroundColor: Int, title: Int) {
- binding.cardViewHeader.apply {
+ binding.cardView.apply {
setBackButtonVisibility(View.VISIBLE)
setIcon(icon)
setTitle(title)
diff --git a/app/src/main/java/com/hover/stax/views/StaxDropdownLayout.kt b/app/src/main/java/com/hover/stax/views/StaxDropdownLayout.kt
index b6ede5ad7..c529efd39 100644
--- a/app/src/main/java/com/hover/stax/views/StaxDropdownLayout.kt
+++ b/app/src/main/java/com/hover/stax/views/StaxDropdownLayout.kt
@@ -46,7 +46,7 @@ open class StaxDropdownLayout(context: Context, attrs: AttributeSet): AbstractSt
if (helperText != null) binding.inputLayout.hint = helperText.toString()
}
- override fun initView() {
+ final override fun initView() {
super.initView()
autoCompleteTextView = binding.autoCompleteView
}
diff --git a/app/src/main/java/com/hover/stax/views/StaxTextInput.kt b/app/src/main/java/com/hover/stax/views/StaxTextInput.kt
index 99ac94b70..c1ce779f3 100644
--- a/app/src/main/java/com/hover/stax/views/StaxTextInput.kt
+++ b/app/src/main/java/com/hover/stax/views/StaxTextInput.kt
@@ -54,7 +54,7 @@ class StaxTextInput(context: Context, attrs: AttributeSet) : AbstractStatefulInp
if (inputType > 0) binding?.inputEditText?.inputType = inputType
}
- fun setMutlipartText(text: String?, subtext: String?) {
+ fun setMultipartText(text: String?, subtext: String?) {
if (text.isNullOrEmpty())
setText(subtext)
else if (subtext.isNullOrEmpty())
@@ -88,18 +88,10 @@ class StaxTextInput(context: Context, attrs: AttributeSet) : AbstractStatefulInp
}
fun addTextChangedListener(listener: TextWatcher) {
-// textWatcher = object : TextWatcher {
-// override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { listener.beforeTextChanged(charSequence, i, i1, i2)}
-// override fun afterTextChanged(editable: Editable) { listener.afterTextChanged(editable)}
-// override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
-// currentText = charSequence.toString()
-// Timber.e("watcher for $hint got an update: %s", currentText)
-// listener.onTextChanged(charSequence, i, i1, i2)
-// }
-// }
editText?.addTextChangedListener(listener)
}
+
@SuppressLint("ClickableViewAccessibility")
override fun setOnClickListener(listener: OnClickListener?) {
editText?.setOnTouchListener { _, event ->
diff --git a/app/src/main/java/com/hover/stax/views/staxcardstack/StaxCardStackAdapter.java b/app/src/main/java/com/hover/stax/views/staxcardstack/StaxCardStackAdapter.java
deleted file mode 100755
index 4c9f9678e..000000000
--- a/app/src/main/java/com/hover/stax/views/staxcardstack/StaxCardStackAdapter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.hover.stax.views.staxcardstack;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public abstract class StaxCardStackAdapter extends StaxCardStackView.Adapter {
-
- private final Context mContext;
- private final LayoutInflater mInflater;
- private final List mData;
-
- public StaxCardStackAdapter(Context context) {
- this.mContext = context;
- this.mInflater = LayoutInflater.from(context);
- this.mData = new ArrayList<>();
- }
-
- public void updateData(List data) {
- this.setData(data);
- this.notifyDataSetChanged();
- }
-
- public void setData(List data) {
- this.mData.clear();
- if (data != null) {
- this.mData.addAll(data);
- }
- }
-
- public LayoutInflater getLayoutInflater() {
- return this.mInflater;
- }
-
- public Context getContext() {
- return this.mContext;
- }
-
- @Override
- public void onBindViewHolder(StaxCardStackView.ViewHolder holder, int position) {
- T data = this.getItem(position);
- this.bindView(data, position, holder);
- }
-
- public abstract void bindView(T data, int position, StaxCardStackView.ViewHolder holder);
-
- @Override
- public int getItemCount() {
- return mData.size();
- }
-
- public T getItem(int position) {
- return this.mData.get(position);
- }
-
-}
diff --git a/app/src/main/java/com/hover/stax/views/staxcardstack/StaxCardStackView.java b/app/src/main/java/com/hover/stax/views/staxcardstack/StaxCardStackView.java
deleted file mode 100755
index 8d03b7e82..000000000
--- a/app/src/main/java/com/hover/stax/views/staxcardstack/StaxCardStackView.java
+++ /dev/null
@@ -1,266 +0,0 @@
-package com.hover.stax.views.staxcardstack;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.Observable;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.hover.stax.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class StaxCardStackView extends ViewGroup {
- public static final int INVALID_TYPE = -1;
- static final int DEFAULT_SELECT_POSITION = -1;
- private static final String TAG = "CardStackView";
-
- private final ViewDataObserver mObserver = new ViewDataObserver();
- private int mTotalLength;
- private int mOverlapGaps;
- private StaxCardStackAdapter mStaxCardStackAdapter;
- private int mShowHeight;
- private List mViewHolders;
-
- public StaxCardStackView(Context context) {
- this(context, null);
- }
-
- public StaxCardStackView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public StaxCardStackView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init(context, attrs, defStyleAttr, 0);
- }
-
- private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CardStackView, defStyleAttr, defStyleRes);
- setOverlapGaps(array.getDimensionPixelSize(R.styleable.CardStackView_stackOverlapGaps, dp2px(20)));
- array.recycle();
-
- mViewHolders = new ArrayList<>();
- }
-
- private int dp2px(int value) {
- final float scale = getContext().getResources().getDisplayMetrics().density;
- return (int) (value * scale + 0.5f);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- checkContentHeightByParent();
- measureChild(widthMeasureSpec, heightMeasureSpec);
- }
-
- private void checkContentHeightByParent() {
- View parentView = (View) getParent();
- mShowHeight = parentView.getMeasuredHeight() - parentView.getPaddingTop() - parentView.getPaddingBottom();
- }
-
- private void measureChild(int widthMeasureSpec, int heightMeasureSpec) {
- int maxWidth = 0;
- mTotalLength = 0;
- mTotalLength += getPaddingTop() + getPaddingBottom();
- for (int i = 0; i < getChildCount(); i++) {
- final View child = getChildAt(i);
- measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
- final int totalLength = mTotalLength;
- final LayoutParams lp =
- (LayoutParams) child.getLayoutParams();
- if (lp.mHeaderHeight == -1) lp.mHeaderHeight = child.getMeasuredHeight();
- final int childHeight = lp.mHeaderHeight;
- mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
- lp.bottomMargin);
- mTotalLength -= mOverlapGaps * 2;
- final int margin = lp.leftMargin + lp.rightMargin;
- final int measuredWidth = child.getMeasuredWidth() + margin;
- maxWidth = Math.max(maxWidth, measuredWidth);
- }
-
- mTotalLength += mOverlapGaps * 2;
- int heightSize = mTotalLength;
- heightSize = Math.max(heightSize, mShowHeight);
- int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
- setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
- heightSizeAndState);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- layoutChild();
- }
-
- private void layoutChild() {
- int childTop = getPaddingTop();
- int childLeft = getPaddingLeft();
-
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- final int childWidth = child.getMeasuredWidth();
- int childHeight = child.getMeasuredHeight();
-
- final LayoutParams lp =
- (LayoutParams) child.getLayoutParams();
- childTop += lp.topMargin;
- if (i != 0) {
- childTop -= mOverlapGaps * 2;
- child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
- } else {
- child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
- }
- childTop += lp.mHeaderHeight;
- }
- }
-
-
- public void setAdapter(StaxCardStackAdapter staxCardStackAdapter) {
- mStaxCardStackAdapter = staxCardStackAdapter;
- mStaxCardStackAdapter.registerObserver(mObserver);
- refreshView();
- }
-
- private void refreshView() {
- removeAllViews();
- mViewHolders.clear();
- for (int i = 0; i < mStaxCardStackAdapter.getItemCount(); i++) {
- ViewHolder holder = getViewHolder(i);
- holder.position = i;
- addView(holder.itemView);
- mStaxCardStackAdapter.bindViewHolder(holder, i);
- }
- requestLayout();
- }
-
- ViewHolder getViewHolder(int i) {
- if (i == DEFAULT_SELECT_POSITION) return null;
- ViewHolder viewHolder;
- if (mViewHolders.size() <= i || mViewHolders.get(i).mItemViewType != mStaxCardStackAdapter.getItemViewType(i)) {
- viewHolder = mStaxCardStackAdapter.createView(this, mStaxCardStackAdapter.getItemViewType(i));
- mViewHolders.add(viewHolder);
- } else {
- viewHolder = mViewHolders.get(i);
- }
- return viewHolder;
- }
-
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams;
- }
-
- public void setOverlapGaps(int overlapGaps) {
- mOverlapGaps = overlapGaps;
- }
-
- public static class LayoutParams extends MarginLayoutParams {
-
- public int mHeaderHeight;
-
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
-
- TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.CardStackView);
- mHeaderHeight = array.getDimensionPixelSize(R.styleable.CardStackView_stackHeaderHeight, -1);
- }
-
- public LayoutParams(int width, int height) {
- super(width, height);
- }
-
- public LayoutParams(ViewGroup.LayoutParams source) {
- super(source);
- }
- }
-
- public static abstract class Adapter {
- private final AdapterDataObservable mObservable = new AdapterDataObservable();
-
- VH createView(ViewGroup parent, int viewType) {
- VH holder = onCreateView(parent, viewType);
- holder.mItemViewType = viewType;
- return holder;
- }
-
- protected abstract VH onCreateView(ViewGroup parent, int viewType);
-
- public void bindViewHolder(VH holder, int position) {
- onBindViewHolder(holder, position);
- }
-
- protected abstract void onBindViewHolder(VH holder, int position);
-
- public abstract int getItemCount();
-
- public int getItemViewType(int position) {
- return 0;
- }
-
- public final void notifyDataSetChanged() {
- mObservable.notifyChanged();
- }
-
- public void registerObserver(AdapterDataObserver observer) {
- mObservable.registerObserver(observer);
- }
- }
-
- public static abstract class ViewHolder {
-
- public View itemView;
- int mItemViewType = INVALID_TYPE;
- int position;
-
- public ViewHolder(View view) {
- itemView = view;
- }
-
- public Context getContext() {
- return itemView.getContext();
- }
- }
-
- public static class AdapterDataObservable extends Observable {
- public boolean hasObservers() {
- return !mObservers.isEmpty();
- }
-
- public void notifyChanged() {
- for (int i = mObservers.size() - 1; i >= 0; i--) {
- mObservers.get(i).onChanged();
- }
- }
- }
-
- public static abstract class AdapterDataObserver {
- public void onChanged() {
- }
- }
-
- private class ViewDataObserver extends AdapterDataObserver {
- @Override
- public void onChanged() {
- refreshView();
- }
- }
-
-}
diff --git a/app/src/main/res/drawable/airtime_illustration.png b/app/src/main/res/drawable/airtime_illustration.png
deleted file mode 100644
index 5839d788b..000000000
Binary files a/app/src/main/res/drawable/airtime_illustration.png and /dev/null differ
diff --git a/app/src/main/res/drawable/button_bg_grey.xml b/app/src/main/res/drawable/button_bg_grey.xml
index 46deb499b..64cc2d951 100644
--- a/app/src/main/res/drawable/button_bg_grey.xml
+++ b/app/src/main/res/drawable/button_bg_grey.xml
@@ -9,7 +9,7 @@
-
-
+
diff --git a/app/src/main/res/drawable/ic_add_white_16.xml b/app/src/main/res/drawable/ic_add_white_16.xml
new file mode 100644
index 000000000..70046c48f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_white_16.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_send_money.xml b/app/src/main/res/drawable/ic_send_money.xml
new file mode 100644
index 000000000..238cf89e8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_send_money.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_shopping_cart.xml b/app/src/main/res/drawable/ic_shopping_cart.xml
new file mode 100644
index 000000000..aeda9020e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shopping_cart.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_tip_of_day.xml b/app/src/main/res/drawable/ic_tip_of_day.xml
new file mode 100644
index 000000000..adc746293
--- /dev/null
+++ b/app/src/main/res/drawable/ic_tip_of_day.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/img_placeholder.xml b/app/src/main/res/drawable/img_placeholder.xml
new file mode 100644
index 000000000..6aeeab6ac
--- /dev/null
+++ b/app/src/main/res/drawable/img_placeholder.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/offline_illustration.png b/app/src/main/res/drawable/offline_illustration.png
deleted file mode 100644
index cc9976c38..000000000
Binary files a/app/src/main/res/drawable/offline_illustration.png and /dev/null differ
diff --git a/app/src/main/res/drawable/request_illustration.png b/app/src/main/res/drawable/request_illustration.png
deleted file mode 100644
index 0253efc69..000000000
Binary files a/app/src/main/res/drawable/request_illustration.png and /dev/null differ
diff --git a/app/src/main/res/drawable/send_illustration.png b/app/src/main/res/drawable/send_illustration.png
deleted file mode 100644
index 197544007..000000000
Binary files a/app/src/main/res/drawable/send_illustration.png and /dev/null differ
diff --git a/app/src/main/res/drawable/splash_logo.xml b/app/src/main/res/drawable/splash_logo.xml
deleted file mode 100644
index 3518aab66..000000000
--- a/app/src/main/res/drawable/splash_logo.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- -
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/stax_logo.xml b/app/src/main/res/drawable/stax_logo.xml
new file mode 100644
index 000000000..2ccb848fe
--- /dev/null
+++ b/app/src/main/res/drawable/stax_logo.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/stax_slide_2.jpg b/app/src/main/res/drawable/stax_slide_2.jpg
deleted file mode 100644
index 075f374d9..000000000
Binary files a/app/src/main/res/drawable/stax_slide_2.jpg and /dev/null differ
diff --git a/app/src/main/res/drawable/tips_fancy_icon.png b/app/src/main/res/drawable/tips_fancy_icon.png
new file mode 100644
index 000000000..b6c4dc5c0
Binary files /dev/null and b/app/src/main/res/drawable/tips_fancy_icon.png differ
diff --git a/app/src/main/res/layout/account_card_manage.xml b/app/src/main/res/layout/account_card_manage.xml
index 16bf933ed..d872e0baf 100644
--- a/app/src/main/res/layout/account_card_manage.xml
+++ b/app/src/main/res/layout/account_card_manage.xml
@@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/margin_16"
app:title="@string/manage_account">
-
diff --git a/app/src/main/res/layout/balance_fragment_container.xml b/app/src/main/res/layout/balance_fragment_container.xml
new file mode 100644
index 000000000..0f12ed4a8
--- /dev/null
+++ b/app/src/main/res/layout/balance_fragment_container.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/balance_item.xml b/app/src/main/res/layout/balance_item.xml
index 862d61d87..141035a6b 100644
--- a/app/src/main/res/layout/balance_item.xml
+++ b/app/src/main/res/layout/balance_item.xml
@@ -6,11 +6,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_13"
- android:paddingStart="@dimen/margin_13"
- android:paddingEnd="0dp"
- app:cardBackgroundColor="@color/cardViewColor"
- app:cardCornerRadius="@dimen/radius"
- app:cardElevation="8dp"
+ app:cardBackgroundColor="@color/colorBackground"
app:cardPreventCornerOverlap="false">
+ app:cardCornerRadius="@dimen/radius">
+ app:cardElevation="0dp">
@@ -30,11 +30,11 @@
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- app:fontFamily="@font/brutalista_medium"
- android:gravity="start"
android:layout_marginBottom="@dimen/margin_8"
+ android:gravity="start"
android:textColor="@color/banner_text"
android:textSize="20sp"
+ app:fontFamily="@font/brutalista_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="This is a test title" />
@@ -51,29 +51,20 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
tools:text="This is test content" />
-
-
-
-
+ app:fontFamily="@font/brutalista_medium" />
+
+
diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml
index 565fa4217..702701c9f 100644
--- a/app/src/main/res/layout/fragment_account.xml
+++ b/app/src/main/res/layout/fragment_account.xml
@@ -7,6 +7,7 @@
@@ -65,12 +66,12 @@
+
+
diff --git a/app/src/main/res/layout/fragment_add_channels.xml b/app/src/main/res/layout/fragment_add_channels.xml
index 0b95b5113..ad1d786a0 100644
--- a/app/src/main/res/layout/fragment_add_channels.xml
+++ b/app/src/main/res/layout/fragment_add_channels.xml
@@ -8,32 +8,12 @@
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
+ android:paddingHorizontal="@dimen/margin_5"
android:layout_marginBottom="@dimen/margin_13"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
-
-
-
-
-
-
-
-
-