- 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:
2022-06-09 22:48:33 +01:00
parent 742875c295
commit 03c11633d8
21 changed files with 280 additions and 188 deletions

97
.gitignore vendored
View File

@@ -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 .gradle
/local.properties .gradle/
/.idea/workspace.xml build/
/.idea/libraries
/.idea/caches # Signing files
.DS_Store .signing/
/build
/captures # 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 .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

Binary file not shown.

20
.idea/gradle.xml generated
View File

@@ -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
View File

@@ -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
View File

@@ -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>

View File

@@ -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>

View File

@@ -97,6 +97,7 @@ dependencies {
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2' implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.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' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2' androidTestImplementation 'com.android.support.test:rules:1.0.2'
// Unit testing // Unit testing
@@ -108,8 +109,8 @@ dependencies {
// android unit testing and espresso // android unit testing and espresso
androidTestImplementation 'androidx.test.ext:junit:1.1.1' 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.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test:rules:1.4.1-alpha06'
//mock websever for testing retrofit responses //mock websever for testing retrofit responses
testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0" testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
@@ -117,7 +118,6 @@ dependencies {
//mockito and livedata testing //mockito and livedata testing
testImplementation 'org.mockito:mockito-inline:2.13.0' testImplementation 'org.mockito:mockito-inline:2.13.0'
implementation 'android.arch.core:core-testing' implementation 'android.arch.core:core-testing'
androidTestImplementation 'androidx.test:rules:1.3.0-rc01'
// Mockk // Mockk
def mockk_ver = "1.10.5" def mockk_ver = "1.10.5"

View File

@@ -1,16 +1,17 @@
package com.appttude.h_mal.atlas_weather.application package com.appttude.h_mal.atlas_weather.application
import androidx.room.Room
import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.idling.CountingIdlingResource 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.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.NetworkModule
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi 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.MockingNetworkInterceptor
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor 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.interceptors.QueryParamsInterceptor
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor 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 import java.io.BufferedReader
class TestAppClass : BaseAppClass() { class TestAppClass : BaseAppClass() {
@@ -22,17 +23,23 @@ class TestAppClass : BaseAppClass() {
IdlingRegistry.getInstance().register(idlingResources) IdlingRegistry.getInstance().register(idlingResources)
} }
override fun createNetworkModule(): Api { override fun createNetworkModule(): WeatherApi {
return NetworkModule().invoke<WeatherApi>( return NetworkModule().invoke<WeatherApi>(
mockingNetworkInterceptor,
NetworkConnectionInterceptor(this), NetworkConnectionInterceptor(this),
QueryParamsInterceptor(), QueryParamsInterceptor(),
loggingInterceptor, loggingInterceptor
mockingNetworkInterceptor ) as WeatherApi
)
} }
override fun createLocationModule() = MockLocationProvider() 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) { fun stubUrl(url: String, rawPath: String) {
val id = resources.getIdentifier(rawPath, "raw", packageName) val id = resources.getIdentifier(rawPath, "raw", packageName)
val iStream = resources.openRawResource(id) val iStream = resources.openRawResource(id)
@@ -40,7 +47,7 @@ class TestAppClass : BaseAppClass() {
mockingNetworkInterceptor.addUrlStub(url = url, data = data) mockingNetworkInterceptor.addUrlStub(url = url, data = data)
} }
fun removeUrlStub(url: String){ fun removeUrlStub(url: String) {
mockingNetworkInterceptor.removeUrlStub(url = url) mockingNetworkInterceptor.removeUrlStub(url = url)
} }

View File

@@ -1,5 +1,12 @@
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite 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.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule import androidx.test.rule.ActivityTestRule
import com.appttude.h_mal.atlas_weather.application.TestAppClass import com.appttude.h_mal.atlas_weather.application.TestAppClass
@@ -21,6 +28,12 @@ open class BaseTest {
testApp = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass testApp = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
setupFeed() setupFeed()
} }
override fun afterActivityLaunched() {
// Dismiss dialog
onView(withText("AGREE")).inRoot(isDialog()).check(matches(isDisplayed())).perform(ViewActions.click())
}
} }
fun stubEndpoint(url: String, stub: Stubs) { fun stubEndpoint(url: String, stub: Stubs) {
@@ -32,8 +45,7 @@ open class BaseTest {
} }
@After @After
fun tearDown() { fun tearDown() {}
}
open fun setupFeed() {} open fun setupFeed() {}
} }

View File

@@ -1,6 +1,11 @@
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite 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 androidx.test.rule.GrantPermissionRule
import com.appttude.h_mal.atlas_weather.monoWeather.robot.homeScreen import com.appttude.h_mal.atlas_weather.monoWeather.robot.homeScreen
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs

View File

@@ -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() {}
}

View File

