Refactor- Final Cleanup (#2694)

Jira Tasks - [MM-82](https://mifosforge.jira.com/browse/MM-82)
This commit is contained in:
Sk Niyaj Ali 2024-09-04 19:19:33 +05:30 committed by GitHub
parent 25389dd8a9
commit 9c0a9b684e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
579 changed files with 52562 additions and 15680 deletions

View File

@ -7,28 +7,180 @@ on:
- '!development'
- '!master'
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
jobs:
build:
name: Build APK
setup:
runs-on: ubuntu-latest
steps:
- name: Checking out repository
uses: actions/checkout@v4
# Set up JDK
- name: Set Up JDK 1.8
uses: actions/setup-java@v1
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: 11
distribution: 'zulu'
java-version: 17
- uses: gradle/actions/setup-gradle@v4
# Install NDK
- name: Install NDK
run: echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;20.0.5594570" --sdk_root=${ANDROID_SDK_ROOT}
- name: Cache Gradle and build outputs
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
build
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
# Update Gradle Permission
- name: Change gradlew Permission
run: chmod +x gradlew
checks:
needs: setup
runs-on: ubuntu-latest
strategy:
matrix:
check: [ build_logic, spotless, detekt ]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Run ${{ matrix.check }}
id: run_check
run: |
if [ "${{ matrix.check }}" = "build_logic" ]; then
./gradlew check -p build-logic
elif [ "${{ matrix.check }}" = "spotless" ]; then
./gradlew spotlessCheck --no-configuration-cache --no-daemon
elif [ "${{ matrix.check }}" = "detekt" ]; then
./gradlew detekt
fi
# Build App
- name: Build with Gradle
run: ./gradlew assemble
- name: Upload Detekt Reports
if: ${{ matrix.check == 'detekt' && steps.run_check.outcome == 'success' }}
uses: actions/upload-artifact@v4
with:
name: detekt-reports
path: |
**/build/reports/detekt/detekt.md
dependency_guard:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Check Dependency Guard
id: dependencyguard_verify
continue-on-error: true
run: ./gradlew dependencyGuard
- name: Prevent updating Dependency Guard baselines if this is a fork
id: checkfork_dependencyguard
if: steps.dependencyguard_verify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::Dependency Guard failed, please update baselines with: ./gradlew dependencyGuardBaseline" && exit 1
# Runs if previous job failed
- name: Generate new Dependency Guard baselines if verification failed and it's a PR
id: dependencyguard_baseline
if: steps.dependencyguard_verify.outcome == 'failure' && github.event_name == 'pull_request'
run: |
./gradlew dependencyGuardBaseline
- name: Push new Dependency Guard baselines if available
uses: stefanzweifel/git-auto-commit-action@v5
if: steps.dependencyguard_baseline.outcome == 'success'
with:
file_pattern: '**/dependencies/*.txt'
disable_globbing: true
commit_message: "🤖 Updates baselines for Dependency Guard"
tests_and_lint:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Run tests
run: |
./gradlew testDebug :lint:test :androidApp:lintRelease :lint:lint
- name: Upload reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-and-lint-reports
path: |
**/build/reports/lint-results-*.html
**/build/test-results/test*UnitTest/**.xml
# Add `createProdDebugUnitTestCoverageReport` if we ever add JVM tests for prod
- name: Generate coverage reports for Debug variants (only API 30)
run: ./gradlew createDebugCombinedCoverageReport
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports-${{ matrix.api-level }}
path: '**/build/reports/androidTests'
- name: Display local test coverage (only API 30)
id: jacoco
uses: madrapps/jacoco-report@v1.6.1
with:
title: Combined test coverage report
min-coverage-overall: 40
min-coverage-changed-files: 60
paths: |
${{ github.workspace }}/**/build/reports/jacoco/**/*Report.xml
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload local coverage reports (XML + HTML) (only API 30)
uses: actions/upload-artifact@v4
with:
name: coverage-reports
if-no-files-found: error
compression-level: 1
overwrite: false
path: '**/build/reports/jacoco/'
build:
needs: [ checks, dependency_guard, tests_and_lint ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Build APKs
run: ./gradlew :androidApp:assembleDebug
- name: Check badging
run: ./gradlew :androidApp:checkReleaseBadging
- name: Upload APKs
uses: actions/upload-artifact@v4
with:
name: APKs
path: '**/build/outputs/apk/**/*.apk'

View File

@ -7,22 +7,24 @@ on:
- 'development'
- 'master'
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
jobs:
build:
name: Build APK
setup:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v4
- name: Cache Gradle and build outputs
uses: actions/cache@v4
@ -34,7 +36,151 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Build With Gradle
run: ./gradlew assembleDebug
checks:
needs: setup
runs-on: ubuntu-latest
strategy:
matrix:
check: [ build_logic, spotless, detekt ]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Run ${{ matrix.check }}
id: run_check
run: |
if [ "${{ matrix.check }}" = "build_logic" ]; then
./gradlew check -p build-logic
elif [ "${{ matrix.check }}" = "spotless" ]; then
./gradlew spotlessCheck --no-configuration-cache --no-daemon
elif [ "${{ matrix.check }}" = "detekt" ]; then
./gradlew detekt
fi
- name: Upload Detekt Reports
if: ${{ matrix.check == 'detekt' && steps.run_check.outcome == 'success' }}
uses: actions/upload-artifact@v4
with:
name: detekt-reports
path: |
**/build/reports/detekt/detekt.md
dependency_guard:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Check Dependency Guard
id: dependencyguard_verify
continue-on-error: true
run: ./gradlew dependencyGuard
- name: Prevent updating Dependency Guard baselines if this is a fork
id: checkfork_dependencyguard
if: steps.dependencyguard_verify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::Dependency Guard failed, please update baselines with: ./gradlew dependencyGuardBaseline" && exit 1
# Runs if previous job failed
- name: Generate new Dependency Guard baselines if verification failed and it's a PR
id: dependencyguard_baseline
if: steps.dependencyguard_verify.outcome == 'failure' && github.event_name == 'pull_request'
run: |
./gradlew dependencyGuardBaseline
- name: Push new Dependency Guard baselines if available
uses: stefanzweifel/git-auto-commit-action@v5
if: steps.dependencyguard_baseline.outcome == 'success'
with:
file_pattern: '**/dependencies/*.txt'
disable_globbing: true
commit_message: "🤖 Updates baselines for Dependency Guard"
tests_and_lint:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Run tests
run: |
./gradlew testDebug :lint:test :androidApp:lintRelease :lint:lint
- name: Upload reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-and-lint-reports
path: |
**/build/reports/lint-results-*.html
**/build/test-results/test*UnitTest/**.xml
# Add `createDebugUnitTestCoverageReport` if we ever add JVM tests for prod
- name: Generate coverage reports for Debug variants (only API 30)
run: ./gradlew createDebugCombinedCoverageReport
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports-${{ matrix.api-level }}
path: '**/build/reports/androidTests'
- name: Display local test coverage (only API 30)
id: jacoco
uses: madrapps/jacoco-report@v1.6.1
with:
title: Combined test coverage report
min-coverage-overall: 40
min-coverage-changed-files: 60
paths: |
${{ github.workspace }}/**/build/reports/jacoco/**/*Report.xml
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload local coverage reports (XML + HTML) (only API 30)
uses: actions/upload-artifact@v4
with:
name: coverage-reports
if-no-files-found: error
compression-level: 1
overwrite: false
path: '**/build/reports/jacoco/'
build:
needs: [ checks, dependency_guard, tests_and_lint ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Build APKs
run: ./gradlew :androidApp:assembleDebug
- name: Check badging
run: ./gradlew :androidApp:checkReleaseBadging
- name: Upload APKs
uses: actions/upload-artifact@v4
with:
name: APKs
path: '**/build/outputs/apk/**/*.apk'

6
.gitignore vendored
View File

@ -26,12 +26,8 @@ build/
.classpath
.project
# Windows thumbnail db
.DS_Store
# IDEA/Android Studio project files, because
# the project can be imported from settings.gradle.kts
*.iml
.idea/*
!.idea/copyright
# Keep the code styles.
@ -40,8 +36,6 @@ build/
!/.idea/codeStyles/Project.xml
!/.idea/codeStyles/codeStyleConfig.xml
# Gradle cache
.gradle
# Kotlin
.kotlin

View File

@ -1,27 +1,34 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifos.android.application)
alias(libs.plugins.mifos.android.application.compose)
alias(libs.plugins.mifos.android.hilt)
alias(libs.plugins.mifos.android.application.firebase)
id("com.google.android.gms.oss-licenses-plugin")
id("kotlin-parcelize")
alias(libs.plugins.roborazzi)
}
apply(from = "../config/quality/quality.gradle")
android {
namespace = "org.mifos.mobile"
defaultConfig {
applicationId = "org.mifos.mobile"
versionCode = 1
versionName = "1.0"
applicationId = "org.mifos.mobile"
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled = true
ndk {
abiFilters.addAll(arrayOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a"))
}
multiDexEnabled = true
}
signingConfigs {
@ -42,40 +49,19 @@ android {
}
}
sourceSets {
val commonTestDir = "src/commonTest/java"
getByName("main"){
java.srcDir(commonTestDir)
}
getByName("androidTest"){
java.srcDir(commonTestDir)
}
getByName("test"){
java.srcDir(commonTestDir)
}
}
buildFeatures {
dataBinding = true
viewBinding = true
compose = true
buildConfig = true
}
lint {
abortOnError = false
disable.add("InvalidPackage")
}
}
dependencyGuard {
configuration("debugCompileClasspath")
configuration("debugRuntimeClasspath")
configuration("releaseCompileClasspath")
configuration("releaseRuntimeClasspath")
configuration("releaseRuntimeClasspath"){
modules = true
tree = true
}
}
dependencies {
implementation (projects.shared)
@ -108,120 +94,40 @@ dependencies {
implementation(projects.feature.auth)
implementation(projects.feature.userProfile)
implementation(libs.androidx.legacy.support.v4)
implementation(libs.androidx.lifecycle.ktx)
implementation(libs.androidx.lifecycle.extensions)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.preference)
// DBFlow
implementation(libs.dbflow)
kapt(libs.dbflow.processor)
implementation(libs.dbflow.core)
implementation(libs.androidx.recyclerview)
implementation(libs.androidx.vectordrawable)
implementation(libs.google.oss.licenses)
// implementation(libs.simplecropview)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.fragment.ktx)
//Country Code picker
// implementation(libs.ccp)
// implementation(libs.countrycodechooser)
//Square dependencies
implementation(libs.squareup.retrofit2) {
// exclude Retrofits OkHttp peer-dependency module and define your own module import
exclude(module = "okhttp")
}
implementation(libs.squareup.retrofit.adapter.rxjava)
implementation(libs.squareup.retrofit.converter.gson)
implementation(libs.squareup.okhttp)
implementation(libs.squareup.logging.interceptor)
//rxjava Dependencies
implementation(libs.reactivex.rxjava2.android)
implementation(libs.reactivex.rxjava2)
//Butter Knife
implementation(libs.jakewharton.butterknife)
implementation(libs.jakewharton.compiler)
//Annotation library
implementation(libs.androidx.annotation)
//qr code
// implementation(libs.zxing.core)
// implementation(libs.zxing)
//sweet error dependency
implementation(libs.sweet.error)
//mifos passcode
implementation(libs.mifos.passcode)
//multidex
implementation(libs.androidx.multidex)
//TableView
// implementation(libs.tableview)
//Biometric Authentication
implementation(libs.androidx.biometric)
// Coroutines
implementation(libs.kotlinx.coroutines.android)
testImplementation(libs.kotlinx.coroutines.test)
// Unit tests dependencies
testImplementation(libs.junit)
testImplementation(libs.mockito.core)
implementation(libs.mockito.core)
//turbine
testImplementation(libs.turbine)
implementation(libs.mockito.android)
androidTestImplementation((libs.junit))
androidTestImplementation(libs.mockito.core)
androidTestImplementation(libs.mockito.android)
androidTestImplementation(libs.androidx.annotation)
implementation(libs.androidx.core.testing)
androidTestImplementation(libs.androidx.espresso.contrib)
androidTestImplementation(libs.espresso.core)
androidTestImplementation(libs.androidx.runner)
androidTestImplementation(libs.androidx.rules)
implementation(libs.uihouse)
implementation(projects.libs.mifosPasscode)
// Jetpack Compose
api(libs.androidx.activity.compose)
api(platform(libs.androidx.compose.bom))
api(libs.androidx.compose.material3)
api(libs.androidx.compose.material)
api(libs.androidx.compose.foundation)
api(libs.androidx.compose.foundation.layout)
api(libs.androidx.compose.material.iconsExtended)
api(libs.androidx.compose.runtime)
api(libs.androidx.compose.ui.tooling.preview)
api(libs.androidx.compose.ui.util)
api(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.foundation.layout)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.ui.util)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.tracing.ktx)
implementation(libs.androidx.profileinstaller)
implementation(libs.google.oss.licenses)
implementation(libs.androidx.multidex)
testImplementation(projects.core.testing)
testImplementation(libs.hilt.android.testing)
testImplementation(libs.work.testing)
androidTestImplementation(kotlin("test"))
androidTestImplementation(projects.core.testing)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.navigation.testing)
androidTestImplementation(libs.hilt.android.testing)
debugApi(libs.androidx.compose.ui.tooling)
api(libs.androidx.hilt.navigation.compose)
//image cropper
implementation(libs.android.image.cropper)
// Google Bar code scanner
implementation(libs.google.app.code.scanner)
//cameraX
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.camera.core)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,278 @@
:core:common
:core:data
:core:datastore
:core:designsystem
:core:logs
:core:model
:core:network
:core:qrcode
:core:ui
:feature:about
:feature:account
:feature:auth
:feature:beneficiary
:feature:client-charge
:feature:guarantor
:feature:help
:feature:home
:feature:loan
:feature:location
:feature:notification
:feature:qr
:feature:recent-transaction
:feature:savings
:feature:settings
:feature:third-party-transfer
:feature:transfer-process
:feature:update-password
:feature:user-profile
:libs:country-code-picker
:libs:mifos-passcode
:libs:pullrefresh
:shared
androidx.activity:activity-compose:1.9.1
androidx.activity:activity-ktx:1.9.1
androidx.activity:activity:1.9.1
androidx.annotation:annotation-experimental:1.4.1
androidx.annotation:annotation-jvm:1.8.1
androidx.annotation:annotation:1.8.1
androidx.appcompat:appcompat-resources:1.7.0
androidx.appcompat:appcompat:1.7.0
androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0
androidx.camera:camera-camera2:1.3.4
androidx.camera:camera-core:1.3.4
androidx.camera:camera-lifecycle:1.3.4
androidx.camera:camera-video:1.3.4
androidx.camera:camera-view:1.3.4
androidx.collection:collection-jvm:1.4.2
androidx.collection:collection-ktx:1.4.2
androidx.collection:collection:1.4.2
androidx.compose.animation:animation-android:1.7.0-rc01
androidx.compose.animation:animation-core-android:1.7.0-rc01
androidx.compose.animation:animation-core:1.7.0-rc01
androidx.compose.animation:animation:1.7.0-rc01
androidx.compose.foundation:foundation-android:1.7.0-rc01
androidx.compose.foundation:foundation-layout-android:1.7.0-rc01
androidx.compose.foundation:foundation-layout:1.7.0-rc01
androidx.compose.foundation:foundation:1.7.0-rc01
androidx.compose.material3:material3-android:1.2.1
androidx.compose.material3:material3:1.2.1
androidx.compose.material:material-android:1.6.8
androidx.compose.material:material-icons-core-android:1.6.8
androidx.compose.material:material-icons-core:1.6.8
androidx.compose.material:material-icons-extended-android:1.6.8
androidx.compose.material:material-icons-extended:1.6.8
androidx.compose.material:material-ripple-android:1.6.8
androidx.compose.material:material-ripple:1.6.8
androidx.compose.material:material:1.6.8
androidx.compose.runtime:runtime-android:1.7.0-rc01
androidx.compose.runtime:runtime-saveable-android:1.7.0-rc01
androidx.compose.runtime:runtime-saveable:1.7.0-rc01
androidx.compose.runtime:runtime:1.7.0-rc01
androidx.compose.ui:ui-android:1.7.0-rc01
androidx.compose.ui:ui-geometry-android:1.7.0-rc01
androidx.compose.ui:ui-geometry:1.7.0-rc01
androidx.compose.ui:ui-graphics-android:1.7.0-rc01
androidx.compose.ui:ui-graphics:1.7.0-rc01
androidx.compose.ui:ui-text-android:1.7.0-rc01
androidx.compose.ui:ui-text:1.7.0-rc01
androidx.compose.ui:ui-tooling-preview-android:1.7.0-rc01
androidx.compose.ui:ui-tooling-preview:1.7.0-rc01
androidx.compose.ui:ui-unit-android:1.7.0-rc01
androidx.compose.ui:ui-unit:1.7.0-rc01
androidx.compose.ui:ui-util-android:1.7.0-rc01
androidx.compose.ui:ui-util:1.7.0-rc01
androidx.compose.ui:ui:1.7.0-rc01
androidx.compose:compose-bom:2024.08.00
androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.13.1
androidx.core:core-splashscreen:1.0.1
androidx.core:core:1.13.1
androidx.cursoradapter:cursoradapter:1.0.0
androidx.customview:customview-poolingcontainer:1.0.0
androidx.customview:customview:1.0.0
androidx.databinding:viewbinding:7.4.2
androidx.datastore:datastore-core:1.0.0
androidx.datastore:datastore-preferences-core:1.0.0
androidx.datastore:datastore-preferences:1.0.0
androidx.datastore:datastore:1.0.0
androidx.documentfile:documentfile:1.0.0
androidx.drawerlayout:drawerlayout:1.0.0
androidx.emoji2:emoji2-views-helper:1.3.0
androidx.emoji2:emoji2:1.3.0
androidx.exifinterface:exifinterface:1.3.2
androidx.fragment:fragment-ktx:1.7.1
androidx.fragment:fragment:1.7.1
androidx.graphics:graphics-path:1.0.1
androidx.hilt:hilt-navigation-compose:1.2.0
androidx.hilt:hilt-navigation:1.2.0
androidx.interpolator:interpolator:1.0.0
androidx.legacy:legacy-support-core-utils:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.8.4
androidx.lifecycle:lifecycle-common-jvm:2.8.4
androidx.lifecycle:lifecycle-common:2.8.4
androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.4
androidx.lifecycle:lifecycle-livedata-core:2.8.4
androidx.lifecycle:lifecycle-livedata:2.8.4
androidx.lifecycle:lifecycle-process:2.8.4
androidx.lifecycle:lifecycle-runtime-android:2.8.4
androidx.lifecycle:lifecycle-runtime-compose-android:2.8.4
androidx.lifecycle:lifecycle-runtime-compose:2.8.4
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.4
androidx.lifecycle:lifecycle-runtime-ktx:2.8.4
androidx.lifecycle:lifecycle-runtime:2.8.4
androidx.lifecycle:lifecycle-viewmodel-android:2.8.4
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.4
androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.4
androidx.lifecycle:lifecycle-viewmodel:2.8.4
androidx.loader:loader:1.0.0
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
androidx.metrics:metrics-performance:1.0.0-beta01
androidx.multidex:multidex:2.0.1
androidx.navigation:navigation-common-ktx:2.8.0-rc01
androidx.navigation:navigation-common:2.8.0-rc01
androidx.navigation:navigation-compose:2.8.0-rc01
androidx.navigation:navigation-runtime-ktx:2.8.0-rc01
androidx.navigation:navigation-runtime:2.8.0-rc01
androidx.print:print:1.0.0
androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05
androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05
androidx.profileinstaller:profileinstaller:1.3.1
androidx.resourceinspection:resourceinspection-annotation:1.0.1
androidx.savedstate:savedstate-ktx:1.2.1
androidx.savedstate:savedstate:1.2.1
androidx.startup:startup-runtime:1.1.1
androidx.tracing:tracing-ktx:1.2.0
androidx.tracing:tracing:1.2.0
androidx.vectordrawable:vectordrawable-animated:1.1.0
androidx.vectordrawable:vectordrawable:1.1.0
androidx.versionedparcelable:versionedparcelable:1.1.1
androidx.viewpager:viewpager:1.0.0
app.cash.turbine:turbine-jvm:1.1.0
app.cash.turbine:turbine:1.1.0
co.touchlab:stately-concurrency-jvm:2.0.6
co.touchlab:stately-concurrency:2.0.6
co.touchlab:stately-concurrent-collections-jvm:2.0.6
co.touchlab:stately-concurrent-collections:2.0.6
co.touchlab:stately-strict-jvm:2.0.6
co.touchlab:stately-strict:2.0.6
com.github.Raizlabs.DBFlow:dbflow-core:4.2.4
com.github.Raizlabs.DBFlow:dbflow:4.2.4
com.google.accompanist:accompanist-pager:0.34.0
com.google.accompanist:accompanist-permissions:0.34.0
com.google.android.datatransport:transport-api:3.2.0
com.google.android.datatransport:transport-backend-cct:3.3.0
com.google.android.datatransport:transport-runtime:3.3.0
com.google.android.gms:play-services-ads-identifier:18.0.0
com.google.android.gms:play-services-base:18.5.0
com.google.android.gms:play-services-basement:18.4.0
com.google.android.gms:play-services-cloud-messaging:17.2.0
com.google.android.gms:play-services-maps:18.2.0
com.google.android.gms:play-services-measurement-api:22.1.0
com.google.android.gms:play-services-measurement-base:22.1.0
com.google.android.gms:play-services-measurement-impl:22.1.0
com.google.android.gms:play-services-measurement-sdk-api:22.1.0
com.google.android.gms:play-services-measurement-sdk:22.1.0
com.google.android.gms:play-services-measurement:22.1.0
com.google.android.gms:play-services-oss-licenses:17.1.0
com.google.android.gms:play-services-stats:17.0.2
com.google.android.gms:play-services-tasks:18.2.0
com.google.auto.value:auto-value-annotations:1.6.3
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.10.1
com.google.dagger:dagger-lint-aar:2.52
com.google.dagger:dagger:2.52
com.google.dagger:hilt-android:2.52
com.google.dagger:hilt-core:2.52
com.google.errorprone:error_prone_annotations:2.26.0
com.google.firebase:firebase-abt:21.1.1
com.google.firebase:firebase-analytics-ktx:22.1.0
com.google.firebase:firebase-analytics:22.1.0
com.google.firebase:firebase-annotations:16.2.0
com.google.firebase:firebase-bom:33.2.0
com.google.firebase:firebase-common-ktx:21.0.0
com.google.firebase:firebase-common:21.0.0
com.google.firebase:firebase-components:18.0.0
com.google.firebase:firebase-config-interop:16.0.1
com.google.firebase:firebase-config:22.0.0
com.google.firebase:firebase-crashlytics-ktx:19.0.3
com.google.firebase:firebase-crashlytics:19.0.3
com.google.firebase:firebase-datatransport:19.0.0
com.google.firebase:firebase-encoders-json:18.0.1
com.google.firebase:firebase-encoders-proto:16.0.0
com.google.firebase:firebase-encoders:17.0.0
com.google.firebase:firebase-iid-interop:17.1.0
com.google.firebase:firebase-installations-interop:17.2.0
com.google.firebase:firebase-installations:18.0.0
com.google.firebase:firebase-measurement-connector:20.0.1
com.google.firebase:firebase-messaging-ktx:24.0.1
com.google.firebase:firebase-messaging:24.0.1
com.google.firebase:firebase-perf-ktx:21.0.1
com.google.firebase:firebase-perf:21.0.1
com.google.firebase:firebase-sessions:2.0.3
com.google.firebase:protolite-well-known-types:18.0.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-android
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.protobuf:protobuf-javalite:3.21.11
com.google.zxing:core:3.5.3
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.6.0
com.squareup.okio:okio:3.6.0
com.squareup.retrofit2:adapter-rxjava2:2.11.0
com.squareup.retrofit2:converter-gson:2.11.0
com.squareup.retrofit2:retrofit:2.11.0
dev.chrisbanes.snapper:snapper:0.2.2
io.github.mr0xf00:easycrop:0.1.1
io.insert-koin:koin-android:3.6.0-Beta4
io.insert-koin:koin-androidx-compose:3.6.0-Beta4
io.insert-koin:koin-compose-jvm:1.2.0-Beta4
io.insert-koin:koin-compose-viewmodel-jvm:1.2.0-Beta4
io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4
io.insert-koin:koin-compose:1.2.0-Beta4
io.insert-koin:koin-core-jvm:3.6.0-Beta4
io.insert-koin:koin-core:3.6.0-Beta4
io.michaelrocks:libphonenumber-android:8.13.35
io.reactivex.rxjava2:rxandroid:2.1.1
io.reactivex.rxjava2:rxjava:2.2.21
jakarta.inject:jakarta.inject-api:2.0.1
javax.inject:javax.inject:1
net.bytebuddy:byte-buddy-agent:1.14.5
net.bytebuddy:byte-buddy:1.14.5
org.checkerframework:checker-qual:3.12.0
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0-rc03
org.jetbrains.androidx.navigation:navigation-compose:2.7.0-alpha06
org.jetbrains.compose.material3:material3:1.6.11
org.jetbrains.compose.material:material:1.6.11
org.jetbrains.compose.runtime:runtime:1.6.10-rc03
org.jetbrains.compose.ui:ui-tooling-preview:1.6.11
org.jetbrains.kotlin:kotlin-android-extensions-runtime:2.0.20
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.20
org.jetbrains.kotlin:kotlin-stdlib-common:2.0.20
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.20
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10
org.jetbrains.kotlin:kotlin-stdlib:2.0.20
org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.7
org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.3
org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3
org.jetbrains:annotations:23.0.0
org.mockito:mockito-core:5.4.0
org.objenesis:objenesis:3.3
org.reactivestreams:reactive-streams:1.0.4

46876
androidApp/lint-baseline.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,138 @@
package: name='org.mifos.mobile' versionCode='1' versionName='1.0' platformBuildVersionName='14' platformBuildVersionCode='34' compileSdkVersion='34' compileSdkVersionCodename='14'
sdkVersion:'26'
targetSdkVersion:'34'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.READ_MEDIA_IMAGES'
uses-permission: name='android.permission.CAMERA'
uses-permission: name='android.permission.READ_PHONE_STATE'
uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE'
uses-permission: name='android.permission.READ_EXTERNAL_STORAGE'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.VIBRATE'
uses-permission: name='android.permission.POST_NOTIFICATIONS'
uses-permission: name='android.permission.WAKE_LOCK'
uses-permission: name='com.google.android.c2dm.permission.RECEIVE'
uses-permission: name='com.google.android.gms.permission.AD_ID'
uses-permission: name='android.permission.ACCESS_ADSERVICES_ATTRIBUTION'
uses-permission: name='android.permission.ACCESS_ADSERVICES_AD_ID'
uses-permission: name='com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE'
uses-permission: name='org.mifos.mobile.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION'
application-label:'Mifos Mobile'
application-label-af:'Mifos Mobile'
application-label-am:'Mifos Mobile'
application-label-ar:'ميفوس موبايل'
application-label-as:'Mifos Mobile'
application-label-az:'Mifos Mobile'
application-label-be:'Mifos Mobile'
application-label-bg:'Mifos Mobile'
application-label-bn:'Mifos Mobile'
application-label-bs:'Mifos Mobile'
application-label-ca:'Mifos Mobile'
application-label-cs:'Mifos Mobile'
application-label-da:'Mifos Mobile'
application-label-de:'Mifos Mobile'
application-label-el:'Mifos Mobile'
application-label-en-AU:'Mifos Mobile'
application-label-en-CA:'Mifos Mobile'
application-label-en-GB:'Mifos Mobile'
application-label-en-IN:'Mifos Mobile'
application-label-en-XC:'Mifos Mobile'
application-label-es:'Mifos Mobile'
application-label-es-US:'Mifos Mobile'
application-label-et:'Mifos Mobile'
application-label-eu:'Mifos Mobile'
application-label-fa:'Mifos Mobile'
application-label-fa-AF:'Mifos Mobile'
application-label-fi:'Mifos Mobile'
application-label-fr:'Mifos Mobile'
application-label-fr-CA:'Mifos Mobile'
application-label-gl:'Mifos Mobile'
application-label-gu:'Mifos Mobile'
application-label-hi:'Mifos Mobile'
application-label-hr:'Mifos Mobile'
application-label-hu:'Mifos Mobile'
application-label-hy:'Mifos Mobile'
application-label-in:'Mifos Mobile'
application-label-is:'Mifos Mobile'
application-label-it:'Mifos Mobile'
application-label-it-IT:'Mifos Mobile'
application-label-iw:'Mifos Mobile'
application-label-ja:'Mifos Mobile'
application-label-ka:'Mifos Mobile'
application-label-kk:'Mifos Mobile'
application-label-km:'Mifos Mobile'
application-label-kn:'Mifos Mobile'
application-label-ko:'Mifos Mobile'
application-label-ky:'Mifos Mobile'
application-label-lo:'Mifos Mobile'
application-label-lt:'Mifos Mobile'
application-label-lv:'Mifos Mobile'
application-label-mk:'Mifos Mobile'
application-label-ml:'Mifos Mobile'
application-label-mn:'Mifos Mobile'
application-label-mr:'Mifos Mobile'
application-label-ms:'Mifos Mobile'
application-label-my:'Mifos မိုဘိုင်း'
application-label-nb:'Mifos Mobile'
application-label-ne:'Mifos Mobile'
application-label-nl:'Mifos Mobile'
application-label-or:'Mifos Mobile'
application-label-pa:'Mifos Mobile'
application-label-pl:'Mifos Mobile'
application-label-pt:'Mifos Mobile'
application-label-pt-BR:'Mifos Mobile'
application-label-pt-PT:'Mifos Mobile'
application-label-ro:'Mifos Mobile'
application-label-ru:'Mifos Mobile'
application-label-ru-RU:'Mifos Mobile'
application-label-si:'Mifos Mobile'
application-label-sk:'Mifos Mobile'
application-label-sl:'Mifos Mobile'
application-label-so:'Mifos Mobile'
application-label-sq:'Mifos Mobile'
application-label-sr:'Mifos Mobile'
application-label-sr-Latn:'Mifos Mobile'
application-label-sv:'Mifos Mobile'
application-label-sw:'Mifos Mobile'
application-label-ta:'Mifos Mobile'
application-label-te:'Mifos Mobile'
application-label-th:'Mifos Mobile'
application-label-tl:'Mifos Mobile'
application-label-tr:'Mifos Mobile'
application-label-tr-TR:'Mifos Mobile'
application-label-uk:'Mifos Mobile'
application-label-ur:'Mifos موبائل'
application-label-uz:'Mifos Mobile'
application-label-vi:'Mifos Mobile'
application-label-zh:'Mifos Mobile'
application-label-zh-CN:'Mifos Mobile'
application-label-zh-HK:'Mifos Mobile'
application-label-zh-TW:'Mifos Mobile'
application-label-zu:'Mifos Mobile'
application-icon-120:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-160:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-240:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-320:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-480:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-640:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-65534:'res/mipmap-anydpi-v26/ic_launcher.xml'
application: label='Mifos Mobile' icon='res/mipmap-anydpi-v26/ic_launcher.xml'
launchable-activity: name='org.mifos.mobile.HomeActivity' label='' icon=''
uses-library-not-required:'org.apache.http.legacy'
property: name='android.adservices.AD_SERVICES_CONFIG' resource='res/xml/ga_ad_services_config.xml'
uses-library-not-required:'android.ext.adservices'
feature-group: label=''
uses-gl-es: '0x20000'
uses-feature-not-required: name='android.hardware.camera'
uses-feature: name='android.hardware.camera.any'
uses-feature: name='android.hardware.faketouch'
uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
main
other-activities
other-receivers
other-services
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--' 'af' 'am' 'ar' 'as' 'az' 'be' 'bg' 'bn' 'bs' 'ca' 'cs' 'da' 'de' 'el' 'en-AU' 'en-CA' 'en-GB' 'en-IN' 'en-XC' 'es' 'es-US' 'et' 'eu' 'fa' 'fa-AF' 'fi' 'fr' 'fr-CA' 'gl' 'gu' 'hi' 'hr' 'hu' 'hy' 'in' 'is' 'it' 'it-IT' 'iw' 'ja' 'ka' 'kk' 'km' 'kn' 'ko' 'ky' 'lo' 'lt' 'lv' 'mk' 'ml' 'mn' 'mr' 'ms' 'my' 'nb' 'ne' 'nl' 'or' 'pa' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'ru-RU' 'si' 'sk' 'sl' 'so' 'sq' 'sr' 'sr-Latn' 'sv' 'sw' 'ta' 'te' 'th' 'tl' 'tr' 'tr-TR' 'uk' 'ur' 'uz' 'vi' 'zh' 'zh-CN' 'zh-HK' 'zh-TW' 'zu'
densities: '120' '160' '240' '320' '480' '640' '65534'
native-code: 'arm64-v8a' 'armeabi-v7a' 'x86' 'x86_64'

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile
import androidx.test.InstrumentationRegistry

View File

@ -1,5 +0,0 @@
<resources>
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">
AIzaSyBbeT2BaMWLj-lReCgYoNmXs_TIyRLr9qQ
</string>
</resources>

View File

@ -1,7 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.mifos.mobile">
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.camera"
@ -10,7 +18,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@ -18,53 +28,32 @@
<application
android:name=".MifosSelfServiceApp"
android:allowBackup="true"
android:icon="@mipmap/core_common_mifos_icon"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/feature_about_app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="barcode_ui"/>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
android:theme="@style/Theme.MifosSplash">
<activity
android:name=".ui.activities.LoginActivity"
android:configChanges="orientation|screenSize"
android:label="@string/feature_about_app_name"
android:windowSoftInputMode="adjustResize" />
<activity android:name="com.canhub.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity
android:name=".ui.activities.SplashActivity"
android:name=".HomeActivity"
android:exported="true"
android:theme="@style/SplashTheme">
android:theme="@style/Theme.MifosSplash"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.activities.HomeActivity"
android:configChanges="screenLayout|screenSize|orientation"
android:label="@string/home"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.activities.PassCodeActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="barcode_ui" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
android:configChanges="orientation|screenSize"
@ -73,21 +62,6 @@
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize" />
<receiver
android:name=".ui.widgets.ChargeAppWidget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/charge_app_widget_info" />
</receiver>
<service
android:name=".ui.widgets.ChargeWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<provider
android:name="androidx.core.content.FileProvider"
@ -98,18 +72,6 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/fileproviderpath" />
</provider>
<service
android:name=".utils.fcm.MifosFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name=".utils.fcm.RegistrationIntentService"
android:exported="false" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -1,51 +0,0 @@
package org.mifos.mobile
import android.content.Context
import android.content.res.Configuration
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDex
import androidx.multidex.MultiDexApplication
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.raizlabs.android.dbflow.config.FlowConfig
import com.raizlabs.android.dbflow.config.FlowManager
import dagger.hilt.android.HiltAndroidApp
import org.mifos.mobile.core.common.utils.LanguageHelper.onAttach
import org.mifos.mobile.core.datastore.PreferencesHelper
import org.mifos.mobile.feature.settings.applySavedTheme
/**
* @author ishan
* @since 08/07/16
*/
@HiltAndroidApp
class MifosSelfServiceApp : MultiDexApplication() {
companion object {
private var instance: MifosSelfServiceApp? = null
operator fun get(context: Context): MifosSelfServiceApp {
return context.applicationContext as MifosSelfServiceApp
}
val context: Context?
get() = instance
init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
}
}
override fun onCreate() {
super.onCreate()
MultiDex.install(this)
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)
instance = this
FlowManager.init(FlowConfig.Builder(this).build())
PreferencesHelper(this).applySavedTheme()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
context?.let { onAttach(it) }
}
}

View File

@ -1,82 +0,0 @@
package org.mifos.mobile.di
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import org.mifos.mobile.core.datastore.DatabaseHelper
import org.mifos.mobile.core.datastore.PreferencesHelper
import org.mifos.mobile.core.network.BaseApiManager
import org.mifos.mobile.core.network.DataManager
import org.mifos.mobile.core.network.services.AuthenticationService
import org.mifos.mobile.core.network.services.BeneficiaryService
import org.mifos.mobile.core.network.services.ClientChargeService
import org.mifos.mobile.core.network.services.ClientService
import org.mifos.mobile.core.network.services.GuarantorService
import org.mifos.mobile.core.network.services.LoanAccountsListService
import org.mifos.mobile.core.network.services.NotificationService
import org.mifos.mobile.core.network.services.RecentTransactionsService
import org.mifos.mobile.core.network.services.RegistrationService
import org.mifos.mobile.core.network.services.SavingAccountsListService
import org.mifos.mobile.core.network.services.ThirdPartyTransferService
import org.mifos.mobile.core.network.services.UserDetailsService
import javax.inject.Singleton
/**
* @author ishan
* @since 08/07/16
*/
@Module
@InstallIn(SingletonComponent::class)
object ApplicationModule {
@Provides
@Singleton
fun providePrefManager(@ApplicationContext context: Context?): PreferencesHelper {
return PreferencesHelper(context)
}
@Provides
@Singleton
fun provideBaseApiManager(
authenticationService: AuthenticationService,
clientsService: ClientService,
savingAccountsListService: SavingAccountsListService,
loanAccountsListService: LoanAccountsListService,
recentTransactionsService: RecentTransactionsService,
clientChargeService: ClientChargeService,
beneficiaryService: BeneficiaryService,
thirdPartyTransferService: ThirdPartyTransferService,
registrationService: RegistrationService,
notificationService: NotificationService,
userDetailsService: UserDetailsService,
guarantorService: GuarantorService
): BaseApiManager {
return BaseApiManager(
authenticationService,
clientsService,
savingAccountsListService,
loanAccountsListService,
recentTransactionsService,
clientChargeService,
beneficiaryService,
thirdPartyTransferService,
registrationService,
notificationService,
userDetailsService,
guarantorService
)
}
@Provides
@Singleton
fun providesDataManager(
preferencesHelper: PreferencesHelper,
baseApiManager: BaseApiManager,
databaseHelper: DatabaseHelper
): DataManager {
return DataManager(preferencesHelper, baseApiManager, databaseHelper)
}
}

View File

@ -1,78 +0,0 @@
package org.mifos.mobile.ui.activities
import android.content.Intent
import android.os.Build
import android.provider.Settings
import android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED
import androidx.activity.result.ActivityResultLauncher
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import org.mifos.mobile.R
import org.mifos.mobile.core.model.enums.BiometricCapability
open class BiometricAuthentication(
val context: FragmentActivity,
) {
private val executor = ContextCompat.getMainExecutor(context)
private val callback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
val intent = Intent(context, HomeActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent)
context.finish()
}
}
private val biometricPrompt = BiometricPrompt(context, executor, callback)
fun launchBiometricEnrollment(resultLauncher: ActivityResultLauncher<Intent>) {
val intent: Intent = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
Intent(Settings.ACTION_BIOMETRIC_ENROLL).putExtra(
EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.BIOMETRIC_WEAK,
)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> {
Intent(Settings.ACTION_FINGERPRINT_ENROLL)
}
else -> {
Intent(Settings.ACTION_SECURITY_SETTINGS)
}
}
resultLauncher.launch(intent)
}
fun getBiometricCapabilities(): BiometricCapability {
val biometricManager = BiometricManager.from(context)
return when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK or BiometricManager.Authenticators.DEVICE_CREDENTIAL)) {
BiometricManager.BIOMETRIC_SUCCESS -> {
BiometricCapability.HAS_BIOMETRIC_AUTH
}
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
BiometricCapability.NOT_ENROLLED
}
else -> {
BiometricCapability.NOT_SUPPORTED
}
}
}
fun authenticateWithBiometrics() {
val promptInfo = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(context.getString(R.string.sign_in_fingerprint))
setDescription(context.getString(R.string.scan_your_fingerprint))
setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK or BiometricManager.Authenticators.DEVICE_CREDENTIAL)
}.build()
biometricPrompt.authenticate(promptInfo)
}
}

