mirror of
https://github.com/hmalik144/Weather-apps.git
synced 2025-12-10 02:05:20 +00:00
release to main (#39)
- change location retrieval accuracy - change location retrieval caching from location provider - Imperial units added - atlas weather notification fix (only for lower versions) - Minor lint fixes - Upgrade gradle dependencies to versions accepted by android 33 - upgrade android gradle to 8.5 - upgrade application to android 34 - upgraded all library dependencies - Snapshot tests added for readme.md - UI corrections during snapshots - Weather API successfully replaces
This commit is contained in:
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.6.0" />
|
<option name="version" value="2.0.0" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
198
app/build.gradle
198
app/build.gradle
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'org.jetbrains.kotlin.android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
id 'kotlin-android-extensions'
|
|
||||||
id 'kotlin-kapt'
|
id 'kotlin-kapt'
|
||||||
id 'androidx.navigation.safeargs'
|
id 'androidx.navigation.safeargs'
|
||||||
}
|
}
|
||||||
@@ -13,16 +12,14 @@ def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS")
|
|||||||
def keystorePath = System.getenv('PWD') + "/app/keystore.jks"
|
def keystorePath = System.getenv('PWD') + "/app/keystore.jks"
|
||||||
def keystore = file(keystorePath).exists() ? file(keystorePath) : null
|
def keystore = file(keystorePath).exists() ? file(keystorePath) : null
|
||||||
android {
|
android {
|
||||||
lintOptions {
|
namespace 'com.appttude.h_mal.atlas_weather'
|
||||||
abortOnError false
|
compileSdk = Integer.parseInt(TARGET_SDK_VERSION)
|
||||||
}
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.appttude.h_mal.atlas_weather"
|
applicationId "com.appttude.h_mal.atlas_weather"
|
||||||
compileSdk 33
|
minSdkVersion MIN_SDK_VERSION
|
||||||
minSdkVersion 26
|
targetSdkVersion TARGET_SDK_VERSION
|
||||||
targetSdkVersion 33
|
versionCode 6
|
||||||
versionCode 5
|
versionName "3.1"
|
||||||
versionName "3.0"
|
|
||||||
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
@@ -35,6 +32,10 @@ android {
|
|||||||
buildConfigField "String", "ParamOne", System.getenv('WEATHER_API')
|
buildConfigField "String", "ParamOne", System.getenv('WEATHER_API')
|
||||||
buildConfigField "String", "ParamTwo", System.getenv('SEARCH_API')
|
buildConfigField "String", "ParamTwo", System.getenv('SEARCH_API')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
resources.excludes.add("META-INF/*")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
android {
|
android {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -78,20 +79,23 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
freeCompilerArgs += [
|
freeCompilerArgs += [
|
||||||
'-Xjvm-default=enable'
|
'-Xjvm-default=all-compatibility'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "default"
|
buildFeatures {
|
||||||
|
flavorDimensions = ["version"]
|
||||||
|
}
|
||||||
productFlavors {
|
productFlavors {
|
||||||
atlasWeather {
|
atlasWeather {
|
||||||
|
dimension "version"
|
||||||
applicationId "com.appttude.h_mal.atlas_weather"
|
applicationId "com.appttude.h_mal.atlas_weather"
|
||||||
versionCode 5
|
versionCode 5
|
||||||
versionName "3.0.0"
|
versionName "3.0.0"
|
||||||
}
|
}
|
||||||
monoWeather {
|
monoWeather {
|
||||||
|
dimension "version"
|
||||||
applicationId "com.appttude.h_mal.monoWeather"
|
applicationId "com.appttude.h_mal.monoWeather"
|
||||||
|
|
||||||
versionCode 7
|
versionCode 7
|
||||||
versionName "4.2.0"
|
versionName "4.2.0"
|
||||||
}
|
}
|
||||||
@@ -108,105 +112,113 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lint {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
testBuildType "debug"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation "androidx.appcompat:appcompat:$APP_COMPAT"
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation "com.google.android.material:material:$MATERIAL_VERSION"
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation "androidx.constraintlayout:constraintlayout:$CONSTR_LAYOUT_VERSION"
|
||||||
implementation 'androidx.fragment:fragment:1.2.0'
|
implementation "androidx.fragment:fragment:$FRAGMENT_VERSION"
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation "androidx.fragment:fragment-ktx:$FRAGMENT_VERSION"
|
||||||
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
|
||||||
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
|
implementation "androidx.preference:preference:$PREFERENCES_VERSION"
|
||||||
implementation "com.google.android.gms:play-services-location:21.0.1"
|
implementation "androidx.core:core:$ANDROID_CORE"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "androidx.customview:customview:$CUSTOM_VIEW"
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation "androidx.cardview:cardview:$CARD_VIEW"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
|
implementation "androidx.lifecycle:lifecycle-common:$ANDROID_LIFECYCLE"
|
||||||
implementation 'androidx.preference:preference:1.2.1'
|
implementation "androidx.lifecycle:lifecycle-livedata-core:$ANDROID_LIFECYCLE"
|
||||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'
|
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$ANDROID_LIFECYCLE"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-viewmodel:$ANDROID_LIFECYCLE"
|
||||||
|
implementation "androidx.recyclerview:recyclerview:$RECYCLER_VIEW"
|
||||||
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:$SWIPE_REFRESH"
|
||||||
|
implementation "com.google.code.gson:gson:$GSON"
|
||||||
|
implementation "com.google.guava:guava:$GUAVA"
|
||||||
|
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
|
||||||
|
// force upgrade to 1.1.0 because its required by androidTestImplementation,
|
||||||
|
// and without this statement AGP will silently downgrade to tracing:1.0.0
|
||||||
|
implementation "androidx.tracing:tracing:1.1.0"
|
||||||
|
/ * Google play services * /
|
||||||
|
implementation "com.google.android.gms:play-services-location:$GOOGLE_PLAY_SERVICE"
|
||||||
/ * Unit testing * /
|
/ * Unit testing * /
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation "junit:junit:$JUNIT_VERSION"
|
||||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
androidTestImplementation project(path: ':app')
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
testRuntimeOnly "org.jetbrains.kotlin:kotlin-test-junit:$KOTLIN_VERSION"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-test:$KOTLIN_VERSION"
|
||||||
/ * Fragment Navigation * /
|
androidTestImplementation "junit:junit:$JUNIT_VERSION"
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$KOTLINX_COROUTINES"
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$KOTLINX_COROUTINES"
|
||||||
|
testImplementation "androidx.cardview:cardview:$CARD_VIEW"
|
||||||
|
testImplementation "com.tomtom.online:sdk-maps-ui-extensions:$TOMTOM_MAP"
|
||||||
|
/ * Navigation * /
|
||||||
|
implementation "androidx.navigation:navigation-common:$NAVIGATION_VERSION"
|
||||||
|
implementation "androidx.navigation:navigation-fragment:$NAVIGATION_VERSION"
|
||||||
|
implementation "androidx.navigation:navigation-runtime:$NAVIGATION_VERSION"
|
||||||
|
implementation "androidx.navigation:navigation-ui:$NAVIGATION_VERSION"
|
||||||
/ * android unit testing and espresso * /
|
/ * android unit testing and espresso * /
|
||||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
androidTestImplementation "androidx.test:rules:$ANDROIDX_TEST"
|
||||||
androidTestImplementation "androidx.test:core:1.5.0"
|
androidTestImplementation "androidx.test:core:$ANDROIDX_TEST"
|
||||||
|
androidTestImplementation "androidx.test:monitor:$TEST_MONITOR"
|
||||||
/ * Android Espresso * /
|
androidTestImplementation "androidx.test.ext:junit:$TEST_JUNIT_VERSION"
|
||||||
def testJunitVersion = "1.1.5"
|
androidTestRuntimeOnly "org.jetbrains.kotlin:kotlin-test-junit:$KOTLIN_VERSION"
|
||||||
def testRunnerVersion = "1.5.2"
|
androidTestImplementation "androidx.test.espresso:espresso-core:$ESPRESSO_VERSION"
|
||||||
def espressoVersion = "3.5.1"
|
androidTestImplementation "androidx.test.espresso:espresso-idling-resource:$ESPRESSO_VERSION"
|
||||||
androidTestImplementation "androidx.test.ext:junit:$testJunitVersion"
|
androidTestImplementation "androidx.test:runner:$TEST_RUNNER_VERSION"
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
androidTestImplementation "androidx.test.espresso:espresso-contrib:$ESPRESSO_VERSION"
|
||||||
androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion"
|
androidTestImplementation "org.hamcrest:hamcrest:$HAMCREST_VERSION"
|
||||||
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$KOTLINX_COROUTINES"
|
||||||
androidTestImplementation "androidx.test:runner:$testRunnerVersion"
|
androidTestImplementation "androidx.cardview:cardview:$CARD_VIEW"
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
|
androidTestImplementation 'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:3.1.2'
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
|
androidTestImplementation "com.tomtom.online:sdk-maps-ui-extensions:$TOMTOM_MAP"
|
||||||
androidTestImplementation "org.hamcrest:hamcrest:2.2"
|
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$KOTLIN_VERSION"
|
||||||
|
atlasWeatherImplementation "androidx.cardview:cardview:$CARD_VIEW"
|
||||||
/ * mock websever for testing retrofit responses * /
|
/ * mock websever for testing retrofit responses * /
|
||||||
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"
|
||||||
|
|
||||||
/ * mockito and livedata testing * /
|
/ * mockito and livedata testing * /
|
||||||
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
testImplementation "org.mockito:mockito-inline:$MOKITO_INLINE_VERSION"
|
||||||
implementation 'androidx.arch.core:core-testing:2.2.0'
|
testImplementation "androidx.arch.core:core-testing:$CORE_TEST_VERSION"
|
||||||
|
androidTestImplementation "androidx.arch.core:core-testing:$CORE_TEST_VERSION"
|
||||||
/ * MockK * /
|
/ * MockK * /
|
||||||
def mockk_ver = "1.10.5"
|
testImplementation "io.mockk:mockk:$MOCKK_VERSION"
|
||||||
testImplementation "io.mockk:mockk:$mockk_ver"
|
androidTestImplementation "io.mockk:mockk-android:$MOCKK_VERSION"
|
||||||
androidTestImplementation "io.mockk:mockk-android:$mockk_ver"
|
/ * Retrofit & Okhttp * /
|
||||||
|
implementation "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"
|
||||||
/ * Retrofit * /
|
implementation "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
|
||||||
def retrofit_ver = "2.9.0"
|
implementation "com.squareup.okhttp3:logging-interceptor:$OKHTTP_VERSION"
|
||||||
implementation "com.squareup.retrofit2:retrofit:$retrofit_ver"
|
implementation "com.squareup.okhttp3:okhttp:$OKHTTP_VERSION"
|
||||||
implementation "com.squareup.retrofit2:converter-gson:$retrofit_ver"
|
/ * Kodein Dependency Injection * /
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:4.9.0"
|
implementation "org.kodein.di:kodein-di-generic-jvm:$KODEIN_VERSION"
|
||||||
|
implementation "org.kodein.di:kodein-di-framework-android-x:$KODEIN_VERSION"
|
||||||
/ * Shared prefs * /
|
implementation "org.kodein.di:kodein-di-core-jvm:$KODEIN_VERSION"
|
||||||
def prefs_ver = "1.2.0"
|
implementation "org.kodein.di:kodein-di-framework-android-core:$KODEIN_VERSION"
|
||||||
implementation "androidx.preference:preference-ktx:$prefs_ver"
|
|
||||||
|
|
||||||
/ *Kodein Dependency Injection * /
|
|
||||||
def kodein_version = "6.2.1"
|
|
||||||
implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version"
|
|
||||||
implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version"
|
|
||||||
|
|
||||||
/ * Room database * /
|
/ * Room database * /
|
||||||
def room_version = "2.4.3"
|
runtimeOnly "androidx.room:room-runtime:$ROOM_VERSION"
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
kapt "androidx.room:room-compiler:$ROOM_VERSION"
|
||||||
kapt "androidx.room:room-compiler:$room_version"
|
implementation "androidx.room:room-ktx:$ROOM_VERSION"
|
||||||
implementation "androidx.room:room-ktx:$room_version"
|
implementation "androidx.room:room-common:$ROOM_VERSION"
|
||||||
|
implementation 'androidx.sqlite:sqlite:2.2.0'
|
||||||
/ * Picasso * /
|
/ * Picasso * /
|
||||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
|
|
||||||
/ * coroutine * /
|
/ * coroutine * /
|
||||||
def coroutine_version = "1.3.9"
|
runtimeOnly "org.jetbrains.kotlinx:kotlinx-coroutines-android:$KOTLINX_COROUTINES"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$KOTLINX_COROUTINES"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$KOTLINX_COROUTINES"
|
||||||
/ * tomtom search * /
|
/ * tomtom search * /
|
||||||
def tomtom_version = "2.4771"
|
implementation "com.tomtom.online:sdk-search:$TOMTOM_SEARCH"
|
||||||
implementation "com.tomtom.online:sdk-search:$tomtom_version"
|
implementation "com.tomtom.online:sdk-maps:$TOMTOM_MAP"
|
||||||
implementation "com.tomtom.online:sdk-maps:2.4807"
|
implementation "com.tomtom.online:sdk-search-core:$TOMTOM_SEARCH"
|
||||||
|
monoWeatherImplementation "com.tomtom.online:sdk-maps-ui-extensions:$TOMTOM_MAP"
|
||||||
/ * coroutines support for firebase operations * /
|
implementation "com.tomtom.online:sdk-search-core:$TOMTOM_SEARCH"
|
||||||
def coroutines_google_ver = "1.6.4"
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutines_google_ver"
|
|
||||||
|
|
||||||
/ * Picasso * /
|
/ * Picasso * /
|
||||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
|
|
||||||
/ * screenshot library * /
|
/ * screenshot library * /
|
||||||
androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
|
androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
|
||||||
/ * Permissions dispatcher * /
|
/ * Permissions dispatcher * /
|
||||||
def dispatcher_ver = "4.9.2"
|
implementation "com.github.permissions-dispatcher:permissionsdispatcher:$PERMISSIONS_DISPATCHER"
|
||||||
implementation "com.github.permissions-dispatcher:permissionsdispatcher:${dispatcher_ver}"
|
kapt "com.github.permissions-dispatcher:permissionsdispatcher-processor:$PERMISSIONS_DISPATCHER"
|
||||||
kapt "com.github.permissions-dispatcher:permissionsdispatcher-processor:${dispatcher_ver}"
|
implementation "com.github.permissions-dispatcher:permissionsdispatcher-annotation:$PERMISSIONS_DISPATCHER"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"cod": 401,
|
|
||||||
"message": "Invalid API key. Please see http://openweathermap.org/faq#error401 for more info."
|
|
||||||
}
|
|
||||||
1
app/src/androidTest/assets/invalid_api_key_response.txt
Normal file
1
app/src/androidTest/assets/invalid_api_key_response.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
No account found with API key 'wrong api key'
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
10603
app/src/androidTest/assets/valid_response_with_alert.json
Normal file
10603
app/src/androidTest/assets/valid_response_with_alert.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@ import org.junit.Before
|
|||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import tools.fastlane.screengrab.Screengrab
|
import tools.fastlane.screengrab.Screengrab
|
||||||
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy
|
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy
|
||||||
|
import tools.fastlane.screengrab.locale.LocaleTestRule
|
||||||
|
|
||||||
@Suppress("EmptyMethod")
|
@Suppress("EmptyMethod")
|
||||||
open class BaseTest<A : Activity>(
|
open class BaseTest<A : Activity>(
|
||||||
@@ -47,9 +48,15 @@ open class BaseTest<A : Activity>(
|
|||||||
@get:Rule
|
@get:Rule
|
||||||
var permissionRule = GrantPermissionRule.grant(Manifest.permission.ACCESS_COARSE_LOCATION)
|
var permissionRule = GrantPermissionRule.grant(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
var writePermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
var snapshotRule: SnapshotRule = SnapshotRule()
|
var snapshotRule: SnapshotRule = SnapshotRule()
|
||||||
|
|
||||||
|
@Rule @JvmField
|
||||||
|
val localeTestRule = LocaleTestRule()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy())
|
Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy())
|
||||||
@@ -73,8 +80,8 @@ open class BaseTest<A : Activity>(
|
|||||||
afterLaunch()
|
afterLaunch()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stubEndpoint(url: String, stub: Stubs, code: Int = 200) {
|
fun stubEndpoint(url: String, stub: Stubs, code: Int = 200, extension: String = ".json") {
|
||||||
testApp.stubUrl(url, stub.id, code)
|
testApp.stubUrl(url, stub.id, code, extension)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unstubEndpoint(url: String) {
|
fun unstubEndpoint(url: String) {
|
||||||
@@ -85,8 +92,18 @@ open class BaseTest<A : Activity>(
|
|||||||
testApp.stubLocation(location, lat, long)
|
testApp.stubLocation(location, lat, long)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearLocation(location: String) {
|
||||||
|
testApp.removeLocation(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearLocation(lat: Double, long: Double) {
|
||||||
|
testApp.removeLocation(lat, long)
|
||||||
|
}
|
||||||
|
|
||||||
fun clearPrefs() = prefs.clearPrefs()
|
fun clearPrefs() = prefs.clearPrefs()
|
||||||
|
|
||||||
|
fun clearDatabase() = testApp.clearDatabase()
|
||||||
|
|
||||||
fun getActivity() = testActivity
|
fun getActivity() = testActivity
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -108,7 +125,6 @@ open class BaseTest<A : Activity>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
fun checkToastMessage(message: String) {
|
fun checkToastMessage(message: String) {
|
||||||
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
|
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ import androidx.test.espresso.action.ViewActions.swipeDown
|
|||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import androidx.test.espresso.contrib.PickerActions
|
import androidx.test.espresso.contrib.PickerActions
|
||||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withClassName
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
||||||
import com.appttude.h_mal.atlas_weather.helpers.checkErrorMessage
|
import com.appttude.h_mal.atlas_weather.helpers.checkErrorMessage
|
||||||
import com.appttude.h_mal.atlas_weather.helpers.checkImage
|
import com.appttude.h_mal.atlas_weather.helpers.checkImage
|
||||||
@@ -31,7 +36,7 @@ open class BaseTestRobot {
|
|||||||
|
|
||||||
fun goBack() = Espresso.pressBack()
|
fun goBack() = Espresso.pressBack()
|
||||||
|
|
||||||
fun fillEditText(resId: Int, text: String?): ViewInteraction =
|
fun fillEditText(resId: Int, text: String): ViewInteraction =
|
||||||
onView(withId(resId)).perform(
|
onView(withId(resId)).perform(
|
||||||
ViewActions.replaceText(text),
|
ViewActions.replaceText(text),
|
||||||
ViewActions.closeSoftKeyboard()
|
ViewActions.closeSoftKeyboard()
|
||||||
@@ -60,7 +65,7 @@ open class BaseTestRobot {
|
|||||||
.atPosition(position).perform(click())
|
.atPosition(position).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? {
|
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction {
|
||||||
return matchView(recyclerId)
|
return matchView(recyclerId)
|
||||||
.perform(
|
.perform(
|
||||||
// scrollTo will fail the test if no item matches.
|
// scrollTo will fail the test if no item matches.
|
||||||
@@ -73,7 +78,7 @@ open class BaseTestRobot {
|
|||||||
fun <VH : ViewHolder> scrollToRecyclerItem(
|
fun <VH : ViewHolder> scrollToRecyclerItem(
|
||||||
recyclerId: Int,
|
recyclerId: Int,
|
||||||
resIdForString: Int
|
resIdForString: Int
|
||||||
): ViewInteraction? {
|
): ViewInteraction {
|
||||||
return matchView(recyclerId)
|
return matchView(recyclerId)
|
||||||
.perform(
|
.perform(
|
||||||
// scrollTo will fail the test if no item matches.
|
// scrollTo will fail the test if no item matches.
|
||||||
@@ -86,7 +91,7 @@ open class BaseTestRobot {
|
|||||||
fun <VH : ViewHolder> scrollToRecyclerItemByPosition(
|
fun <VH : ViewHolder> scrollToRecyclerItemByPosition(
|
||||||
recyclerId: Int,
|
recyclerId: Int,
|
||||||
position: Int
|
position: Int
|
||||||
): ViewInteraction? {
|
): ViewInteraction {
|
||||||
return matchView(recyclerId)
|
return matchView(recyclerId)
|
||||||
.perform(
|
.perform(
|
||||||
// scrollTo will fail the test if no item matches.
|
// scrollTo will fail the test if no item matches.
|
||||||
@@ -129,6 +134,16 @@ open class BaseTestRobot {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> clickSubViewInRecycler(
|
||||||
|
recyclerId: Int,
|
||||||
|
position: Int,
|
||||||
|
) {
|
||||||
|
scrollToRecyclerItemByPosition<VH>(recyclerId, position)
|
||||||
|
?.perform(
|
||||||
|
RecyclerViewActions.actionOnItemAtPosition<VH>(position, click())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun checkErrorOnTextEntry(resId: Int, errorMessage: String): ViewInteraction =
|
fun checkErrorOnTextEntry(resId: Int, errorMessage: String): ViewInteraction =
|
||||||
onView(withId(resId)).check(matches(checkErrorMessage(errorMessage)))
|
onView(withId(resId)).check(matches(checkErrorMessage(errorMessage)))
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ class MockLocationProvider : LocationProvider {
|
|||||||
|
|
||||||
fun addLocationToList(name: String, lat: Double, long: Double) {
|
fun addLocationToList(name: String, lat: Double, long: Double) {
|
||||||
val latLong = Pair(lat, long)
|
val latLong = Pair(lat, long)
|
||||||
feedMap.put(name, latLong)
|
feedMap[name] = latLong
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeLocationFromList(name: String) {
|
||||||
|
feedMap.remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeLocationFromList(lat: Double, long: Double) {
|
||||||
|
feedMap.filterValues { it.first == lat && it.second == long }.keys.firstOrNull()?.let {
|
||||||
|
feedMap.remove(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,9 +16,9 @@ class MockingNetworkInterceptor(
|
|||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
idlingResource.increment()
|
idlingResource.increment()
|
||||||
val original = chain.request()
|
val original = chain.request()
|
||||||
val originalHttpUrl = original.url.toString().split("?")[0]
|
val originalHttpUrl = original.url.toString()
|
||||||
|
|
||||||
feedMap[originalHttpUrl]?.let { responsePair ->
|
feedMap[feedMap.keys.first { originalHttpUrl.contains(it) }]?.let { responsePair ->
|
||||||
val code = responsePair.second
|
val code = responsePair.second
|
||||||
val jsonBody = responsePair.first
|
val jsonBody = responsePair.first
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.utils
|
package com.appttude.h_mal.atlas_weather.utils
|
||||||
|
|
||||||
|
const val baseUrl = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/"
|
||||||
enum class Stubs(
|
enum class Stubs(
|
||||||
val id: String
|
val id: String
|
||||||
) {
|
) {
|
||||||
@@ -7,5 +8,5 @@ enum class Stubs(
|
|||||||
Imperial("valid_response_imperial"),
|
Imperial("valid_response_imperial"),
|
||||||
WrongLocation("wrong_location_response"),
|
WrongLocation("wrong_location_response"),
|
||||||
InvalidKey("invalid_api_key_response"),
|
InvalidKey("invalid_api_key_response"),
|
||||||
Sydney("valid_response_metric_sydney")
|
Sydney("valid_response_metric_sydney"),
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ fun <T> LiveData<T>.getOrAwaitValue(
|
|||||||
var data: T? = null
|
var data: T? = null
|
||||||
val latch = CountDownLatch(1)
|
val latch = CountDownLatch(1)
|
||||||
val observer = object : Observer<T> {
|
val observer = object : Observer<T> {
|
||||||
override fun onChanged(o: T?) {
|
override fun onChanged(o: T) {
|
||||||
data = o
|
data = o
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
this@getOrAwaitValue.removeObserver(this)
|
this@getOrAwaitValue.removeObserver(this)
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
|||||||
import com.appttude.h_mal.atlas_weather.data.room.Converter
|
import com.appttude.h_mal.atlas_weather.data.room.Converter
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
|
||||||
class TestAppClass : BaseAppClass() {
|
class TestAppClass : AppClass() {
|
||||||
private val idlingResources = CountingIdlingResource("Data_loader")
|
private val idlingResources = CountingIdlingResource("Data_loader")
|
||||||
private val mockingNetworkInterceptor = MockingNetworkInterceptor(idlingResources)
|
private val mockingNetworkInterceptor = MockingNetworkInterceptor(idlingResources)
|
||||||
|
|
||||||
lateinit var database: AppDatabase
|
lateinit var database: AppDatabase
|
||||||
lateinit var locationProvider: MockLocationProvider
|
private val locationProvider: MockLocationProvider = MockLocationProvider()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
@@ -38,20 +38,20 @@ class TestAppClass : BaseAppClass() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun createLocationModule(): LocationProvider {
|
override fun createLocationModule(): LocationProvider {
|
||||||
locationProvider = MockLocationProvider()
|
|
||||||
return locationProvider
|
return locationProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createRoomDatabase(): AppDatabase {
|
override fun createRoomDatabase(): AppDatabase {
|
||||||
database = Room.inMemoryDatabaseBuilder(this, AppDatabase::class.java)
|
database = Room.inMemoryDatabaseBuilder(applicationContext, AppDatabase::class.java)
|
||||||
|
.allowMainThreadQueries()
|
||||||
.addTypeConverter(Converter(this))
|
.addTypeConverter(Converter(this))
|
||||||
.build()
|
.build()
|
||||||
return database
|
return database
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stubUrl(url: String, rawPath: String, code: Int = 200) {
|
fun stubUrl(url: String, rawPath: String, code: Int = 200, extension: String = ".json") {
|
||||||
val iStream =
|
val iStream =
|
||||||
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath.json")
|
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath$extension")
|
||||||
val data = iStream.bufferedReader().use(BufferedReader::readText)
|
val data = iStream.bufferedReader().use(BufferedReader::readText)
|
||||||
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
|
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
|
||||||
}
|
}
|
||||||
@@ -64,4 +64,15 @@ class TestAppClass : BaseAppClass() {
|
|||||||
locationProvider.addLocationToList(location, lat, long)
|
locationProvider.addLocationToList(location, lat, long)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeLocation(location: String) {
|
||||||
|
locationProvider.removeLocationFromList(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeLocation(lat: Double, long: Double) {
|
||||||
|
locationProvider.removeLocationFromList(lat, long)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearDatabase() {
|
||||||
|
database.getWeatherDao().deleteAll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.robot
|
||||||
|
|
||||||
|
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
||||||
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
|
|
||||||
|
fun furtherInfoScreen(func: FurtherInfoScreen.() -> Unit) = FurtherInfoScreen().apply { func() }
|
||||||
|
class FurtherInfoScreen : BaseTestRobot() {
|
||||||
|
fun verifyMaxTemperature(temperature: Int) =
|
||||||
|
matchText(R.id.maxtemp, StringBuilder().append(temperature).append("°").toString())
|
||||||
|
fun verifyAverageTemperature(temperature: Int) =
|
||||||
|
matchText(R.id.averagetemp, StringBuilder().append(temperature).append("°").toString())
|
||||||
|
fun verifyMinTemperature(temperature: Int) =
|
||||||
|
matchText(R.id.minimumtemp, StringBuilder().append(temperature).append("°").toString())
|
||||||
|
|
||||||
|
fun verifyWindSpeed(speedText: String) =
|
||||||
|
matchText(R.id.windtext, speedText)
|
||||||
|
|
||||||
|
fun verifyHumidity(humidity: Int) =
|
||||||
|
matchText(R.id.humiditytext, humidity.toString())
|
||||||
|
fun verifyPrecipitation(precipitation: Int) =
|
||||||
|
matchText(R.id.preciptext, precipitation.toString())
|
||||||
|
|
||||||
|
fun verifyCloudCoverage(coverage: Int) =
|
||||||
|
matchText(R.id.cloudtext, coverage.toString())
|
||||||
|
|
||||||
|
fun verifyUvIndex(uv: Int) =
|
||||||
|
matchText(R.id.uvtext, uv.toString())
|
||||||
|
fun verifySunrise(sunrise: String) =
|
||||||
|
matchText(R.id.sunrisetext, sunrise)
|
||||||
|
fun verifySunset(sunset: String) =
|
||||||
|
matchText(R.id.sunsettext, sunset)
|
||||||
|
|
||||||
|
fun refresh() = pullToRefresh(R.id.swipe_refresh)
|
||||||
|
fun isDisplayed() = matchViewWaitFor(R.id.maxtemp)
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.robot
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions
|
||||||
|
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||||
|
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
||||||
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
|
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType.Companion.getLabel
|
||||||
|
|
||||||
|
|
||||||
|
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
|
||||||
|
class SettingsScreen : BaseTestRobot() {
|
||||||
|
|
||||||
|
fun selectWeatherUnits(unitType: UnitType) {
|
||||||
|
onView(withId(androidx.preference.R.id.recycler_view))
|
||||||
|
.perform(
|
||||||
|
RecyclerViewActions.actionOnItem<ViewHolder>(
|
||||||
|
ViewMatchers.hasDescendant(withText(R.string.weather_units)),
|
||||||
|
click()))
|
||||||
|
val label = unitType.getLabel()
|
||||||
|
|
||||||
|
onView(withText(label))
|
||||||
|
.inRoot(isDialog())
|
||||||
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
.perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun verifyCurrentTemperature(temperature: Int) =
|
||||||
|
matchText(R.id.temp_main_4, temperature.toString())
|
||||||
|
|
||||||
|
fun verifyCurrentLocation(location: String) = matchText(R.id.location_main_4, location)
|
||||||
|
fun refresh() = pullToRefresh(R.id.swipe_refresh)
|
||||||
|
|
||||||
|
fun verifyUnableToRetrieve() {
|
||||||
|
matchText(R.id.header_text, R.string.retrieve_warning)
|
||||||
|
matchText(R.id.body_text, R.string.empty_retrieve_warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isDisplayed() {
|
||||||
|
waitForView(
|
||||||
|
withText("Metric")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.robot
|
||||||
|
|
||||||
|
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
||||||
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
|
import com.appttude.h_mal.atlas_weather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily
|
||||||
|
|
||||||
|
fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() }
|
||||||
|
class WeatherScreen : BaseTestRobot() {
|
||||||
|
fun verifyCurrentTemperature(temperature: Int) =
|
||||||
|
matchText(R.id.temp_main_4, temperature.toString())
|
||||||
|
|
||||||
|
fun verifyCurrentLocation(location: String) = matchText(R.id.location_main_4, location)
|
||||||
|
fun refresh() = pullToRefresh(R.id.swipe_refresh)
|
||||||
|
fun isDisplayed() = matchViewWaitFor(R.id.temp_main_4)
|
||||||
|
|
||||||
|
fun verifyUnableToRetrieve() {
|
||||||
|
matchText(R.id.header_text, R.string.retrieve_warning)
|
||||||
|
matchText(R.id.body_text, R.string.empty_retrieve_warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tapDayInformationByPosition(position: Int) {
|
||||||
|
clickSubViewInRecycler<ViewHolderForecastDaily>(R.id.forecast_listview, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.snapshot
|
||||||
|
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
|
import com.appttude.h_mal.atlas_weather.robot.furtherInfoScreen
|
||||||
|
import com.appttude.h_mal.atlas_weather.robot.settingsScreen
|
||||||
|
import com.appttude.h_mal.atlas_weather.robot.weatherScreen
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
|
import org.junit.Test
|
||||||
|
import tools.fastlane.screengrab.Screengrab
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@TargetApi(27)
|
||||||
|
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
|
override fun beforeLaunch() {
|
||||||
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
|
stubLocation("London", 51.51, -0.13)
|
||||||
|
clearPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun testFinished() {
|
||||||
|
super.testFinished()
|
||||||
|
clearLocation("London")
|
||||||
|
clearDatabase()
|
||||||
|
clearPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun homeAndFurtherInfoPageCapture() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
Screengrab.screenshot("HomeScreen")
|
||||||
|
tapDayInformationByPosition(4)
|
||||||
|
}
|
||||||
|
furtherInfoScreen {
|
||||||
|
isDisplayed()
|
||||||
|
Screengrab.screenshot("FurtherInfoScreen")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun settingsPageCapture() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
openMenuItem()
|
||||||
|
}
|
||||||
|
settingsScreen {
|
||||||
|
stubEndpoint(baseUrl, Stubs.Imperial)
|
||||||
|
Screengrab.screenshot("SettingsScreen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +1,27 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.tests
|
package com.appttude.h_mal.atlas_weather.tests
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.BaseTest
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
|
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.InvalidKey, 400)
|
stubEndpoint(baseUrl, Stubs.InvalidKey, 400, ".txt")
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun loadApp_invalidKeyWeatherResponse_returnsEmptyViewPage() {
|
|
||||||
homeScreen {
|
|
||||||
waitFor(2000)
|
|
||||||
// verify empty
|
|
||||||
verifyUnableToRetrieve()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
|
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
|
||||||
homeScreen {
|
homeScreen {
|
||||||
waitFor(2000)
|
|
||||||
// verify empty
|
// verify empty
|
||||||
verifyUnableToRetrieve()
|
verifyUnableToRetrieve()
|
||||||
|
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
refresh()
|
refresh()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,20 @@ import com.appttude.h_mal.atlas_weather.BaseTest
|
|||||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun loadApp_validWeatherResponse_returnsValidPage() {
|
fun loadApp_validWeatherResponse_returnsValidPage() {
|
||||||
homeScreen {
|
homeScreen {
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.application
|
||||||
|
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.test.espresso.IdlingRegistry
|
||||||
|
import androidx.test.espresso.idling.CountingIdlingResource
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.location.MockLocationProvider
|
||||||
|
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 org.kodein.di.LazyKodein
|
||||||
|
import java.io.BufferedReader
|
||||||
|
|
||||||
|
class TestAppClass : AppClass() {
|
||||||
|
override val kodein: LazyKodein = super.kodein
|
||||||
|
|
||||||
|
private val idlingResources = CountingIdlingResource("Data_loader")
|
||||||
|
private val mockingNetworkInterceptor = MockingNetworkInterceptor(idlingResources)
|
||||||
|
|
||||||
|
lateinit var database: AppDatabase
|
||||||
|
private val locationProvider: MockLocationProvider = MockLocationProvider()
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
IdlingRegistry.getInstance().register(idlingResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createNetworkModule(): WeatherApi {
|
||||||
|
return NetworkModule().invoke<WeatherApi>(
|
||||||
|
mockingNetworkInterceptor,
|
||||||
|
NetworkConnectionInterceptor(this),
|
||||||
|
QueryParamsInterceptor(),
|
||||||
|
loggingInterceptor
|
||||||
|
) as WeatherApi
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createLocationModule(): LocationProvider {
|
||||||
|
return locationProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createRoomDatabase(): AppDatabase {
|
||||||
|
database = Room.inMemoryDatabaseBuilder(applicationContext, AppDatabase::class.java)
|
||||||
|
.allowMainThreadQueries()
|
||||||
|
.addTypeConverter(Converter(this))
|
||||||
|
.build()
|
||||||
|
return database
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stubUrl(url: String, rawPath: String, code: Int = 200, extension: String = ".json") {
|
||||||
|
val iStream =
|
||||||
|
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath$extension")
|
||||||
|
val data = iStream.bufferedReader().use(BufferedReader::readText)
|
||||||
|
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeUrlStub(url: String) {
|
||||||
|
mockingNetworkInterceptor.removeUrlStub(url = url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stubLocation(location: String, lat: Double = 0.00, long: Double = 0.00) {
|
||||||
|
locationProvider.addLocationToList(location, lat, long)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeLocation(location: String) {
|
||||||
|
locationProvider.removeLocationFromList(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeLocation(lat: Double, long: Double) {
|
||||||
|
locationProvider.removeLocationFromList(lat, long)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearDatabase() {
|
||||||
|
database.getWeatherDao().deleteAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.snapshot
|
||||||
|
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
|
import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen
|
||||||
|
import com.appttude.h_mal.monoWeather.robot.settingsScreen
|
||||||
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
|
import org.junit.Test
|
||||||
|
import tools.fastlane.screengrab.Screengrab
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@TargetApi(27)
|
||||||
|
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
|
override fun beforeLaunch() {
|
||||||
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
|
stubLocation("London", 51.5064, -0.12721)
|
||||||
|
clearPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun testFinished() {
|
||||||
|
super.testFinished()
|
||||||
|
clearLocation("London")
|
||||||
|
clearDatabase()
|
||||||
|
clearPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun homeAndFurtherInfoPageCapture() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
Screengrab.screenshot("HomeScreen")
|
||||||
|
tapDayInformationByPosition(4)
|
||||||
|
}
|
||||||
|
furtherInfoScreen {
|
||||||
|
isDisplayed()
|
||||||
|
Screengrab.screenshot("FurtherInfoScreen")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun settingsPageCapture() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
openMenuItem()
|
||||||
|
}
|
||||||
|
settingsScreen {
|
||||||
|
stubEndpoint(baseUrl, Stubs.Imperial)
|
||||||
|
Screengrab.screenshot("SettingsScreen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.appttude.h_mal.monoWeather.robot
|
||||||
|
|
||||||
|
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
||||||
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
|
import com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily
|
||||||
|
import com.appttude.h_mal.monoWeather.ui.home.adapter.further.ViewHolderFurtherDetails
|
||||||
|
|
||||||
|
fun furtherInfoScreen(func: FurtherInfoScreen.() -> Unit) = FurtherInfoScreen().apply { func() }
|
||||||
|
class FurtherInfoScreen : BaseTestRobot() {
|
||||||
|
fun verifyMaxTemperature(temperature: Int) =
|
||||||
|
matchText(R.id.maxtemp, StringBuilder().append(temperature).append("°").toString())
|
||||||
|
fun verifyAverageTemperature(temperature: Int) =
|
||||||
|
matchText(R.id.averagetemp, StringBuilder().append(temperature).append("°").toString())
|
||||||
|
fun verifyMinTemperature(temperature: Int) =
|
||||||
|
matchText(R.id.minimumtemp, StringBuilder().append(temperature).append("°").toString())
|
||||||
|
|
||||||
|
fun verifyWindSpeed(speedText: String) =
|
||||||
|
matchText(R.id.windtext, speedText)
|
||||||
|
|
||||||
|
fun verifyHumidity(humidity: Int) =
|
||||||
|
matchText(R.id.humiditytext, humidity.toString())
|
||||||
|
fun verifyPrecipitation(precipitation: Int) =
|
||||||
|
matchText(R.id.preciptext, precipitation.toString())
|
||||||
|
|
||||||
|
fun verifyCloudCoverage(coverage: Int) =
|
||||||
|
matchText(R.id.cloudtext, coverage.toString())
|
||||||
|
|
||||||
|
fun verifyUvIndex(uv: Int) =
|
||||||
|
matchText(R.id.uvtext, uv.toString())
|
||||||
|
fun verifySunrise(sunrise: String) =
|
||||||
|
matchText(R.id.sunrisetext, sunrise)
|
||||||
|
fun verifySunset(sunset: String) =
|
||||||
|
matchText(R.id.sunsettext, sunset)
|
||||||
|
|
||||||
|
fun refresh() = pullToRefresh(R.id.swipe_refresh)
|
||||||
|
fun isDisplayed() = matchViewWaitFor(R.id.maxtemp)
|
||||||
|
}
|
||||||
@@ -11,7 +11,9 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
|
|||||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
|
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType.Companion.getLabel
|
||||||
|
|
||||||
|
|
||||||
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
|
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
|
||||||
@@ -23,10 +25,7 @@ class SettingsScreen : BaseTestRobot() {
|
|||||||
RecyclerViewActions.actionOnItem<ViewHolder>(
|
RecyclerViewActions.actionOnItem<ViewHolder>(
|
||||||
ViewMatchers.hasDescendant(withText(R.string.weather_units)),
|
ViewMatchers.hasDescendant(withText(R.string.weather_units)),
|
||||||
click()))
|
click()))
|
||||||
val label = when (unitType) {
|
val label = unitType.getLabel()
|
||||||
UnitType.METRIC -> "Metric"
|
|
||||||
UnitType.IMPERIAL -> "Imperial"
|
|
||||||
}
|
|
||||||
|
|
||||||
onView(withText(label))
|
onView(withText(label))
|
||||||
.inRoot(isDialog())
|
.inRoot(isDialog())
|
||||||
@@ -45,4 +44,10 @@ class SettingsScreen : BaseTestRobot() {
|
|||||||
matchText(R.id.header_text, R.string.retrieve_warning)
|
matchText(R.id.header_text, R.string.retrieve_warning)
|
||||||
matchText(R.id.body_text, R.string.empty_retrieve_warning)
|
matchText(R.id.body_text, R.string.empty_retrieve_warning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isDisplayed() {
|
||||||
|
waitForView(
|
||||||
|
withText("Metric")
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.appttude.h_mal.monoWeather.robot
|
|||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
|
import com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily
|
||||||
|
|
||||||
fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() }
|
fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() }
|
||||||
class WeatherScreen : BaseTestRobot() {
|
class WeatherScreen : BaseTestRobot() {
|
||||||
@@ -16,4 +17,8 @@ class WeatherScreen : BaseTestRobot() {
|
|||||||
matchText(R.id.header_text, R.string.retrieve_warning)
|
matchText(R.id.header_text, R.string.retrieve_warning)
|
||||||
matchText(R.id.body_text, R.string.empty_retrieve_warning)
|
matchText(R.id.body_text, R.string.empty_retrieve_warning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun tapDayInformationByPosition(position: Int) {
|
||||||
|
clickSubViewInRecycler<ViewHolderForecastDaily>(R.id.forecast_listview, position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,21 +4,17 @@ package com.appttude.h_mal.monoWeather.tests
|
|||||||
import com.appttude.h_mal.atlas_weather.BaseTest
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.InvalidKey, 400)
|
stubEndpoint(baseUrl, Stubs.InvalidKey, 400, ".txt")
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun loadApp_invalidKeyWeatherResponse_returnsEmptyViewPage() {
|
|
||||||
weatherScreen {
|
|
||||||
// verify empty
|
|
||||||
verifyUnableToRetrieve()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -27,10 +23,11 @@ class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
// verify empty
|
// verify empty
|
||||||
verifyUnableToRetrieve()
|
verifyUnableToRetrieve()
|
||||||
|
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
refresh()
|
refresh()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ package com.appttude.h_mal.monoWeather.tests
|
|||||||
|
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.BaseTest
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
import com.appttude.h_mal.monoWeather.robot.settingsScreen
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
|
import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen
|
||||||
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
clearPrefs()
|
clearPrefs()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,29 +20,24 @@ class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
fun loadApp_validWeatherResponse_returnsValidPage() {
|
fun loadApp_validWeatherResponse_returnsValidPage() {
|
||||||
weatherScreen {
|
weatherScreen {
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun loadApp_changeToImperial_returnsValidPage() {
|
fun loadApp_validWeatherResponse_viewFurtherDetailsPage() {
|
||||||
weatherScreen {
|
weatherScreen {
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial)
|
tapDayInformationByPosition(4)
|
||||||
openMenuItem()
|
|
||||||
}
|
}
|
||||||
settingsScreen {
|
furtherInfoScreen {
|
||||||
selectWeatherUnits(UnitType.IMPERIAL)
|
|
||||||
goBack()
|
|
||||||
}
|
|
||||||
weatherScreen {
|
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
refresh()
|
verifyMaxTemperature(15)
|
||||||
verifyCurrentTemperature(58)
|
verifyAverageTemperature(11)
|
||||||
verifyCurrentLocation("Mock Location")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.appttude.h_mal.monoWeather.tests
|
||||||
|
|
||||||
|
|
||||||
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||||
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
|
import com.appttude.h_mal.monoWeather.robot.settingsScreen
|
||||||
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class SettingsPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
|
override fun beforeLaunch() {
|
||||||
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
|
clearPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadApp_changeToImperial_returnsValidPage() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
verifyCurrentTemperature(13)
|
||||||
|
verifyCurrentLocation("Mock Location")
|
||||||
|
stubEndpoint(baseUrl, Stubs.Imperial)
|
||||||
|
openMenuItem()
|
||||||
|
}
|
||||||
|
settingsScreen {
|
||||||
|
selectWeatherUnits(UnitType.IMPERIAL)
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
refresh()
|
||||||
|
verifyCurrentTemperature(56)
|
||||||
|
verifyCurrentLocation("Mock Location")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ package com.appttude.h_mal.monoWeather.tests
|
|||||||
import com.appttude.h_mal.atlas_weather.BaseTest
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
import com.appttude.h_mal.monoWeather.robot.ContainerRobot.Tab.WORLD
|
import com.appttude.h_mal.monoWeather.robot.ContainerRobot.Tab.WORLD
|
||||||
import com.appttude.h_mal.monoWeather.robot.addLocation
|
import com.appttude.h_mal.monoWeather.robot.addLocation
|
||||||
import com.appttude.h_mal.monoWeather.robot.container
|
import com.appttude.h_mal.monoWeather.robot.container
|
||||||
@@ -14,7 +15,8 @@ import org.junit.Test
|
|||||||
class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
|
stubLocation("London", 51.5064,-0.12721)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -26,8 +28,8 @@ class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
clickFab()
|
clickFab()
|
||||||
}
|
}
|
||||||
addLocation {
|
addLocation {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Sydney)
|
stubEndpoint(baseUrl, Stubs.Sydney)
|
||||||
stubLocation("Sydney", -33.89, -151.12)
|
stubLocation("Sydney",-33.8696,151.207)
|
||||||
setLocation("Sydney")
|
setLocation("Sydney")
|
||||||
submit()
|
submit()
|
||||||
}
|
}
|
||||||
@@ -36,7 +38,7 @@ class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
}
|
}
|
||||||
weatherScreen {
|
weatherScreen {
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
verifyCurrentTemperature(12)
|
verifyCurrentTemperature(16)
|
||||||
verifyCurrentLocation("Sydney")
|
verifyCurrentLocation("Sydney")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
|
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@@ -27,23 +28,8 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".notification.NotificationReceiver"
|
android:name=".service.notification.NotificationReceiver"
|
||||||
android:exported="true"
|
android:exported="false"/>
|
||||||
android:parentActivityName=".MainActivity" />
|
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name=".widget.NewAppWidget"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
|
|
||||||
<action android:name="com.example.h_mal.weather_app.app.ACTION_DATA_UPDATED" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.appwidget.provider"
|
|
||||||
android:resource="@xml/new_app_widget_info" />
|
|
||||||
</receiver>
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.application
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
|
||||||
|
import com.appttude.h_mal.atlas_weather.service.notification.NotificationService
|
||||||
|
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
|
||||||
|
import com.appttude.h_mal.atlas_weather.viewmodel.SettingsViewModel
|
||||||
|
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationViewModelFactory(
|
||||||
|
private val application: Application,
|
||||||
|
private val locationProvider: LocationProvider,
|
||||||
|
private val source: WeatherSource,
|
||||||
|
private val settingsRepository: SettingsRepository,
|
||||||
|
private val notificationService: NotificationService
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
with(modelClass) {
|
||||||
|
return when {
|
||||||
|
isAssignableFrom(WorldViewModel::class.java) -> WorldViewModel(
|
||||||
|
locationProvider,
|
||||||
|
source
|
||||||
|
)
|
||||||
|
|
||||||
|
isAssignableFrom(MainViewModel::class.java) -> MainViewModel(
|
||||||
|
locationProvider,
|
||||||
|
source
|
||||||
|
)
|
||||||
|
|
||||||
|
isAssignableFrom(SettingsViewModel::class.java) -> SettingsViewModel(
|
||||||
|
application, locationProvider, source, settingsRepository, notificationService
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
} as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.application
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.appttude.h_mal.atlas_weather.service.notification.NotificationHelper
|
||||||
|
import com.appttude.h_mal.atlas_weather.service.notification.NotificationService
|
||||||
|
import org.kodein.di.Kodein
|
||||||
|
import org.kodein.di.generic.bind
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import org.kodein.di.generic.provider
|
||||||
|
import org.kodein.di.generic.singleton
|
||||||
|
|
||||||
|
fun getFlavourModule(application: Application) = FlavourModule(application).build()
|
||||||
|
class FlavourModule(val application: Application) {
|
||||||
|
fun build() = Kodein.Module("Flavour") {
|
||||||
|
bind() from singleton {
|
||||||
|
NotificationHelper(
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind() from singleton {
|
||||||
|
NotificationService(application)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind() from provider {
|
||||||
|
ApplicationViewModelFactory(
|
||||||
|
application,
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
instance()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.notification
|
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
|
|
||||||
data class NotificationData(
|
|
||||||
val temp: String,
|
|
||||||
val icon: Bitmap
|
|
||||||
)
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.notification
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Notification
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.app.TaskStackBuilder
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
|
||||||
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
|
||||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
|
||||||
import com.appttude.h_mal.atlas_weather.utils.displayToast
|
|
||||||
import org.kodein.di.KodeinAware
|
|
||||||
import org.kodein.di.LateInitKodein
|
|
||||||
import org.kodein.di.generic.instance
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by h_mal on 29/04/2018.
|
|
||||||
* Updated by h_mal on 27/11/2020
|
|
||||||
*/
|
|
||||||
const val NOTIFICATION_CHANNEL_ID = "my_notification_channel_1"
|
|
||||||
|
|
||||||
class NotificationReceiver : BroadcastReceiver() {
|
|
||||||
|
|
||||||
private val kodein = LateInitKodein()
|
|
||||||
private val helper: ServicesHelper by kodein.instance()
|
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(
|
|
||||||
context,
|
|
||||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
|
||||||
) != PackageManager.PERMISSION_GRANTED
|
|
||||||
) {
|
|
||||||
context.displayToast("Please enable location permissions")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// notification validation
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun pushNotif(context: Context?, weather: FullWeather) {
|
|
||||||
val notificationIntent = Intent(context, MainActivity::class.java)
|
|
||||||
|
|
||||||
val stackBuilder = TaskStackBuilder.create(context).apply {
|
|
||||||
addParentStack(MainActivity::class.java)
|
|
||||||
addNextIntent(notificationIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
val pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
||||||
|
|
||||||
val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
|
|
||||||
|
|
||||||
val notification = builder.setContentTitle("Weather App")
|
|
||||||
.setContentText(weather.current?.main + "°C")
|
|
||||||
.setSmallIcon(R.mipmap.ic_notif) //change icon
|
|
||||||
// .setLargeIcon(Icon.createWithResource(context, getImageResource(forecastItem.getCurrentForecast().getIconURL(), context)))
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setContentIntent(pendingIntent).build()
|
|
||||||
builder.setChannelId(NOTIFICATION_CHANNEL_ID)
|
|
||||||
val notificationManager =
|
|
||||||
context!!.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
notificationManager.notify(0, notification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.service.notification
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import androidx.annotation.RequiresPermission
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||||
|
|
||||||
|
class NotificationHelper(
|
||||||
|
private val weatherSource: WeatherSource,
|
||||||
|
private val locationProvider: LocationProvider
|
||||||
|
) {
|
||||||
|
|
||||||
|
@RequiresPermission(value = Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
suspend fun fetchData(): FullWeather? {
|
||||||
|
return try {
|
||||||
|
// Get location
|
||||||
|
val latLong = locationProvider.getCurrentLatLong()
|
||||||
|
weatherSource.getWeather(latLon = latLong)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.service.notification
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.TaskStackBuilder
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.annotation.RequiresPermission
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.displayToast
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.kodein.di.KodeinAware
|
||||||
|
import org.kodein.di.LateInitKodein
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by h_mal on 29/04/2018.
|
||||||
|
* Updated by h_mal on 27/11/2020
|
||||||
|
*/
|
||||||
|
const val NOTIFICATION_CHANNEL_ID = "my_notification_channel_1"
|
||||||
|
const val NOTIFICATION_ID = 505
|
||||||
|
class NotificationReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
|
||||||
|
private val kodein = LateInitKodein()
|
||||||
|
private val helper: NotificationHelper by kodein.instance()
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
||||||
|
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
pushNotification(context)
|
||||||
|
} else {
|
||||||
|
context.displayToast("Please enable location permissions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresPermission(value = Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
private fun pushNotification(context: Context) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
// Retrieve weather data
|
||||||
|
val weather = runBlocking { helper.fetchData() } ?: return@launch
|
||||||
|
|
||||||
|
// Build notification
|
||||||
|
val notificationIntent = Intent(context, MainActivity::class.java)
|
||||||
|
|
||||||
|
val stackBuilder = TaskStackBuilder.create(context).apply {
|
||||||
|
addParentStack(MainActivity::class.java)
|
||||||
|
addNextIntent(notificationIntent)
|
||||||
|
}
|
||||||
|
val pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
val bmp: Bitmap = runBlocking { Picasso.get().load(weather.current?.icon).get() }
|
||||||
|
|
||||||
|
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.mipmap.ic_notif)
|
||||||
|
.setLargeIcon(bmp)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setContentTitle("My notification")
|
||||||
|
.setContentText("Much longer text that cannot fit one line...")
|
||||||
|
.setStyle(NotificationCompat.BigTextStyle()
|
||||||
|
.bigText("Much longer text that cannot fit one line..."))
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
|
|
||||||
|
// Create the NotificationChannel, but only on API 26+ because
|
||||||
|
// the NotificationChannel class is not in the Support Library.
|
||||||
|
val name = context.getString(R.string.channel_name)
|
||||||
|
val descriptionText = context.getString(R.string.channel_description)
|
||||||
|
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance).apply {
|
||||||
|
description = descriptionText
|
||||||
|
}
|
||||||
|
// Register the channel with the system.
|
||||||
|
val notificationManager: NotificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
|
||||||
|
with(NotificationManagerCompat.from(context)) {
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
// TODO: Consider calling
|
||||||
|
// ActivityCompat#requestPermissions
|
||||||
|
// here to request the missing permissions, and then overriding
|
||||||
|
// public fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
|
||||||
|
// grantResults: IntArray)
|
||||||
|
// to handle the case where the user grants the permission. See the documentation
|
||||||
|
// for ActivityCompat#requestPermissions for more details.
|
||||||
|
|
||||||
|
return@with
|
||||||
|
}
|
||||||
|
// notificationId is a unique int for each notification that you must define.
|
||||||
|
notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.service.notification
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Context.ALARM_SERVICE
|
||||||
|
import android.content.Intent
|
||||||
|
import android.icu.util.Calendar
|
||||||
|
import android.icu.util.GregorianCalendar
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationService(context: Context) {
|
||||||
|
|
||||||
|
private val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager
|
||||||
|
private val alarmPendingIntent by lazy {
|
||||||
|
val intent = Intent(context, NotificationReceiver::class.java)
|
||||||
|
PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val notificationManager = NotificationManagerCompat.from(context)
|
||||||
|
|
||||||
|
fun schedulePushNotifications() {
|
||||||
|
val calendar = getCalendarForNotification()
|
||||||
|
|
||||||
|
alarmManager.setWindow(
|
||||||
|
AlarmManager.RTC_WAKEUP,
|
||||||
|
calendar.timeInMillis,
|
||||||
|
AlarmManager.INTERVAL_HOUR,
|
||||||
|
alarmPendingIntent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unschedulePushNotifications() {
|
||||||
|
alarmManager.cancel(alarmPendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun areNotificationsEnabled() = when {
|
||||||
|
notificationManager.areNotificationsEnabled().not() -> false
|
||||||
|
else -> {
|
||||||
|
notificationManager.notificationChannels.firstOrNull { channel ->
|
||||||
|
channel.importance == NotificationManager.IMPORTANCE_NONE
|
||||||
|
} == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCalendarForNotification(): Calendar {
|
||||||
|
// return GregorianCalendar.getInstance().apply {
|
||||||
|
// if (get(Calendar.HOUR_OF_DAY) >= HOUR_TO_SHOW_PUSH) {
|
||||||
|
// add(Calendar.DAY_OF_MONTH, 1)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// set(Calendar.HOUR_OF_DAY, HOUR_TO_SHOW_PUSH)
|
||||||
|
// set(Calendar.MINUTE, 0)
|
||||||
|
// set(Calendar.SECOND, 0)
|
||||||
|
// set(Calendar.MILLISECOND, 0)
|
||||||
|
// }
|
||||||
|
|
||||||
|
return GregorianCalendar.getInstance().apply {
|
||||||
|
// add(Calendar.MINUTE, 1)
|
||||||
|
add(Calendar.SECOND, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,11 +6,12 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
||||||
import com.appttude.h_mal.atlas_weather.ui.home.adapter.WeatherRecyclerAdapter
|
import com.appttude.h_mal.atlas_weather.ui.home.adapter.WeatherRecyclerAdapter
|
||||||
import com.appttude.h_mal.atlas_weather.utils.navigateTo
|
import com.appttude.h_mal.atlas_weather.utils.navigateTo
|
||||||
import kotlinx.android.synthetic.main.fragment_home.*
|
|
||||||
|
|
||||||
|
|
||||||
class WorldItemFragment : Fragment() {
|
class WorldItemFragment : Fragment() {
|
||||||
@@ -40,7 +41,7 @@ class WorldItemFragment : Fragment() {
|
|||||||
|
|
||||||
param1?.let { recyclerAdapter.addCurrent(it) }
|
param1?.let { recyclerAdapter.addCurrent(it) }
|
||||||
|
|
||||||
forecast_listview.apply {
|
view.findViewById<RecyclerView>(R.id.forecast_listview).apply {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = recyclerAdapter
|
adapter = recyclerAdapter
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
|
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
|
||||||
import kotlinx.android.synthetic.main.activity_further_info.*
|
|
||||||
|
|
||||||
|
|
||||||
private const val WEATHER = "param1"
|
private const val WEATHER = "param1"
|
||||||
@@ -36,14 +37,12 @@ class FurtherInfoFragment : Fragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
maxtemp.text = param1?.mainTemp
|
view.findViewById<TextView>(R.id.maxtemp).text = param1?.mainTemp
|
||||||
averagetemp.text = param1?.averageTemp
|
view.findViewById<TextView>(R.id.averagetemp).text = param1?.averageTemp
|
||||||
minimumtemp.text = param1?.minorTemp
|
view.findViewById<TextView>(R.id.minimumtemp).text = param1?.minorTemp
|
||||||
windtext.text = param1?.windText
|
view.findViewById<TextView>(R.id.windtext).text = param1?.windText
|
||||||
preciptext.text = param1?.precipitation
|
view.findViewById<TextView>(R.id.preciptext).text = param1?.precipitation
|
||||||
humiditytext.text = param1?.humidity
|
view.findViewById<TextView>(R.id.sunrisetext).text = param1?.sunrise
|
||||||
uvtext.text = param1?.uvi
|
view.findViewById<TextView>(R.id.sunsettext).text = param1?.sunset
|
||||||
sunrisetext.text = param1?.sunrise
|
|
||||||
sunsettext.text = param1?.sunset
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.ui.home
|
package com.appttude.h_mal.atlas_weather.ui.home
|
||||||
|
|
||||||
import android.Manifest.permission.ACCESS_COARSE_LOCATION
|
import android.Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
import android.Manifest.permission.POST_NOTIFICATIONS
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
@@ -10,18 +12,19 @@ import android.view.View
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.Navigation.findNavController
|
import androidx.navigation.Navigation.findNavController
|
||||||
import androidx.navigation.ui.onNavDestinationSelected
|
import androidx.navigation.ui.onNavDestinationSelected
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
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.base.BaseFragment
|
import com.appttude.h_mal.atlas_weather.base.BaseFragment
|
||||||
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
|
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
|
||||||
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
||||||
|
import com.appttude.h_mal.atlas_weather.service.notification.NotificationService
|
||||||
import com.appttude.h_mal.atlas_weather.ui.dialog.PermissionsDeclarationDialog
|
import com.appttude.h_mal.atlas_weather.ui.dialog.PermissionsDeclarationDialog
|
||||||
import com.appttude.h_mal.atlas_weather.ui.home.adapter.WeatherRecyclerAdapter
|
import com.appttude.h_mal.atlas_weather.ui.home.adapter.WeatherRecyclerAdapter
|
||||||
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.navigateTo
|
import com.appttude.h_mal.atlas_weather.utils.navigateTo
|
||||||
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
|
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
|
||||||
import kotlinx.android.synthetic.main.fragment_home.*
|
import org.kodein.di.generic.instance
|
||||||
import permissions.dispatcher.NeedsPermission
|
import permissions.dispatcher.NeedsPermission
|
||||||
import permissions.dispatcher.OnNeverAskAgain
|
import permissions.dispatcher.OnNeverAskAgain
|
||||||
import permissions.dispatcher.OnPermissionDenied
|
import permissions.dispatcher.OnPermissionDenied
|
||||||
@@ -37,14 +40,17 @@ import permissions.dispatcher.RuntimePermissions
|
|||||||
@RuntimePermissions
|
@RuntimePermissions
|
||||||
class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
||||||
|
|
||||||
lateinit var recyclerAdapter: WeatherRecyclerAdapter
|
private val notificationService by instance<NotificationService>()
|
||||||
|
|
||||||
|
private lateinit var recyclerAdapter: WeatherRecyclerAdapter
|
||||||
|
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||||
|
|
||||||
@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)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
|
|
||||||
swipe_refresh.apply {
|
swipeRefresh = view.findViewById<SwipeRefreshLayout>(R.id.swipe_refresh).apply {
|
||||||
setOnRefreshListener {
|
setOnRefreshListener {
|
||||||
showLocationWithPermissionCheck()
|
showLocationWithPermissionCheck()
|
||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
@@ -55,7 +61,9 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
|||||||
navigateToFurtherDetails(it)
|
navigateToFurtherDetails(it)
|
||||||
})
|
})
|
||||||
|
|
||||||
forecast_listview.adapter = recyclerAdapter
|
view.findViewById<RecyclerView>(R.id.forecast_listview).adapter = recyclerAdapter
|
||||||
|
|
||||||
|
scheduleNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
@@ -66,7 +74,7 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
|||||||
|
|
||||||
override fun onSuccess(data: Any?) {
|
override fun onSuccess(data: Any?) {
|
||||||
super.onSuccess(data)
|
super.onSuccess(data)
|
||||||
swipe_refresh.isRefreshing = false
|
swipeRefresh.isRefreshing = false
|
||||||
|
|
||||||
if (data is WeatherDisplay) {
|
if (data is WeatherDisplay) {
|
||||||
recyclerAdapter.addCurrent(data)
|
recyclerAdapter.addCurrent(data)
|
||||||
@@ -75,7 +83,7 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
|||||||
|
|
||||||
override fun onFailure(error: Any?) {
|
override fun onFailure(error: Any?) {
|
||||||
super.onFailure(error)
|
super.onFailure(error)
|
||||||
swipe_refresh.isRefreshing = false
|
swipeRefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToFurtherDetails(forecast: Forecast) {
|
private fun navigateToFurtherDetails(forecast: Forecast) {
|
||||||
@@ -93,12 +101,25 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
|||||||
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
|
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
@Deprecated("Deprecated in Java")
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
// NOTE: delegate the permission handling to generated method
|
// NOTE: delegate the permission handling to generated method
|
||||||
onRequestPermissionsResult(requestCode, grantResults)
|
onRequestPermissionsResult(requestCode, grantResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun scheduleNotification() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
sendNotification()
|
||||||
|
} else {
|
||||||
|
notificationService.schedulePushNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
@NeedsPermission(ACCESS_COARSE_LOCATION)
|
@NeedsPermission(ACCESS_COARSE_LOCATION)
|
||||||
fun showLocation() {
|
fun showLocation() {
|
||||||
@@ -123,4 +144,29 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
|||||||
fun onLocationNeverAskAgain() {
|
fun onLocationNeverAskAgain() {
|
||||||
displayToast("Location permissions have been to never ask again")
|
displayToast("Location permissions have been to never ask again")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@NeedsPermission(POST_NOTIFICATIONS)
|
||||||
|
fun sendNotification() {
|
||||||
|
notificationService.schedulePushNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnShowRationale(POST_NOTIFICATIONS)
|
||||||
|
fun showRationaleForNotification(request: PermissionRequest) {
|
||||||
|
// PermissionsDeclarationDialog(requireContext()).showDialog({
|
||||||
|
// request.proceed()
|
||||||
|
// }, {
|
||||||
|
// request.cancel()
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnPermissionDenied(POST_NOTIFICATIONS)
|
||||||
|
fun onNotificationDenied() {
|
||||||
|
displayToast("Notification permissions have been denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnNeverAskAgain(POST_NOTIFICATIONS)
|
||||||
|
fun onNotificationNeverAskAgain() {
|
||||||
|
displayToast("Notification permissions have been to never ask again")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,73 +1,61 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.ui.settings
|
package com.appttude.h_mal.atlas_weather.ui.settings
|
||||||
|
|
||||||
import android.app.AlarmManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.appwidget.AppWidgetManager
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
import com.appttude.h_mal.atlas_weather.notification.NotificationReceiver
|
import com.appttude.h_mal.atlas_weather.base.BasePreferencesFragment
|
||||||
import com.appttude.h_mal.atlas_weather.widget.NewAppWidget
|
import com.appttude.h_mal.atlas_weather.utils.displayToast
|
||||||
import java.util.Calendar
|
import com.appttude.h_mal.atlas_weather.viewmodel.SettingsViewModel
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
class SettingsFragment : BasePreferencesFragment<SettingsViewModel>(R.xml.prefs) {
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun preferenceChanged(key: String) {
|
||||||
setPreferencesFromResource(R.xml.prefs, rootKey)
|
when (key) {
|
||||||
|
|
||||||
//listener on changed sort order preference:
|
"temp_units" -> viewModel.refreshWeatherData()
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
"notif_boolean" -> {
|
||||||
prefs.registerOnSharedPreferenceChangeListener { _, key ->
|
// TODO: update notification
|
||||||
if (key == "temp_units") {
|
// viewModel.updateWidget()
|
||||||
val intent = Intent(requireContext(), NewAppWidget::class.java)
|
// displayToast("Widget background has been updates")
|
||||||
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
|
||||||
val ids = AppWidgetManager.getInstance(requireContext())
|
|
||||||
.getAppWidgetIds(ComponentName(requireContext(), NewAppWidget::class.java))
|
|
||||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
|
||||||
requireContext().sendBroadcast(intent)
|
|
||||||
}
|
|
||||||
if (key == "notif_boolean") {
|
|
||||||
setupNotificationBroadcaster(requireContext())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == "widget_black_background") {
|
|
||||||
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
|
|
||||||
val widgetManager = AppWidgetManager.getInstance(requireContext())
|
|
||||||
val ids =
|
|
||||||
widgetManager.getAppWidgetIds(
|
|
||||||
ComponentName(
|
|
||||||
requireContext(),
|
|
||||||
NewAppWidget::class.java
|
|
||||||
)
|
|
||||||
)
|
|
||||||
AppWidgetManager.getInstance(requireContext())
|
|
||||||
.notifyAppWidgetViewDataChanged(ids, R.id.whole_widget_view)
|
|
||||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
|
||||||
requireContext().sendBroadcast(intent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupNotificationBroadcaster(context: Context) {
|
override fun onSuccess(data: Any?) {
|
||||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
super.onSuccess(data)
|
||||||
val notificationIntent = Intent(context, NotificationReceiver::class.java)
|
if (data is String) displayToast(data)
|
||||||
val broadcast = PendingIntent.getBroadcast(
|
|
||||||
context, 100, notificationIntent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
val cal: Calendar = Calendar.getInstance()
|
|
||||||
cal.set(Calendar.HOUR_OF_DAY, 6)
|
|
||||||
cal.set(Calendar.MINUTE, 8)
|
|
||||||
cal.set(Calendar.SECOND, 5)
|
|
||||||
alarmManager.setRepeating(
|
|
||||||
AlarmManager.RTC_WAKEUP,
|
|
||||||
cal.timeInMillis,
|
|
||||||
AlarmManager.INTERVAL_DAY,
|
|
||||||
broadcast
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
// setPreferencesFromResource(R.xml.prefs, rootKey)
|
||||||
|
//
|
||||||
|
// //listener on changed sort order preference:
|
||||||
|
// val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
// prefs.registerOnSharedPreferenceChangeListener { _, key ->
|
||||||
|
// if (key == "temp_units") {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// if (key == "notif_boolean") {
|
||||||
|
// setupNotificationBroadcaster(requireContext())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun setupNotificationBroadcaster(context: Context) {
|
||||||
|
// val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
// val notificationIntent = Intent(context, NotificationReceiver::class.java)
|
||||||
|
// val broadcast = PendingIntent.getBroadcast(
|
||||||
|
// context, 100, notificationIntent,
|
||||||
|
// PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
// )
|
||||||
|
// val cal: Calendar = Calendar.getInstance()
|
||||||
|
// cal.set(Calendar.HOUR_OF_DAY, 6)
|
||||||
|
// cal.set(Calendar.MINUTE, 8)
|
||||||
|
// cal.set(Calendar.SECOND, 5)
|
||||||
|
// alarmManager.setRepeating(
|
||||||
|
// AlarmManager.RTC_WAKEUP,
|
||||||
|
// cal.timeInMillis,
|
||||||
|
// AlarmManager.INTERVAL_DAY,
|
||||||
|
// broadcast
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -2,13 +2,13 @@ package com.appttude.h_mal.atlas_weather.ui.world
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
import com.appttude.h_mal.atlas_weather.base.BaseFragment
|
import com.appttude.h_mal.atlas_weather.base.BaseFragment
|
||||||
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.goBack
|
import com.appttude.h_mal.atlas_weather.utils.goBack
|
||||||
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
||||||
import kotlinx.android.synthetic.main.activity_add_forecast.location_name_tv
|
|
||||||
import kotlinx.android.synthetic.main.activity_add_forecast.submit
|
|
||||||
|
|
||||||
|
|
||||||
class AddLocationFragment : BaseFragment<WorldViewModel>(R.layout.activity_add_forecast) {
|
class AddLocationFragment : BaseFragment<WorldViewModel>(R.layout.activity_add_forecast) {
|
||||||
@@ -16,8 +16,11 @@ class AddLocationFragment : BaseFragment<WorldViewModel>(R.layout.activity_add_f
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val submit = view.findViewById<Button>(R.id.submit)
|
||||||
|
|
||||||
submit.setOnClickListener {
|
submit.setOnClickListener {
|
||||||
val locationName = location_name_tv.text?.trim()?.toString()
|
val locationName =
|
||||||
|
view.findViewById<TextView>(R.id.location_name_tv).text?.trim()?.toString()
|
||||||
if (locationName.isNullOrBlank()) {
|
if (locationName.isNullOrBlank()) {
|
||||||
submit.error = "Location cannot be blank"
|
submit.error = "Location cannot be blank"
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
import com.appttude.h_mal.atlas_weather.base.BaseFragment
|
import com.appttude.h_mal.atlas_weather.base.BaseFragment
|
||||||
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
||||||
import com.appttude.h_mal.atlas_weather.utils.navigateTo
|
import com.appttude.h_mal.atlas_weather.utils.navigateTo
|
||||||
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
||||||
import kotlinx.android.synthetic.atlasWeather.fragment_add_location.floatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import kotlinx.android.synthetic.atlasWeather.fragment_add_location.world_recycler
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,19 +29,21 @@ class WorldFragment : BaseFragment<WorldViewModel>(R.layout.fragment_add_locatio
|
|||||||
navigateTo(direction)
|
navigateTo(direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
world_recycler.apply {
|
view.findViewById<RecyclerView>(R.id.world_recycler).apply {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = recyclerAdapter
|
adapter = recyclerAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
floatingActionButton.setOnClickListener {
|
view.findViewById<FloatingActionButton>(R.id.floatingActionButton).setOnClickListener {
|
||||||
navigateTo(R.id.action_worldFragment_to_addLocationFragment)
|
navigateTo(R.id.action_worldFragment_to_addLocationFragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun onSuccess(data: Any?) {
|
override fun onSuccess(data: Any?) {
|
||||||
super.onSuccess(data)
|
super.onSuccess(data)
|
||||||
if (data is List<*>) recyclerAdapter.addCurrent(data as List<WeatherDisplay>)
|
if (data is List<*>)
|
||||||
|
recyclerAdapter.addCurrent(data as List<WeatherDisplay>)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.viewmodel
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import com.appttude.h_mal.atlas_weather.application.BaseAppClass
|
||||||
|
import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseAndroidViewModel
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
|
||||||
|
import com.appttude.h_mal.atlas_weather.service.notification.NotificationService
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class SettingsViewModel(
|
||||||
|
application: Application,
|
||||||
|
private val locationProvider: LocationProvider,
|
||||||
|
private val weatherSource: WeatherSource,
|
||||||
|
private val settingsRepository: SettingsRepository,
|
||||||
|
private val notificationService: NotificationService
|
||||||
|
) : BaseAndroidViewModel(application) {
|
||||||
|
|
||||||
|
private fun getContext() = getApplication<BaseAppClass>().applicationContext
|
||||||
|
|
||||||
|
fun refreshWeatherData() {
|
||||||
|
onStart()
|
||||||
|
job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
try {
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
getContext(),
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
// Get location
|
||||||
|
val latLong = locationProvider.getCurrentLatLong()
|
||||||
|
weatherSource.forceFetchWeather(latLong)
|
||||||
|
}
|
||||||
|
val units = settingsRepository.getUnitType().name.lowercase(Locale.ROOT)
|
||||||
|
onSuccess("Units have been changes to $units")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
onError(e.message ?: "Retrieving weather failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleNotifications() {
|
||||||
|
if (notificationService.areNotificationsEnabled()) {
|
||||||
|
notificationService.unschedulePushNotifications()
|
||||||
|
} else {
|
||||||
|
notificationService.schedulePushNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,320 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout 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">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_margin="12dp"
|
|
||||||
android:layout_weight="2">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
style="@style/icon_style__further_details"
|
|
||||||
android:src="@drawable/somethingnew" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="2">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:text="@string/max" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/maxtemp"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
tools:text="11" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:text="@string/average" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/averagetemp"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
tools:text="11" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:text="@string/min" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/minimumtemp"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
tools:text="11" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_margin="12dp"
|
|
||||||
android:layout_weight="2">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
style="@style/icon_style__further_details"
|
|
||||||
android:src="@drawable/breeze" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="2">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:layout_weight="3">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:text="Wind: " />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/windtext"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
android:text="7mph" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</RelativeLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_margin="12dp"
|
|
||||||
android:layout_weight="2">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
style="@style/icon_style__further_details"
|
|
||||||
android:src="@drawable/water_drop" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="2">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:text="Humidity: " />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/humiditytext"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
android:text="85%" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:text="Precip: " />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/preciptext"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
android:text="11mm" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_margin="12dp"
|
|
||||||
android:layout_weight="2">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
style="@style/icon_style__further_details"
|
|
||||||
android:src="@drawable/sunrise" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="2">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:text="UV: " />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/uvtext"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
android:text="7" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:text="Sunrise:" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/sunrisetext"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
android:text="05:30am" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:text="Sunset:" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/sunsettext"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="3"
|
|
||||||
android:text="06:12pm" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</FrameLayout>
|
|
||||||
@@ -58,5 +58,5 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/settings_fragment"
|
android:id="@+id/settings_fragment"
|
||||||
android:name="com.appttude.h_mal.atlas_weather.ui.settings.SettingsFragment"
|
android:name="com.appttude.h_mal.atlas_weather.ui.settings.SettingsFragment"
|
||||||
android:label="SettingsFragment" />
|
android:label="Settings" />
|
||||||
</navigation>
|
</navigation>
|
||||||
@@ -2,11 +2,13 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<color name="colorPrimary">#3F51B5</color>
|
<color name="colorPrimary">#3F51B5</color>
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
<color name="colorPrimaryDark">#303F9F</color>
|
||||||
<color name="colorAccent">#FF4081</color>
|
<color name="colorAccent">@android:color/white</color>
|
||||||
|
|
||||||
<color name="colour_one">#E8D0DD</color>
|
<color name="colour_one">#E8D0DD</color>
|
||||||
<color name="colour_two">#5F8E7B</color>
|
<color name="colour_two">#5F8E7B</color>
|
||||||
<color name="colour_three">#B3C0CA</color>
|
<color name="colour_three">#B3C0CA</color>
|
||||||
<color name="colour_four">#8C98AD</color>
|
<color name="colour_four">#8C98AD</color>
|
||||||
<color name="colour_five">#2E3532</color>
|
<color name="colour_five">#2E3532</color>
|
||||||
|
|
||||||
|
<color name="weather_cell_colour">@android:color/transparent</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:configure="com.appttude.h_mal.atlas_weather.ui.widget.WidgetLocationPermissionActivity"
|
|
||||||
android:initialKeyguardLayout="@layout/weather_app_widget"
|
|
||||||
android:initialLayout="@layout/weather_app_widget"
|
|
||||||
android:minWidth="320.0dp"
|
|
||||||
android:minHeight="110.0dp"
|
|
||||||
android:minResizeWidth="320.0dp"
|
|
||||||
android:minResizeHeight="110.0dp"
|
|
||||||
android:previewImage="@drawable/widget_screenshot"
|
|
||||||
android:resizeMode="vertical"
|
|
||||||
android:updatePeriodMillis="1800000"
|
|
||||||
android:widgetCategory="home_screen">
|
|
||||||
|
|
||||||
</appwidget-provider>
|
|
||||||
@@ -1,42 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<ListPreference
|
||||||
<!-- <PreferenceCategory android:title="Units">-->
|
android:title="@string/weather_units"
|
||||||
<!-- <ListPreference-->
|
android:entries="@array/units"
|
||||||
<!-- android:defaultValue="°C"-->
|
android:entryValues="@array/units"
|
||||||
<!-- android:entries="@array/list_preference_temp"-->
|
android:defaultValue="Metric"
|
||||||
<!-- android:entryValues="@array/list_preference_temp"-->
|
android:key="UnitType"
|
||||||
<!-- android:key="temp_units"-->
|
/>
|
||||||
<!-- android:title="Temperature Units" />-->
|
|
||||||
<!-- <ListPreference-->
|
|
||||||
<!-- android:defaultValue="kph"-->
|
|
||||||
<!-- android:entries="@array/list_preference_wind"-->
|
|
||||||
<!-- android:entryValues="@array/list_preference_wind_values"-->
|
|
||||||
<!-- android:key="wind_units"-->
|
|
||||||
<!-- android:title="Wind Units" />-->
|
|
||||||
<!-- <ListPreference-->
|
|
||||||
<!-- android:defaultValue="mm"-->
|
|
||||||
<!-- android:entries="@array/list_preference_precip"-->
|
|
||||||
<!-- android:entryValues="@array/list_preference_precip_values"-->
|
|
||||||
<!-- android:key="precip_units"-->
|
|
||||||
<!-- android:title="Precipitation Units" />-->
|
|
||||||
<!-- <ListPreference-->
|
|
||||||
<!-- android:defaultValue="km"-->
|
|
||||||
<!-- android:entries="@array/list_preference_vis"-->
|
|
||||||
<!-- android:entryValues="@array/list_preference_vis_values"-->
|
|
||||||
<!-- android:key="vis_units"-->
|
|
||||||
<!-- android:title="Visibility Units" />-->
|
|
||||||
<!-- </PreferenceCategory>-->
|
|
||||||
<PreferenceCategory android:title="Notification Settings">
|
<PreferenceCategory android:title="Notification Settings">
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="notif_boolean"
|
android:key="notif_boolean"
|
||||||
android:title="Notification" />
|
android:title="Notifications enabled" />
|
||||||
</PreferenceCategory>
|
|
||||||
<PreferenceCategory android:title="Widget Settings">
|
|
||||||
<SwitchPreference
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="widget_black_background"
|
|
||||||
android:title="Set widget background black" />
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
18
app/src/debug/AndroidManifest.xml
Normal file
18
app/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<!-- Allows storing screenshots on external storage, where it can be accessed by ADB -->
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<!-- Allows changing locales -->
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.CHANGE_CONFIGURATION"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
|
<!-- Allows changing SystemUI demo mode -->
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.DUMP"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
package="com.appttude.h_mal.atlas_weather">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
|
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
android:required="true" />
|
android:required="true" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
tools:ignore="MissingApplicationIcon" />
|
tools:ignore="MissingApplicationIcon" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.application
|
package com.appttude.h_mal.atlas_weather.application
|
||||||
|
|
||||||
|
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.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
|
||||||
@@ -8,9 +9,7 @@ import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInt
|
|||||||
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.AppDatabase
|
||||||
|
|
||||||
const val LOCATION_PERMISSION_REQUEST = 505
|
open class AppClass : BaseAppClass() {
|
||||||
|
|
||||||
class AppClass : BaseAppClass() {
|
|
||||||
|
|
||||||
override fun createNetworkModule(): WeatherApi {
|
override fun createNetworkModule(): WeatherApi {
|
||||||
return NetworkModule().invoke<WeatherApi>(
|
return NetworkModule().invoke<WeatherApi>(
|
||||||
@@ -20,7 +19,7 @@ class AppClass : BaseAppClass() {
|
|||||||
) as WeatherApi
|
) as WeatherApi
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createLocationModule() = LocationProviderImpl(this)
|
override fun createLocationModule(): LocationProvider = LocationProviderImpl(this)
|
||||||
|
|
||||||
override fun createRoomDatabase(): AppDatabase = AppDatabase(this)
|
override fun createRoomDatabase(): AppDatabase = AppDatabase(this)
|
||||||
|
|
||||||
|
|||||||
@@ -3,29 +3,33 @@ package com.appttude.h_mal.atlas_weather.application
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
||||||
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.network.Api
|
||||||
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.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
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||||
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||||
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
import org.kodein.di.KodeinAware
|
import org.kodein.di.KodeinAware
|
||||||
import org.kodein.di.android.x.androidXModule
|
import org.kodein.di.android.x.androidXModule
|
||||||
import org.kodein.di.generic.bind
|
import org.kodein.di.generic.bind
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
import org.kodein.di.generic.provider
|
|
||||||
import org.kodein.di.generic.singleton
|
import org.kodein.di.generic.singleton
|
||||||
|
|
||||||
abstract class BaseAppClass : Application(), KodeinAware {
|
abstract class BaseAppClass : Application(), KodeinAware {
|
||||||
|
|
||||||
// Kodein creation of modules to be retrieve within the app
|
// Kodein aware to initialise the classes used for DI
|
||||||
override val kodein = Kodein.lazy {
|
override val kodein = Kodein.lazy {
|
||||||
|
import(parentModule)
|
||||||
|
import(getFlavourModule(application = this@BaseAppClass))
|
||||||
|
}
|
||||||
|
|
||||||
|
val parentModule = Kodein.Module("Parent Module", allowSilentOverride = true) {
|
||||||
import(androidXModule(this@BaseAppClass))
|
import(androidXModule(this@BaseAppClass))
|
||||||
|
|
||||||
bind() from singleton { createNetworkModule() }
|
bind() from singleton { createNetworkModule() as WeatherApi }
|
||||||
bind() from singleton { createLocationModule() }
|
bind() from singleton { createLocationModule() }
|
||||||
|
|
||||||
bind() from singleton { Gson() }
|
bind() from singleton { Gson() }
|
||||||
@@ -35,10 +39,9 @@ abstract class BaseAppClass : Application(), KodeinAware {
|
|||||||
bind() from singleton { SettingsRepositoryImpl(instance()) }
|
bind() from singleton { SettingsRepositoryImpl(instance()) }
|
||||||
bind() from singleton { ServicesHelper(instance(), instance(), instance()) }
|
bind() from singleton { ServicesHelper(instance(), instance(), instance()) }
|
||||||
bind() from singleton { WeatherSource(instance(), instance()) }
|
bind() from singleton { WeatherSource(instance(), instance()) }
|
||||||
bind() from provider { ApplicationViewModelFactory(this@BaseAppClass, instance(), instance(),instance()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun createNetworkModule(): WeatherApi
|
abstract fun createNetworkModule(): Api
|
||||||
abstract fun createLocationModule(): LocationProvider
|
abstract fun createLocationModule(): LocationProvider
|
||||||
abstract fun createRoomDatabase(): AppDatabase
|
abstract fun createRoomDatabase(): AppDatabase
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ abstract class BaseActivity : AppCompatActivity(), KodeinAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
loadingView?.hide()
|
loadingView?.hide()
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import androidx.fragment.app.createViewModelLazy
|
|||||||
import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseViewModel
|
import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseViewModel
|
||||||
import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt
|
import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt
|
||||||
import com.appttude.h_mal.atlas_weather.model.ViewState
|
import com.appttude.h_mal.atlas_weather.model.ViewState
|
||||||
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
|
import com.appttude.h_mal.atlas_weather.application.ApplicationViewModelFactory
|
||||||
import org.kodein.di.KodeinAware
|
import org.kodein.di.KodeinAware
|
||||||
import org.kodein.di.android.x.kodein
|
import org.kodein.di.android.x.kodein
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ import androidx.annotation.XmlRes
|
|||||||
import androidx.fragment.app.createViewModelLazy
|
import androidx.fragment.app.createViewModelLazy
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
|
||||||
import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseAndroidViewModel
|
import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseAndroidViewModel
|
||||||
import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt
|
import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt
|
||||||
import com.appttude.h_mal.atlas_weather.model.ViewState
|
import com.appttude.h_mal.atlas_weather.model.ViewState
|
||||||
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
|
import com.appttude.h_mal.atlas_weather.application.ApplicationViewModelFactory
|
||||||
import org.kodein.di.KodeinAware
|
import org.kodein.di.KodeinAware
|
||||||
import org.kodein.di.android.x.kodein
|
import org.kodein.di.android.x.kodein
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
@@ -48,7 +47,7 @@ abstract class BasePreferencesFragment<V : BaseAndroidViewModel>(@XmlRes private
|
|||||||
|
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
prefs.registerOnSharedPreferenceChangeListener { _, s ->
|
prefs.registerOnSharedPreferenceChangeListener { _, s ->
|
||||||
preferenceChanged(s)
|
s?.let { preferenceChanged(s) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import com.appttude.h_mal.atlas_weather.data.repository.Repository
|
|||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
|
||||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.getSymbol
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class WeatherSource(
|
class WeatherSource(
|
||||||
@@ -33,12 +33,14 @@ class WeatherSource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
suspend fun forceFetchWeather(latLon: Pair<Double, Double>,
|
suspend fun forceFetchWeather(
|
||||||
locationType: LocationType = LocationType.Town): FullWeather {
|
latLon: Pair<Double, Double>,
|
||||||
|
locationType: LocationType = LocationType.Town
|
||||||
|
): FullWeather {
|
||||||
// get data from database
|
// get data from database
|
||||||
val weatherEntity = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
val weatherEntity = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
||||||
// check unit type - if same do nothing
|
// check unit type - if same do nothing
|
||||||
val units = if (repository.getUnitType() == UnitType.METRIC) "°C" else "°F"
|
val units = repository.getUnitType().getSymbol()
|
||||||
if (weatherEntity.weather.temperatureUnit == units) return weatherEntity.weather
|
if (weatherEntity.weather.temperatureUnit == units) return weatherEntity.weather
|
||||||
// load data for forced
|
// load data for forced
|
||||||
return fetchWeather(
|
return fetchWeather(
|
||||||
@@ -55,11 +57,13 @@ class WeatherSource(
|
|||||||
// Get weather from api
|
// Get weather from api
|
||||||
val weather = repository
|
val weather = repository
|
||||||
.getWeatherFromApi(latLon.first.toString(), latLon.second.toString())
|
.getWeatherFromApi(latLon.first.toString(), latLon.second.toString())
|
||||||
|
val lat = weather.latitude ?: latLon.first
|
||||||
|
val long = weather.longitude ?: latLon.second
|
||||||
val currentLocation =
|
val currentLocation =
|
||||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon, locationType)
|
locationProvider.getLocationNameFromLatLong(lat, long, locationType)
|
||||||
val unit = repository.getUnitType()
|
val unit = repository.getUnitType().getSymbol()
|
||||||
val fullWeather = FullWeather(weather).apply {
|
val fullWeather = weather.mapData().apply {
|
||||||
temperatureUnit = if (unit == UnitType.METRIC) "°C" else "°F"
|
temperatureUnit = unit
|
||||||
locationString = currentLocation
|
locationString = currentLocation
|
||||||
}
|
}
|
||||||
val entityItem = EntityItem(locationName, fullWeather)
|
val entityItem = EntityItem(locationName, fullWeather)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network
|
package com.appttude.h_mal.atlas_weather.data.network
|
||||||
|
|
||||||
class NetworkModule : BaseNetworkModule() {
|
class NetworkModule : BaseNetworkModule() {
|
||||||
override fun baseUrl(): String = "https://api.openweathermap.org/data/2.5/"
|
override fun baseUrl(): String = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network
|
package com.appttude.h_mal.atlas_weather.data.network
|
||||||
|
|
||||||
import org.json.JSONException
|
import retrofit2.HttpException
|
||||||
import org.json.JSONObject
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
abstract class ResponseUnwrap {
|
abstract class ResponseUnwrap {
|
||||||
|
|
||||||
@@ -15,18 +13,7 @@ abstract class ResponseUnwrap {
|
|||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
return response.body()!!
|
return response.body()!!
|
||||||
} else {
|
} else {
|
||||||
val error = response.errorBody()?.string()
|
throw HttpException(response)
|
||||||
|
|
||||||
val errorMessage = error?.let {
|
|
||||||
try {
|
|
||||||
JSONObject(it).getString("message")
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} ?: "Error Code: ${response.code()}"
|
|
||||||
|
|
||||||
throw IOException(errorMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network
|
package com.appttude.h_mal.atlas_weather.data.network
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
|
||||||
interface WeatherApi : Api {
|
interface WeatherApi : Api {
|
||||||
|
|
||||||
@GET("onecall?")
|
@GET("{location}")
|
||||||
suspend fun getFromApi(
|
suspend fun getFromApi(
|
||||||
@Query("lat") query: String,
|
@Path("location") location: String,
|
||||||
@Query("lon") lon: String,
|
@Query("contentType") exclude: String = "json",
|
||||||
@Query("exclude") exclude: String = "minutely",
|
@Query("unitGroup") units: String = "metric"
|
||||||
@Query("units") units: String = "metric"
|
): Response<WeatherApiResponse>
|
||||||
): Response<WeatherResponse>
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class QueryParamsInterceptor : Interceptor {
|
|||||||
val original = chain.request()
|
val original = chain.request()
|
||||||
|
|
||||||
val url = original.url.newBuilder()
|
val url = original.url.newBuilder()
|
||||||
.addQueryParameter("appid", id)
|
.addQueryParameter("key", id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// Request customization: add request headers
|
// Request customization: add request headers
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.networkUtils
|
package com.appttude.h_mal.atlas_weather.data.network.networkUtils
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkInterceptor
|
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkInterceptor
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
@@ -34,6 +36,13 @@ fun buildOkHttpClient(
|
|||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createGsonConverterFactory(): GsonConverterFactory {
|
||||||
|
val gson = GsonBuilder()
|
||||||
|
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
|
||||||
|
.create()
|
||||||
|
return GsonConverterFactory.create(gson)
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> createRetrofit(
|
fun <T> createRetrofit(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
okHttpClient: OkHttpClient,
|
okHttpClient: OkHttpClient,
|
||||||
@@ -42,7 +51,7 @@ fun <T> createRetrofit(
|
|||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
.baseUrl(baseUrl)
|
.baseUrl(baseUrl)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(createGsonConverterFactory())
|
||||||
.build()
|
.build()
|
||||||
.create(service)
|
.create(service)
|
||||||
}
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Current(
|
|
||||||
|
|
||||||
@field:SerializedName("sunrise")
|
|
||||||
val sunrise: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("temp")
|
|
||||||
val temp: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("visibility")
|
|
||||||
val visibility: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("uvi")
|
|
||||||
val uvi: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("pressure")
|
|
||||||
val pressure: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("clouds")
|
|
||||||
val clouds: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("feels_like")
|
|
||||||
val feelsLike: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dt")
|
|
||||||
val dt: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_deg")
|
|
||||||
val windDeg: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dew_point")
|
|
||||||
val dewPoint: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("sunset")
|
|
||||||
val sunset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("weather")
|
|
||||||
val weather: List<WeatherItem?>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("humidity")
|
|
||||||
val humidity: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_speed")
|
|
||||||
val windSpeed: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class DailyItem(
|
|
||||||
|
|
||||||
@field:SerializedName("sunrise")
|
|
||||||
val sunrise: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("temp")
|
|
||||||
val temp: Temp? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("uvi")
|
|
||||||
val uvi: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("pressure")
|
|
||||||
val pressure: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("clouds")
|
|
||||||
val clouds: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("feels_like")
|
|
||||||
val feelsLike: FeelsLike? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dt")
|
|
||||||
val dt: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("pop")
|
|
||||||
val pop: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_deg")
|
|
||||||
val windDeg: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dew_point")
|
|
||||||
val dewPoint: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("sunset")
|
|
||||||
val sunset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("weather")
|
|
||||||
val weather: List<WeatherItem?>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("humidity")
|
|
||||||
val humidity: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_speed")
|
|
||||||
val windSpeed: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("rain")
|
|
||||||
val rain: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class FeelsLike(
|
|
||||||
|
|
||||||
@field:SerializedName("eve")
|
|
||||||
val eve: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("night")
|
|
||||||
val night: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("day")
|
|
||||||
val day: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("morn")
|
|
||||||
val morn: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Hour(
|
|
||||||
|
|
||||||
@field:SerializedName("sunrise")
|
|
||||||
val sunrise: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("temp")
|
|
||||||
val temp: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("visibility")
|
|
||||||
val visibility: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("uvi")
|
|
||||||
val uvi: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("pressure")
|
|
||||||
val pressure: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("clouds")
|
|
||||||
val clouds: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("feels_like")
|
|
||||||
val feelsLike: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dt")
|
|
||||||
val dt: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_deg")
|
|
||||||
val windDeg: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dew_point")
|
|
||||||
val dewPoint: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("sunset")
|
|
||||||
val sunset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("weather")
|
|
||||||
val weather: List<WeatherItem?>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("humidity")
|
|
||||||
val humidity: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_speed")
|
|
||||||
val windSpeed: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Response(
|
|
||||||
|
|
||||||
@field:SerializedName("current")
|
|
||||||
val current: Current? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("timezone")
|
|
||||||
val timezone: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("timezone_offset")
|
|
||||||
val timezoneOffset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("daily")
|
|
||||||
val daily: List<DailyItem?>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("lon")
|
|
||||||
val lon: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("lat")
|
|
||||||
val lat: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Temp(
|
|
||||||
|
|
||||||
@field:SerializedName("min")
|
|
||||||
val min: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("max")
|
|
||||||
val max: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("eve")
|
|
||||||
val eve: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("night")
|
|
||||||
val night: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("day")
|
|
||||||
val day: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("morn")
|
|
||||||
val morn: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class WeatherItem(
|
|
||||||
|
|
||||||
@field:SerializedName("icon")
|
|
||||||
val icon: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("description")
|
|
||||||
val description: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("main")
|
|
||||||
val main: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("id")
|
|
||||||
val id: Int? = null
|
|
||||||
)
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class WeatherResponse(
|
|
||||||
|
|
||||||
@field:SerializedName("current")
|
|
||||||
val current: Current? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("timezone")
|
|
||||||
val timezone: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("timezone_offset")
|
|
||||||
val timezoneOffset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("hourly")
|
|
||||||
val hourly: List<Hour>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("daily")
|
|
||||||
val daily: List<DailyItem>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("lon")
|
|
||||||
val lon: Double = 0.00,
|
|
||||||
|
|
||||||
@field:SerializedName("lat")
|
|
||||||
val lat: Double = 0.00
|
|
||||||
)
|
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class Alerts(
|
||||||
|
@SerializedName("event") var event: String? = null,
|
||||||
|
@SerializedName("headline") var headline: String? = null,
|
||||||
|
@SerializedName("ends") var ends: String? = null,
|
||||||
|
@SerializedName("endsEpoch") var endsEpoch: Int? = null,
|
||||||
|
@SerializedName("onset") var onset: String? = null,
|
||||||
|
@SerializedName("onsetEpoch") var onsetEpoch: Int? = null,
|
||||||
|
@SerializedName("id") var id: String? = null,
|
||||||
|
@SerializedName("language") var language: String? = null,
|
||||||
|
@SerializedName("link") var link: String? = null,
|
||||||
|
@SerializedName("description") var description: String? = null,
|
||||||
|
)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
|
||||||
|
data class CurrentConditions(
|
||||||
|
@SerializedName("datetime") var datetime: String? = null,
|
||||||
|
@SerializedName("datetimeEpoch") var datetimeEpoch: Int? = null,
|
||||||
|
@SerializedName("temp") var temp: Double? = null,
|
||||||
|
@SerializedName("feelslike") var feelslike: Double? = null,
|
||||||
|
@SerializedName("humidity") var humidity: Double? = null,
|
||||||
|
@SerializedName("dew") var dew: Double? = null,
|
||||||
|
@SerializedName("precip") var precip: Double? = null,
|
||||||
|
@SerializedName("precipprob") var precipprob: Double? = null,
|
||||||
|
@SerializedName("snow") var snow: Int? = null,
|
||||||
|
@SerializedName("snowdepth") var snowdepth: Int? = null,
|
||||||
|
@SerializedName("preciptype") var preciptype: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("windgust") var windgust: Double? = null,
|
||||||
|
@SerializedName("windspeed") var windspeed: Double? = null,
|
||||||
|
@SerializedName("winddir") var winddir: Double? = null,
|
||||||
|
@SerializedName("pressure") var pressure: Double? = null,
|
||||||
|
@SerializedName("visibility") var visibility: Double? = null,
|
||||||
|
@SerializedName("cloudcover") var cloudcover: Double? = null,
|
||||||
|
@SerializedName("solarradiation") var solarradiation: Double? = null,
|
||||||
|
@SerializedName("solarenergy") var solarenergy: Double? = null,
|
||||||
|
@SerializedName("uvindex") var uvindex: Int? = null,
|
||||||
|
@SerializedName("conditions") var conditions: String? = null,
|
||||||
|
@SerializedName("icon") var icon: String? = null,
|
||||||
|
@SerializedName("stations") var stations: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("source") var source: String? = null,
|
||||||
|
@SerializedName("sunrise") var sunrise: String? = null,
|
||||||
|
@SerializedName("sunriseEpoch") var sunriseEpoch: Int? = null,
|
||||||
|
@SerializedName("sunset") var sunset: String? = null,
|
||||||
|
@SerializedName("sunsetEpoch") var sunsetEpoch: Int? = null,
|
||||||
|
@SerializedName("moonphase") var moonphase: Double? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
|
||||||
|
data class Days(
|
||||||
|
@SerializedName("datetime") var datetime: String? = null,
|
||||||
|
@SerializedName("datetimeEpoch") var datetimeEpoch: Int? = null,
|
||||||
|
@SerializedName("tempmax") var tempmax: Double? = null,
|
||||||
|
@SerializedName("tempmin") var tempmin: Double? = null,
|
||||||
|
@SerializedName("temp") var temp: Double? = null,
|
||||||
|
@SerializedName("feelslikemax") var feelslikemax: Double? = null,
|
||||||
|
@SerializedName("feelslikemin") var feelslikemin: Double? = null,
|
||||||
|
@SerializedName("feelslike") var feelslike: Double? = null,
|
||||||
|
@SerializedName("dew") var dew: Double? = null,
|
||||||
|
@SerializedName("humidity") var humidity: Double? = null,
|
||||||
|
@SerializedName("precip") var precip: Number? = null,
|
||||||
|
@SerializedName("precipprob") var precipprob: Double? = null,
|
||||||
|
@SerializedName("precipcover") var precipcover: Double? = null,
|
||||||
|
@SerializedName("preciptype") var preciptype: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("snow") var snow: Int? = null,
|
||||||
|
@SerializedName("snowdepth") var snowdepth: Int? = null,
|
||||||
|
@SerializedName("windgust") var windgust: Double? = null,
|
||||||
|
@SerializedName("windspeed") var windspeed: Double? = null,
|
||||||
|
@SerializedName("winddir") var winddir: Double? = null,
|
||||||
|
@SerializedName("pressure") var pressure: Double? = null,
|
||||||
|
@SerializedName("cloudcover") var cloudcover: Double? = null,
|
||||||
|
@SerializedName("visibility") var visibility: Double? = null,
|
||||||
|
@SerializedName("solarradiation") var solarradiation: Double? = null,
|
||||||
|
@SerializedName("solarenergy") var solarenergy: Double? = null,
|
||||||
|
@SerializedName("uvindex") var uvindex: Int? = null,
|
||||||
|
@SerializedName("severerisk") var severerisk: Int? = null,
|
||||||
|
@SerializedName("sunrise") var sunrise: String? = null,
|
||||||
|
@SerializedName("sunriseEpoch") var sunriseEpoch: Int? = null,
|
||||||
|
@SerializedName("sunset") var sunset: String? = null,
|
||||||
|
@SerializedName("sunsetEpoch") var sunsetEpoch: Int? = null,
|
||||||
|
@SerializedName("moonphase") var moonphase: Double? = null,
|
||||||
|
@SerializedName("conditions") var conditions: String? = null,
|
||||||
|
@SerializedName("description") var description: String? = null,
|
||||||
|
@SerializedName("icon") var icon: String? = null,
|
||||||
|
@SerializedName("stations") var stations: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("source") var source: String? = null,
|
||||||
|
@SerializedName("hours") var hours: ArrayList<Hours> = arrayListOf()
|
||||||
|
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
|
||||||
|
data class Hours(
|
||||||
|
@SerializedName("datetime") var datetime: String? = null,
|
||||||
|
@SerializedName("datetimeEpoch") var datetimeEpoch: Int? = null,
|
||||||
|
@SerializedName("temp") var temp: Double? = null,
|
||||||
|
@SerializedName("feelslike") var feelslike: Double? = null,
|
||||||
|
@SerializedName("humidity") var humidity: Double? = null,
|
||||||
|
@SerializedName("dew") var dew: Double? = null,
|
||||||
|
@SerializedName("precip") var precip: Number? = null,
|
||||||
|
@SerializedName("precipprob") var precipprob: Double? = null,
|
||||||
|
@SerializedName("snow") var snow: Int? = null,
|
||||||
|
@SerializedName("snowdepth") var snowdepth: Int? = null,
|
||||||
|
@SerializedName("preciptype") var preciptype: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("windgust") var windgust: Double? = null,
|
||||||
|
@SerializedName("windspeed") var windspeed: Double? = null,
|
||||||
|
@SerializedName("winddir") var winddir: Double? = null,
|
||||||
|
@SerializedName("pressure") var pressure: Double? = null,
|
||||||
|
@SerializedName("visibility") var visibility: Double? = null,
|
||||||
|
@SerializedName("cloudcover") var cloudcover: Double? = null,
|
||||||
|
@SerializedName("solarradiation") var solarradiation: Double? = null,
|
||||||
|
@SerializedName("solarenergy") var solarenergy: Double? = null,
|
||||||
|
@SerializedName("uvindex") var uvindex: Int? = null,
|
||||||
|
@SerializedName("severerisk") var severerisk: Int? = null,
|
||||||
|
@SerializedName("conditions") var conditions: String? = null,
|
||||||
|
@SerializedName("icon") var icon: String? = null,
|
||||||
|
@SerializedName("stations") var stations: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("source") var source: String? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.DataMapper
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.weather.Current
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.weather.DailyWeather
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.weather.Hour as FullWeatherHour
|
||||||
|
|
||||||
|
|
||||||
|
data class WeatherApiResponse(
|
||||||
|
@SerializedName("queryCost") var queryCost: Int? = null,
|
||||||
|
@SerializedName("latitude") var latitude: Double? = null,
|
||||||
|
@SerializedName("longitude") var longitude: Double? = null,
|
||||||
|
@SerializedName("resolvedAddress") var resolvedAddress: String? = null,
|
||||||
|
@SerializedName("address") var address: String? = null,
|
||||||
|
@SerializedName("timezone") var timezone: String? = null,
|
||||||
|
@SerializedName("tzoffset") var tzoffset: Int? = null,
|
||||||
|
@SerializedName("description") var description: String? = null,
|
||||||
|
@SerializedName("days") var days: ArrayList<Days> = arrayListOf(),
|
||||||
|
@SerializedName("alerts") var alerts: ArrayList<Alerts> = arrayListOf(),
|
||||||
|
@SerializedName("currentConditions") var currentConditions: CurrentConditions? = CurrentConditions()
|
||||||
|
): DataMapper<FullWeather> {
|
||||||
|
|
||||||
|
override fun mapData(): FullWeather {
|
||||||
|
val hours = mutableListOf(days[0].hours).apply { add(days[1].hours) }.flatten().subList(0,23).map { FullWeatherHour(it) }.toList()
|
||||||
|
val collectedDays = mutableListOf(days.subList(0,7)).flatten().map { DailyWeather(it) }.toList()
|
||||||
|
return FullWeather(
|
||||||
|
current = Current(currentConditions),
|
||||||
|
timezone = timezone,
|
||||||
|
timezoneOffset = tzoffset,
|
||||||
|
hourly = hours,
|
||||||
|
daily = collectedDays,
|
||||||
|
lat = latitude,
|
||||||
|
lon = longitude,
|
||||||
|
locationString = address,
|
||||||
|
temperatureUnit = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.repository
|
package com.appttude.h_mal.atlas_weather.data.repository
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||||
|
|
||||||
interface Repository {
|
interface Repository {
|
||||||
|
|
||||||
suspend fun getWeatherFromApi(lat: String, long: String): WeatherResponse
|
suspend fun getWeatherFromApi(lat: String, long: String): WeatherApiResponse
|
||||||
suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem)
|
suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem)
|
||||||
suspend fun saveWeatherListToRoom(list: List<EntityItem>)
|
suspend fun saveWeatherListToRoom(list: List<EntityItem>)
|
||||||
fun loadRoomWeatherLiveData(): LiveData<List<EntityItem>>
|
fun loadRoomWeatherLiveData(): LiveData<List<EntityItem>>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.appttude.h_mal.atlas_weather.data.repository
|
|||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.ResponseUnwrap
|
import com.appttude.h_mal.atlas_weather.data.network.ResponseUnwrap
|
||||||
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.response.forecast.WeatherResponse
|
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
|
||||||
import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
|
import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
|
||||||
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.room.AppDatabase
|
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||||
@@ -20,8 +20,9 @@ class RepositoryImpl(
|
|||||||
override suspend fun getWeatherFromApi(
|
override suspend fun getWeatherFromApi(
|
||||||
lat: String,
|
lat: String,
|
||||||
long: String
|
long: String
|
||||||
): WeatherResponse {
|
): WeatherApiResponse {
|
||||||
return responseUnwrap { api.getFromApi(lat, long, units = prefs.getUnitsType().name.lowercase()) }
|
val unit = if (prefs.getUnitsType() == UnitType.METRIC) "metric" else "us"
|
||||||
|
return responseUnwrap { api.getFromApi(location = "$lat,$long", units = unit) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) {
|
override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.room
|
package com.appttude.h_mal.atlas_weather.data.room
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
@@ -32,4 +34,7 @@ interface WeatherDao {
|
|||||||
@Query("DELETE FROM EntityItem WHERE id = :userId")
|
@Query("DELETE FROM EntityItem WHERE id = :userId")
|
||||||
fun deleteEntry(userId: String): Int
|
fun deleteEntry(userId: String): Int
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Query("DELETE FROM EntityItem")
|
||||||
|
fun deleteAll(): Int
|
||||||
}
|
}
|
||||||
@@ -11,14 +11,13 @@ import com.appttude.h_mal.atlas_weather.data.repository.Repository
|
|||||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
|
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
|
||||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
|
||||||
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetCellData
|
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetCellData
|
||||||
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetData
|
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetData
|
||||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetData
|
import com.appttude.h_mal.atlas_weather.model.widget.WidgetData
|
||||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetError
|
import com.appttude.h_mal.atlas_weather.model.widget.WidgetError
|
||||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetState
|
import com.appttude.h_mal.atlas_weather.model.widget.WidgetState
|
||||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetWeatherCollection
|
import com.appttude.h_mal.atlas_weather.model.widget.WidgetWeatherCollection
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.getSymbol
|
||||||
import com.appttude.h_mal.atlas_weather.utils.toSmallDayName
|
import com.appttude.h_mal.atlas_weather.utils.toSmallDayName
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import com.squareup.picasso.Target
|
import com.squareup.picasso.Target
|
||||||
@@ -45,9 +44,11 @@ class ServicesHelper(
|
|||||||
// Get weather from api
|
// Get weather from api
|
||||||
val weather = repository
|
val weather = repository
|
||||||
.getWeatherFromApi(latLong.first.toString(), latLong.second.toString())
|
.getWeatherFromApi(latLong.first.toString(), latLong.second.toString())
|
||||||
|
val lat = weather.latitude ?: latLong.first
|
||||||
|
val long = weather.longitude ?: latLong.second
|
||||||
val currentLocation =
|
val currentLocation =
|
||||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
locationProvider.getLocationNameFromLatLong(lat, long)
|
||||||
val fullWeather = FullWeather(weather).apply {
|
val fullWeather = weather.mapData().apply {
|
||||||
temperatureUnit = "°C"
|
temperatureUnit = "°C"
|
||||||
locationString = currentLocation
|
locationString = currentLocation
|
||||||
}
|
}
|
||||||
@@ -105,8 +106,11 @@ class ServicesHelper(
|
|||||||
return WidgetState.HasError(error)
|
return WidgetState.HasError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val lat = weather.latitude ?: latLong.first
|
||||||
|
val long = weather.longitude ?: latLong.second
|
||||||
|
|
||||||
val currentLocation = try {
|
val currentLocation = try {
|
||||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
locationProvider.getLocationNameFromLatLong(lat, long)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
val data = getWidgetWeatherCollection()
|
val data = getWidgetWeatherCollection()
|
||||||
data?.let {
|
data?.let {
|
||||||
@@ -120,8 +124,8 @@ class ServicesHelper(
|
|||||||
return WidgetState.HasError(error)
|
return WidgetState.HasError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
val fullWeather = FullWeather(weather).apply {
|
val fullWeather = weather.mapData().apply {
|
||||||
temperatureUnit = if (repository.getUnitType() == UnitType.METRIC) "°C" else "°F"
|
temperatureUnit = repository.getUnitType().getSymbol()
|
||||||
locationString = currentLocation
|
locationString = currentLocation
|
||||||
}
|
}
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||||
@@ -140,7 +144,7 @@ class ServicesHelper(
|
|||||||
|
|
||||||
result.weather.let {
|
result.weather.let {
|
||||||
val bitmap = it.current?.icon
|
val bitmap = it.current?.icon
|
||||||
val location = locationProvider.getLocationNameFromLatLong(it.lat, it.lon)
|
val location = locationProvider.getLocationNameFromLatLong(it.lat!!, it.lon!!)
|
||||||
val temp = it.current?.temp?.toInt().toString()
|
val temp = it.current?.temp?.toInt().toString()
|
||||||
|
|
||||||
WidgetData(location, bitmap, temp, epoc)
|
WidgetData(location, bitmap, temp, epoc)
|
||||||
@@ -177,7 +181,7 @@ class ServicesHelper(
|
|||||||
|
|
||||||
val widgetData = result.weather.let {
|
val widgetData = result.weather.let {
|
||||||
val bitmap = it.current?.icon
|
val bitmap = it.current?.icon
|
||||||
val location = locationProvider.getLocationNameFromLatLong(it.lat, it.lon)
|
val location = locationProvider.getLocationNameFromLatLong(it.lat!!, it.lon!!)
|
||||||
val temp = it.current?.temp?.toInt().toString()
|
val temp = it.current?.temp?.toInt().toString()
|
||||||
val epoc = System.currentTimeMillis()
|
val epoc = System.currentTimeMillis()
|
||||||
|
|
||||||
@@ -186,7 +190,7 @@ class ServicesHelper(
|
|||||||
|
|
||||||
val list = mutableListOf<InnerWidgetCellData>()
|
val list = mutableListOf<InnerWidgetCellData>()
|
||||||
|
|
||||||
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather ->
|
result.weather.daily?.drop(1)?.dropLast(1)?.forEach { dailyWeather ->
|
||||||
val day = dailyWeather.dt?.toSmallDayName()
|
val day = dailyWeather.dt?.toSmallDayName()
|
||||||
val icon = dailyWeather.icon
|
val icon = dailyWeather.icon
|
||||||
val temp = dailyWeather.max?.toInt().toString()
|
val temp = dailyWeather.max?.toInt().toString()
|
||||||
@@ -216,7 +220,7 @@ class ServicesHelper(
|
|||||||
|
|
||||||
val list = mutableListOf<InnerWidgetCellData>()
|
val list = mutableListOf<InnerWidgetCellData>()
|
||||||
|
|
||||||
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather ->
|
result.weather.daily?.drop(1)?.dropLast(1)?.forEach { dailyWeather ->
|
||||||
val day = dailyWeather.dt?.toSmallDayName()
|
val day = dailyWeather.dt?.toSmallDayName()
|
||||||
val icon = dailyWeather.icon
|
val icon = dailyWeather.icon
|
||||||
val temp = dailyWeather.max?.toInt().toString()
|
val temp = dailyWeather.max?.toInt().toString()
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.model
|
||||||
|
|
||||||
|
interface DataMapper <T: Any> {
|
||||||
|
fun mapData(): T
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.model
|
||||||
|
|
||||||
|
enum class IconMapper(val label: String) {
|
||||||
|
snow("13d"),
|
||||||
|
snow_showers_day("13d"),
|
||||||
|
snow_showers_night("13n"),
|
||||||
|
thunder_rain("11d"),
|
||||||
|
thunder_showers_day("11d"),
|
||||||
|
thunder_showers_night("11n"),
|
||||||
|
rain("10d"),
|
||||||
|
showers_day("10d"),
|
||||||
|
showers_night("10n"),
|
||||||
|
fog("50d"),
|
||||||
|
wind("50d"),
|
||||||
|
cloudy("04d"),
|
||||||
|
partly_cloudy_day("03d"),
|
||||||
|
partly_cloudy_night("03n"),
|
||||||
|
clear_day("01d"),
|
||||||
|
clear_night("01n");
|
||||||
|
|
||||||
|
companion object{
|
||||||
|
fun findIconCode(iconId: String?): String? {
|
||||||
|
val label = iconId?.replace("-", "_")
|
||||||
|
val enumName = IconMapper.entries.find { it.name == label }
|
||||||
|
return enumName?.label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,8 +40,7 @@ data class Forecast(
|
|||||||
parcel.readString(),
|
parcel.readString(),
|
||||||
parcel.readString(),
|
parcel.readString(),
|
||||||
parcel.readString()
|
parcel.readString()
|
||||||
) {
|
)
|
||||||
}
|
|
||||||
|
|
||||||
constructor(dailyWeather: DailyWeather) : this(
|
constructor(dailyWeather: DailyWeather) : this(
|
||||||
dailyWeather.dt?.toDayString(),
|
dailyWeather.dt?.toDayString(),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ data class WeatherDisplay(
|
|||||||
val averageTemp: Double?,
|
val averageTemp: Double?,
|
||||||
var unit: String?,
|
var unit: String?,
|
||||||
var location: String?,
|
var location: String?,
|
||||||
val iconURL: String?,
|
var iconURL: String?,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val hourly: List<Hour>?,
|
val hourly: List<Hour>?,
|
||||||
val forecast: List<Forecast>?,
|
val forecast: List<Forecast>?,
|
||||||
@@ -40,8 +40,7 @@ data class WeatherDisplay(
|
|||||||
parcel.readDouble(),
|
parcel.readDouble(),
|
||||||
parcel.readDouble(),
|
parcel.readDouble(),
|
||||||
parcel.readString()
|
parcel.readString()
|
||||||
) {
|
)
|
||||||
}
|
|
||||||
|
|
||||||
constructor(entity: EntityItem) : this(
|
constructor(entity: EntityItem) : this(
|
||||||
entity.weather.current?.temp,
|
entity.weather.current?.temp,
|
||||||
@@ -56,8 +55,8 @@ data class WeatherDisplay(
|
|||||||
entity.weather.daily?.get(0)?.pop?.times(100)?.toInt()?.toString(),
|
entity.weather.daily?.get(0)?.pop?.times(100)?.toInt()?.toString(),
|
||||||
entity.weather.current?.humidity?.toString(),
|
entity.weather.current?.humidity?.toString(),
|
||||||
entity.weather.current?.clouds?.toString(),
|
entity.weather.current?.clouds?.toString(),
|
||||||
entity.weather.lat,
|
entity.weather.lat!!,
|
||||||
entity.weather.lon,
|
entity.weather.lon!!,
|
||||||
entity.weather.locationString
|
entity.weather.locationString
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ enum class UnitType {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getByName(name: String?): UnitType? {
|
fun getByName(name: String?): UnitType? {
|
||||||
return values().firstOrNull {
|
return entries.firstOrNull {
|
||||||
it.name.lowercase(Locale.ROOT) == name?.lowercase(
|
it.name.lowercase(Locale.ROOT) == name?.lowercase(
|
||||||
Locale.ROOT
|
Locale.ROOT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun UnitType.getLabel() = name.lowercase().replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.model.weather
|
package com.appttude.h_mal.atlas_weather.model.weather
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.Current
|
import com.appttude.h_mal.atlas_weather.data.network.response.weather.CurrentConditions
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.IconMapper
|
||||||
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
||||||
|
|
||||||
data class Current(
|
data class Current(
|
||||||
@@ -23,23 +24,23 @@ data class Current(
|
|||||||
val windSpeed: Double? = null
|
val windSpeed: Double? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(dailyItem: Current) : this(
|
constructor(currentConditions: CurrentConditions?) : this(
|
||||||
dailyItem.dt,
|
dt = currentConditions?.datetimeEpoch,
|
||||||
dailyItem.sunrise,
|
sunrise = currentConditions?.sunriseEpoch,
|
||||||
dailyItem.sunset,
|
sunset = currentConditions?.sunsetEpoch,
|
||||||
dailyItem.temp,
|
temp = currentConditions?.temp,
|
||||||
dailyItem.visibility,
|
visibility = currentConditions?.visibility?.toInt(),
|
||||||
dailyItem.uvi,
|
uvi = currentConditions?.uvindex?.toDouble(),
|
||||||
dailyItem.pressure,
|
pressure = currentConditions?.pressure?.toInt(),
|
||||||
dailyItem.clouds,
|
clouds = currentConditions?.cloudcover?.toInt(),
|
||||||
dailyItem.feelsLike,
|
feelsLike = currentConditions?.feelslike,
|
||||||
dailyItem.windDeg,
|
windDeg = currentConditions?.winddir?.toInt(),
|
||||||
dailyItem.dewPoint,
|
dewPoint = currentConditions?.dew,
|
||||||
generateIconUrlString(dailyItem.weather?.getOrNull(0)?.icon),
|
icon = generateIconUrlString(IconMapper.findIconCode(currentConditions?.icon)),
|
||||||
dailyItem.weather?.get(0)?.description,
|
description = currentConditions?.conditions,
|
||||||
dailyItem.weather?.get(0)?.main,
|
main = currentConditions?.conditions,
|
||||||
dailyItem.weather?.get(0)?.id,
|
id = currentConditions?.datetimeEpoch,
|
||||||
dailyItem.humidity,
|
humidity = currentConditions?.humidity?.toInt(),
|
||||||
dailyItem.windSpeed
|
windSpeed = currentConditions?.windspeed
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.model.weather
|
package com.appttude.h_mal.atlas_weather.model.weather
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.DailyItem
|
import com.appttude.h_mal.atlas_weather.data.network.response.weather.Days
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.IconMapper
|
||||||
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
||||||
|
|
||||||
|
|
||||||
@@ -27,28 +28,29 @@ data class DailyWeather(
|
|||||||
val rain: Double?
|
val rain: Double?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(dailyItem: DailyItem) : this(
|
constructor(days: Days) : this(
|
||||||
dailyItem.dt,
|
days.datetimeEpoch,
|
||||||
dailyItem.sunrise,
|
days.sunriseEpoch,
|
||||||
dailyItem.sunset,
|
days.sunsetEpoch,
|
||||||
dailyItem.temp?.min,
|
days.tempmin,
|
||||||
dailyItem.temp?.max,
|
days.tempmax,
|
||||||
dailyItem.temp?.day,
|
days.temp,
|
||||||
dailyItem.feelsLike?.day,
|
days.feelslike,
|
||||||
dailyItem.pressure,
|
days.pressure?.toInt(),
|
||||||
dailyItem.humidity,
|
days.humidity?.toInt(),
|
||||||
dailyItem.dewPoint,
|
days.dew,
|
||||||
dailyItem.windSpeed,
|
days.windspeed,
|
||||||
dailyItem.windDeg,
|
days.winddir?.toInt(),
|
||||||
generateIconUrlString(dailyItem.weather?.getOrNull(0)?.icon),
|
generateIconUrlString(
|
||||||
dailyItem.weather?.get(0)?.description,
|
IconMapper.findIconCode(days.icon)
|
||||||
dailyItem.weather?.get(0)?.main,
|
),
|
||||||
dailyItem.weather?.get(0)?.id,
|
days.description,
|
||||||
dailyItem.clouds,
|
days.conditions,
|
||||||
dailyItem.pop,
|
days.datetimeEpoch,
|
||||||
dailyItem.uvi,
|
days.cloudcover?.toInt(),
|
||||||
dailyItem.rain
|
days.precipprob,
|
||||||
|
days.uvindex?.toDouble(),
|
||||||
|
days.precip?.toDouble()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,15 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.model.weather
|
package com.appttude.h_mal.atlas_weather.model.weather
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
|
||||||
|
|
||||||
data class FullWeather(
|
data class FullWeather(
|
||||||
val current: Current? = null,
|
val current: Current? = null,
|
||||||
val timezone: String? = null,
|
val timezone: String? = null,
|
||||||
val timezoneOffset: Int? = null,
|
val timezoneOffset: Int? = null,
|
||||||
val hourly: List<Hour>? = null,
|
val hourly: List<Hour>? = null,
|
||||||
val daily: List<DailyWeather>? = null,
|
val daily: List<DailyWeather>? = null,
|
||||||
val lon: Double = 0.00,
|
val lon: Double? = null,
|
||||||
val lat: Double = 0.00,
|
val lat: Double? = null,
|
||||||
var locationString: String? = null,
|
var locationString: String? = null,
|
||||||
var temperatureUnit: String? = null
|
var temperatureUnit: String? = null
|
||||||
) {
|
)
|
||||||
|
|
||||||
constructor(weatherResponse: WeatherResponse) : this(
|
|
||||||
weatherResponse.current?.let { Current(it) },
|
|
||||||
weatherResponse.timezone,
|
|
||||||
weatherResponse.timezoneOffset,
|
|
||||||
weatherResponse.hourly?.subList(0, 23)?.map { Hour(it) },
|
|
||||||
weatherResponse.daily?.map { DailyWeather(it) },
|
|
||||||
weatherResponse.lon,
|
|
||||||
weatherResponse.lat
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package com.appttude.h_mal.atlas_weather.model.weather
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.IconMapper
|
||||||
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.Hour as ForecastHour
|
import com.appttude.h_mal.atlas_weather.data.network.response.weather.Hours as WeatherHour
|
||||||
|
|
||||||
|
|
||||||
data class Hour(
|
data class Hour(
|
||||||
@@ -16,13 +17,13 @@ data class Hour(
|
|||||||
parcel.readValue(Int::class.java.classLoader) as? Int,
|
parcel.readValue(Int::class.java.classLoader) as? Int,
|
||||||
parcel.readValue(Double::class.java.classLoader) as? Double,
|
parcel.readValue(Double::class.java.classLoader) as? Double,
|
||||||
parcel.readString()
|
parcel.readString()
|
||||||
) {
|
)
|
||||||
}
|
|
||||||
|
|
||||||
constructor(hour: ForecastHour) : this(
|
|
||||||
hour.dt,
|
constructor(weatherHour: WeatherHour) : this(
|
||||||
hour.temp,
|
weatherHour.datetimeEpoch,
|
||||||
generateIconUrlString(hour.weather?.getOrNull(0)?.icon)
|
weatherHour.temp,
|
||||||
|
generateIconUrlString(IconMapper.findIconCode(weatherHour.icon))
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import androidx.navigation.ui.setupWithNavController
|
|||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
import com.appttude.h_mal.atlas_weather.base.BaseActivity
|
import com.appttude.h_mal.atlas_weather.base.BaseActivity
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import kotlinx.android.synthetic.main.activity_main_navigation.toolbar
|
|
||||||
|
|
||||||
class MainActivity : BaseActivity() {
|
class MainActivity : BaseActivity() {
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class MainActivity : BaseActivity() {
|
|||||||
setContentView(R.layout.activity_main_navigation)
|
setContentView(R.layout.activity_main_navigation)
|
||||||
|
|
||||||
val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
|
|
||||||
navHost = supportFragmentManager
|
navHost = supportFragmentManager
|
||||||
.findFragmentById(R.id.container) as NavHostFragment
|
.findFragmentById(R.id.container) as NavHostFragment
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.utils
|
package com.appttude.h_mal.atlas_weather.utils
|
||||||
|
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||||
|
|
||||||
|
|
||||||
fun generateIconUrlString(icon: String?): String? {
|
fun generateIconUrlString(icon: String?): String? {
|
||||||
return icon?.let {
|
return icon?.let {
|
||||||
@@ -9,4 +11,6 @@ fun generateIconUrlString(icon: String?): String? {
|
|||||||
.append("@2x.png")
|
.append("@2x.png")
|
||||||
.toString()
|
.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun UnitType.getSymbol(): String = if (this == UnitType.METRIC) "°C" else "°F"
|
||||||
@@ -2,6 +2,7 @@ package com.appttude.h_mal.atlas_weather.utils
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -11,7 +12,12 @@ import android.view.inputmethod.InputMethodManager
|
|||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.AnimRes
|
import androidx.annotation.AnimRes
|
||||||
|
import androidx.annotation.IdRes
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleObserver
|
||||||
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
|
|
||||||
@@ -55,4 +61,63 @@ fun View.triggerAnimation(@AnimRes id: Int, complete: (View) -> Unit) {
|
|||||||
override fun onAnimationRepeat(a: Animation?) {}
|
override fun onAnimationRepeat(a: Animation?) {}
|
||||||
})
|
})
|
||||||
startAnimation(animation)
|
startAnimation(animation)
|
||||||
|
}
|
||||||
|
|
||||||
|
class BindViewDelegate<T>(
|
||||||
|
private val createView: () -> T,
|
||||||
|
private val getLifecycle: () -> Lifecycle
|
||||||
|
) : Lazy<T>, LifecycleObserver {
|
||||||
|
|
||||||
|
private var view: T? = null
|
||||||
|
|
||||||
|
private val lifecycle: Lifecycle?
|
||||||
|
get() = try {
|
||||||
|
getLifecycle()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.message?.let { Log.e("BindViewDelegate", it) }
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override val value: T
|
||||||
|
get() {
|
||||||
|
if (view == null) {
|
||||||
|
lifecycle?.removeObserver(this)
|
||||||
|
view = createView()
|
||||||
|
lifecycle?.addObserver(this)
|
||||||
|
}
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return view as T
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||||
|
fun onDestroy() {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reset() {
|
||||||
|
lifecycle?.removeObserver(this)
|
||||||
|
view = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isInitialized(): Boolean = view != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : View> Fragment.bindView(@IdRes resource: Int): Lazy<T> = BindViewDelegate(
|
||||||
|
createView = { requireView().findViewById<T>(resource) },
|
||||||
|
getLifecycle = { viewLifecycleOwner.lifecycle }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun <T : View> Activity.bindView(@IdRes res: Int): Lazy<T> {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return lazy(LazyThreadSafetyMode.NONE) { findViewById<T>(res) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : View> View.bindView(@IdRes res: Int): Lazy<T> {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return lazy(LazyThreadSafetyMode.NONE) { findViewById<T>(res) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : View> RecyclerView.ViewHolder.bindView(@IdRes res: Int): Lazy<T> {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return lazy(LazyThreadSafetyMode.NONE) { itemView.findViewById<T>(res) }
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_gravity="center"
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_margin="24dp"
|
android:layout_margin="24dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
@@ -33,8 +30,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:text="@string/submit"
|
android:text="@string/submit"
|
||||||
android:textColor="#ffffff"
|
android:textColor="@color/colour_one"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</FrameLayout>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="3"
|
android:layout_weight="3"
|
||||||
tools:text="85%" />
|
tools:text="33" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="3"
|
android:layout_weight="3"
|
||||||
tools:ignore="InOrMmUsage"
|
tools:ignore="InOrMmUsage"
|
||||||
tools:text="11mm" />
|
tools:text="30" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="3"
|
android:layout_weight="3"
|
||||||
tools:ignore="InOrMmUsage"
|
tools:ignore="InOrMmUsage"
|
||||||
tools:text="11mm" />
|
tools:text="27" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
android:paddingTop="6dp"
|
android:paddingTop="6dp"
|
||||||
android:paddingRight="24dp"
|
android:paddingRight="24dp"
|
||||||
android:paddingBottom="6dp"
|
android:paddingBottom="6dp"
|
||||||
android:background="@color/colorPrimaryDark"
|
android:background="@color/weather_cell_colour"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
android:id="@+id/forecast_listview"
|
android:id="@+id/forecast_listview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:backgroundTint="@color/colorPrimaryDark"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
tools:listitem="@layout/db_list_item" />
|
tools:listitem="@layout/db_list_item" />
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
<string name="unit_key">Units</string>
|
<string name="unit_key">Units</string>
|
||||||
<string name="widget_black_background">widget_black_background</string>
|
<string name="widget_black_background">widget_black_background</string>
|
||||||
<string name="weather_units">Weather units</string>
|
<string name="weather_units">Weather units</string>
|
||||||
|
<string name="channel_name">channel name</string>
|
||||||
|
<string name="channel_description">channel description</string>
|
||||||
|
|
||||||
<string-array name="units">
|
<string-array name="units">
|
||||||
<item>Metric</item>
|
<item>Metric</item>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
|
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.viewmodel
|
package com.appttude.h_mal.atlas_weather.application
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
@@ -6,6 +6,9 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
||||||
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.repository.SettingsRepository
|
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
|
||||||
|
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
|
||||||
|
import com.appttude.h_mal.atlas_weather.viewmodel.SettingsViewModel
|
||||||
|
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
||||||
|
|
||||||
|
|
||||||
class ApplicationViewModelFactory(
|
class ApplicationViewModelFactory(
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.application
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import org.kodein.di.Kodein
|
||||||
|
import org.kodein.di.generic.bind
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import org.kodein.di.generic.provider
|
||||||
|
|
||||||
|
fun getFlavourModule(application: Application) = FlavourModule(application).build()
|
||||||
|
class FlavourModule(val application: Application) {
|
||||||
|
fun build() = Kodein.Module("Flavour") {
|
||||||
|
bind() from provider {
|
||||||
|
ApplicationViewModelFactory(
|
||||||
|
application,
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user