@@ -1,5 +1,6 @@
package com.appttude.h_mal.atlas_weather.application 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.LocationProvider
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl 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.Api
@@ -26,14 +27,16 @@ const val LOCATION_PERMISSION_REQUEST = 505
class AppClass : BaseAppClass() { class AppClass : BaseAppClass() {
override fun createNetworkModule(): Api { override fun createNetworkModule(): WeatherApi {
return NetworkModule().invoke<WeatherApi>( return NetworkModule().invoke<WeatherApi>(
NetworkConnectionInterceptor(this), NetworkConnectionInterceptor(this),
QueryParamsInterceptor(), QueryParamsInterceptor(),
loggingInterceptor loggingInterceptor
) ) as WeatherApi
} }
override fun createLocationModule() = LocationProviderImpl(this) override fun createLocationModule() = LocationProviderImpl(this)
override fun createRoomDatabase(): AppDatabase = AppDatabase(this)
} }

View File

@@ -1,15 +1,8 @@
package com.appttude.h_mal.atlas_weather.application package com.appttude.h_mal.atlas_weather.application
import android.app.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.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.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.prefs.PreferenceProvider
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl
@@ -31,11 +24,11 @@ abstract class BaseAppClass : Application(), KodeinAware {
override val kodein = Kodein.lazy { override val kodein = Kodein.lazy {
import(androidXModule(this@BaseAppClass)) import(androidXModule(this@BaseAppClass))
bind() from singleton { createNetworkModule() as WeatherApi} bind() from singleton { createNetworkModule() }
bind() from singleton { createLocationModule() } bind() from singleton { createLocationModule() }
bind() from singleton { Gson() } bind() from singleton { Gson() }
bind() from singleton { AppDatabase(instance()) } bind() from singleton { createRoomDatabase() }
bind() from singleton { PreferenceProvider(instance()) } bind() from singleton { PreferenceProvider(instance()) }
bind() from singleton { RepositoryImpl(instance(), instance(), instance()) } bind() from singleton { RepositoryImpl(instance(), instance(), instance()) }
bind() from singleton { SettingsRepositoryImpl(instance()) } bind() from singleton { SettingsRepositoryImpl(instance()) }
@@ -43,7 +36,8 @@ abstract class BaseAppClass : Application(), KodeinAware {
bind() from provider { ApplicationViewModelFactory(instance(), instance()) } bind() from provider { ApplicationViewModelFactory(instance(), instance()) }
} }
abstract fun createNetworkModule() : Api abstract fun createNetworkModule(): WeatherApi
abstract fun createLocationModule() : LocationProvider abstract fun createLocationModule(): LocationProvider
abstract fun createRoomDatabase(): AppDatabase
} }

View File

@@ -10,19 +10,18 @@ import org.kodein.di.android.kodein
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
@ProvidedTypeConverter @ProvidedTypeConverter
class Converter(context: Context): KodeinAware{ class Converter(context: Context) : KodeinAware {
override val kodein by kodein(context) override val kodein by kodein(context)
private val gson by instance<Gson>() private val gson by instance<Gson>()
@TypeConverter @TypeConverter
fun fullWeatherToString(fullWeather: FullWeather): String{ fun fullWeatherToString(fullWeather: FullWeather): String {
return gson.toJson(fullWeather) return gson.toJson(fullWeather)
} }
@TypeConverter @TypeConverter
fun stringToFullWeather(string: String): FullWeather{ fun stringToFullWeather(string: String): FullWeather {
return gson.fromJson(string, FullWeather::class.java) return gson.fromJson(string, FullWeather::class.java)
} }
} }

View File

@@ -20,7 +20,7 @@ abstract class BaseDeclarationDialog(val context: Context): DeclarationBuilder {
abstract override val link: String abstract override val link: String
abstract override val message: 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 myMessage = buildMessage()
val builder = AlertDialog.Builder(context) val builder = AlertDialog.Builder(context)

View File

@@ -2,15 +2,18 @@ package com.appttude.h_mal.atlas_weather.monoWeather.ui
import android.animation.Animator import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.annotation.LayoutRes
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout 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.Event
import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.atlas_weather.utils.displayToast
import com.appttude.h_mal.atlas_weather.utils.hide 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 org.kodein.di.generic.instance
import kotlin.properties.Delegates import kotlin.properties.Delegates
abstract class BaseFragment : Fragment(), KodeinAware { abstract class BaseFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId), KodeinAware {
override val kodein by kodein() override val kodein by kodein()
val factory by instance<ApplicationViewModelFactory>() 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() {}
} }

View File