View File

@ -1,135 +0,0 @@
package org.mifos.mobile.ui.activities
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.Handler
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import dagger.hilt.android.AndroidEntryPoint
import org.mifos.mobile.R
import org.mifos.mobile.core.common.Constants
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
import org.mifos.mobile.feature.home.navigation.HomeNavigation
import org.mifos.mobile.feature.user.profile.viewmodel.UserDetailViewModel
import org.mifos.mobile.navigation.RootNavGraph
import org.mifos.mobile.utils.Toaster
import org.mifos.mobile.utils.fcm.RegistrationIntentService
/**
* @author Vishwajeet
* @since 14/07/2016
*/
@AndroidEntryPoint
class HomeActivity : ComponentActivity() {
private val viewModel: UserDetailViewModel by viewModels()
private var isReceiverRegistered = false
private var doubleBackToExitPressedOnce = false
private lateinit var navHostController: NavHostController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (checkPlayServices()) {
// Start IntentService to register this application with GCM.
val intent = Intent(this, RegistrationIntentService::class.java)
startService(intent)
}
enableEdgeToEdge()
setContent {
MifosMobileTheme {
navHostController = rememberNavController()
RootNavGraph(
startDestination = HomeNavigation.HomeBase.route,
navController = navHostController,
)
}
}
}
override fun onPause() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(registerReceiver)
isReceiverRegistered = false
super.onPause()
}
override fun onResume() {
super.onResume()
if (!isReceiverRegistered) {
LocalBroadcastManager.getInstance(this).registerReceiver(
registerReceiver,
IntentFilter(Constants.REGISTER_ON_SERVER),
)
isReceiverRegistered = true
}
}
/**
* Handling back press
*/
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
val currentRoute = navHostController.currentBackStackEntry?.destination?.route
if (currentRoute == HomeNavigation.HomeScreen.route) {
// if (doubleBackToExitPressedOnce && stackCount() == 0) {
// finish()
// return
// }
doubleBackToExitPressedOnce = true
Toaster.show(findViewById(android.R.id.content), getString(R.string.exit_message))
Handler().postDelayed({ doubleBackToExitPressedOnce = false }, 2000)
}
super.onBackPressed()
// if (stackCount() != 0) {
// super.onBackPressed()
// }
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private fun checkPlayServices(): Boolean {
val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(this)
if (resultCode != ConnectionResult.SUCCESS) {
if (apiAvailability.isUserResolvableError(resultCode)) {
apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST)
?.show()
} else {
Log.i(HomeActivity::class.java.name, "This device is not supported.")
finish()
}
return false
}
return true
}
private val registerReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val token = intent.getStringExtra(Constants.TOKEN)
token?.let { viewModel.registerNotification(it) }
}
}
companion object {
private const val PLAY_SERVICES_RESOLUTION_REQUEST = 9000
}
}

