mirror of
https://github.com/hmalik144/EasyCC_Master.git
synced 2025-12-10 03:05:29 +00:00
Update gradle dependencies (#7)
- Synthetic views replaced with latest view binding - Hilt DI replaces kodein - Viewmodel factories removed - Updated to latest android version - Migrated to Hilt instrumentation testing
This commit is contained in:
@@ -1,36 +1,53 @@
|
||||
version: 2
|
||||
# Use the latest 2.1 version of CircleCI pipeline process engine.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference
|
||||
# For a detailed guide to building and testing on Android, read the docs:
|
||||
# https://circleci.com/docs/2.0/language-android/ for more details.
|
||||
version: 2.1
|
||||
|
||||
# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.
|
||||
# See: https://circleci.com/docs/2.0/orb-intro/
|
||||
orbs:
|
||||
android: circleci/android@1.0.3
|
||||
|
||||
# Define a job to be invoked later in a workflow.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
||||
jobs:
|
||||
build:
|
||||
working_directory: ~/code
|
||||
docker:
|
||||
- image: circleci/android:api-29
|
||||
environment:
|
||||
- CIRCLE_COMPARE_URL: https://github.com/hmalik144/EasyCC_Master/compare/7e995468c9fdc5528a6d1a5489ba301bb9f14c00...14293254ec6d68b93bba50eea79df8808aec9de9
|
||||
- JVM_OPTS: -Xmx3200m
|
||||
# Below is the definition of your job to build and test your app, you can rename and customize it as you want.
|
||||
build-and-test:
|
||||
# These next lines define the Android machine image executor.
|
||||
# See: https://circleci.com/docs/2.0/executor-types/
|
||||
executor:
|
||||
name: android/android-machine
|
||||
|
||||
# Add steps to the job
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
|
||||
- run:
|
||||
name: Chmod permissions
|
||||
command: sudo chmod +x ./gradlew
|
||||
- run:
|
||||
name: Download Dependencies
|
||||
command: ./gradlew androidDependencies
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.gradle
|
||||
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
|
||||
- run:
|
||||
name: Run Tests
|
||||
command: ./gradlew lint test
|
||||
- store_artifacts:
|
||||
path: app/build/reports
|
||||
destination: reports
|
||||
- store_test_results:
|
||||
path: app/build/test-results
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
|
||||
- run:
|
||||
name: Chmod permissions
|
||||
command: sudo chmod +x ./gradlew
|
||||
- run:
|
||||
name: Download Dependencies
|
||||
command: ./gradlew androidDependencies
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.gradle
|
||||
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
|
||||
- run:
|
||||
name: Run Tests
|
||||
command: ./gradlew lint test
|
||||
- store_artifacts:
|
||||
path: app/build/reports
|
||||
destination: reports
|
||||
- store_test_results:
|
||||
path: app/build/test-results
|
||||
|
||||
# Invoke jobs via workflows
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
|
||||
workflows:
|
||||
version: 2
|
||||
workflow:
|
||||
sample: # This is the name of the workflow, feel free to change it to better match your workflow.
|
||||
# Inside the workflow, you define the jobs you want to run.
|
||||
jobs:
|
||||
- build
|
||||
- build-and-test
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.dagger.hilt.android'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 32
|
||||
defaultConfig {
|
||||
applicationId "com.appttude.h_mal.easycc"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
targetSdkVersion 32
|
||||
versionCode 5
|
||||
versionName "4.1"
|
||||
|
||||
@@ -20,35 +22,36 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
viewBinding.enabled = true
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0-beta02'
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'androidx.annotation:annotation:1.4.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||
implementation 'androidx.activity:activity-ktx:1.5.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-test:1.7.10'
|
||||
|
||||
// Coroutines
|
||||
def coroutines_version = "1.6.2"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
||||
|
||||
//Retrofit and GSON
|
||||
def retrofit_ver = "2.8.1"
|
||||
@@ -56,32 +59,27 @@ dependencies {
|
||||
implementation "com.squareup.retrofit2:converter-gson:$retrofit_ver"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:4.9.0"
|
||||
|
||||
//Kotlin Coroutines
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
|
||||
|
||||
// ViewModel and LiveData
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
||||
|
||||
//New Material Design
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha10'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
|
||||
//Kodein Dependency Injection
|
||||
implementation "org.kodein.di:kodein-di-generic-jvm:6.2.1"
|
||||
implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1"
|
||||
// Hilt dependency injection
|
||||
def hilt_ver = "2.43.2"
|
||||
implementation "com.google.dagger:hilt-android:$hilt_ver"
|
||||
kapt "com.google.dagger:hilt-compiler:$hilt_ver"
|
||||
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_ver"
|
||||
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_ver"
|
||||
|
||||
//mockito and livedata testing
|
||||
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||
testImplementation 'androidx.arch.core:core-testing:2.1.0'
|
||||
|
||||
//Android Room
|
||||
implementation "androidx.room:room-runtime:2.2.0-rc01"
|
||||
implementation "androidx.room:room-ktx:2.2.0-rc01"
|
||||
kapt "androidx.room:room-compiler:2.2.0-rc01"
|
||||
|
||||
implementation "androidx.preference:preference-ktx:1.1.0"
|
||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||
|
||||
//mock websever for testing retrofit responses
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"
|
||||
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-test:1.7.10"
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.appttude.h_mal.easycc.application
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
import com.appttude.h_mal.easycc.application.modules.MockRepository
|
||||
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
|
||||
import com.appttude.h_mal.easycc.ui.main.MainViewModelFactory
|
||||
import org.kodein.di.Kodein
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.x.androidXModule
|
||||
import org.kodein.di.generic.bind
|
||||
import org.kodein.di.generic.instance
|
||||
import org.kodein.di.generic.provider
|
||||
import org.kodein.di.generic.singleton
|
||||
|
||||
class TestApplication : Application(), KodeinAware {
|
||||
|
||||
companion object {
|
||||
val idlingResources = CountingIdlingResource("Data_loader")
|
||||
}
|
||||
|
||||
// KODEIN DI components declaration
|
||||
override val kodein by Kodein.lazy {
|
||||
import(androidXModule(this@TestApplication))
|
||||
|
||||
bind() from singleton { MockRepository() }
|
||||
bind() from singleton { CurrencyDataHelper(instance()) }
|
||||
bind() from provider { MainViewModelFactory(instance(), instance()) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,11 +2,18 @@ package com.appttude.h_mal.easycc.application
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
|
||||
import androidx.test.runner.AndroidJUnitRunner
|
||||
import dagger.hilt.android.testing.HiltTestApplication
|
||||
|
||||
|
||||
class TestRunner : AndroidJUnitRunner() {
|
||||
|
||||
companion object {
|
||||
val idlingResources = CountingIdlingResource("Data_loader")
|
||||
}
|
||||
|
||||
@Throws(
|
||||
InstantiationException::class,
|
||||
IllegalAccessException::class,
|
||||
@@ -17,6 +24,6 @@ class TestRunner : AndroidJUnitRunner() {
|
||||
className: String?,
|
||||
context: Context?
|
||||
): Application {
|
||||
return super.newApplication(cl, TestApplication::class.java.name, context)
|
||||
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.appttude.h_mal.easycc.application.modules
|
||||
|
||||
import com.appttude.h_mal.easycc.application.TestApplication.Companion.idlingResources
|
||||
import com.appttude.h_mal.easycc.application.TestRunner.Companion.idlingResources
|
||||
import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
|
||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.models.CurrencyObject
|
||||
import kotlinx.coroutines.delay
|
||||
import javax.inject.Inject
|
||||
|
||||
class MockRepository : Repository {
|
||||
class MockRepository @Inject constructor() : Repository {
|
||||
|
||||
override suspend fun getDataFromApi(fromCurrency: String, toCurrency: String): ResponseObject {
|
||||
idlingResources.increment()
|
||||
|
||||
@@ -18,11 +18,11 @@ fun currencyRobot(func: CurrencyRobot.() -> Unit) = CurrencyRobot()
|
||||
class CurrencyRobot {
|
||||
|
||||
fun clickOnTopList() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.currency_one)).perform(ViewActions.click())
|
||||
Espresso.onView(ViewMatchers.withId(R.id.currencyOne)).perform(ViewActions.click())
|
||||
}
|
||||
|
||||
fun clickOnBottomList() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.currency_two)).perform(ViewActions.click())
|
||||
Espresso.onView(ViewMatchers.withId(R.id.currencyTwo)).perform(ViewActions.click())
|
||||
}
|
||||
|
||||
fun searchInCurrencyList(search: String) {
|
||||
|
||||
@@ -2,23 +2,47 @@
|
||||
|
||||
package com.appttude.h_mal.easycc.ui.main
|
||||
|
||||
|
||||
import androidx.test.filters.LargeTest
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.appttude.h_mal.easycc.application.modules.MockRepository
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
|
||||
import com.appttude.h_mal.easycc.di.AppModule
|
||||
import com.appttude.h_mal.easycc.robots.currencyRobot
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.UninstallModules
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@HiltAndroidTest
|
||||
@UninstallModules(AppModule::class)
|
||||
class MainActivityTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule(order = 0)
|
||||
var hiltAndroidRule = HiltAndroidRule(this)
|
||||
|
||||
@get:Rule(order = 1)
|
||||
var mActivityTestRule = ActivityTestRule(MainActivity::class.java)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
hiltAndroidRule.inject()
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object TestAppModule {
|
||||
@Provides
|
||||
fun provideRepository(impl: MockRepository): Repository {
|
||||
return impl
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mainActivityTest() {
|
||||
currencyRobot {
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<receiver android:name="com.appttude.h_mal.easycc.widget.CurrencyAppWidgetKotlin">
|
||||
<receiver android:name="com.appttude.h_mal.easycc.widget.CurrencyAppWidgetKotlin"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
@@ -30,12 +31,14 @@
|
||||
android:resource="@xml/currency_app_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<activity android:name="com.appttude.h_mal.easycc.ui.widget.CurrencyAppWidgetConfigureActivityKotlin">
|
||||
<activity android:name="com.appttude.h_mal.easycc.ui.widget.CurrencyAppWidgetConfigureActivityKotlin"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.appttude.h_mal.easycc.ui.main.MainActivity">
|
||||
<activity android:name="com.appttude.h_mal.easycc.ui.main.MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
||||
@@ -1,42 +1,7 @@
|
||||
package com.appttude.h_mal.easycc.application
|
||||
|
||||
import android.app.Application
|
||||
import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi
|
||||
import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
|
||||
import com.appttude.h_mal.easycc.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.easycc.data.network.interceptors.QueryInterceptor
|
||||
import com.appttude.h_mal.easycc.data.network.interceptors.loggingInterceptor
|
||||
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
|
||||
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
|
||||
import com.appttude.h_mal.easycc.helper.WidgetHelper
|
||||
import com.appttude.h_mal.easycc.ui.main.MainViewModelFactory
|
||||
import com.appttude.h_mal.easycc.ui.widget.WidgetViewModelFactory
|
||||
import org.kodein.di.Kodein
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.x.androidXModule
|
||||
import org.kodein.di.generic.bind
|
||||
import org.kodein.di.generic.instance
|
||||
import org.kodein.di.generic.provider
|
||||
import org.kodein.di.generic.singleton
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
class AppClass : Application(), KodeinAware {
|
||||
|
||||
// KODEIN DI components declaration
|
||||
override val kodein by Kodein.lazy {
|
||||
import(androidXModule(this@AppClass))
|
||||
|
||||
bind() from singleton { NetworkConnectionInterceptor(instance()) }
|
||||
bind() from singleton { loggingInterceptor() }
|
||||
bind() from singleton { QueryInterceptor(instance()) }
|
||||
bind() from singleton { CurrencyApi(instance(), instance(), instance()) }
|
||||
bind() from singleton { BackupCurrencyApi(instance(), instance()) }
|
||||
bind() from singleton { PreferenceProvider(instance()) }
|
||||
bind() from singleton { RepositoryImpl(instance(), instance(), instance()) }
|
||||
bind() from singleton { CurrencyDataHelper(instance()) }
|
||||
bind() from singleton { WidgetHelper(instance(), instance()) }
|
||||
bind() from provider { MainViewModelFactory(instance(), instance()) }
|
||||
bind() from provider { WidgetViewModelFactory(instance()) }
|
||||
}
|
||||
|
||||
}
|
||||
@HiltAndroidApp
|
||||
class AppClass : Application()
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.appttude.h_mal.easycc.data.network.api
|
||||
|
||||
interface Api
|
||||
@@ -13,7 +13,7 @@ import retrofit2.http.Query
|
||||
/**
|
||||
* Retrofit Network class to currency api calls
|
||||
*/
|
||||
interface BackupCurrencyApi {
|
||||
interface BackupCurrencyApi : Api {
|
||||
|
||||
@GET("latest?")
|
||||
suspend fun getCurrencyRate(
|
||||
@@ -21,24 +21,4 @@ interface BackupCurrencyApi {
|
||||
@Query("to") currencyTo: String
|
||||
): Response<CurrencyResponse>
|
||||
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
networkConnectionInterceptor: NetworkConnectionInterceptor,
|
||||
interceptor: HttpLoggingInterceptor
|
||||
): BackupCurrencyApi {
|
||||
|
||||
val okkHttpclient = OkHttpClient.Builder()
|
||||
.addInterceptor(interceptor)
|
||||
.addNetworkInterceptor(networkConnectionInterceptor)
|
||||
.build()
|
||||
|
||||
return Retrofit.Builder()
|
||||
.client(okkHttpclient)
|
||||
.baseUrl("https://api.frankfurter.app/")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create(BackupCurrencyApi::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,32 +14,10 @@ import retrofit2.http.Query
|
||||
/**
|
||||
* Retrofit Network class to currency api calls
|
||||
*/
|
||||
interface CurrencyApi {
|
||||
interface CurrencyApi : Api{
|
||||
|
||||
// Get rate from server with arguments passed in Repository
|
||||
@GET("convert?")
|
||||
suspend fun getCurrencyRate(@Query("q") currency: String): Response<ResponseObject>
|
||||
|
||||
// interface invokation to be used in application class
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
networkConnectionInterceptor: NetworkConnectionInterceptor,
|
||||
queryInterceptor: QueryInterceptor,
|
||||
interceptor: HttpLoggingInterceptor
|
||||
): CurrencyApi {
|
||||
|
||||
val okkHttpclient = OkHttpClient.Builder()
|
||||
.addInterceptor(interceptor)
|
||||
.addInterceptor(queryInterceptor)
|
||||
.addNetworkInterceptor(networkConnectionInterceptor)
|
||||
.build()
|
||||
|
||||
return Retrofit.Builder()
|
||||
.client(okkHttpclient)
|
||||
.baseUrl("https://free.currencyconverterapi.com/api/v3/")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create(CurrencyApi::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.appttude.h_mal.easycc.data.network.api
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
class RemoteDataSource @Inject constructor(){
|
||||
|
||||
fun <Api> buildApi(
|
||||
okkHttpclient: OkHttpClient,
|
||||
baseUrl: String,
|
||||
api: Class<Api>
|
||||
): Api {
|
||||
|
||||
return Retrofit.Builder()
|
||||
.client(okkHttpclient)
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create(api)
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,19 @@ import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Interceptor used in network classes to check network status
|
||||
*
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
class NetworkConnectionInterceptor(
|
||||
context: Context
|
||||
class NetworkConnectionInterceptor @Inject constructor(
|
||||
@ApplicationContext context: Context
|
||||
) : Interceptor {
|
||||
|
||||
private val applicationContext = context.applicationContext
|
||||
@@ -27,13 +29,12 @@ class NetworkConnectionInterceptor(
|
||||
}
|
||||
|
||||
private fun isInternetAvailable(): Boolean {
|
||||
var result = false
|
||||
val connectivityManager =
|
||||
applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
|
||||
connectivityManager?.let {
|
||||
return connectivityManager?.let {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
|
||||
result = when {
|
||||
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.run {
|
||||
when {
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||
else -> false
|
||||
@@ -41,7 +42,7 @@ class NetworkConnectionInterceptor(
|
||||
}
|
||||
} else {
|
||||
it.activeNetworkInfo?.run {
|
||||
result = when (type) {
|
||||
when (type) {
|
||||
ConnectivityManager.TYPE_WIFI -> true
|
||||
ConnectivityManager.TYPE_MOBILE -> true
|
||||
ConnectivityManager.TYPE_ETHERNET -> true
|
||||
@@ -49,8 +50,7 @@ class NetworkConnectionInterceptor(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
} ?: false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,17 +2,19 @@ package com.appttude.h_mal.easycc.data.network.interceptors
|
||||
|
||||
import android.content.Context
|
||||
import com.appttude.h_mal.easycc.R
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Interceptor used in CurrencyApi
|
||||
* Adds apiKey to query parameters
|
||||
*/
|
||||
class QueryInterceptor(
|
||||
val context: Context
|
||||
class QueryInterceptor @Inject constructor(
|
||||
@ApplicationContext val context: Context
|
||||
) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
|
||||
@@ -6,18 +6,18 @@ import com.appttude.h_mal.easycc.models.CurrencyObject
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ResponseObject(
|
||||
@field:SerializedName("query")
|
||||
var query: Any? = null,
|
||||
@field:SerializedName("results")
|
||||
var results: Map<String, CurrencyObject>? = null
|
||||
@field:SerializedName("query")
|
||||
var query: Any? = null,
|
||||
@field:SerializedName("results")
|
||||
var results: Map<String, CurrencyObject>? = null
|
||||
) : CurrencyModelInterface {
|
||||
|
||||
override fun getCurrencyModel(): CurrencyModel {
|
||||
val res = results?.iterator()?.next()?.value
|
||||
return CurrencyModel(
|
||||
res?.fr,
|
||||
res?.to,
|
||||
res?.value ?: 0.0
|
||||
res?.fr,
|
||||
res?.to,
|
||||
res?.value ?: 0.0
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.appttude.h_mal.easycc.R
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Shared prefs class used for storing conversion name values as pairs
|
||||
@@ -13,7 +15,7 @@ import com.appttude.h_mal.easycc.R
|
||||
private const val CURRENCY_ONE = "conversion_one"
|
||||
private const val CURRENCY_TWO = "conversion_two"
|
||||
|
||||
class PreferenceProvider(context: Context) {
|
||||
class PreferenceProvider @Inject constructor(@ApplicationContext context: Context) {
|
||||
|
||||
private val appContext = context.applicationContext
|
||||
|
||||
|
||||
@@ -9,11 +9,12 @@ import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
|
||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
||||
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.easycc.utils.convertPairsListToString
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Default implementation of [Repository]. Single entry point for managing currency' data.
|
||||
*/
|
||||
class RepositoryImpl(
|
||||
class RepositoryImpl @Inject constructor(
|
||||
private val api: CurrencyApi,
|
||||
private val backUpApi: BackupCurrencyApi,
|
||||
private val prefs: PreferenceProvider
|
||||
|
||||
60
app/src/main/java/com/appttude/h_mal/easycc/di/AppModule.kt
Normal file
60
app/src/main/java/com/appttude/h_mal/easycc/di/AppModule.kt
Normal file
@@ -0,0 +1,60 @@
|
||||
package com.appttude.h_mal.easycc.di
|
||||
|
||||
import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi
|
||||
import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
|
||||
import com.appttude.h_mal.easycc.data.network.api.RemoteDataSource
|
||||
import com.appttude.h_mal.easycc.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class AppModule {
|
||||
|
||||
@Provides
|
||||
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor = HttpLoggingInterceptor()
|
||||
|
||||
@Provides
|
||||
fun provideOkHttpclient(
|
||||
interceptor: HttpLoggingInterceptor,
|
||||
networkConnectionInterceptor: NetworkConnectionInterceptor,
|
||||
) = OkHttpClient.Builder()
|
||||
.addInterceptor(interceptor)
|
||||
.addNetworkInterceptor(networkConnectionInterceptor)
|
||||
.build()
|
||||
|
||||
@Provides
|
||||
fun provideCurrencyApi(
|
||||
remoteDataSource: RemoteDataSource,
|
||||
okHttpClient: OkHttpClient
|
||||
): CurrencyApi {
|
||||
return remoteDataSource.buildApi(
|
||||
okHttpClient,
|
||||
"https://free.currencyconverterapi.com/api/v3/",
|
||||
CurrencyApi::class.java
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideBackupCurrencyApi(
|
||||
remoteDataSource: RemoteDataSource,
|
||||
okHttpClient: OkHttpClient
|
||||
): BackupCurrencyApi {
|
||||
return remoteDataSource.buildApi(
|
||||
okHttpClient,
|
||||
"https://api.frankfurter.app/",
|
||||
BackupCurrencyApi::class.java
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideRepository(impl: RepositoryImpl): Repository {
|
||||
return impl
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,9 @@ package com.appttude.h_mal.easycc.helper
|
||||
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.models.CurrencyModelInterface
|
||||
import javax.inject.Inject
|
||||
|
||||
class CurrencyDataHelper(
|
||||
class CurrencyDataHelper @Inject constructor(
|
||||
val repository: Repository
|
||||
) {
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ package com.appttude.h_mal.easycc.helper
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.models.CurrencyModel
|
||||
import com.appttude.h_mal.easycc.utils.trimToThree
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetHelper(
|
||||
class WidgetHelper @Inject constructor(
|
||||
private val helper: CurrencyDataHelper,
|
||||
val repository: Repository
|
||||
) {
|
||||
|
||||
@@ -7,8 +7,10 @@ import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.WindowManager
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import com.appttude.h_mal.easycc.R
|
||||
import kotlinx.android.synthetic.main.custom_dialog.*
|
||||
|
||||
/**
|
||||
* Custom dialog when selecting currencies from list with filter
|
||||
@@ -35,10 +37,11 @@ class CustomDialogClass(
|
||||
android.R.layout.simple_list_item_1
|
||||
)
|
||||
|
||||
val list_view = findViewById<ListView>(R.id.list_view)
|
||||
list_view.adapter = arrayAdapter
|
||||
|
||||
// Edit text to filter @arrayAdapter
|
||||
search_text.addTextChangedListener(object : TextWatcher {
|
||||
findViewById<TextView>(R.id.search_text).addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
||||
arrayAdapter.filter.filter(charSequence)
|
||||
|
||||
@@ -6,77 +6,66 @@ import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.appttude.h_mal.easycc.R
|
||||
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
|
||||
import com.appttude.h_mal.easycc.utils.clearEditText
|
||||
import com.appttude.h_mal.easycc.utils.displayToast
|
||||
import com.appttude.h_mal.easycc.utils.hideView
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.kodein
|
||||
import org.kodein.di.generic.instance
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class MainActivity : AppCompatActivity(), KodeinAware, View.OnClickListener {
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
|
||||
// Retrieve MainViewModelFactory via dependency injection
|
||||
override val kodein by kodein()
|
||||
private val factory: MainViewModelFactory by instance()
|
||||
|
||||
lateinit var viewModel: MainViewModel
|
||||
private val viewModel: MainViewModel by viewModels()
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// Keyboard is not overlapping views
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
/*
|
||||
* Prevent keyboard overlapping views
|
||||
*/
|
||||
window.setSoftInputMode(
|
||||
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN or
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, factory)
|
||||
.get(MainViewModel::class.java)
|
||||
|
||||
// Bind viewmodel to layout with view binding
|
||||
DataBindingUtil
|
||||
.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
|
||||
.apply {
|
||||
viewmodel = viewModel
|
||||
lifecycleOwner = this@MainActivity
|
||||
}
|
||||
|
||||
viewModel.initiate(intent.extras)
|
||||
|
||||
binding.currencyOne.text = viewModel.rateIdTo
|
||||
binding.currencyTwo.text = viewModel.rateIdFrom
|
||||
|
||||
setUpListeners()
|
||||
setUpObservers()
|
||||
}
|
||||
|
||||
private fun setUpObservers() {
|
||||
viewModel.operationStartedListener.observe(this, {
|
||||
progressBar.hideView(false)
|
||||
})
|
||||
viewModel.operationFinishedListener.observe(this, { pair ->
|
||||
viewModel.operationStartedListener.observe(this) {
|
||||
binding.progressBar.hideView(false)
|
||||
}
|
||||
viewModel.operationFinishedListener.observe(this) { pair ->
|
||||
// hide progress bar
|
||||
progressBar.hideView(true)
|
||||
binding.progressBar.hideView(true)
|
||||
if (pair.first) {
|
||||
// Operation was successful remove text in EditTexts
|
||||
bottomInsertValues.clearEditText()
|
||||
topInsertValue.clearEditText()
|
||||
binding.bottomInsertValues.clearEditText()
|
||||
binding.topInsertValue.clearEditText()
|
||||
} else {
|
||||
// Display Toast with error message returned from Viewmodel
|
||||
pair.second?.let { displayToast(it) }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpListeners() {
|
||||
topInsertValue.addTextChangedListener(textWatcherClass)
|
||||
bottomInsertValues.addTextChangedListener(textWatcherClass2)
|
||||
binding.topInsertValue.addTextChangedListener(textWatcherClass)
|
||||
binding.bottomInsertValues.addTextChangedListener(textWatcherClass2)
|
||||
|
||||
currency_one.setOnClickListener(this)
|
||||
currency_two.setOnClickListener(this)
|
||||
binding.currencyOne.setOnClickListener(this)
|
||||
binding.currencyTwo.setOnClickListener(this)
|
||||
}
|
||||
|
||||
private fun showCustomDialog(view: View?) {
|
||||
@@ -97,32 +86,32 @@ class MainActivity : AppCompatActivity(), KodeinAware, View.OnClickListener {
|
||||
private val textWatcherClass: TextWatcher = object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
||||
// Remove text watcher on other text watcher to prevent infinite loop
|
||||
bottomInsertValues.removeTextChangedListener(textWatcherClass2)
|
||||
binding.bottomInsertValues.removeTextChangedListener(textWatcherClass2)
|
||||
// Clear any values if current EditText is empty
|
||||
if (topInsertValue.text.isNullOrEmpty())
|
||||
bottomInsertValues.setText("")
|
||||
if (binding.topInsertValue.text.isNullOrEmpty())
|
||||
binding.bottomInsertValues.setText("")
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
bottomInsertValues.setText(viewModel.getConversion(s.toString()))
|
||||
binding.bottomInsertValues.setText(viewModel.getConversion(s.toString()))
|
||||
// add Text watcher back as it is safe to do so
|
||||
bottomInsertValues.addTextChangedListener(textWatcherClass2)
|
||||
binding.bottomInsertValues.addTextChangedListener(textWatcherClass2)
|
||||
}
|
||||
}
|
||||
|
||||
private val textWatcherClass2: TextWatcher = object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
||||
|
||||
topInsertValue.removeTextChangedListener(textWatcherClass)
|
||||
if (bottomInsertValues.text.isNullOrEmpty())
|
||||
topInsertValue.clearEditText()
|
||||
binding.topInsertValue.removeTextChangedListener(textWatcherClass)
|
||||
if (binding.bottomInsertValues.text.isNullOrEmpty())
|
||||
binding.topInsertValue.clearEditText()
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
topInsertValue.setText(viewModel.getReciprocalConversion(s.toString()))
|
||||
topInsertValue.addTextChangedListener(textWatcherClass)
|
||||
binding.topInsertValue.setText(viewModel.getReciprocalConversion(s.toString()))
|
||||
binding.topInsertValue.addTextChangedListener(textWatcherClass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,25 @@ package com.appttude.h_mal.easycc.ui.main
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
|
||||
import com.appttude.h_mal.easycc.utils.toTwoDpString
|
||||
import com.appttude.h_mal.easycc.utils.trimToThree
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* ViewModel for the task Main Activity Screen
|
||||
*/
|
||||
class MainViewModel(
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
private val currencyDataHelper: CurrencyDataHelper,
|
||||
private val repository: Repository
|
||||
) : ViewModel() {
|
||||
@@ -48,7 +53,7 @@ class MainViewModel(
|
||||
return
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// Non-null assertion (!!) as values have been null checked and have not changed
|
||||
val exchangeResponse = currencyDataHelper.getDataFromApi(
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.appttude.h_mal.easycc.ui.main
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
|
||||
|
||||
/**
|
||||
* Viewmodel factory for [MainViewModel]
|
||||
* inject repository into viewmodel
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class MainViewModelFactory(
|
||||
private val repository: Repository,
|
||||
private val helper: CurrencyDataHelper
|
||||
) : ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return MainViewModel(helper, repository) as T
|
||||
}
|
||||
}
|
||||
@@ -6,38 +6,32 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.appttude.h_mal.easycc.R
|
||||
import com.appttude.h_mal.easycc.databinding.CurrencyAppWidgetConfigureBinding
|
||||
import com.appttude.h_mal.easycc.ui.main.ClickListener
|
||||
import com.appttude.h_mal.easycc.ui.main.CustomDialogClass
|
||||
import com.appttude.h_mal.easycc.utils.displayToast
|
||||
import com.appttude.h_mal.easycc.utils.transformIntToArray
|
||||
import com.appttude.h_mal.easycc.widget.CurrencyAppWidgetKotlin
|
||||
import kotlinx.android.synthetic.main.currency_app_widget_configure.*
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.kodein
|
||||
import org.kodein.di.generic.instance
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
* The configuration screen for the [CurrencyAppWidgetKotlin] AppWidget.
|
||||
*/
|
||||
class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAware,
|
||||
@AndroidEntryPoint
|
||||
class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(),
|
||||
View.OnClickListener {
|
||||
|
||||
override val kodein by kodein()
|
||||
private val factory: WidgetViewModelFactory by instance()
|
||||
val viewModel: WidgetViewModel by viewModels()
|
||||
|
||||
var mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
|
||||
companion object {
|
||||
lateinit var viewModel: WidgetViewModel
|
||||
}
|
||||
private lateinit var binding: CurrencyAppWidgetConfigureBinding
|
||||
private var mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
|
||||
public override fun onCreate(icicle: Bundle?) {
|
||||
super.onCreate(icicle)
|
||||
binding = CurrencyAppWidgetConfigureBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// Set the result to CANCELED. This will cause the widget host to cancel
|
||||
// out of the widget placement if the user presses the back button.
|
||||
@@ -57,23 +51,20 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAwar
|
||||
return
|
||||
}
|
||||
|
||||
// ViewModel setup
|
||||
viewModel = ViewModelProviders.of(this, factory).get(WidgetViewModel::class.java)
|
||||
viewModel.initiate(mAppWidgetId)
|
||||
|
||||
setupDataBinding()
|
||||
setupObserver()
|
||||
setupClickListener()
|
||||
}
|
||||
|
||||
private fun setupClickListener() {
|
||||
submit_widget.setOnClickListener(this)
|
||||
currency_one.setOnClickListener(this)
|
||||
currency_two.setOnClickListener(this)
|
||||
binding.submitWidget.setOnClickListener(this)
|
||||
binding.currencyOne.setOnClickListener(this)
|
||||
binding.currencyTwo.setOnClickListener(this)
|
||||
}
|
||||
|
||||
private fun setupObserver() {
|
||||
viewModel.operationFinishedListener.observe(this, {
|
||||
viewModel.operationFinishedListener.observe(this) {
|
||||
|
||||
// it.first is a the success of the operation
|
||||
if (it.first) {
|
||||
@@ -82,17 +73,6 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAwar
|
||||
// failed operation - display toast with message from it.second
|
||||
it.second?.let { message -> displayToast(message) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupDataBinding() {
|
||||
// data binding to @R.layout.currency_app_widget_configure
|
||||
DataBindingUtil.setContentView<CurrencyAppWidgetConfigureBinding>(
|
||||
this,
|
||||
R.layout.currency_app_widget_configure
|
||||
).apply {
|
||||
viewmodel = viewModel
|
||||
lifecycleOwner = this@CurrencyAppWidgetConfigureActivityKotlin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import com.appttude.h_mal.easycc.R
|
||||
import kotlinx.android.synthetic.main.confirm_dialog.*
|
||||
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
|
||||
import com.appttude.h_mal.easycc.databinding.ConfirmDialogBinding
|
||||
|
||||
|
||||
/**
|
||||
@@ -17,19 +18,22 @@ class WidgetSubmitDialog(
|
||||
private val dialogInterface: DialogSubmit
|
||||
) : Dialog(context) {
|
||||
|
||||
private lateinit var binding: ConfirmDialogBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.confirm_dialog)
|
||||
binding = ConfirmDialogBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
// layer behind dialog to be transparent
|
||||
window!!.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
// Dialog cannot be cancelled by clicking away
|
||||
setCancelable(false)
|
||||
|
||||
confirm_text.text = messageString
|
||||
binding.confirmText.text = messageString
|
||||
|
||||
// handle dialog buttons
|
||||
confirm_yes.setOnClickListener { dialogInterface.onSubmit() }
|
||||
confirm_no.setOnClickListener { dismiss() }
|
||||
binding.confirmYes.setOnClickListener { dialogInterface.onSubmit() }
|
||||
binding.confirmNo.setOnClickListener { dismiss() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.utils.trimToThree
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetViewModel(
|
||||
@HiltViewModel
|
||||
class WidgetViewModel @Inject constructor(
|
||||
private val repository: Repository
|
||||
) : ViewModel() {
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.appttude.h_mal.easycc.ui.widget
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class WidgetViewModelFactory(
|
||||
private val repository: RepositoryImpl
|
||||
) : ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return WidgetViewModel(repository) as T
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,19 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.appttude.h_mal.easycc.helper.WidgetHelper
|
||||
import com.appttude.h_mal.easycc.widget.WidgetServiceIntent.Companion.enqueueWork
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.LateInitKodein
|
||||
import org.kodein.di.generic.instance
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of App Widget functionality.
|
||||
* App Widget Configuration implemented in [CurrencyAppWidgetKotlin]
|
||||
*/
|
||||
|
||||
@AndroidEntryPoint
|
||||
class CurrencyAppWidgetKotlin : AppWidgetProvider() {
|
||||
|
||||
//DI with kodein to use in CurrencyAppWidgetKotlin
|
||||
private val kodein = LateInitKodein()
|
||||
private val repository: WidgetHelper by kodein.instance()
|
||||
@Inject
|
||||
lateinit var helper: WidgetHelper
|
||||
|
||||
//update trigger either on timed update or from from first start
|
||||
override fun onUpdate(
|
||||
@@ -33,10 +31,9 @@ class CurrencyAppWidgetKotlin : AppWidgetProvider() {
|
||||
}
|
||||
|
||||
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
||||
// When the user deletes the widget, delete the preference associated with it.
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
repository.removeWidgetData(appWidgetId)
|
||||
helper.removeWidgetData(appWidgetId)
|
||||
}
|
||||
super.onDeleted(context, appWidgetIds)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.appttude.h_mal.easycc.widget
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
@@ -11,22 +13,20 @@ import com.appttude.h_mal.easycc.R
|
||||
import com.appttude.h_mal.easycc.helper.WidgetHelper
|
||||
import com.appttude.h_mal.easycc.ui.main.MainActivity
|
||||
import com.appttude.h_mal.easycc.utils.transformIntToArray
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.LateInitKodein
|
||||
import org.kodein.di.generic.instance
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WidgetServiceIntent : JobIntentService() {
|
||||
|
||||
//DI with kodein to use in CurrencyAppWidgetKotlin
|
||||
private val kodein = LateInitKodein()
|
||||
private val repository: WidgetHelper by kodein.instance()
|
||||
@Inject
|
||||
lateinit var helper: WidgetHelper
|
||||
|
||||
override fun onHandleWork(intent: Intent) {
|
||||
kodein.baseKodein = (application as KodeinAware).kodein
|
||||
|
||||
val appWidgetManager = AppWidgetManager.getInstance(this)
|
||||
val thisAppWidget = ComponentName(packageName, CurrencyAppWidgetKotlin::class.java.name)
|
||||
val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)
|
||||
@@ -45,7 +45,7 @@ class WidgetServiceIntent : JobIntentService() {
|
||||
val views = RemoteViews(context.packageName, R.layout.currency_app_widget)
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val exchangeResponse = repository.getWidgetData()
|
||||
val exchangeResponse = helper.getWidgetData()
|
||||
|
||||
exchangeResponse?.let {
|
||||
val titleString = "${it.from}${it.to}"
|
||||
@@ -63,7 +63,7 @@ class WidgetServiceIntent : JobIntentService() {
|
||||
val configPendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, appWidgetId, clickIntentTemplate,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.widget_view, configPendingIntent)
|
||||
}
|
||||
@@ -87,14 +87,14 @@ class WidgetServiceIntent : JobIntentService() {
|
||||
context,
|
||||
appWidgetId,
|
||||
updateIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
private fun clickingIntent(
|
||||
context: Context
|
||||
): Intent {
|
||||
val pair = repository.repository.getConversionPair()
|
||||
val pair = helper.repository.getConversionPair()
|
||||
val s1 = pair.first
|
||||
val s2 = pair.second
|
||||
return Intent(context, MainActivity::class.java).apply {
|
||||
|
||||
@@ -1,126 +1,113 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/ic_background"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.main.MainActivity">
|
||||
|
||||
<data>
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<variable
|
||||
name="viewmodel"
|
||||
type="com.appttude.h_mal.easycc.ui.main.MainViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/ic_background"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.main.MainActivity">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
<LinearLayout
|
||||
android:id="@+id/whole_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="18dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView style="@style/cardview_theme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currencyOne"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:autoSizeMaxTextSize="12dp"
|
||||
android:tag="top"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/topInsertValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/round_edit_text"
|
||||
android:ems="10"
|
||||
android:hint="insert value one"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:tag="from"
|
||||
android:textColorHighlight="#608d91" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/whole_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="18dp"
|
||||
android:orientation="vertical">
|
||||
<androidx.cardview.widget.CardView style="@style/cardview_theme">
|
||||
|
||||
<androidx.cardview.widget.CardView style="@style/cardview_theme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currency_one"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:autoSizeMaxTextSize="12dp"
|
||||
android:tag="top"
|
||||
android:text="@={viewmodel.rateIdFrom}"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/topInsertValue"
|
||||
<TextView
|
||||
android:id="@+id/currencyTwo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/round_edit_text"
|
||||
android:ems="10"
|
||||
android:hint="insert value one"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:tag="from"
|
||||
android:textColorHighlight="#608d91" />
|
||||
android:layout_margin="12dp"
|
||||
android:tag="bottom"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
<EditText
|
||||
android:id="@+id/bottomInsertValues"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView style="@style/cardview_theme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currency_two"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:tag="bottom"
|
||||
android:text="@={viewmodel.rateIdTo}"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/bottomInsertValues"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_weight="7"
|
||||
android:background="@drawable/round_edit_text"
|
||||
android:ems="10"
|
||||
android:hint="insert value two"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:tag="to"
|
||||
android:textColorHighlight="#608d91" />
|
||||
|
||||
</LinearLayout>
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_weight="7"
|
||||
android:background="@drawable/round_edit_text"
|
||||
android:ems="10"
|
||||
android:hint="insert value two"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:tag="to"
|
||||
android:textColorHighlight="#608d91" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colour_four"
|
||||
android:indeterminateTintMode="src_atop"
|
||||
android:visibility="gone" />
|
||||
</RelativeLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colour_four"
|
||||
android:indeterminateTintMode="src_atop"
|
||||
android:visibility="gone" />
|
||||
</RelativeLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -1,128 +1,115 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/ic_background"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.main.MainActivity">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewmodel"
|
||||
type="com.appttude.h_mal.easycc.ui.main.MainViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/ic_background"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="true"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="9dp"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.main.MainActivity">
|
||||
app:layout_constraintBottom_toTopOf="@id/middle"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintWidth_percent=".9">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintWidth_percent=".9"
|
||||
android:layout_marginBottom="9dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toTopOf="@id/middle"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent">
|
||||
<androidx.cardview.widget.CardView style="@style/cardview_theme">
|
||||
|
||||
<androidx.cardview.widget.CardView style="@style/cardview_theme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currency_one"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:tag="top"
|
||||
android:text="@={viewmodel.rateIdFrom}"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/topInsertValue"
|
||||
<TextView
|
||||
android:id="@+id/currencyOne"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/round_edit_text"
|
||||
android:ems="10"
|
||||
android:hint="insert value one"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:tag="from"
|
||||
android:textColorHighlight="#608d91" />
|
||||
|
||||
</LinearLayout>
|
||||
android:layout_margin="12dp"
|
||||
android:tag="top"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
<android.widget.Space
|
||||
android:id="@+id/middle"
|
||||
<EditText
|
||||
android:id="@+id/topInsertValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintWidth_percent=".9"
|
||||
android:layout_marginTop="9dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/middle">
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/round_edit_text"
|
||||
android:ems="10"
|
||||
android:hint="insert value one"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:tag="from"
|
||||
android:textColorHighlight="#608d91" />
|
||||
|
||||
<androidx.cardview.widget.CardView style="@style/cardview_theme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currency_two"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:tag="bottom"
|
||||
android:text="@={viewmodel.rateIdTo}"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/bottomInsertValues"
|
||||
<android.widget.Space
|
||||
android:id="@+id/middle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="9dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/middle"
|
||||
app:layout_constraintWidth_percent=".9">
|
||||
|
||||
<androidx.cardview.widget.CardView style="@style/cardview_theme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currencyTwo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_weight="7"
|
||||
android:background="@drawable/round_edit_text"
|
||||
android:ems="10"
|
||||
android:hint="insert value two"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:tag="to"
|
||||
android:textColorHighlight="#608d91" />
|
||||
android:layout_margin="12dp"
|
||||
android:tag="bottom"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
<EditText
|
||||
android:id="@+id/bottomInsertValues"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colour_four"
|
||||
android:indeterminateTintMode="src_atop"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_weight="7"
|
||||
android:background="@drawable/round_edit_text"
|
||||
android:ems="10"
|
||||
android:hint="insert value two"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:tag="to"
|
||||
android:textColorHighlight="#608d91" />
|
||||
|
||||
</layout>
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colour_four"
|
||||
android:indeterminateTintMode="src_atop"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/confirm_text"
|
||||
android:id="@+id/confirmText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
@@ -36,7 +36,7 @@
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/confirm_yes"
|
||||
android:id="@+id/confirmYes"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
@@ -48,7 +48,7 @@
|
||||
android:textColor="@color/colour_five" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/confirm_no"
|
||||
android:id="@+id/confirmNo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
|
||||
@@ -1,83 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewmodel"
|
||||
type="com.appttude.h_mal.easycc.ui.widget.WidgetViewModel" />
|
||||
</data>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.widget.CurrencyAppWidgetConfigureActivityKotlin">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="true"
|
||||
tools:context=".ui.widget.CurrencyAppWidgetConfigureActivityKotlin">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_margin="12dp">
|
||||
|
||||
<RelativeLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/whole_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_margin="12dp">
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/whole_view">
|
||||
<androidx.cardview.widget.CardView
|
||||
style="@style/cardview_theme"
|
||||
android:layout_margin="11dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
style="@style/cardview_theme"
|
||||
android:layout_margin="11dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currency_one"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:tag="top"
|
||||
android:text="@={viewmodel.rateIdFrom}"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp"
|
||||
tools:text="Currency One" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
<TextView
|
||||
android:id="@+id/currencyOne"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:tag="top"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp"
|
||||
tools:text="Currency One" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
style="@style/cardview_theme"
|
||||
android:layout_margin="11dp">
|
||||
<androidx.cardview.widget.CardView
|
||||
style="@style/cardview_theme"
|
||||
android:layout_margin="11dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currency_two"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:tag="bottom"
|
||||
android:text="@={viewmodel.rateIdTo}"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp"
|
||||
tools:text="Currency Two" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_marginEnd="22dp"
|
||||
android:id="@+id/submit_widget"
|
||||
android:tag="submit"
|
||||
android:padding="12dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/whole_view"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:textColor="@color/colour_five"
|
||||
android:text="Submit" />
|
||||
|
||||
</RelativeLayout>
|
||||
<TextView
|
||||
android:id="@+id/currencyTwo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:tag="bottom"
|
||||
android:textColor="@color/colour_five"
|
||||
android:textSize="18sp"
|
||||
tools:text="Currency Two" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/submitWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/whole_view"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="22dp"
|
||||
android:padding="12dp"
|
||||
android:tag="submit"
|
||||
android:text="Submit"
|
||||
android:textColor="@color/colour_five" />
|
||||
</RelativeLayout>
|
||||
</layout>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -6,7 +6,9 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
|
||||
import com.appttude.h_mal.easycc.utils.MainCoroutineRule
|
||||
import com.appttude.h_mal.easycc.utils.observeOnce
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
@@ -23,6 +25,9 @@ class MainViewModelTest {
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@get:Rule
|
||||
var mainCoroutineRule = MainCoroutineRule()
|
||||
|
||||
lateinit var viewModel: MainViewModel
|
||||
|
||||
@Mock
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.appttude.h_mal.easycc.utils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.*
|
||||
import org.junit.rules.TestWatcher
|
||||
import org.junit.runner.Description
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class MainCoroutineRule(private val dispatcher: TestDispatcher = StandardTestDispatcher()) :
|
||||
TestWatcher() {
|
||||
|
||||
override fun starting(description: Description) {
|
||||
super.starting(description)
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
override fun finished(description: Description) {
|
||||
super.finished(description)
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
}
|
||||
26
build.gradle
26
build.gradle
@@ -1,25 +1,9 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.4.10'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" }
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
plugins {
|
||||
id 'com.android.application' version '7.2.2' apply false
|
||||
id 'com.android.library' version '7.2.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
|
||||
id 'com.google.dagger.hilt.android' version '2.43.2' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
||||
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Sat Jun 12 22:27:25 BST 2021
|
||||
#Thu Aug 04 22:17:29 BST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1 +1,16 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "EasyCC"
|
||||
include ':app'
|
||||
|
||||
Reference in New Issue
Block a user