mirror of
https://github.com/hmalik144/Weather-apps.git
synced 2025-12-10 02:05:20 +00:00
- Popup for google playstore permissions added
- UI tests with stubbing added - linting clean ups - changes to fragments and base fragment Took 3 hours 57 minutes
This commit is contained in:
97
.gitignore
vendored
97
.gitignore
vendored
@@ -1,11 +1,90 @@
|
||||
*.iml
|
||||
### AndroidStudio ###
|
||||
# Covers files to be ignored for android development using Android Studio.
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
|
||||
# Gradle files
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
/.idea/caches
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Signing files
|
||||
.signing/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio
|
||||
/*/build/
|
||||
/*/local.properties
|
||||
/*/out
|
||||
/*/*/build
|
||||
/*/*/production
|
||||
captures/
|
||||
.navigation/
|
||||
*.ipr
|
||||
*~
|
||||
*.swp
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Android Patch
|
||||
gen-external-apklibs
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
/projectFilesBackup
|
||||
|
||||
# IntelliJ IDEA
|
||||
*.iml
|
||||
*.iws
|
||||
/out/
|
||||
|
||||
# User-specific configurations
|
||||
.idea/caches/
|
||||
.idea/libraries/
|
||||
.idea/shelf/
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/.name
|
||||
.idea/compiler.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
.idea/encodings.xml
|
||||
.idea/misc.xml
|
||||
.idea/modules.xml
|
||||
.idea/scopes/scope_settings.xml
|
||||
.idea/dictionaries
|
||||
.idea/vcs.xml
|
||||
.idea/jsLibraryMappings.xml
|
||||
.idea/datasources.xml
|
||||
.idea/dataSources.ids
|
||||
.idea/sqlDataSources.xml
|
||||
.idea/dynamic.xml
|
||||
.idea/uiDesigner.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/gradle.xml
|
||||
.idea/jarRepositorie
|
||||
|
||||
BIN
.idea/caches/build_file_checksums.ser
generated
BIN
.idea/caches/build_file_checksums.ser
generated
Binary file not shown.
20
.idea/gradle.xml
generated
20
.idea/gradle.xml
generated
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
48
.idea/misc.xml
generated
48
.idea/misc.xml
generated
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="12">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
||||
<item index="6" class="java.lang.String" itemvalue="android.annotation.Nullable" />
|
||||
<item index="7" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="11">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
||||
<item index="5" class="java.lang.String" itemvalue="android.annotation.NonNull" />
|
||||
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
11
.idea/modules.xml
generated
11
.idea/modules.xml
generated
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Altas_-_Weather.iml" filepath="$PROJECT_DIR$/.idea/modules/Altas_-_Weather.iml" group="Altas_-_Weather" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Altas_-_Weather-app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Altas_-_Weather-app.iml" group="Altas_-_Weather/app" />
|
||||
<module fileurl="file://$PROJECT_DIR$/Weather_app.iml" filepath="$PROJECT_DIR$/Weather_app.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/runConfigurations.xml
generated
12
.idea/runConfigurations.xml
generated
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -97,6 +97,7 @@ dependencies {
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
|
||||
implementation 'androidx.test.espresso:espresso-idling-resource:3.4.0'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
|
||||
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||
// Unit testing
|
||||
@@ -108,8 +109,8 @@ dependencies {
|
||||
|
||||
// android unit testing and espresso
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
implementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.4.1-alpha06'
|
||||
//mock websever for testing retrofit responses
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"
|
||||
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
|
||||
@@ -117,7 +118,6 @@ dependencies {
|
||||
//mockito and livedata testing
|
||||
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||
implementation 'android.arch.core:core-testing'
|
||||
androidTestImplementation 'androidx.test:rules:1.3.0-rc01'
|
||||
|
||||
// Mockk
|
||||
def mockk_ver = "1.10.5"
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package com.appttude.h_mal.atlas_weather.application
|
||||
|
||||
import androidx.room.Room
|
||||
import androidx.test.espresso.IdlingRegistry
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.data.location.MockLocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.network.Api
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.MockingNetworkInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||
import com.appttude.h_mal.atlas_weather.data.room.Converter
|
||||
import java.io.BufferedReader
|
||||
|
||||
class TestAppClass : BaseAppClass() {
|
||||
@@ -22,17 +23,23 @@ class TestAppClass : BaseAppClass() {
|
||||
IdlingRegistry.getInstance().register(idlingResources)
|
||||
}
|
||||
|
||||
override fun createNetworkModule(): Api {
|
||||
override fun createNetworkModule(): WeatherApi {
|
||||
return NetworkModule().invoke<WeatherApi>(
|
||||
mockingNetworkInterceptor,
|
||||
NetworkConnectionInterceptor(this),
|
||||
QueryParamsInterceptor(),
|
||||
loggingInterceptor,
|
||||
mockingNetworkInterceptor
|
||||
)
|
||||
loggingInterceptor
|
||||
) as WeatherApi
|
||||
}
|
||||
|
||||
override fun createLocationModule() = MockLocationProvider()
|
||||
|
||||
override fun createRoomDatabase(): AppDatabase {
|
||||
return Room.inMemoryDatabaseBuilder(this, AppDatabase::class.java)
|
||||
.addTypeConverter(Converter(this))
|
||||
.build()
|
||||
}
|
||||
|
||||
fun stubUrl(url: String, rawPath: String) {
|
||||
val id = resources.getIdentifier(rawPath, "raw", packageName)
|
||||
val iStream = resources.openRawResource(id)
|
||||
@@ -40,7 +47,7 @@ class TestAppClass : BaseAppClass() {
|
||||
mockingNetworkInterceptor.addUrlStub(url = url, data = data)
|
||||
}
|
||||
|
||||
fun removeUrlStub(url: String){
|
||||
fun removeUrlStub(url: String) {
|
||||
mockingNetworkInterceptor.removeUrlStub(url = url)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.pressBack
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import com.appttude.h_mal.atlas_weather.application.TestAppClass
|
||||
@@ -21,6 +28,12 @@ open class BaseTest {
|
||||
testApp = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
|
||||
setupFeed()
|
||||
}
|
||||
|
||||
override fun afterActivityLaunched() {
|
||||
|
||||
// Dismiss dialog
|
||||
onView(withText("AGREE")).inRoot(isDialog()).check(matches(isDisplayed())).perform(ViewActions.click())
|
||||
}
|
||||
}
|
||||
|
||||
fun stubEndpoint(url: String, stub: Stubs) {
|
||||
@@ -32,8 +45,7 @@ open class BaseTest {
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
}
|
||||
fun tearDown() {}
|
||||
|
||||
open fun setupFeed() {}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
|
||||
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.RootMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ActivityScenario.launch
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.RootMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.application.TestAppClass
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4ClassRunner::class)
|
||||
class HomePageUITestScenario : BaseMainScenario() {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var mGrantPermissionRule: GrantPermissionRule =
|
||||
GrantPermissionRule.grant(
|
||||
"android.permission.ACCESS_COARSE_LOCATION")
|
||||
|
||||
override fun setupFeed() {
|
||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Valid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadApp_validWeatherResponse_returnsValidPage() {
|
||||
homeScreen {
|
||||
verifyCurrentTemperature(2)
|
||||
verifyCurrentLocation("Mock Location")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class BaseMainScenario {
|
||||
|
||||
lateinit var scenario: ActivityScenario<MainActivity>
|
||||
private lateinit var testApp : TestAppClass
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
scenario = launch(MainActivity::class.java)
|
||||
scenario.moveToState(Lifecycle.State.INITIALIZED)
|
||||
scenario.onActivity {
|
||||
runBlocking {
|
||||
testApp = it.application as TestAppClass
|
||||
setupFeed()
|
||||
}
|
||||
}
|
||||
|
||||
scenario.moveToState(Lifecycle.State.CREATED).onActivity {
|
||||
Espresso.onView(ViewMatchers.withText("AGREE"))
|
||||
.inRoot(RootMatchers.isDialog())
|
||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||
.perform(ViewActions.click())
|
||||
}
|
||||
}
|
||||
|
||||
fun stubEndpoint(url: String, stub: Stubs) {
|
||||
testApp.stubUrl(url, stub.id)
|
||||
}
|
||||
|
||||
fun unstubEndpoint(url: String) {
|
||||
testApp.removeUrlStub(url)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {}
|
||||
|
||||
open fun setupFeed() {}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.appttude.h_mal.atlas_weather.application
|
||||
|
||||
import androidx.room.RoomDatabase
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.network.Api
|
||||
@@ -26,14 +27,16 @@ const val LOCATION_PERMISSION_REQUEST = 505
|
||||
|
||||
class AppClass : BaseAppClass() {
|
||||
|
||||
override fun createNetworkModule(): Api {
|
||||
override fun createNetworkModule(): WeatherApi {
|
||||
return NetworkModule().invoke<WeatherApi>(
|
||||
NetworkConnectionInterceptor(this),
|
||||
QueryParamsInterceptor(),
|
||||
loggingInterceptor
|
||||
)
|
||||
) as WeatherApi
|
||||
}
|
||||
|
||||
override fun createLocationModule() = LocationProviderImpl(this)
|
||||
|
||||
override fun createRoomDatabase(): AppDatabase = AppDatabase(this)
|
||||
|
||||
}
|
||||
@@ -1,15 +1,8 @@
|
||||
package com.appttude.h_mal.atlas_weather.application
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.network.Api
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl
|
||||
@@ -31,11 +24,11 @@ abstract class BaseAppClass : Application(), KodeinAware {
|
||||
override val kodein = Kodein.lazy {
|
||||
import(androidXModule(this@BaseAppClass))
|
||||
|
||||
bind() from singleton { createNetworkModule() as WeatherApi}
|
||||
bind() from singleton { createNetworkModule() }
|
||||
bind() from singleton { createLocationModule() }
|
||||
|
||||
bind() from singleton { Gson() }
|
||||
bind() from singleton { AppDatabase(instance()) }
|
||||
bind() from singleton { createRoomDatabase() }
|
||||
bind() from singleton { PreferenceProvider(instance()) }
|
||||
bind() from singleton { RepositoryImpl(instance(), instance(), instance()) }
|
||||
bind() from singleton { SettingsRepositoryImpl(instance()) }
|
||||
@@ -43,7 +36,8 @@ abstract class BaseAppClass : Application(), KodeinAware {
|
||||
bind() from provider { ApplicationViewModelFactory(instance(), instance()) }
|
||||
}
|
||||
|
||||
abstract fun createNetworkModule() : Api
|
||||
abstract fun createLocationModule() : LocationProvider
|
||||
abstract fun createNetworkModule(): WeatherApi
|
||||
abstract fun createLocationModule(): LocationProvider
|
||||
abstract fun createRoomDatabase(): AppDatabase
|
||||
|
||||
}
|
||||
@@ -10,19 +10,18 @@ import org.kodein.di.android.kodein
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
@ProvidedTypeConverter
|
||||
class Converter(context: Context): KodeinAware{
|
||||
class Converter(context: Context) : KodeinAware {
|
||||
override val kodein by kodein(context)
|
||||
private val gson by instance<Gson>()
|
||||
|
||||
@TypeConverter
|
||||
fun fullWeatherToString(fullWeather: FullWeather): String{
|
||||
fun fullWeatherToString(fullWeather: FullWeather): String {
|
||||
return gson.toJson(fullWeather)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringToFullWeather(string: String): FullWeather{
|
||||
fun stringToFullWeather(string: String): FullWeather {
|
||||
return gson.fromJson(string, FullWeather::class.java)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ abstract class BaseDeclarationDialog(val context: Context): DeclarationBuilder {
|
||||
abstract override val link: String
|
||||
abstract override val message: String
|
||||
|
||||
fun showDialog(agreeCallback: () -> Unit = { Unit }, disagreeCallback: () -> Unit = { Unit }) {
|
||||
fun showDialog(agreeCallback: () -> Unit = { }, disagreeCallback: () -> Unit = { Unit }) {
|
||||
val myMessage = buildMessage()
|
||||
|
||||
val builder = AlertDialog.Builder(context)
|
||||
|
||||
@@ -2,15 +2,18 @@ package com.appttude.h_mal.atlas_weather.monoWeather.ui
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST
|
||||
import com.appttude.h_mal.atlas_weather.utils.Event
|
||||
import com.appttude.h_mal.atlas_weather.utils.displayToast
|
||||
import com.appttude.h_mal.atlas_weather.utils.hide
|
||||
@@ -24,7 +27,7 @@ import org.kodein.di.android.x.kodein
|
||||
import org.kodein.di.generic.instance
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
abstract class BaseFragment : Fragment(), KodeinAware {
|
||||
abstract class BaseFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId), KodeinAware {
|
||||
|
||||
override val kodein by kodein()
|
||||
val factory by instance<ApplicationViewModelFactory>()
|
||||
@@ -119,4 +122,21 @@ abstract class BaseFragment : Fragment(), KodeinAware {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == LOCATION_PERMISSION_REQUEST) {
|
||||
if (grantResults.isNotEmpty() &&
|
||||
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
permissionsGranted()
|
||||
displayToast("Permission granted")
|
||||
} else {
|
||||
permissionsRefused()
|
||||
displayToast("Permission denied")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun permissionsGranted() {}
|
||||
open fun permissionsRefused() {}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_home.*
|
||||
|
||||
|
||||
class WorldItemFragment : BaseFragment() {
|
||||
class WorldItemFragment : BaseFragment(R.layout.fragment_home) {
|
||||
|
||||
private val viewModel by getFragmentViewModel<WorldViewModel>()
|
||||
private var param1: String? = null
|
||||
@@ -24,12 +24,6 @@ class WorldItemFragment : BaseFragment() {
|
||||
param1 = WorldItemFragmentArgs.fromBundle(requireArguments()).locationName
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home
|
||||
|
||||
import android.Manifest
|
||||
import android.Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
@@ -11,6 +12,7 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.observe
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST
|
||||
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.dialog.PermissionsDeclarationDialog
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.BaseFragment
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.WeatherRecyclerAdapter
|
||||
@@ -24,36 +26,29 @@ import kotlinx.android.synthetic.main.fragment_home.*
|
||||
* A simple [Fragment] subclass.
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
class HomeFragment : BaseFragment() {
|
||||
class HomeFragment : BaseFragment(R.layout.fragment_home) {
|
||||
|
||||
private val viewModel by getFragmentViewModel<MainViewModel>()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val recyclerAdapter = WeatherRecyclerAdapter {
|
||||
val directions =
|
||||
HomeFragmentDirections.actionHomeFragmentToFurtherDetailsFragment(it)
|
||||
navigateTo(directions)
|
||||
}
|
||||
val recyclerAdapter = WeatherRecyclerAdapter(itemClick = {
|
||||
navigateToFurtherDetails(it)
|
||||
})
|
||||
|
||||
forecast_listview.adapter = recyclerAdapter
|
||||
|
||||
PermissionsDeclarationDialog(requireContext()).showDialog(agreeCallback = {
|
||||
getPermissionResult(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) {
|
||||
getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) {
|
||||
viewModel.fetchData()
|
||||
}
|
||||
})
|
||||
|
||||
swipe_refresh.apply {
|
||||
setOnRefreshListener {
|
||||
getPermissionResult(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) {
|
||||
getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) {
|
||||
viewModel.fetchData()
|
||||
isRefreshing = true
|
||||
}
|
||||
@@ -70,16 +65,13 @@ class HomeFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == LOCATION_PERMISSION_REQUEST) {
|
||||
if (grantResults.isNotEmpty()
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
viewModel.fetchData()
|
||||
displayToast("Permission granted")
|
||||
} else {
|
||||
displayToast("Permission denied")
|
||||
}
|
||||
}
|
||||
override fun permissionsGranted() {
|
||||
viewModel.fetchData()
|
||||
}
|
||||
|
||||
private fun navigateToFurtherDetails(forecast: Forecast){
|
||||
val directions = HomeFragmentDirections
|
||||
.actionHomeFragmentToFurtherDetailsFragment(forecast)
|
||||
navigateTo(directions)
|
||||
}
|
||||
}
|
||||
@@ -14,16 +14,10 @@ import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
||||
import kotlinx.android.synthetic.main.activity_add_forecast.*
|
||||
|
||||
|
||||
class AddLocationFragment : BaseFragment() {
|
||||
class AddLocationFragment : BaseFragment(R.layout.activity_add_forecast) {
|
||||
|
||||
private val viewModel by getFragmentViewModel<WorldViewModel>()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.activity_add_forecast, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import kotlinx.android.synthetic.main.fragment_add_location.world_recycler
|
||||
* A simple [Fragment] subclass.
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
class WorldFragment : BaseFragment() {
|
||||
class WorldFragment : BaseFragment(R.layout.fragment__two) {
|
||||
private val viewModel by getFragmentViewModel<WorldViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -31,12 +31,6 @@ class WorldFragment : BaseFragment() {
|
||||
viewModel.fetchAllLocations()
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment__two, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import com.appttude.h_mal.atlas_weather.utils.generateView
|
||||
import com.appttude.h_mal.atlas_weather.utils.loadImage
|
||||
|
||||
class WorldRecyclerAdapter(
|
||||
val itemClick: (WeatherDisplay) -> Unit,
|
||||
val itemLongClick: (String) -> Unit
|
||||
private val itemClick: (WeatherDisplay) -> Unit,
|
||||
private val itemLongClick: (String) -> Unit
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
var weather: MutableList<WeatherDisplay> = mutableListOf()
|
||||
|
||||
@@ -78,23 +78,28 @@ class WorldRecyclerAdapter(
|
||||
return if (weather.size == 0) 1 else weather.size
|
||||
}
|
||||
|
||||
internal class WorldHolderCurrent(listItemView: View) : RecyclerView.ViewHolder(listItemView) {
|
||||
internal class WorldHolderCurrent(cellView: View) : BaseViewHolder<WeatherDisplay>(cellView) {
|
||||
|
||||
var locationTV: TextView = listItemView.findViewById(R.id.db_location)
|
||||
var conditionTV: TextView = listItemView.findViewById(R.id.db_condition)
|
||||
var weatherIV: ImageView = listItemView.findViewById(R.id.db_icon)
|
||||
var avgTempTV: TextView = listItemView.findViewById(R.id.db_main_temp)
|
||||
var tempUnit: TextView = listItemView.findViewById(R.id.db_temp_unit)
|
||||
private val locationTV: TextView = cellView.findViewById(R.id.db_location)
|
||||
private val conditionTV: TextView = cellView.findViewById(R.id.db_condition)
|
||||
private val weatherIV: ImageView = cellView.findViewById(R.id.db_icon)
|
||||
private val avgTempTV: TextView = cellView.findViewById(R.id.db_main_temp)
|
||||
private val tempUnit: TextView = cellView.findViewById(R.id.db_temp_unit)
|
||||
|
||||
fun bindData(weather: WeatherDisplay?){
|
||||
locationTV.text = weather?.displayName
|
||||
conditionTV.text = weather?.description
|
||||
weatherIV.loadImage(weather?.iconURL)
|
||||
avgTempTV.text = weather?.forecast?.get(0)?.mainTemp
|
||||
override fun bindData(data: WeatherDisplay?){
|
||||
locationTV.text = data?.displayName
|
||||
conditionTV.text = data?.description
|
||||
weatherIV.loadImage(data?.iconURL)
|
||||
avgTempTV.text = data?.forecast?.get(0)?.mainTemp
|
||||
tempUnit.text = itemView.context.getString(R.string.degrees)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class BaseViewHolder<T : Any>(cellView: View) : RecyclerView.ViewHolder(cellView) {
|
||||
|
||||
abstract fun bindData(data : T?)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user