View File

@ -1,33 +0,0 @@
package org.mifos.mobile.ui.activities
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
import org.mifos.mobile.feature.auth.navigation.AuthenticationNavigation
import org.mifos.mobile.navigation.RootNavGraph
/**
* @author Vishwajeet
* @since 05/06/16
*/
@AndroidEntryPoint
class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MifosMobileTheme {
val navController = rememberNavController()
RootNavGraph(
startDestination = AuthenticationNavigation.AuthenticationBase.route,
navController = navController,
)
}
}
}
}

View File

@ -1,35 +0,0 @@
package org.mifos.mobile.ui.activities
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.ui.Modifier
import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
import org.mifos.mobile.navigation.PASSCODE_SCREEN
import org.mifos.mobile.navigation.RootNavGraph
@AndroidEntryPoint
class PassCodeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MifosMobileTheme {
val navController = rememberNavController()
Box(modifier = Modifier.statusBarsPadding()) {
RootNavGraph(
startDestination = PASSCODE_SCREEN,
navController = navController,
)
}
}
}
}
}

View File

@ -1,20 +0,0 @@
package org.mifos.mobile.ui.activities
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
/*
* Created by saksham on 01/June/2018
*/
class SplashActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TODO:: check for user logged in or not, and if logged in move to PasscodeActivity instead.
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
finish()
}
}

View File

@ -1,13 +0,0 @@
package org.mifos.mobile.ui.views
/**
* @author Rajan Maurya
*/
interface BaseActivityCallback {
fun showProgressDialog(message: String?)
fun hideProgressDialog()
fun setToolbarTitle(title: String?)
}

View File

@ -1,64 +0,0 @@
package org.mifos.mobile.ui.widgets
import android.annotation.TargetApi
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.os.Build
import android.widget.RemoteViews
import org.mifos.mobile.R
/**
* Implementation of App Widget functionality.
*/
class ChargeAppWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
super.onUpdate(context, appWidgetManager, appWidgetIds)
}
override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}
override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
}
companion object {
fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
) {
val views = RemoteViews(context.packageName, R.layout.charge_app_widget)
// Set up the collection
setRemoteAdapter(context, views)
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
/**
* Sets the remote adapter used to fill in the list items
*
* @param views RemoteViews to set the RemoteAdapter
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private fun setRemoteAdapter(context: Context, views: RemoteViews) {
views.setRemoteAdapter(
R.id.lv_charges,
Intent(context, ChargeWidgetService::class.java),
)
}
}
}

View File

@ -1,110 +0,0 @@
package org.mifos.mobile.ui.widgets
import android.content.Context
import android.widget.RemoteViews
import android.widget.RemoteViewsService.RemoteViewsFactory
import android.widget.Toast
import dagger.hilt.android.qualifiers.ApplicationContext
import org.mifos.mobile.R
import org.mifos.mobile.core.data.repository.ClientChargeRepository
import org.mifos.mobile.core.datastore.model.Charge
import java.util.concurrent.locks.ReentrantLock
import javax.inject.Inject
/**
* ChargeWidgetDataProvider acts as the adapter for the collection view widget,
* providing RemoteViews to the widget in the getViewAt method.
*/
class ChargeWidgetDataProvider(@param:ApplicationContext private val context: Context) :
RemoteViewsFactory {
@Inject
lateinit var clientChargeRepository: ClientChargeRepository
private var charges: List<Charge?>?
private val `object`: ReentrantLock = ReentrantLock()
private val condition = `object`.newCondition()
override fun onCreate() {
}
override fun onDataSetChanged() {
// TODO Make ClientId Dynamic
//clientChargeViewModel.loadClientLocalCharges()
synchronized(`object`) {
try {
// Calling wait() will block this thread until another thread
// calls notify() on the object.
condition.await()
} catch (e: InterruptedException) {
// Happens if someone interrupts your thread.
}
}
}
override fun getCount(): Int {
return if (charges != null) {
charges!!.size
} else {
0
}
}
override fun getViewAt(position: Int): RemoteViews {
val charge = charges?.get(position)
val itemId = R.layout.item_widget_client_charge
val view = RemoteViews(context.packageName, itemId)
view.setTextViewText(R.id.tv_charge_name, charge?.name)
view.setTextViewText(R.id.tv_charge_amount, charge?.amount.toString())
view.setImageViewResource(R.id.circle_status, R.drawable.ic_attach_money_black_24dp)
return view
}
override fun getLoadingView(): RemoteViews? {
return null
}
override fun getViewTypeCount(): Int {
return 1
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun hasStableIds(): Boolean {
return false
}
fun showErrorFetchingClientCharges(message: String?) {
Toast.makeText(
context,
context.getString(R.string.error_client_charge_loading),
Toast.LENGTH_SHORT,
).show()
}
fun showClientCharges(clientChargesList: List<Charge?>?) {
charges = clientChargesList
synchronized(`object`) {
condition.signal()
}
}
fun showProgress() {}
fun hideProgress() {}
override fun onDestroy() {
}
companion object {
private val LOG_TAG = ChargeWidgetDataProvider::class.java.simpleName
}
init {
charges = ArrayList()
}
}

View File

@ -1,14 +0,0 @@
package org.mifos.mobile.ui.widgets
import android.content.Intent
import android.widget.RemoteViewsService
/**
* ChargeWidgetService is the [RemoteViewsService] that will return our RemoteViewsFactory
*/
class ChargeWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
return ChargeWidgetDataProvider(this)
}
}