@@ -14,7 +14,7 @@ import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.*
class WorldItemFragment : BaseFragment() { class WorldItemFragment : BaseFragment(R.layout.fragment_home) {
private val viewModel by getFragmentViewModel<WorldViewModel>() private val viewModel by getFragmentViewModel<WorldViewModel>()
private var param1: String? = null private var param1: String? = null
@@ -24,12 +24,6 @@ class WorldItemFragment : BaseFragment() {
param1 = WorldItemFragmentArgs.fromBundle(requireArguments()).locationName 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@@ -1,6 +1,7 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home package com.appttude.h_mal.atlas_weather.monoWeather.ui.home
import android.Manifest import android.Manifest
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
@@ -11,6 +12,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.observe import androidx.lifecycle.observe
import com.appttude.h_mal.atlas_weather.R 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.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.dialog.PermissionsDeclarationDialog
import com.appttude.h_mal.atlas_weather.monoWeather.ui.BaseFragment import com.appttude.h_mal.atlas_weather.monoWeather.ui.BaseFragment
import com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.WeatherRecyclerAdapter 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. * A simple [Fragment] subclass.
* create an instance of this fragment. * create an instance of this fragment.
*/ */
class HomeFragment : BaseFragment() { class HomeFragment : BaseFragment(R.layout.fragment_home) {
private val viewModel by getFragmentViewModel<MainViewModel>() 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") @SuppressLint("MissingPermission")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val recyclerAdapter = WeatherRecyclerAdapter { val recyclerAdapter = WeatherRecyclerAdapter(itemClick = {
val directions = navigateToFurtherDetails(it)
HomeFragmentDirections.actionHomeFragmentToFurtherDetailsFragment(it) })
navigateTo(directions)
}
forecast_listview.adapter = recyclerAdapter forecast_listview.adapter = recyclerAdapter
PermissionsDeclarationDialog(requireContext()).showDialog(agreeCallback = { PermissionsDeclarationDialog(requireContext()).showDialog(agreeCallback = {
getPermissionResult(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) { getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) {
viewModel.fetchData() viewModel.fetchData()
} }
}) })
swipe_refresh.apply { swipe_refresh.apply {
setOnRefreshListener { setOnRefreshListener {
getPermissionResult(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) { getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) {
viewModel.fetchData() viewModel.fetchData()
isRefreshing = true isRefreshing = true
} }
@@ -70,16 +65,13 @@ class HomeFragment : BaseFragment() {
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) { override fun permissionsGranted() {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) viewModel.fetchData()
if (requestCode == LOCATION_PERMISSION_REQUEST) { }
if (grantResults.isNotEmpty()
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) { private fun navigateToFurtherDetails(forecast: Forecast){
viewModel.fetchData() val directions = HomeFragmentDirections
displayToast("Permission granted") .actionHomeFragmentToFurtherDetailsFragment(forecast)
} else { navigateTo(directions)
displayToast("Permission denied")
}
}
} }
} }

View File

@@ -14,16 +14,10 @@ import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
import kotlinx.android.synthetic.main.activity_add_forecast.* 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>() 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@@ -22,7 +22,7 @@ import kotlinx.android.synthetic.main.fragment_add_location.world_recycler
* A simple [Fragment] subclass. * A simple [Fragment] subclass.
* create an instance of this fragment. * create an instance of this fragment.
*/ */
class WorldFragment : BaseFragment() { class WorldFragment : BaseFragment(R.layout.fragment__two) {
private val viewModel by getFragmentViewModel<WorldViewModel>() private val viewModel by getFragmentViewModel<WorldViewModel>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -31,12 +31,6 @@ class WorldFragment : BaseFragment() {
viewModel.fetchAllLocations() 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@@ -12,8 +12,8 @@ import com.appttude.h_mal.atlas_weather.utils.generateView
import com.appttude.h_mal.atlas_weather.utils.loadImage import com.appttude.h_mal.atlas_weather.utils.loadImage
class WorldRecyclerAdapter( class WorldRecyclerAdapter(
val itemClick: (WeatherDisplay) -> Unit, private val itemClick: (WeatherDisplay) -> Unit,
val itemLongClick: (String) -> Unit private val itemLongClick: (String) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var weather: MutableList<WeatherDisplay> = mutableListOf() var weather: MutableList<WeatherDisplay> = mutableListOf()
@@ -78,23 +78,28 @@ class WorldRecyclerAdapter(
return if (weather.size == 0) 1 else weather.size 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) private val locationTV: TextView = cellView.findViewById(R.id.db_location)
var conditionTV: TextView = listItemView.findViewById(R.id.db_condition) private val conditionTV: TextView = cellView.findViewById(R.id.db_condition)
var weatherIV: ImageView = listItemView.findViewById(R.id.db_icon) private val weatherIV: ImageView = cellView.findViewById(R.id.db_icon)
var avgTempTV: TextView = listItemView.findViewById(R.id.db_main_temp) private val avgTempTV: TextView = cellView.findViewById(R.id.db_main_temp)
var tempUnit: TextView = listItemView.findViewById(R.id.db_temp_unit) private val tempUnit: TextView = cellView.findViewById(R.id.db_temp_unit)
fun bindData(weather: WeatherDisplay?){ override fun bindData(data: WeatherDisplay?){
locationTV.text = weather?.displayName locationTV.text = data?.displayName
conditionTV.text = weather?.description conditionTV.text = data?.description
weatherIV.loadImage(weather?.iconURL) weatherIV.loadImage(data?.iconURL)
avgTempTV.text = weather?.forecast?.get(0)?.mainTemp avgTempTV.text = data?.forecast?.get(0)?.mainTemp
tempUnit.text = itemView.context.getString(R.string.degrees) tempUnit.text = itemView.context.getString(R.string.degrees)
} }
} }
abstract class BaseViewHolder<T : Any>(cellView: View) : RecyclerView.ViewHolder(cellView) {
abstract fun bindData(data : T?)
}
} }