View File

@ -1,152 +0,0 @@
package org.mifos.mobile.utils
import android.annotation.TargetApi
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import org.mifos.mobile.R
import org.mifos.mobile.core.datastore.PreferencesHelper
/**
* Created by dilpreet on 14/7/17.
*/
object CheckSelfPermissionAndRequest {
/**
* This Method Check the Permission is granted or not to the App. If the Permission granted,
* returns true and If not permission denied then returns false.
*
* @param context Context
* @param permission Manifest.permission...Permission...
* @return Boolean True or False.
*/
@JvmStatic
fun checkSelfPermission(context: Context?, permission: String?): Boolean {
return ContextCompat.checkSelfPermission(context!!, permission!!) ==
PackageManager.PERMISSION_GRANTED
}
/**
* This Method is requesting to device to grant the permission. When App is trying to
* request the device to grant the permission, then their is Three cases.
* 1. First case Device Prompt the Permission Dialog to user and user accepted or denied the
* Permission.
* 2. Second case will come, if user will denied the permission, after onclick dialog denied
* button and next time App ask for permission, It will show a Material Dialog and there
* will be a message to tell the user that you have denied the permission before, So do
* you want to give this permission to app or not, If yes then click on Re-Try dialog button
* and if not then click on Dialog button "I'm Sure", to not to give this permission to the
* app.
*
*
* And as user will click on "Re-Try" dialog button, he will be prompt with the with
* permission dialog with "[-] never ask again" and have two options first one to click on
* denied button again and put Un check the never ask check box. In this case, user will
* prompt with permission dialog with "[-] never ask again" in the loop, whenever app ask
* for that permission.
*
*
* and If user will click on "[_/] never ask again" check box then permission dialog with
* that permission will not prompt to the user.
* 3. Third case will came. when user have denied to accept permission with never ask again.
* then user will prompt with dialog and message that you have denied this permission with
* never ask again. but this is necessary permission to this app feature. and to grant
* this permission please click on dialog app settings button and give the permission to
* work with this feature.
*
* @param activity AppCompatActivity
* @param permission Manifest.permission...Permission...
* @param permissionRequestCode Permission Request Code.
* @param dialogMessageRetry Dialog Message Retry
* @param messageNeverAskAgain Dialog Message Never Ask Again
* @param permissionDeniedStatus Permission Denied Status
*/
@JvmStatic
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
fun requestPermission(
activity: AppCompatActivity,
permission: String,
permissionRequestCode: Int,
dialogMessageRetry: String?,
messageNeverAskAgain: String?,
permissionDeniedStatus: String?,
) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
MaterialDialog.Builder().init(activity)
.setTitle(R.string.dialog_permission_denied)
.setMessage(dialogMessageRetry)
.setPositiveButton(
R.string.dialog_action_re_try,
DialogInterface.OnClickListener { _, _ ->
ActivityCompat.requestPermissions(
activity,
arrayOf(permission),
permissionRequestCode,
)
},
)
.setNegativeButton(R.string.dialog_action_i_am_sure)
.createMaterialDialog()
.show()
} else {
// Requesting Permission, first time to the device.
val preferencesHelper = PreferencesHelper(activity.applicationContext)
if (preferencesHelper.getBoolean(permissionDeniedStatus, true) == true) {
preferencesHelper.putBoolean(permissionDeniedStatus, false)
ActivityCompat.requestPermissions(
activity,
arrayOf(permission),
permissionRequestCode,
)
} else {
// Requesting Permission, more the one time and opening the setting to change
// the Permission in App Settings.
MaterialDialog.Builder().init(activity)
.setMessage(messageNeverAskAgain)
.setNegativeButton(R.string.dialog_action_cancel)
.setPositiveButton(
R.string.dialog_action_app_settings,
DialogInterface.OnClickListener { _, _ -> // Making the Intent to grant the permission
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts(
activity.resources.getString(
R.string.package_name,
),
activity.packageName,
null,
)
intent.data = uri
val pm = activity.packageManager
if (intent.resolveActivity(pm) != null) {
activity.startActivityForResult(
intent,
org.mifos.mobile.core.common.Constants.REQUEST_PERMISSION_SETTING,
)
} else {
Toast.makeText(
activity,
activity.getString(
R.string.msg_setting_activity_not_found,
),
Toast.LENGTH_LONG,
).show()
}
},
)
.createMaterialDialog()
.show()
}
}
}
}

View File

@ -1,24 +0,0 @@
package org.mifos.mobile.utils
import org.mifos.mobile.core.model.entity.accounts.Account
/**
* Created by dilpreet on 14/6/17.
*/
open class ComparatorBasedOnId : Comparator<Account?> {
/**
* Compares [Account] based on their Id
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return a negative integer, zero, or a positive integer as the
* first argument is less than, equal to, or greater than the
* second.
*/
override fun compare(o1: Account?, o2: Account?): Int {
return if (o1 != null && o2 != null) {
o2.id.toFloat().compareTo(o1.id.toFloat())
} else {
0
}
}
}

View File

@ -1,130 +0,0 @@
package org.mifos.mobile.utils
import android.content.Context
import android.content.Intent
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import androidx.preference.DialogPreference.TargetFragment
import androidx.preference.Preference
import androidx.preference.PreferenceDialogFragmentCompat
import butterknife.BindView
import butterknife.ButterKnife
import com.google.android.material.textfield.TextInputLayout
import org.mifos.mobile.R
import org.mifos.mobile.core.datastore.PreferencesHelper
import org.mifos.mobile.ui.activities.LoginActivity
import java.net.MalformedURLException
import java.net.URL
/**
* Created by dilpreet on 11/03/18.
*/
class ConfigurationDialogFragmentCompat :
PreferenceDialogFragmentCompat(),
TargetFragment,
TextWatcher {
@JvmField
@BindView(R.id.et_tenant)
var etTenant: EditText? = null
@JvmField
@BindView(R.id.et_base_url)
var etBaseUrl: EditText? = null
@JvmField
@BindView(R.id.til_tenant)
var tilTenant: TextInputLayout? = null
@JvmField
@BindView(R.id.til_base_url)
var tilBaseUrl: TextInputLayout? = null
private var preferencesHelper: PreferencesHelper? = null
override fun onCreateDialogView(context: Context): View {
return LayoutInflater.from(context).inflate(R.layout.preference_configuration, null)
}
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
ButterKnife.bind(this, view)
preferencesHelper = PreferencesHelper(context)
val preference = preference as ConfigurationPreference
etBaseUrl?.setText(preference.baseUrl)
etTenant?.setText(preference.tenant)
etBaseUrl?.text?.length?.let { etBaseUrl?.setSelection(it) }
etTenant?.addTextChangedListener(this)
etBaseUrl?.addTextChangedListener(this)
}
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult && !isFieldEmpty && isUrlValid) {
val preference = preference as ConfigurationPreference
if (!(
preference.baseUrl.toString()
.equals(etBaseUrl!!.text.toString()) && preference.tenant.toString()
.equals(etTenant!!.text.toString())
)
) {
preference.updateConfigurations(
etBaseUrl?.text.toString(),
etTenant?.text.toString(),
)
preferencesHelper?.clear()
val loginIntent = Intent(activity, LoginActivity::class.java)
loginIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(loginIntent)
activity?.finish()
}
}
}
override fun findPreference(key: CharSequence): Preference {
return preference
}
val isFieldEmpty: Boolean
get() {
if (etBaseUrl?.text.toString().trim { it <= ' ' }.isEmpty()) {
return true
}
return etTenant?.text.toString().trim { it <= ' ' }.isEmpty()
}
val isUrlValid: Boolean
get() = try {
URL(etBaseUrl?.text.toString())
true
} catch (e: MalformedURLException) {
false
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (s.toString().isEmpty()) {
if (etBaseUrl?.text.toString().isEmpty()) {
tilBaseUrl?.isErrorEnabled = true
tilBaseUrl?.error = getString(
R.string.error_validation_blank,
getString(R.string.base_url),
)
}
if (etTenant?.text.toString().isEmpty()) {
tilTenant?.isErrorEnabled = true
tilTenant?.error = getString(
R.string.error_validation_blank,
getString(R.string.tenant),
)
}
} else {
if (etBaseUrl?.text.toString().length != 0) {
tilBaseUrl?.isErrorEnabled = false
}
if (etTenant?.text.toString().length != 0) {
tilTenant?.isErrorEnabled = false
}
}
}
override fun afterTextChanged(s: Editable) {}
}

View File

@ -1,44 +0,0 @@
package org.mifos.mobile.utils
import android.content.Context
import android.util.AttributeSet
import androidx.preference.DialogPreference
import org.mifos.mobile.core.datastore.PreferencesHelper
/**
* Created by dilpreet on 11/03/18.
*/
class ConfigurationPreference : DialogPreference {
private var preferencesHelper: PreferencesHelper? = null
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr,
) {
init()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context?) : super(context) {
init()
}
private fun init() {
isPersistent = false
preferencesHelper = PreferencesHelper(context)
}
val baseUrl: String?
get() = preferencesHelper?.baseUrl
fun updateConfigurations(baseUrl: String?, tenant: String?) {
preferencesHelper?.updateConfiguration(baseUrl, tenant)
}
val tenant: String?
get() = preferencesHelper?.tenant
}

View File

@ -1,8 +0,0 @@
package org.mifos.mobile.utils
/**
* Created by dilpreet on 6/3/17.
*/
enum class DatePick {
START, END
}

View File

@ -1,110 +0,0 @@
package org.mifos.mobile.utils
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private var visibleThreshold = 5
// The current offset index of data you have loaded
private var currentPage = 0
// The total number of items in the dataset after the last load
private var previousTotalItemCount = 0
// True if we are still waiting for the last set of data to load.
private var loading = true
// Sets the starting page index
private val startingPageIndex = 0
var mLayoutManager: RecyclerView.LayoutManager
constructor(layoutManager: LinearLayoutManager) {
mLayoutManager = layoutManager
}
constructor(layoutManager: GridLayoutManager) {
mLayoutManager = layoutManager
visibleThreshold = visibleThreshold * layoutManager.spanCount
}
constructor(layoutManager: StaggeredGridLayoutManager) {
mLayoutManager = layoutManager
visibleThreshold = visibleThreshold * layoutManager.spanCount
}
fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
var maxSize = 0
for (i in lastVisibleItemPositions.indices) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i]
} else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i]
}
}
return maxSize
}
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
var lastVisibleItemPosition = 0
val totalItemCount = mLayoutManager.itemCount
if (mLayoutManager is StaggeredGridLayoutManager) {
val lastVisibleItemPositions =
(mLayoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(
null,
)
// get maximum element within the list
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions)
} else if (mLayoutManager is GridLayoutManager) {
lastVisibleItemPosition =
(mLayoutManager as GridLayoutManager).findLastVisibleItemPosition()
} else if (mLayoutManager is LinearLayoutManager) {
lastVisibleItemPosition =
(mLayoutManager as LinearLayoutManager).findLastVisibleItemPosition()
}
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
currentPage = startingPageIndex
previousTotalItemCount = totalItemCount
if (totalItemCount == 0) {
loading = true
}
}
// If its still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && totalItemCount > previousTotalItemCount) {
loading = false
previousTotalItemCount = totalItemCount
}
// If it isnt currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
currentPage++
onLoadMore(currentPage, totalItemCount, view)
loading = true
}
}
// Call this method whenever performing new searches
fun resetState() {
currentPage = startingPageIndex
previousTotalItemCount = 0
loading = true
}
// Defines the process for actually loading more data based on page
abstract fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?)
}

View File

@ -1,28 +0,0 @@
package org.mifos.mobile.utils
import org.mifos.mobile.core.model.entity.guarantor.GuarantorApplicationPayload
import org.mifos.mobile.core.model.entity.guarantor.GuarantorPayload
import org.mifos.mobile.core.model.entity.guarantor.GuarantorTemplatePayload
sealed class GuarantorUiState {
object Loading : GuarantorUiState()
data class ShowError(val message: String?) : GuarantorUiState()
data class GuarantorDeletedSuccessfully(val message: String?) : GuarantorUiState()
data class GuarantorUpdatedSuccessfully(val message: String?) : GuarantorUiState()
data class ShowGuarantorListSuccessfully(val payload: List<GuarantorPayload?>?) :
GuarantorUiState()
data class ShowGuarantorApplication(val template: GuarantorTemplatePayload?) :
GuarantorUiState()
data class ShowGuarantorUpdation(val template: GuarantorTemplatePayload?) : GuarantorUiState()
data class SubmittedSuccessfully(
val message: String?,
val payload: GuarantorApplicationPayload?
) : GuarantorUiState()
}

View File

@ -1,136 +0,0 @@
package org.mifos.mobile.utils
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.util.Log
/**
* Created by dilpreet on 10/8/17.
*/
class ImageUtil {
// Default width and height
fun compressImage(decodedBytes: ByteArray): Bitmap? {
return compress(decodedBytes, 816.0f, 612.0f)
}
fun compressImage(decodedBytes: ByteArray, maxHeight: Float, maxWidth: Float): Bitmap? {
return compress(decodedBytes, maxHeight, maxWidth)
}
private fun compress(decodedBytes: ByteArray, maxHeight: Float, maxWidth: Float): Bitmap? {
var scaledBitmap: Bitmap? = null
val options = BitmapFactory.Options()
// by setting this field as true, the actual bitmap pixels are not loaded in the memory.
// Just the bounds are loaded. If
// you try the use the bitmap here, you will get null.
options.inJustDecodeBounds = true
var bmp = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size, options)
var actualHeight = options.outHeight
var actualWidth = options.outWidth
var imgRatio = actualWidth / actualHeight.toFloat()
val maxRatio = maxWidth / maxHeight
// width and height values are set maintaining the aspect ratio of the image
if (actualHeight > maxHeight || actualWidth > maxWidth) {
if (imgRatio < maxRatio) {
imgRatio = maxHeight / actualHeight
actualWidth = (imgRatio * actualWidth).toInt()
actualHeight = maxHeight.toInt()
} else if (imgRatio > maxRatio) {
imgRatio = maxWidth / actualWidth
actualHeight = (imgRatio * actualHeight).toInt()
actualWidth = maxWidth.toInt()
} else {
actualHeight = maxHeight.toInt()
actualWidth = maxWidth.toInt()
}
}
// setting inSampleSize value allows to load a scaled down version of the original image
options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight)
// inJustDecodeBounds set to false to load the actual bitmap
options.inJustDecodeBounds = false
// this options allow android to claim the bitmap memory if it runs low on memory
options.inPurgeable = true
options.inInputShareable = true
options.inTempStorage = ByteArray(16 * 1024)
try {
bmp = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size, options)
} catch (exception: OutOfMemoryError) {
Log.e(ImageUtil::class.java.name, exception.toString())
}
try {
scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888)
} catch (exception: OutOfMemoryError) {
Log.e(ImageUtil::class.java.name, exception.toString())
}
val ratioX = actualWidth / options.outWidth.toFloat()
val ratioY = actualHeight / options.outHeight.toFloat()
val middleX = actualWidth / 2.0f
val middleY = actualHeight / 2.0f
val scaleMatrix = Matrix()
scaleMatrix.setScale(ratioX, ratioY, middleX, middleY)
val canvas = Canvas(scaledBitmap!!)
canvas.setMatrix(scaleMatrix)
canvas.drawBitmap(
bmp,
middleX - bmp.width / 2,
middleY - bmp.height / 2,
Paint(Paint.FILTER_BITMAP_FLAG),
)
scaledBitmap = Bitmap.createBitmap(
scaledBitmap,
0,
0,
scaledBitmap.width,
scaledBitmap.height,
null,
true,
)
return scaledBitmap
}
private fun calculateInSampleSize(
options: BitmapFactory.Options,
reqWidth: Int,
reqHeight: Int,
): Int {
val height = options.outHeight
val width = options.outWidth
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val heightRatio = Math.round(height.toFloat() / reqHeight.toFloat())
val widthRatio = Math.round(width.toFloat() / reqWidth.toFloat())
inSampleSize = if (heightRatio < widthRatio) heightRatio else widthRatio
}
val totalPixels = width * height.toFloat()
val totalReqPixelsCap = reqWidth * reqHeight * 2.toFloat()
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++
}
return inSampleSize
}
companion object {
/**
* Reference : https://developer.android.com/topic/performance/graphics/load-bitmap.html
* And for scaling :
* https://stackoverflow.com/questions/8722359/scale-rotate-bitmap-using-matrix-in-android/8722592#8722592
*/
@JvmStatic
var instance: ImageUtil? = null
get() {
if (field == null) {
field = ImageUtil()
}
return field
}
private set
}
}

View File

@ -1,28 +0,0 @@
package org.mifos.mobile.utils
import com.google.gson.Gson
import io.reactivex.plugins.RxJavaPlugins
import org.mifos.mobile.core.model.entity.mifoserror.MifosError
import retrofit2.HttpException
object MFErrorParser {
const val LOG_TAG = "MFErrorParser"
private val gson = Gson()
fun parseError(serverResponse: String?): MifosError {
return gson.fromJson(serverResponse, MifosError::class.java)
}
@JvmStatic
fun errorMessage(throwableError: Throwable?): String? {
var errorMessage: String? = ""
try {
if (throwableError is HttpException) {
errorMessage = throwableError.response()?.errorBody()?.string()
errorMessage = parseError(errorMessage).errors[0].defaultUserMessage
}
} catch (throwable: Throwable) {
RxJavaPlugins.getErrorHandler()
}
return errorMessage
}
}

View File

@ -1,205 +0,0 @@
package org.mifos.mobile.utils
import android.content.Context
import android.content.DialogInterface
import android.view.View
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.mifos.mobile.R
/**
* This Class is the Material Dialog Builder Class
* Created by Rajan Maurya on 03/08/16.
*/
class MaterialDialog {
class Builder {
private var mMaterialDialogBuilder: AlertDialog.Builder? = null
private var context: Context? = null
// This is the Default Builder Initialization with Material Style
fun init(context: Context?): Builder {
mMaterialDialogBuilder = MaterialAlertDialogBuilder(context!!)
this.context = context
return this
}
// This method set the custom Material Style
fun init(context: Context?, theme: Int): Builder {
mMaterialDialogBuilder = AlertDialog.Builder(context!!, theme)
this.context = context
return this
}
// This method set the String Title
fun setTitle(title: String?): Builder {
mMaterialDialogBuilder?.setTitle(title)
return this
}
// This Method set the String Resources to Title
fun setTitle(@StringRes title: Int): Builder {
mMaterialDialogBuilder?.setTitle(title)
return this
}
// This Method set the String Message
fun setMessage(message: String?): Builder {
mMaterialDialogBuilder?.setMessage(message)
return this
}
// This Method set the String Resources message
fun setMessage(@StringRes message: Int): Builder {
mMaterialDialogBuilder?.setMessage(message)
return this
}
// This Method set String Test to the Positive Button and set the Onclick null
fun setPositiveButton(positiveText: String?): Builder {
mMaterialDialogBuilder?.setPositiveButton(positiveText, null)
return this
}
// This Method Set the String Resources Text To Positive Button
fun setPositiveButton(@StringRes positiveText: Int): Builder {
mMaterialDialogBuilder?.setPositiveButton(positiveText, null)
return this
}
// This Method set the String Text to Positive Button and set the OnClick Event to it
fun setPositiveButton(
positiveText: String?,
listener: DialogInterface.OnClickListener?,
): Builder {
mMaterialDialogBuilder?.setPositiveButton(positiveText, listener)
return this
}
// This method set the String Resources text To Positive button and set the Onclick Event
fun setPositiveButton(
@StringRes positiveText: Int,
listener: DialogInterface.OnClickListener?,
): Builder {
mMaterialDialogBuilder?.setPositiveButton(positiveText, listener)
return this
}
// This Method the String Text to Negative Button and Set the onclick event to null
fun setNegativeButton(negativeText: String?): Builder {
mMaterialDialogBuilder?.setNegativeButton(negativeText, null)
return this
}
// This Method set the String Resources Text to Negative button
// and set the onclick event to null
fun setNegativeButton(@StringRes negativeText: Int): Builder {
mMaterialDialogBuilder?.setNegativeButton(negativeText, null)
return this
}
// This Method set String Text to Negative Button and
// Set the Onclick event
fun setNegativeButton(
negativeText: String?,
listener: DialogInterface.OnClickListener?,
): Builder {
mMaterialDialogBuilder?.setNegativeButton(negativeText, listener)
return this
}
// This method set String Resources Text to Negative Button and set Onclick Event
fun setNegativeButton(
@StringRes negativeText: Int,
listener: DialogInterface.OnClickListener?,
): Builder {
mMaterialDialogBuilder?.setNegativeButton(negativeText, listener)
return this
}
// This Method the String Text to Neutral Button and Set the onclick event to null
fun setNeutralButton(neutralText: String?): Builder {
mMaterialDialogBuilder?.setNeutralButton(neutralText, null)
return this
}
// This Method set the String Resources Text to Neutral button
// and set the onclick event to null
fun setNeutralButton(@StringRes neutralText: Int): Builder {
mMaterialDialogBuilder?.setNeutralButton(neutralText, null)
return this
}
// This Method set String Text to Neutral Button and
// Set the Onclick event
fun setNeutralButton(
neutralText: String?,
listener: DialogInterface.OnClickListener?,
): Builder {
mMaterialDialogBuilder?.setNeutralButton(neutralText, listener)
return this
}
// This method set String Resources Text to Neutral Button and set Onclick Event
fun setNeutralButton(
@StringRes neutralText: Int,
listener: DialogInterface.OnClickListener?,
): Builder {
mMaterialDialogBuilder?.setNeutralButton(neutralText, listener)
return this
}
fun setCancelable(cancelable: Boolean?): Builder {
mMaterialDialogBuilder?.setCancelable(cancelable!!)
return this
}
fun setItems(items: Int, listener: DialogInterface.OnClickListener?): Builder {
mMaterialDialogBuilder?.setItems(items, listener)
return this
}
fun setItems(items: Array<String>?, listener: DialogInterface.OnClickListener?): Builder {
mMaterialDialogBuilder?.setItems(items, listener)
return this
}
fun addView(view: View?): Builder {
mMaterialDialogBuilder?.setView(view)
return this
}
// This Method Create the Final Material Dialog
fun createMaterialDialog(): Builder {
mMaterialDialogBuilder?.create()
return this
}
// This Method Show the Dialog
fun show(): Builder {
val dialog = mMaterialDialogBuilder?.show()
dialog?.getButton(DialogInterface.BUTTON_POSITIVE)?.setTextColor(
ContextCompat.getColor(
context!!,
R.color.accent,
),
)
dialog?.getButton(DialogInterface.BUTTON_NEGATIVE)?.setTextColor(
ContextCompat.getColor(
context!!,
R.color.gray_dark,
),
)
dialog?.getButton(DialogInterface.BUTTON_NEUTRAL)?.setTextColor(
ContextCompat.getColor(
context!!,
R.color.black,
),
)
return this
}
}
}

View File

@ -1,72 +0,0 @@
package org.mifos.mobile.utils
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat
import org.mifos.mobile.R
import org.mifos.mobile.ui.getThemeAttributeColor
/**
* Created by dilpreet on 30/6/17.
*/
class ProcessView : View {
private var valueStr: String? = null
private var textPaint: Paint? = null
private var backgroundPaint: Paint? = null
constructor(context: Context?) : super(context, null)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ProcessView)
valueStr = typedArray.getString(R.styleable.ProcessView_value)
typedArray.recycle()
textPaint = Paint()
textPaint?.color = getColorCompat(android.R.color.white)
textPaint?.isAntiAlias = true
textPaint?.style = Paint.Style.FILL
textPaint?.strokeWidth = 1f
textPaint?.textSize = 40f
backgroundPaint = Paint()
backgroundPaint?.color = getColorCompat(R.color.gray_dark)
backgroundPaint?.isAntiAlias = true
backgroundPaint?.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val xPos =
canvas.width / 2 - (textPaint?.measureText(valueStr.toString())?.div(2))?.toInt()!!
val yPos = (
canvas.height / 2 - (
(
textPaint?.descent()
?.plus(textPaint?.ascent()!!)
)?.div(2)
)!!
).toInt()
val usableWidth = width - (paddingLeft + paddingRight)
val usableHeight = height - (paddingTop + paddingBottom)
val radius = usableWidth.coerceAtMost(usableHeight) / 2
val cx = paddingLeft + usableWidth / 2
val cy = paddingTop + usableHeight / 2
canvas.drawCircle(cx.toFloat(), cy.toFloat(), radius.toFloat(), backgroundPaint!!)
canvas.drawText(valueStr!!, xPos.toFloat(), yPos.toFloat(), textPaint!!)
}
fun setCurrentActive() {
backgroundPaint?.color = context.getThemeAttributeColor(R.attr.colorPrimary)
invalidate()
}
fun setCurrentCompleted() {
backgroundPaint?.color = context.getThemeAttributeColor(R.attr.colorPrimary)
valueStr = "\u2713"
invalidate()
}
private fun getColorCompat(colorId: Int): Int {
return ContextCompat.getColor(context, colorId)
}
}

View File

@ -1,24 +0,0 @@
package org.mifos.mobile.utils
import okhttp3.MediaType
import okhttp3.Protocol
import okhttp3.ResponseBody
import retrofit2.HttpException
import retrofit2.Response
/**
* Created by dilpreet on 29/7/17.
*/
object RetrofitUtils {
fun getResponseForError(errorCode: Int): Exception {
val message = if (errorCode == 401) "UnAuthorized" else "Not Found"
val responseBody =
ResponseBody.create(MediaType.parse("application/json"), "{\"message\":\"$message\"}")
val response = okhttp3.Response.Builder().code(errorCode)
.message(message)
.protocol(Protocol.HTTP_1_1)
.request(okhttp3.Request.Builder().url("http://localhost/").build())
.build()
return HttpException(Response.error<Any>(responseBody, response))
}
}

View File

@ -1,24 +0,0 @@
package org.mifos.mobile.utils
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
/*
* Created by saksham on 31/July/2018
*/
object RxBus {
@JvmStatic
private val publisher = PublishSubject.create<Any>()
@JvmStatic
fun publish(event: Any?) {
if (event != null) publisher.onNext(event)
}
@JvmStatic
fun <T> listen(eventType: Class<T>): Observable<T> {
return publisher.ofType(eventType)
}
}

View File

@ -1,16 +0,0 @@
package org.mifos.mobile.utils
import org.mifos.mobile.core.model.entity.guarantor.GuarantorApplicationPayload
/*
* Created by saksham on 29/July/2018
*/
class RxEvent {
data class AddGuarantorEvent(var payload: GuarantorApplicationPayload?, var index: Int?)
data class DeleteGuarantorEvent(var index: Int?)
data class UpdateGuarantorEvent(var payload: GuarantorApplicationPayload?, var index: Int?)
}

View File

@ -1,31 +0,0 @@
package org.mifos.mobile.utils
import org.mifos.mobile.core.model.entity.accounts.savings.SavingsWithAssociations
import org.mifos.mobile.core.model.entity.accounts.savings.Transactions
import org.mifos.mobile.core.model.entity.templates.account.AccountOptionsTemplate
import org.mifos.mobile.core.model.entity.templates.savings.SavingsAccountTemplate
sealed class SavingsAccountUiState {
object Initial : SavingsAccountUiState()
object Loading : SavingsAccountUiState()
object Error : SavingsAccountUiState()
data class SuccessLoadingSavingsWithAssociations(val savingAccount: SavingsWithAssociations) :
SavingsAccountUiState()
data class ShowFilteredTransactionsList(val savingAccountsTransactionList: List<Transactions?>?) :
SavingsAccountUiState()
object SavingsAccountUpdateSuccess : SavingsAccountUiState()
object SavingsAccountApplicationSuccess : SavingsAccountUiState()
data class ShowUserInterfaceSavingAccountUpdate(val template: SavingsAccountTemplate) :
SavingsAccountUiState()
data class ShowUserInterfaceSavingAccountApplication(val template: SavingsAccountTemplate) :
SavingsAccountUiState()
data class ErrorMessage(val error: Throwable) : SavingsAccountUiState()
object HideProgress : SavingsAccountUiState()
object SavingsAccountWithdrawSuccess : SavingsAccountUiState()
data class ShowSavingsAccountTemplate(val accountOptionsTemplate: AccountOptionsTemplate) : SavingsAccountUiState()
}

View File

@ -1,63 +0,0 @@
package org.mifos.mobile.utils
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.floatingactionbutton.FloatingActionButton.OnVisibilityChangedListener
/**
* Created by dilpreet on 23/8/17.
*/
class ScrollFabBehavior(context: Context?, attrs: AttributeSet?) : FloatingActionButton.Behavior() {
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: FloatingActionButton,
directTargetChild: View,
target: View,
nestedScrollAxes: Int,
): Boolean {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
super.onStartNestedScroll(
coordinatorLayout,
child,
directTargetChild,
target,
nestedScrollAxes,
)
}
override fun onNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: FloatingActionButton,
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
) {
super.onNestedScroll(
coordinatorLayout,
child,
target,
dxConsumed,
dyConsumed,
dxUnconsumed,
dyUnconsumed,
)
if (dyConsumed > 0 && child.visibility == View.VISIBLE) {
// Reason to set fab visiblity as INVISIBLE :
// https://stackoverflow.com/a/41386278/4672688
child.hide(object : OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton) {
super.onHidden(fab)
fab.visibility = View.INVISIBLE
}
})
} else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
child.show()
}
}
}

View File

@ -1,70 +0,0 @@
package org.mifos.mobile.utils
import android.content.Context
import androidx.core.content.ContextCompat
import org.mifos.mobile.R
import org.mifos.mobile.core.model.entity.CheckboxStatus
/**
* Created by dilpreet on 3/7/17.
*/
object StatusUtils {
fun getSavingsAccountTransactionList(context: Context?): List<CheckboxStatus> {
val arrayList = ArrayList<CheckboxStatus>()
arrayList.add(
CheckboxStatus(
context?.getString(R.string.feature_account_deposit),
ContextCompat
.getColor(context!!, R.color.deposit_green),
),
)
arrayList.add(
CheckboxStatus(
context.getString(R.string.feature_account_dividend_payout),
ContextCompat
.getColor(context, R.color.red_light),
),
)
arrayList.add(
CheckboxStatus(
context.getString(R.string.feature_account_withdrawal),
ContextCompat
.getColor(context, R.color.red_light),
),
)
arrayList.add(
CheckboxStatus(
context.getString(R.string.feature_account_interest_posting),
ContextCompat.getColor(context, R.color.green_light),
),
)
arrayList.add(
CheckboxStatus(
context.getString(R.string.feature_account_fee_deduction),
ContextCompat
.getColor(context, R.color.red_light),
),
)
arrayList.add(
CheckboxStatus(
context.getString(R.string.feature_account_withdrawal_transfer),
ContextCompat.getColor(context, R.color.red_light),
),
)
arrayList.add(
CheckboxStatus(
context.getString(R.string.feature_account_rejected_transfer),
ContextCompat.getColor(context, R.color.green_light),
),
)
arrayList.add(
CheckboxStatus(
context.getString(R.string.feature_account_overdraft_fee),
ContextCompat
.getColor(context, R.color.red_light),
),
)
return arrayList
}
}

View File

@ -1,281 +0,0 @@
package org.mifos.mobile.utils
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.RectF
import android.graphics.Typeface
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.OvalShape
import android.graphics.drawable.shapes.RectShape
import android.graphics.drawable.shapes.RoundRectShape
import java.util.Locale
class TextDrawable private constructor(builder: Builder) : ShapeDrawable(builder.shape) {
private val textPaint: Paint
private val borderPaint: Paint
private val text: String
private val color: Int
private val shape: RectShape
private val height: Int
private val width: Int
private val fontSize: Int
private val radius: Float
private val borderThickness: Int
private fun getDarkerShade(color: Int): Int {
return Color.rgb(
(SHADE_FACTOR * Color.red(color)).toInt(),
(SHADE_FACTOR * Color.green(color)).toInt(),
(SHADE_FACTOR * Color.blue(color)).toInt(),
)
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
val r = bounds
// draw border
if (borderThickness > 0) {
drawBorder(canvas)
}
val count = canvas.save()
canvas.translate(r.left.toFloat(), r.top.toFloat())
// draw text
val width = if (width < 0) r.width() else width
val height = if (height < 0) r.height() else height
val fontSize = if (fontSize < 0) Math.min(width, height) / 2 else fontSize
textPaint.textSize = fontSize.toFloat()
canvas.drawText(
text,
width / 2.toFloat(),
height / 2 - (textPaint.descent() + textPaint.ascent()) / 2,
textPaint,
)
canvas.restoreToCount(count)
}
private fun drawBorder(canvas: Canvas) {
val rect = RectF(bounds)
rect.inset(borderThickness / 2.toFloat(), borderThickness / 2.toFloat())
if (shape is OvalShape) {
canvas.drawOval(rect, borderPaint)
} else if (shape is RoundRectShape) {
canvas.drawRoundRect(rect, radius, radius, borderPaint)
} else {
canvas.drawRect(rect, borderPaint)
}
}
override fun setAlpha(alpha: Int) {
textPaint.alpha = alpha
}
override fun setColorFilter(cf: ColorFilter?) {
textPaint.colorFilter = cf
}
override fun getOpacity(): Int {
return PixelFormat.TRANSLUCENT
}
override fun getIntrinsicWidth(): Int {
return width
}
override fun getIntrinsicHeight(): Int {
return height
}
class Builder : IConfigBuilder, IShapeBuilder, IBuilder {
var text = ""
var color: Int
var borderThickness: Int
var iconWidth: Int
var iconHeight: Int
var font: Typeface
var shape: RectShape
var iconTextColor: Int
var iconFontSize: Int
var isBold: Boolean
var iconToUpperCase: Boolean
var radius = 0f
override fun width(width: Int): IConfigBuilder {
iconWidth = width
return this
}
override fun height(height: Int): IConfigBuilder {
iconHeight = height
return this
}
override fun textColor(color: Int): IConfigBuilder {
iconTextColor = color
return this
}
override fun withBorder(thickness: Int): IConfigBuilder {
borderThickness = thickness
return this
}
override fun useFont(font: Typeface): IConfigBuilder {
this.font = font
return this
}
override fun fontSize(size: Int): IConfigBuilder {
iconFontSize = size
return this
}
override fun bold(): IConfigBuilder {
isBold = true
return this
}
override fun toUpperCase(): IConfigBuilder {
iconToUpperCase = true
return this
}
override fun beginConfig(): IConfigBuilder {
return this
}
override fun endConfig(): IShapeBuilder {
return this
}
override fun rect(): IBuilder {
shape = RectShape()
return this
}
override fun round(): IBuilder {
shape = OvalShape()
return this
}
override fun roundRect(radius: Int): IBuilder {
this.radius = radius.toFloat()
val radii = floatArrayOf(
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
)
shape = RoundRectShape(radii, null, null)
return this
}
override fun buildRect(text: String, color: Int): TextDrawable {
rect()
return build(text, color)
}
override fun buildRoundRect(text: String, color: Int, radius: Int): TextDrawable {
roundRect(radius)
return build(text, color)
}
override fun buildRound(text: String?, color: Int): TextDrawable {
round()
return build(text!!, color)
}
override fun build(text: String, color: Int): TextDrawable {
this.color = color
this.text = text
return TextDrawable(this)
}
init {
color = Color.GRAY
iconTextColor = Color.WHITE
borderThickness = 0
iconWidth = -1
iconHeight = -1
shape = RectShape()
font = Typeface.create("sans-serif-light", Typeface.NORMAL)
iconFontSize = -1
isBold = false
iconToUpperCase = false
}
}
interface IConfigBuilder {
fun width(width: Int): IConfigBuilder
fun height(height: Int): IConfigBuilder
fun textColor(color: Int): IConfigBuilder
fun withBorder(thickness: Int): IConfigBuilder
fun useFont(font: Typeface): IConfigBuilder
fun fontSize(size: Int): IConfigBuilder
fun bold(): IConfigBuilder
fun toUpperCase(): IConfigBuilder
fun endConfig(): IShapeBuilder
}
interface IBuilder {
fun build(text: String, color: Int): TextDrawable
}
interface IShapeBuilder {
fun beginConfig(): IConfigBuilder
fun rect(): IBuilder
fun round(): IBuilder
fun roundRect(radius: Int): IBuilder
fun buildRect(text: String, color: Int): TextDrawable
fun buildRoundRect(text: String, color: Int, radius: Int): TextDrawable
fun buildRound(text: String?, color: Int): TextDrawable
}
companion object {
private const val SHADE_FACTOR = 0.9f
fun builder(): IShapeBuilder {
return Builder()
}
}
init {
// shape properties
shape = builder.shape
height = builder.iconHeight
width = builder.iconWidth
radius = builder.radius
// text and color
text = if (builder.iconToUpperCase) builder.text.uppercase(Locale.ROOT) else builder.text
color = builder.color
// text paint settings
fontSize = builder.iconFontSize
textPaint = Paint()
textPaint.color = builder.iconTextColor
textPaint.isAntiAlias = true
textPaint.isFakeBoldText = builder.isBold
textPaint.style = Paint.Style.FILL
textPaint.typeface = builder.font
textPaint.textAlign = Paint.Align.CENTER
textPaint.strokeWidth = builder.borderThickness.toFloat()
// border paint settings
borderThickness = builder.borderThickness
borderPaint = Paint()
borderPaint.color = getDarkerShade(color)
borderPaint.style = Paint.Style.STROKE
borderPaint.strokeWidth = borderThickness.toFloat()
// drawable paint color
val paint = paint
paint.color = color
}
}

View File

@ -1,56 +0,0 @@
package org.mifos.mobile.utils
import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
import com.google.android.material.snackbar.Snackbar
import org.mifos.mobile.MifosSelfServiceApp
object Toaster {
private val snackbarsQueue = ArrayList<Snackbar>()
@JvmOverloads
fun show(view: View?, text: String?, duration: Int = Snackbar.LENGTH_LONG) {
val imm =
MifosSelfServiceApp.context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view?.windowToken, 0)
val snackbar = Snackbar.make(view!!, text!!, duration)
snackbar.setAction("OK") { }
snackbar.addCallback(object : Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar, event: Int) {
super.onDismissed(transientBottomBar, event)
if (snackbarsQueue.isNotEmpty()) {
snackbarsQueue.removeAt(0)
if (snackbarsQueue.isNotEmpty()) {
snackbarsQueue[0].show()
}
}
}
})
snackbarsQueue.add(snackbar)
if (!snackbarsQueue[0].isShown) {
snackbarsQueue[0].show()
}
}
fun show(view: View, res: Int, duration: Int) {
show(view, MifosSelfServiceApp.context?.resources?.getString(res), duration)
}
fun cancelTransfer(
view: View?,
text: String?,
buttonText: String?,
listener: View.OnClickListener?,
) {
val snackbar = Snackbar.make(view!!, text!!, Snackbar.LENGTH_LONG)
snackbar.setAction(buttonText, listener)
snackbar.show()
}
fun show(view: View?, res: Int) {
show(view, MifosSelfServiceApp.context?.resources?.getString(res))
}
}

View File

@ -1,94 +0,0 @@
/**
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mifos.mobile.utils.fcm
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import org.mifos.mobile.R
import org.mifos.mobile.ui.activities.HomeActivity
class MifosFirebaseMessagingService : FirebaseMessagingService() {
// [START receive_message]
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val from = remoteMessage.from
val data = remoteMessage.data
val message = data[getString(R.string.message)]
if ((from?.startsWith("/topics/") == true)) {
// message received from some topic.
} else {
// normal downstream message.
}
sendNotification(message)
val registrationComplete = Intent(org.mifos.mobile.core.common.Constants.NOTIFY_HOME_FRAGMENT)
LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete)
}
// [END receive_message]
/**
* Create and show a simple notification containing the received FCM message.
*
* @param message FCM message received.
*/
private fun sendNotification(message: String?) {
val intent = Intent(this, HomeActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(getString(R.string.notification), true)
val pendingIntent = PendingIntent.getActivity(
this,
0 /* Request code */,
intent,
PendingIntent.FLAG_ONE_SHOT,
)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.core_common_mifos_icon)
.setContentTitle(getString(R.string.feature_about_app_name))
.setContentText(message)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = org.mifos.mobile.core.datastore.model.MifosNotification()
notification.msg = message
notification.timeStamp = System.currentTimeMillis()
notification.setRead(false)
notificationManager.notify(0, notificationBuilder.build())
}
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
override fun onNewToken(s: String) {
super.onNewToken(s)
Log.d(TAG, "Refreshed token: $s")
val intent = Intent(this, RegistrationIntentService::class.java)
startService(intent)
}
companion object {
private val TAG = MifosFirebaseMessagingService::class.java.simpleName
}
}

View File

@ -1,51 +0,0 @@
/**
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mifos.mobile.utils.fcm
import android.app.IntentService
import android.content.Intent
import android.util.Log
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.messaging.FirebaseMessaging
class RegistrationIntentService : IntentService(TAG) {
override fun onHandleIntent(intent: Intent?) {
FirebaseMessaging.getInstance().token
.addOnCompleteListener(
OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(TAG, "getInstanceId failed", task.exception)
return@OnCompleteListener
}
// Get new Instance ID token
val token = task.result
sendRegistrationToServer(token)
},
)
}
private fun sendRegistrationToServer(token: String?) {
val registrationComplete = Intent(org.mifos.mobile.core.common.Constants.REGISTER_ON_SERVER)
registrationComplete.putExtra(org.mifos.mobile.core.common.Constants.TOKEN, token)
LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete)
}
companion object {
private const val TAG = "RegIntentService"
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.mifos.mobile.HomeActivityUiState.Success
import org.mifos.mobile.core.data.utils.NetworkMonitor
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
import org.mifos.mobile.navigation.MifosNavGraph.AUTH_GRAPH
import org.mifos.mobile.navigation.MifosNavGraph.PASSCODE_GRAPH
import org.mifos.mobile.navigation.RootNavGraph
import org.mifos.mobile.ui.rememberMifosMobileState
import javax.inject.Inject
@AndroidEntryPoint
class HomeActivity : ComponentActivity() {
@Inject
lateinit var networkMonitor: NetworkMonitor
private val viewModel: HomeActivityViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
var uiState: HomeActivityUiState by mutableStateOf(HomeActivityUiState.Loading)
// Update the uiState
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState
.onEach { uiState = it }
.collect()
}
}
splashScreen.setKeepOnScreenCondition {
when (uiState) {
HomeActivityUiState.Loading -> true
is Success -> false
}
}
enableEdgeToEdge()
setContent {
val navController = rememberNavController()
val appState = rememberMifosMobileState(networkMonitor = networkMonitor)
val navDestination = when (uiState) {
is Success -> if ((uiState as Success).userData.isAuthenticated) {
PASSCODE_GRAPH
} else {
AUTH_GRAPH
}
else -> AUTH_GRAPH
}
CompositionLocalProvider {
MifosMobileTheme {
RootNavGraph(
appState = appState,
navHostController = navController,
startDestination = navDestination,
onClickLogout = {
viewModel.logOut()
navController.navigate(AUTH_GRAPH) {
popUpTo(navController.graph.id) {
inclusive = true
}
}
},
)
}
}
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.mifos.library.passcode.data.PasscodeManager
import org.mifos.mobile.core.data.repository.UserDataRepository
import org.mifos.mobile.core.model.UserData
import javax.inject.Inject
@HiltViewModel
class HomeActivityViewModel @Inject constructor(
private val userDataRepository: UserDataRepository,
private val passcodeManager: PasscodeManager,
) : ViewModel() {
val uiState: StateFlow<HomeActivityUiState> = userDataRepository.userData.map {
HomeActivityUiState.Success(it)
}.stateIn(
scope = viewModelScope,
initialValue = HomeActivityUiState.Loading,
started = SharingStarted.WhileSubscribed(5_000),
)
fun logOut() {
viewModelScope.launch {
userDataRepository.logOut()
passcodeManager.clearPasscode()
}
}
}
sealed interface HomeActivityUiState {
data object Loading : HomeActivityUiState
data class Success(val userData: UserData) : HomeActivityUiState
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile
import androidx.multidex.MultiDex
import androidx.multidex.MultiDexApplication
import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.hilt.android.HiltAndroidApp
import org.mifos.mobile.core.datastore.PreferencesHelper
import org.mifos.mobile.feature.settings.applySavedTheme
@HiltAndroidApp
class MifosSelfServiceApp : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
MultiDex.install(this)
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)
PreferencesHelper(this).applySavedTheme()
}
}

View File

@ -0,0 +1,17 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.navigation
internal object MifosNavGraph {
const val ROOT_GRAPH = "root_graph"
const val AUTH_GRAPH = "auth_graph"
const val PASSCODE_GRAPH = "passcode_graph"
const val MAIN_GRAPH = "main_graph"
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.navigation
import android.app.Activity
@ -7,16 +16,14 @@ import android.net.Uri
import android.provider.Settings
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.NavOptions
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import com.mifos.compose.component.PasscodeScreen
import org.mifos.mobile.HomeActivity
import org.mifos.mobile.R
import org.mifos.mobile.core.common.Constants.INTIAL_LOGIN
import org.mifos.mobile.core.common.Constants.TRANSFER_PAY_TO
import org.mifos.mobile.core.model.enums.AccountType
import org.mifos.mobile.core.model.enums.ChargeType
@ -24,7 +31,6 @@ import org.mifos.mobile.feature.about.navigation.aboutUsNavGraph
import org.mifos.mobile.feature.about.navigation.navigateToAboutUsScreen
import org.mifos.mobile.feature.account.navigation.clientAccountsNavGraph
import org.mifos.mobile.feature.account.navigation.navigateToClientAccountsScreen
import org.mifos.mobile.feature.auth.navigation.authenticationNavGraph
import org.mifos.mobile.feature.auth.navigation.navigateToLoginScreen
import org.mifos.mobile.feature.beneficiary.navigation.beneficiaryNavGraph
import org.mifos.mobile.feature.beneficiary.navigation.navigateToAddBeneficiaryScreen
@ -37,8 +43,8 @@ import org.mifos.mobile.feature.guarantor.navigation.navigateToGuarantorScreen
import org.mifos.mobile.feature.help.navigation.helpNavGraph
import org.mifos.mobile.feature.help.navigation.navigateToHelpScreen
import org.mifos.mobile.feature.home.navigation.HomeDestinations
import org.mifos.mobile.feature.home.navigation.HomeNavigation
import org.mifos.mobile.feature.home.navigation.homeNavGraph
import org.mifos.mobile.feature.home.navigation.navigateToHomeScreen
import org.mifos.mobile.feature.loan.navigation.loanNavGraph
import org.mifos.mobile.feature.loan.navigation.navigateToLoanApplication
import org.mifos.mobile.feature.loan.navigation.navigateToLoanDetailScreen
@ -62,51 +68,44 @@ import org.mifos.mobile.feature.transaction.navigation.navigateToRecentTransacti
import org.mifos.mobile.feature.transaction.navigation.recentTransactionNavGraph
import org.mifos.mobile.feature.transfer.process.navigation.navigateToTransferProcessScreen
import org.mifos.mobile.feature.transfer.process.navigation.transferProcessNavGraph
import org.mifos.mobile.feature.update_password.navigation.navigateToUpdatePassword
import org.mifos.mobile.feature.update_password.navigation.updatePasswordNavGraph
import org.mifos.mobile.feature.update.password.navigation.navigateToUpdatePassword
import org.mifos.mobile.feature.update.password.navigation.updatePasswordNavGraph
import org.mifos.mobile.feature.user.profile.navigation.navigateToUserProfile
import org.mifos.mobile.feature.user.profile.navigation.userProfileNavGraph
import org.mifos.mobile.ui.activities.HomeActivity
import org.mifos.mobile.ui.activities.PassCodeActivity
@Composable
fun RootNavGraph(
startDestination: String,
navController: NavHostController,
fun MifosNavHost(
onClickLogout: () -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = startDestination,
route = MifosNavGraph.MAIN_GRAPH,
startDestination = HomeNavigation.HomeBase.route,
modifier = modifier,
) {
homeNavGraph(
onNavigate = { handleHomeNavigation(navController, it, context) },
onNavigate = { handleHomeNavigation(navController, it, onClickLogout, context) },
callHelpline = { callHelpline(context) },
mailHelpline = { mailHelpline(context) }
mailHelpline = { mailHelpline(context) },
)
authenticationNavGraph(
navController = navController,
navigateToPasscodeScreen = { startPassCodeActivity(context = context) }
)
guarantorNavGraph(
navController = navController,
)
guarantorNavGraph(navController = navController)
loanNavGraph(
navController = navController,
viewQr = navController::navigateToQrDisplayScreen,
viewGuarantor = navController::navigateToGuarantorScreen,
viewCharges = navController::navigateToClientChargeScreen,
makePayment = navController::navigateToSavingsMakeTransfer
makePayment = navController::navigateToSavingsMakeTransfer,
)
userProfileNavGraph(
navigateBack = navController::popBackStack,
navigateToChangePassword = navController::navigateToUpdatePassword
navigateToChangePassword = navController::navigateToUpdatePassword,
)
updatePasswordNavGraph(navigateBack = navController::popBackStack)
@ -114,24 +113,20 @@ fun RootNavGraph(
thirdPartyTransferNavGraph(
navigateBack = navController::popBackStack,
addBeneficiary = navController::navigateToAddBeneficiaryScreen,
reviewTransfer = navController::navigateToTransferProcessScreen
reviewTransfer = navController::navigateToTransferProcessScreen,
)
settingsNavGraph(
navigateBack = navController::popBackStack,
changePassword = navController::navigateToUpdatePassword,
changePasscode = {}, // { navigateToUpdatePasscodeActivity(it, context) },
changePasscode = {},
navigateToLoginScreen = navController::navigateToLoginScreen,
languageChanged = { startActivity(context, HomeActivity::class.java) }
languageChanged = { startActivity(context, HomeActivity::class.java) },
)
recentTransactionNavGraph(
navigateBack = navController::popBackStack
)
recentTransactionNavGraph(navigateBack = navController::popBackStack)
notificationNavGraph(
navigateBack = navController::popBackStack
)
notificationNavGraph(navigateBack = navController::popBackStack)
locationsNavGraph()
@ -139,16 +134,16 @@ fun RootNavGraph(
findLocations = navController::navigateToLocationsScreen,
navigateBack = navController::popBackStack,
callHelpline = { callHelpline(context) },
mailHelpline = { mailHelpline(context) }
mailHelpline = { mailHelpline(context) },
)
clientChargeNavGraph(
navigateBack = navController::popBackStack
)
clientChargeNavGraph(navigateBack = navController::popBackStack)
aboutUsNavGraph(
navController = navController,
navigateToOssLicense = { startActivity(context, OssLicensesMenuActivity::class.java) }
navigateToOssLicense = {
context.startActivity(Intent(context, OssLicensesMenuActivity::class.java))
},
)
transferProcessNavGraph(navigateBack = navController::popBackStack)
@ -156,7 +151,7 @@ fun RootNavGraph(
beneficiaryNavGraph(
navController = navController,
openQrImportScreen = navController::navigateToQrImportScreen,
openQrReaderScreen = navController::navigateToQrReaderScreen
openQrReaderScreen = navController::navigateToQrReaderScreen,
)
qrNavGraph(
@ -169,7 +164,7 @@ fun RootNavGraph(
viewCharges = navController::navigateToClientChargeScreen,
viewQrCode = navController::navigateToQrDisplayScreen,
callHelpline = { callHelpline(context) },
reviewTransfer = navController::navigateToTransferProcessScreen
reviewTransfer = navController::navigateToTransferProcessScreen,
)
clientAccountsNavGraph(
@ -182,38 +177,28 @@ fun RootNavGraph(
AccountType.LOAN -> navController.navigateToLoanDetailScreen(loanId = id)
AccountType.SHARE -> {}
}
}
},
)
composable(PASSCODE_SCREEN) {
PasscodeScreen(
onForgotButton = navController::navigateToHomeScreen,
onSkipButton = navController::navigateToHomeScreen,
onPasscodeConfirm = {
navController.navigateToHomeScreen()
},
onPasscodeRejected = {}
)
}
}
}
const val PASSCODE_SCREEN = "passcode_screen"
fun NavController.navigateToPasscodeScreen(navOptions: NavOptions? = null) {
return this.navigate(PASSCODE_SCREEN, navOptions)
}
fun handleHomeNavigation(
navController: NavHostController,
homeDestinations: HomeDestinations,
context: Context
onClickLogout: () -> Unit,
context: Context,
) {
when (homeDestinations) {
HomeDestinations.HOME -> Unit
HomeDestinations.ACCOUNTS -> navController.navigateToClientAccountsScreen()
HomeDestinations.LOAN_ACCOUNT -> navController.navigateToClientAccountsScreen(accountType = AccountType.LOAN)
HomeDestinations.SAVINGS_ACCOUNT -> navController.navigateToClientAccountsScreen(accountType = AccountType.SAVINGS)
HomeDestinations.LOAN_ACCOUNT -> {
navController.navigateToClientAccountsScreen(accountType = AccountType.LOAN)
}
HomeDestinations.SAVINGS_ACCOUNT -> {
navController.navigateToClientAccountsScreen(accountType = AccountType.SAVINGS)
}
HomeDestinations.RECENT_TRANSACTIONS -> navController.navigateToRecentTransaction()
HomeDestinations.CHARGES -> navController.navigateToClientChargeScreen(ChargeType.CLIENT)
HomeDestinations.THIRD_PARTY_TRANSFER -> navController.navigateToThirdPartyTransfer()
@ -228,10 +213,10 @@ fun handleHomeNavigation(
openAppInfo(context)
}
HomeDestinations.LOGOUT -> navController.navigateToLoginScreen()
HomeDestinations.LOGOUT -> onClickLogout.invoke()
HomeDestinations.TRANSFER -> navController.navigateToSavingsMakeTransfer(
accountId = 1,
transferType = TRANSFER_PAY_TO
transferType = TRANSFER_PAY_TO,
)
HomeDestinations.BENEFICIARIES -> navController.navigateToBeneficiaryListScreen()
@ -245,20 +230,6 @@ fun <T : Activity> startActivity(context: Context, clazz: Class<T>) {
context.startActivity(Intent(context, clazz))
}
private fun startPassCodeActivity(context: Context) {
val intent = Intent(context, PassCodeActivity::class.java)
intent.putExtra(INTIAL_LOGIN, true)
context.startActivity(intent)
}
//
//private fun navigateToUpdatePasscodeActivity(passcode: String, context: Context) {
// val intent = Intent(context, PassCodeActivity::class.java).apply {
// putExtra(CURR_PASSWORD, passcode)
// putExtra(IS_TO_UPDATE_PASS_CODE, true)s
// }
// context.startActivity(intent)ss
//}
private fun callHelpline(context: Context) {
val intent = Intent(Intent.ACTION_DIAL)
intent.data =
@ -271,11 +242,11 @@ private fun mailHelpline(context: Context) {
data = Uri.parse("mailto:")
putExtra(
Intent.EXTRA_EMAIL,
arrayOf(context.getString(org.mifos.mobile.feature.home.R.string.contact_email))
arrayOf(context.getString(org.mifos.mobile.feature.home.R.string.contact_email)),
)
putExtra(
Intent.EXTRA_SUBJECT,
context.getString(org.mifos.mobile.feature.home.R.string.user_query)
context.getString(org.mifos.mobile.feature.home.R.string.user_query),
)
}
try {

View File

@ -0,0 +1,42 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.navigation
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.navigation
import org.mifos.library.passcode.PASSCODE_SCREEN
import org.mifos.library.passcode.passcodeRoute
internal fun NavGraphBuilder.passcodeNavGraph(navController: NavHostController) {
navigation(
route = MifosNavGraph.PASSCODE_GRAPH,
startDestination = PASSCODE_SCREEN,
) {
passcodeRoute(
onForgotButton = {
navController.popBackStack()
navController.navigate(MifosNavGraph.MAIN_GRAPH)
},
onSkipButton = {
navController.popBackStack()
navController.navigate(MifosNavGraph.MAIN_GRAPH)
},
onPasscodeConfirm = {
navController.popBackStack()
navController.navigate(MifosNavGraph.MAIN_GRAPH)
},
onPasscodeRejected = {
navController.popBackStack()
navController.navigate(MifosNavGraph.MAIN_GRAPH)
},
)
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.navigation
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import org.mifos.library.passcode.navigateToPasscodeScreen
import org.mifos.mobile.feature.auth.navigation.authenticationNavGraph
import org.mifos.mobile.navigation.MifosNavGraph.AUTH_GRAPH
import org.mifos.mobile.ui.MifosApp
import org.mifos.mobile.ui.MifosMobileState
@Composable
internal fun RootNavGraph(
appState: MifosMobileState,
navHostController: NavHostController,
startDestination: String,
onClickLogout: () -> Unit,
modifier: Modifier = Modifier,
) {
NavHost(
navController = navHostController,
startDestination = startDestination,
route = MifosNavGraph.ROOT_GRAPH,
modifier = modifier,
) {
authenticationNavGraph(
navController = navHostController,
route = AUTH_GRAPH,
navigateToPasscodeScreen = navHostController::navigateToPasscodeScreen,
)
passcodeNavGraph(navHostController)
composable(MifosNavGraph.MAIN_GRAPH) {
MifosApp(
appState = appState,
onClickLogout = onClickLogout,
)
}
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration.Indefinite
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.mifos.mobile.R
import org.mifos.mobile.core.designsystem.theme.MifosBackground
import org.mifos.mobile.navigation.MifosNavHost
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun MifosApp(
appState: MifosMobileState,
onClickLogout: () -> Unit,
modifier: Modifier = Modifier,
) {
MifosBackground(modifier) {
val snackbarHostState = remember { SnackbarHostState() }
val isOffline by appState.isOffline.collectAsStateWithLifecycle()
// If user is not connected to the internet show a snack bar to inform them.
val notConnectedMessage = stringResource(R.string.not_connected)
LaunchedEffect(isOffline) {
if (isOffline) {
snackbarHostState.showSnackbar(
message = notConnectedMessage,
duration = Indefinite,
)
}
}
Scaffold(
modifier = Modifier.semantics {
testTagsAsResourceId = true
},
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,
snackbarHost = { SnackbarHost(snackbarHostState) },
) { padding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.consumeWindowInsets(padding)
.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal,
),
),
) {
MifosNavHost(
onClickLogout = onClickLogout,
modifier = Modifier,
)
}
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.mifos.mobile.core.data.utils.NetworkMonitor
@Composable
fun rememberMifosMobileState(
networkMonitor: NetworkMonitor,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
navController: NavHostController = rememberNavController(),
): MifosMobileState {
return remember(
navController,
coroutineScope,
networkMonitor,
) {
MifosMobileState(
navController = navController,
coroutineScope = coroutineScope,
networkMonitor = networkMonitor,
)
}
}
@Stable
class MifosMobileState(
val navController: NavHostController,
coroutineScope: CoroutineScope,
networkMonitor: NetworkMonitor,
) {
val currentDestination: NavDestination?
@Composable get() = navController
.currentBackStackEntryAsState().value?.destination
val isOffline = networkMonitor.isOnline
.map(Boolean::not)
.stateIn(
scope = coroutineScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = false,
)
}

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:ordering="sequentially"
android:shareInterpolator="false">
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="pathData"
android:valueFrom="M6,11 l0,0 l0,0"
android:valueTo="M6,11 l3.5,4 l0,0"
android:valueType="pathType" />
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="pathData"
android:valueFrom="M6,11 l3.5,4 l0,0"
android:valueTo="M6,11 l3.5,4 l8,-7"
android:valueType="pathType" />
</set>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/gray_dark"
android:pathData="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2 0,0 1,2 0H6V2H2M22,0A2,2 0,0 1,24 2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0,0 1,0 22V18H2M22,22V18H24V22A2,2 0,0 1,22 24H18V22H22Z" />
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_check_circle_green_24px">
<target
android:name="tick"
android:animation="@anim/check_animation" />
</animated-vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_check_circle_green_24px">
<target android:name="tick" />
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M4,10v7h3v-7L4,10zM10,10v7h3v-7h-3zM2,22h19v-3L2,19v3zM16,10v7h3v-7h-3zM11.5,1L2,6v2h19L21,6l-9.5,-5z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M21,18v1c0,1.1 -0.9,2 -2,2L5,21c-1.11,0 -2,-0.9 -2,-2L3,5c0,-1.1 0.89,-2 2,-2h14c1.1,0 2,0.9 2,2v1h-9c-1.11,0 -2,0.9 -2,2v8c0,1.1 0.89,2 2,2h9zM12,16h10L22,8L12,8v8zM16,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/gray_dark"
android:pathData="M21,18v1c0,1.1 -0.9,2 -2,2L5,21c-1.11,0 -2,-0.9 -2,-2L3,5c0,-1.1 0.89,-2 2,-2h14c1.1,0 2,0.9 2,2v1h-9c-1.11,0 -2,0.9 -2,2v8c0,1.1 0.89,2 2,2h9zM12,16h10L22,8L12,8v8zM16,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M7,10l5,5 5,-5z" />
</vector>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_arrow_drop_down"
android:fromDegrees="180"
android:toDegrees="0" />

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM10,17l-4,-4 1.41,-1.41L10,14.17l6.59,-6.59L18,9l-8,8z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M44,6L14,6c-1.38,0 -2.47,0.7 -3.19,1.76L0,23.99l10.81,16.23C11.53,41.28 12.62,42 14,42h30c2.21,0 4,-1.79 4,-4L48,10c0,-2.21 -1.79,-4 -4,-4zM38,31.17L35.17,34 28,26.83 20.83,34 18,31.17 25.17,24 18,16.83 20.83,14 28,21.17 35.17,14 38,16.83 30.83,24 38,31.17z" />
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4c-1.48,0 -2.85,0.43 -4.01,1.17l1.46,1.46C10.21,6.23 11.08,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3 0,1.13 -0.64,2.11 -1.56,2.62l1.45,1.45C23.16,18.16 24,16.68 24,15c0,-2.64 -2.05,-4.78 -4.65,-4.96zM3,5.27l2.75,2.74C2.56,8.15 0,10.77 0,14c0,3.31 2.69,6 6,6h11.73l2,2L21,20.73 4.27,4 3,5.27zM7.73,10l8,8H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h1.73z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9s9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36c-0.98,1.37 -2.58,2.26 -4.4,2.26c-2.98,0 -5.4,-2.42 -5.4,-5.4c0,-1.81 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3L12,3z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M33,24c2.76,0 4.98,-2.24 4.98,-5s-2.22,-5 -4.98,-5c-2.76,0 -5,2.24 -5,5s2.24,5 5,5zM18,22c3.31,0 5.98,-2.69 5.98,-6s-2.67,-6 -5.98,-6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6zM33,28c-3.67,0 -11,1.84 -11,5.5L22,38h22v-4.5c0,-3.66 -7.33,-5.5 -11,-5.5zM18,26c-4.67,0 -14,2.34 -14,7v5h14v-4.5c0,-1.7 0.67,-4.67 4.74,-6.94C21,26.19 19.31,26 18,26z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="@color/gray_dark"
android:pathData="M28,4L12,4C9.79,4 8.02,5.79 8.02,8L8,40c0,2.21 1.77,4 3.98,4L36,44c2.21,0 4,-1.79 4,-4L40,16L28,4zM32,32h-6v6h-4v-6h-6v-4h6v-6h4v6h6v4zM26,18L26,7l11,11L26,18z" />
</vector>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_beneficiaries_48px" />
</layer-list>

View File

@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M17.75,7L14,3.25l-10,10L4,17h3.75l10,-10zM20.71,4.04c0.39,-0.39 0.39,-1.02 0,-1.41L18.37,0.29c-0.39,-0.39 -1.02,-0.39 -1.41,0L15,2.25 18.75,6l1.96,-1.96z" />
<path
android:fillAlpha=".36"
android:fillColor="#FF000000"
android:pathData="M0,20h24v4H0z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M12,6c1.11,0 2,-0.9 2,-2 0,-0.38 -0.1,-0.73 -0.29,-1.03L12,0l-1.71,2.97c-0.19,0.3 -0.29,0.65 -0.29,1.03 0,1.1 0.9,2 2,2zM16.6,15.99l-1.07,-1.07 -1.08,1.07c-1.3,1.3 -3.58,1.31 -4.89,0l-1.07,-1.07 -1.09,1.07C6.75,16.64 5.88,17 4.96,17c-0.73,0 -1.4,-0.23 -1.96,-0.61L3,21c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-4.61c-0.56,0.38 -1.23,0.61 -1.96,0.61 -0.92,0 -1.79,-0.36 -2.44,-1.01zM18,9h-5L13,7h-2v2L6,9c-1.66,0 -3,1.34 -3,3v1.54c0,1.08 0.88,1.96 1.96,1.96 0.52,0 1.02,-0.2 1.38,-0.57l2.14,-2.13 2.13,2.13c0.74,0.74 2.03,0.74 2.77,0l2.14,-2.13 2.13,2.13c0.37,0.37 0.86,0.57 1.38,0.57 1.08,0 1.96,-0.88 1.96,-1.96L20.99,12C21,10.34 19.66,9 18,9z" />
</vector>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_cake_24dp" />
</layer-list>

View File

@ -1,46 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="147.0"
android:viewportHeight="146.0">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M89.2,96.26C89.66,97.79 89.9,99.37 89.9,100.98C89.9,115.77 69.78,127.75 44.95,127.75C20.12,127.75 0,115.77 0,100.98C0,99.37 0.24,97.79 0.7,96.26C4.44,107.94 22.83,116.8 44.95,116.8C67.07,116.8 85.46,107.94 89.2,96.26Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M89.2,114.51C89.66,116.04 89.9,117.62 89.9,119.23C89.9,134.02 69.78,146 44.95,146C20.12,146 0,134.02 0,119.23C0,117.62 0.24,116.04 0.7,114.51C4.44,126.19 22.83,135.05 44.95,135.05C67.07,135.05 85.46,126.19 89.2,114.51Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M0,86.99a44.95,24.94 0,1 0,89.9 0a44.95,24.94 0,1 0,-89.9 0z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M102.05,49.88C126.88,49.88 147,38.72 147,24.94C147,11.17 126.88,0 102.05,0C77.22,0 57.1,11.17 57.1,24.94C57.1,38.72 77.22,49.88 102.05,49.88Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M85.13,64.96C90.35,66.22 96.06,66.92 102.05,66.92C126.88,66.92 147,54.93 147,40.15C147,38.54 146.76,36.96 146.3,35.43C142.56,47.1 124.17,55.97 102.05,55.97C79.93,55.97 61.54,47.1 57.8,35.43C57.34,36.96 57.1,38.54 57.1,40.15C57.1,46.64 60.98,52.59 67.43,57.23C73.33,58.16 79.62,60.38 85.13,64.96Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M96.05,82.5C98.01,82.65 100.01,82.73 102.05,82.73C126.88,82.73 147,70.75 147,55.97C147,54.35 146.76,52.78 146.3,51.24C142.56,62.92 124.17,71.78 102.05,71.78C98.17,71.78 94.41,71.51 90.82,71C92.77,73.62 94.51,76.7 95.98,80.3L96.05,82.5Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M96.6,99.57C98.38,99.7 100.2,99.77 102.05,99.77C126.88,99.77 147,87.78 147,73C147,71.39 146.76,69.81 146.3,68.28C142.56,79.95 124.17,88.82 102.05,88.82C100.08,88.82 98.14,88.75 96.24,88.61L96.6,99.57Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M97.15,116.64C98.76,116.75 100.39,116.8 102.05,116.8C126.88,116.8 147,104.82 147,90.03C147,88.42 146.76,86.84 146.3,85.31C142.56,96.99 124.17,105.85 102.05,105.85C100.27,105.85 98.52,105.79 96.79,105.68L97.15,116.64Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -1,20 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<group android:name="background">
<path
android:name="circle"
android:fillColor="#4CAF50"
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0" />
</group>
<group android:name="check">
<path
android:name="tick"
android:pathData="M6,11 l0,0 l0,0"
android:strokeWidth="1"
android:strokeColor="@color/white" />
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1001 B

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3zM14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4z" />
</vector>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFF"
android:pathData="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c0.39-0.39 0.39 -1.02 0-1.41l-2.34-2.34c-0.39-0.39-1.02-0.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
<path android:pathData="M0 0h24v24H0z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/gray_dark"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_edit_black_24dp" />
</selector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z" />
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.93"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/gray_dark"
android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z" />
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.8"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M3.27,3L2,4.27l5,5V13h3v9l3.58,-6.14L17.73,20 19,18.73 3.27,3zM17,10h-4l4,-8H7v2.18l8.46,8.46L17,10z" />
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.8"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z" />
</vector>

View File

@ -1,45 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m266.859,466.778h-21.719c-3.952,0 -7.155,-3.203 -7.155,-7.155v-407.247c0,-3.952 3.203,-7.155 7.155,-7.155h21.719c3.952,0 7.155,3.203 7.155,7.155v407.247c0,3.952 -3.203,7.155 -7.155,7.155z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m266.859,466.778h-10.859v-421.557h10.859c3.952,0 7.155,3.203 7.155,7.155v407.247c0,3.952 -3.203,7.155 -7.155,7.155z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M423.74,80.381m-60.946,0a60.946,60.946 0,1 1,121.892 0a60.946,60.946 0,1 1,-121.892 0" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m511.914,326.904 l-15.196,-128.529c-1.537,-13 -12.557,-22.794 -25.647,-22.794h-94.663c-13.09,0 -24.11,9.794 -25.647,22.794l-15.196,128.529c-0.857,7.248 4.805,13.618 12.104,13.618l21.612,0.081 -0.567,5.1 -3.536,31.799c-0.401,3.61 2.424,6.767 6.057,6.767h0.674l8.044,97.113c0.523,6.319 5.805,11.182 12.146,11.182h63.283c6.341,0 11.623,-4.862 12.146,-11.182l8.044,-97.113h0.674c3.632,0 6.458,-3.157 6.057,-6.767l-3.536,-31.799 -0.573,-5.181h21.618c7.297,0.001 12.959,-6.37 12.102,-13.618z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m467.197,241.673c-0.686,-6.172 -5.903,-10.841 -12.113,-10.841h-31.344,-31.344c-6.21,0 -11.427,4.669 -12.113,10.841l-15.105,135.829c-0.401,3.61 2.424,6.767 6.057,6.767h52.506,52.506c3.632,0 6.458,-3.157 6.057,-6.767z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m476.25,384.275h-18.605c3.628,0 6.45,-3.163 6.057,-6.77l-15.111,-135.836c-0.682,-6.171 -5.902,-10.832 -12.114,-10.832h18.605c6.212,0 11.432,4.662 12.114,10.832l15.111,135.836c0.393,3.607 -2.429,6.77 -6.057,6.77z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m484.684,80.376c0,33.664 -27.287,60.951 -60.941,60.951 -3.525,0 -6.977,-0.3 -10.336,-0.878 28.734,-4.9 50.605,-29.933 50.605,-60.073 0,-30.129 -21.871,-55.163 -50.605,-60.062 3.359,-0.579 6.811,-0.879 10.336,-0.879 33.654,0 60.941,27.287 60.941,60.941z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m475.568,384.275 l-8.041,97.107c-0.527,6.326 -5.809,11.184 -12.145,11.184h-20.672c6.336,0 11.618,-4.858 12.145,-11.184l8.041,-97.107z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m499.806,340.523h-20.61c7.277,-0.041 12.899,-6.388 12.042,-13.623l-15.194,-128.528c-1.53,-12.992 -12.558,-22.791 -25.644,-22.791h20.672c13.085,0 24.114,9.799 25.644,22.791l15.193,128.528c0.858,7.256 -4.796,13.623 -12.103,13.623z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M89.783,80.381m-60.946,0a60.946,60.946 0,1 1,121.892 0a60.946,60.946 0,1 1,-121.892 0" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m153.745,175.581h-127.924c-14.26,0 -25.821,11.561 -25.821,25.821v126.932c0,6.731 5.457,12.188 12.188,12.188h22.141v3.667,136.187c0,6.731 5.457,12.188 12.188,12.188h17.063c6.731,0 12.188,-5.457 12.188,-12.188v-82.322c0,-7.613 6.171,-13.784 13.784,-13.784h0.463c7.613,0 13.784,6.172 13.784,13.784v82.322c0,6.731 5.457,12.188 12.188,12.188h17.063c6.731,0 12.188,-5.457 12.188,-12.188v-136.186,-3.667h22.141c6.731,0 12.188,-5.456 12.188,-12.188v-126.933c0,-14.26 -11.561,-25.821 -25.822,-25.821z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m150.728,80.376c0,33.664 -27.287,60.951 -60.941,60.951 -3.886,0 -7.69,-0.362 -11.37,-1.065 28.217,-5.323 49.571,-30.109 49.571,-59.887 0,-29.768 -21.354,-54.553 -49.571,-59.876 3.68,-0.703 7.483,-1.065 11.37,-1.065 33.654,0.001 60.941,27.288 60.941,60.942z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="m179.565,201.4v126.936c0,6.729 -5.457,12.186 -12.186,12.186h-22.14s11.587,-5.457 11.587,-12.186v-126.936c0,-14.253 -11.556,-25.819 -25.819,-25.819h22.739c14.264,0 25.819,11.566 25.819,25.819z" />
</vector>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_gender_24dp" />
</layer-list>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16z" />
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="192.0"
android:viewportHeight="192.0">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M133,80.33L133,76L125,76L125,79.69C120.18,80.93 114.15,84.37 118,94C118,94 120,99 132,100C132,100 141,105 131,107C131,107 125,106 124,103L117,103L117,114L124,114L124,111.27C124.62,111.67 125.29,112.06 126,112.42L126,117L133,117L133,113.87C136.05,113.51 139.22,111.64 142,107C142,107 145,97 134,94C123,91 124,92 124,88C124,84 132,87 132,87L135,90L142,90L142,79L135,79L135,81.22L133,80.33ZM169.68,86.67C172.38,91.25 174,97.11 174,103.5C174,107.63 173.32,111.53 172.12,115.01C191.98,117.09 188,139 188,139C188,139 173,160 154,175C135,190 99,192 83,180C67,168 55,171 55,171L54,182L7,182C2,172 6,105 6,105L53,105L54,110C64.11,105.28 76.95,106.47 84.24,107.75C84.08,106.36 84,104.95 84,103.5C84,96.27 86.08,89.71 89.45,84.93C91.73,80.16 102.29,58.75 111.27,52.47C111.27,52.47 131.79,57.53 149,52.47C149,52.47 162.9,71.03 169.68,86.67ZM56.37,115.19C56.37,115.19 100.02,103.28 135.73,135.03C135.73,135.03 141.68,154.86 118.87,148.91C96.05,142.96 101.01,142.96 101.01,142.96L98.03,150.9C98.03,150.9 136.72,171.73 143.66,146.93L168,123C168,123 172.06,117.07 180,126C187.94,134.93 176,142 176,142L155.57,162.8C155.57,162.8 126.8,196.53 81.17,170.74C81.17,170.74 62.32,159.82 53.4,162.8C53.4,162.8 54.39,118.16 52.4,117.17C50.42,116.18 56.37,115.19 56.37,115.19ZM107,27C109.5,35.73 117,48 117,48C117,48 132,53 148,48C148,48 165,24 154,12C154,12 150,9 144,15C138,21 135,22 135,22C135,22 133,23 130,22C130,22 114,10 109,14C106.76,15.79 104.96,19.86 107,27Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11,17h2v-1h1c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1h-3v-1h4L15,8h-2L13,7h-2v1h-1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1h3v1L9,14v2h2v1zM20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18L4,6h16v12z" />
</vector>

Some files were not shown because too many files have changed in this diff Show More