Readme and screenshot (#35)
- 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 - readme.md added - Snapshot tests added for readme.md - UI corrections during snapshots
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>
|
||||||
190
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,14 +12,12 @@ 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 5
|
versionCode 5
|
||||||
versionName "3.0"
|
versionName "3.0"
|
||||||
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
||||||
@@ -78,20 +75,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 +108,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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -31,7 +31,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()
|
||||||
@@ -129,6 +129,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)))
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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 java.io.BufferedReader
|
||||||
|
|
||||||
|
class TestAppClass : AtlasApp() {
|
||||||
|
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) {
|
||||||
|
val iStream =
|
||||||
|
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath.json")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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,55 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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 = when (unitType) {
|
||||||
|
UnitType.METRIC -> "Metric"
|
||||||
|
UnitType.IMPERIAL -> "Imperial"
|
||||||
|
}
|
||||||
|
|
||||||
|
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,50 @@
|
|||||||
|
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 org.junit.Test
|
||||||
|
import tools.fastlane.screengrab.Screengrab
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@TargetApi(27)
|
||||||
|
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
|
override fun beforeLaunch() {
|
||||||
|
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
||||||
|
stubLocation("London", 51.51, -0.13)
|
||||||
|
clearPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun homeAndFurtherInfoPageCapture() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
Screengrab.screenshot("HomeScreen")
|
||||||
|
tapDayInformationByPosition(4)
|
||||||
|
}
|
||||||
|
furtherInfoScreen {
|
||||||
|
isDisplayed()
|
||||||
|
Screengrab.screenshot("FurtherInfoScreen")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun settingsPageCapture() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
openMenuItem()
|
||||||
|
}
|
||||||
|
settingsScreen {
|
||||||
|
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial)
|
||||||
|
Screengrab.screenshot("SettingsScreen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 : MonoApp() {
|
||||||
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,12 +38,12 @@ 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
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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.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("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
||||||
|
stubLocation("London", 51.51, -0.13)
|
||||||
|
clearPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun homeAndFurtherInfoPageCapture() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
Screengrab.screenshot("HomeScreen")
|
||||||
|
tapDayInformationByPosition(4)
|
||||||
|
}
|
||||||
|
furtherInfoScreen {
|
||||||
|
isDisplayed()
|
||||||
|
Screengrab.screenshot("FurtherInfoScreen")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun settingsPageCapture() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
openMenuItem()
|
||||||
|
}
|
||||||
|
settingsScreen {
|
||||||
|
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", 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,6 +11,7 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@@ -45,4 +46,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,8 @@ 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
|
||||||
|
import com.appttude.h_mal.monoWeather.ui.home.adapter.further.ViewHolderFurtherDetails
|
||||||
|
|
||||||
fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() }
|
fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() }
|
||||||
class WeatherScreen : BaseTestRobot() {
|
class WeatherScreen : BaseTestRobot() {
|
||||||
@@ -16,4 +18,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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ 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.monoWeather.robot.weatherScreen
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
@@ -21,6 +22,7 @@ class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore("Test is flakey - must investigate")
|
||||||
@Test
|
@Test
|
||||||
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
|
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
|
||||||
weatherScreen {
|
weatherScreen {
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ 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.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.furtherInfoScreen
|
||||||
import com.appttude.h_mal.monoWeather.robot.settingsScreen
|
import com.appttude.h_mal.monoWeather.robot.settingsScreen
|
||||||
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import tools.fastlane.screengrab.Screengrab
|
||||||
|
|
||||||
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
@@ -25,6 +27,22 @@ class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadApp_validWeatherResponse_viewFurtherDetailsPage() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
verifyCurrentTemperature(2)
|
||||||
|
verifyCurrentLocation("Mock Location")
|
||||||
|
tapDayInformationByPosition(4)
|
||||||
|
}
|
||||||
|
furtherInfoScreen {
|
||||||
|
isDisplayed()
|
||||||
|
verifyMaxTemperature(12)
|
||||||
|
verifyAverageTemperature(9)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun loadApp_changeToImperial_returnsValidPage() {
|
fun loadApp_changeToImperial_returnsValidPage() {
|
||||||
weatherScreen {
|
weatherScreen {
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
<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:name="com.appttude.h_mal.atlas_weather.application.AtlasApp"
|
||||||
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 +29,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,46 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.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.generic.bind
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import org.kodein.di.generic.provider
|
||||||
|
import org.kodein.di.generic.singleton
|
||||||
|
|
||||||
|
|
||||||
|
open class AtlasApp : AppClass() {
|
||||||
|
|
||||||
|
private lateinit var notificationService: NotificationService
|
||||||
|
|
||||||
|
override val flavourModule = super.flavourModule.copy {
|
||||||
|
bind() from singleton {
|
||||||
|
NotificationHelper(
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind() from singleton {
|
||||||
|
NotificationService(this@AtlasApp).apply { notificationService = this }
|
||||||
|
}
|
||||||
|
|
||||||
|
bind() from provider {
|
||||||
|
ApplicationViewModelFactory(
|
||||||
|
this@AtlasApp,
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
instance()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// override fun onCreate() {
|
||||||
|
// super.onCreate()
|
||||||
|
// notificationService.schedulePushNotifications()
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun scheduleNotifications() = notificationService.schedulePushNotifications()
|
||||||
|
|
||||||
|
fun unscheduleNotifications() = notificationService.unschedulePushNotifications()
|
||||||
|
}
|
||||||
@@ -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,9 +12,10 @@ 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.application.AtlasApp
|
||||||
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
|
||||||
@@ -21,7 +24,7 @@ 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 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,15 @@ 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 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 +59,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 +72,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 +81,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 +99,21 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
|||||||
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
|
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
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 {
|
||||||
|
(requireActivity().application as AtlasApp).scheduleNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
@NeedsPermission(ACCESS_COARSE_LOCATION)
|
@NeedsPermission(ACCESS_COARSE_LOCATION)
|
||||||
fun showLocation() {
|
fun showLocation() {
|
||||||
@@ -123,4 +138,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() {
|
||||||
|
(requireActivity().application as AtlasApp).scheduleNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
@@ -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" />
|
||||||
|
|||||||
@@ -9,20 +9,23 @@ 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(flavourModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
val parentModule = Kodein.Module("Parent Module", allowSilentOverride = true) {
|
||||||
import(androidXModule(this@BaseAppClass))
|
import(androidXModule(this@BaseAppClass))
|
||||||
|
|
||||||
bind() from singleton { createNetworkModule() }
|
bind() from singleton { createNetworkModule() }
|
||||||
@@ -35,7 +38,10 @@ 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()) }
|
}
|
||||||
|
|
||||||
|
open val flavourModule = Kodein.Module("Flavour") {
|
||||||
|
import(parentModule)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun createNetworkModule(): WeatherApi
|
abstract fun createNetworkModule(): WeatherApi
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,7 @@
|
|||||||
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:name="com.appttude.h_mal.atlas_weather.application.MonoApp"
|
||||||
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,19 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.application
|
||||||
|
|
||||||
|
import org.kodein.di.generic.bind
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import org.kodein.di.generic.provider
|
||||||
|
|
||||||
|
open class MonoApp : AppClass() {
|
||||||
|
|
||||||
|
override val flavourModule = super.flavourModule.copy {
|
||||||
|
bind() from provider {
|
||||||
|
ApplicationViewModelFactory(
|
||||||
|
this@MonoApp,
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
instance(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,16 @@ package com.appttude.h_mal.monoWeather.ui
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.model.forecast.WeatherDisplay
|
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
||||||
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.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 com.appttude.h_mal.monoWeather.ui.home.adapter.WeatherRecyclerAdapter
|
import com.appttude.h_mal.monoWeather.ui.home.adapter.WeatherRecyclerAdapter
|
||||||
import kotlinx.android.synthetic.main.fragment_home.forecast_listview
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.swipe_refresh
|
|
||||||
|
|
||||||
|
|
||||||
class WorldItemFragment : BaseFragment<WorldViewModel>(R.layout.fragment_home) {
|
class WorldItemFragment : BaseFragment<WorldViewModel>(R.layout.fragment_home) {
|
||||||
@@ -19,6 +21,7 @@ class WorldItemFragment : BaseFragment<WorldViewModel>(R.layout.fragment_home) {
|
|||||||
private var retrievedLocationName: String? = null
|
private var retrievedLocationName: String? = null
|
||||||
|
|
||||||
private lateinit var recyclerAdapter: WeatherRecyclerAdapter
|
private lateinit var recyclerAdapter: WeatherRecyclerAdapter
|
||||||
|
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -35,12 +38,12 @@ class WorldItemFragment : BaseFragment<WorldViewModel>(R.layout.fragment_home) {
|
|||||||
navigateTo(directions)
|
navigateTo(directions)
|
||||||
}
|
}
|
||||||
|
|
||||||
forecast_listview.apply {
|
view.findViewById<RecyclerView>(R.id.forecast_listview).apply {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = recyclerAdapter
|
adapter = recyclerAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
swipe_refresh.apply {
|
swipeRefresh = view.findViewById<SwipeRefreshLayout>(R.id.swipe_refresh).apply {
|
||||||
setOnRefreshListener {
|
setOnRefreshListener {
|
||||||
retrievedLocationName?.let {
|
retrievedLocationName?.let {
|
||||||
viewModel.fetchDataForSingleLocation(it)
|
viewModel.fetchDataForSingleLocation(it)
|
||||||
@@ -57,11 +60,11 @@ class WorldItemFragment : BaseFragment<WorldViewModel>(R.layout.fragment_home) {
|
|||||||
recyclerAdapter.addCurrent(data)
|
recyclerAdapter.addCurrent(data)
|
||||||
}
|
}
|
||||||
super.onSuccess(data)
|
super.onSuccess(data)
|
||||||
swipe_refresh.isRefreshing = false
|
swipeRefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(error: Any?) {
|
override fun onFailure(error: Any?) {
|
||||||
super.onFailure(error)
|
super.onFailure(error)
|
||||||
swipe_refresh.isRefreshing = false
|
swipeRefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,9 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
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.model.forecast.Forecast
|
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
|
||||||
import kotlinx.android.synthetic.main.activity_further_info.*
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,18 +34,18 @@ 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.appendWith(requireContext().getString(R.string.degrees))
|
view.findViewById<TextView>(R.id.maxtemp).text = param1?.mainTemp.appendWith(requireContext().getString(R.string.degrees))
|
||||||
averagetemp.text =
|
view.findViewById<TextView>(R.id.averagetemp).text =
|
||||||
param1?.averageTemp.appendWith(requireContext().getString(R.string.degrees))
|
param1?.averageTemp.appendWith(requireContext().getString(R.string.degrees))
|
||||||
minimumtemp.text =
|
view.findViewById<TextView>(R.id.minimumtemp).text =
|
||||||
param1?.minorTemp.appendWith(requireContext().getString(R.string.degrees))
|
param1?.minorTemp.appendWith(requireContext().getString(R.string.degrees))
|
||||||
windtext.text = param1?.windText.appendWith(" km")
|
view.findViewById<TextView>(R.id.windtext).text = param1?.windText.appendWith(" km")
|
||||||
preciptext.text = param1?.precipitation.appendWith(" %")
|
view.findViewById<TextView>(R.id.preciptext).text = param1?.precipitation.appendWith(" %")
|
||||||
cloudtext.text = param1?.cloud.appendWith(" %")
|
view.findViewById<TextView>(R.id.cloudtext).text = param1?.cloud.appendWith(" %")
|
||||||
humiditytext.text = param1?.humidity.appendWith(" %")
|
view.findViewById<TextView>(R.id.humiditytext).text = param1?.humidity.appendWith(" %")
|
||||||
uvtext.text = param1?.uvi
|
view.findViewById<TextView>(R.id.uvtext).text = param1?.uvi
|
||||||
sunrisetext.text = param1?.sunrise
|
view.findViewById<TextView>(R.id.sunrisetext).text = param1?.sunrise
|
||||||
sunsettext.text = param1?.sunset
|
view.findViewById<TextView>(R.id.sunsettext).text = param1?.sunset
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String?.appendWith(suffix: String): String? {
|
fun String?.appendWith(suffix: String): String? {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ 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.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.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
|
||||||
@@ -19,7 +21,7 @@ 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 com.appttude.h_mal.monoWeather.dialog.PermissionsDeclarationDialog
|
import com.appttude.h_mal.monoWeather.dialog.PermissionsDeclarationDialog
|
||||||
import com.appttude.h_mal.monoWeather.ui.home.adapter.WeatherRecyclerAdapter
|
import com.appttude.h_mal.monoWeather.ui.home.adapter.WeatherRecyclerAdapter
|
||||||
import kotlinx.android.synthetic.main.fragment_home.*
|
|
||||||
import permissions.dispatcher.NeedsPermission
|
import permissions.dispatcher.NeedsPermission
|
||||||
import permissions.dispatcher.OnNeverAskAgain
|
import permissions.dispatcher.OnNeverAskAgain
|
||||||
import permissions.dispatcher.OnPermissionDenied
|
import permissions.dispatcher.OnPermissionDenied
|
||||||
@@ -36,13 +38,14 @@ import permissions.dispatcher.RuntimePermissions
|
|||||||
class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
|
||||||
|
|
||||||
lateinit var recyclerAdapter: WeatherRecyclerAdapter
|
lateinit var recyclerAdapter: WeatherRecyclerAdapter
|
||||||
|
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
|
||||||
@@ -53,7 +56,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
@@ -64,7 +67,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)
|
||||||
@@ -73,7 +76,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) {
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import android.content.Context
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ImageView
|
||||||
|
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.utils.generateView
|
import com.appttude.h_mal.atlas_weather.utils.generateView
|
||||||
import kotlinx.android.synthetic.monoWeather.mono_item_two_cell.view.mono_item_cell
|
|
||||||
import kotlinx.android.synthetic.monoWeather.mono_item_two_cell.view.mono_text_cell
|
|
||||||
|
|
||||||
|
|
||||||
class GridAdapter(
|
class GridAdapter(
|
||||||
@@ -20,8 +22,8 @@ class GridAdapter(
|
|||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
|
|
||||||
return view.apply {
|
return view.apply {
|
||||||
mono_item_cell.setImageResource(item!!.first)
|
findViewById<ImageView>(R.id.mono_item_cell).setImageResource(item!!.first)
|
||||||
mono_text_cell.text = item.second
|
findViewById<TextView>(R.id.mono_text_cell).text = item.second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import android.content.Intent
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -18,8 +19,8 @@ import com.appttude.h_mal.atlas_weather.R
|
|||||||
import com.appttude.h_mal.atlas_weather.utils.displayToast
|
import com.appttude.h_mal.atlas_weather.utils.displayToast
|
||||||
import com.appttude.h_mal.monoWeather.dialog.DeclarationBuilder
|
import com.appttude.h_mal.monoWeather.dialog.DeclarationBuilder
|
||||||
import com.appttude.h_mal.monoWeather.dialog.PermissionsDeclarationDialog
|
import com.appttude.h_mal.monoWeather.dialog.PermissionsDeclarationDialog
|
||||||
import kotlinx.android.synthetic.monoWeather.permissions_declaration_dialog.cancel
|
|
||||||
import kotlinx.android.synthetic.monoWeather.permissions_declaration_dialog.submit
|
|
||||||
import permissions.dispatcher.NeedsPermission
|
import permissions.dispatcher.NeedsPermission
|
||||||
import permissions.dispatcher.OnNeverAskAgain
|
import permissions.dispatcher.OnNeverAskAgain
|
||||||
import permissions.dispatcher.OnPermissionDenied
|
import permissions.dispatcher.OnPermissionDenied
|
||||||
@@ -60,7 +61,7 @@ class WidgetLocationPermissionActivity : AppCompatActivity(), DeclarationBuilder
|
|||||||
movementMethod = LinkMovementMethod.getInstance()
|
movementMethod = LinkMovementMethod.getInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
submit.setOnClickListener {
|
findViewById<Button>(R.id.submit).setOnClickListener {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
showBackgroundLocationWithPermissionCheck()
|
showBackgroundLocationWithPermissionCheck()
|
||||||
} else {
|
} else {
|
||||||
@@ -68,7 +69,7 @@ class WidgetLocationPermissionActivity : AppCompatActivity(), DeclarationBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel.setOnClickListener { finish() }
|
findViewById<Button>(R.id.cancel).setOnClickListener { finish() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun submitWidget() {
|
private fun submitWidget() {
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ package com.appttude.h_mal.monoWeather.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.utils.hideKeyboard
|
import com.appttude.h_mal.atlas_weather.utils.hideKeyboard
|
||||||
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) {
|
||||||
@@ -17,10 +19,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)
|
||||||
|
|
||||||
submit.setOnClickListener {
|
view.findViewById<Button>(R.id.submit).setOnClickListener {
|
||||||
val locationName = location_name_tv.text?.trim()?.toString()
|
val locationNameView = view.findViewById<TextView>(R.id.location_name_tv)
|
||||||
|
val locationName = locationNameView.text?.trim()?.toString()
|
||||||
if (locationName.isNullOrBlank()) {
|
if (locationName.isNullOrBlank()) {
|
||||||
location_name_tv.error = "Location cannot be blank"
|
locationNameView.error = "Location cannot be blank"
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
viewModel.fetchDataForSingleLocationSearch(locationName)
|
viewModel.fetchDataForSingleLocationSearch(locationName)
|
||||||
|
|||||||
@@ -2,17 +2,18 @@ package com.appttude.h_mal.monoWeather.ui.world
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
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.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.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 com.appttude.h_mal.monoWeather.ui.world.WorldFragmentDirections.actionWorldFragmentToWorldItemFragment
|
import com.appttude.h_mal.monoWeather.ui.world.WorldFragmentDirections.actionWorldFragmentToWorldItemFragment
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.android.synthetic.monoWeather.fragment__two.floatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import kotlinx.android.synthetic.monoWeather.fragment__two.world_recycler
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,12 +48,12 @@ class WorldFragment : BaseFragment<WorldViewModel>(R.layout.fragment__two) {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
@@ -58,6 +58,6 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/settings_fragment"
|
android:id="@+id/settings_fragment"
|
||||||
android:name="com.appttude.h_mal.monoWeather.ui.settings.SettingsFragment"
|
android:name="com.appttude.h_mal.monoWeather.ui.settings.SettingsFragment"
|
||||||
android:label="SettingsFragment" />
|
android:label="Settings" />
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
@@ -5,4 +5,6 @@
|
|||||||
|
|
||||||
<color name="colour_one">#E8D0DD</color>
|
<color name="colour_one">#E8D0DD</color>
|
||||||
<color name="colour_four">#8C98AD</color>
|
<color name="colour_four">#8C98AD</color>
|
||||||
|
|
||||||
|
<color name="weather_cell_colour">@android:color/transparent</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -16,7 +16,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)
|
||||||
|
|||||||
19
build.gradle
@@ -1,24 +1,21 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
|
||||||
kotlin_version = '1.5.20'
|
|
||||||
}
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { url "https://www.jitpack.io" }
|
maven { url "https://www.jitpack.io" }
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.4.1")
|
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$NAVIGATION_VERSION"
|
||||||
classpath ('com.android.tools.build:gradle:7.2.2')
|
classpath "com.android.tools.build:gradle:$GRADLE_PLUGIN_VERSION"
|
||||||
classpath ("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_GRADLE_PLUGIN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '7.2.2' apply false
|
id 'com.android.application' version "$GRADLE_PLUGIN_VERSION" apply false
|
||||||
id 'com.android.library' version '7.2.2' apply false
|
id 'com.android.library' version "$GRADLE_PLUGIN_VERSION" apply false
|
||||||
id 'com.google.gms.google-services' version '4.3.15' apply false
|
id 'com.google.gms.google-services' version "$GOOGLE_SERVICES" apply false
|
||||||
id 'androidx.navigation.safeargs.kotlin' version '2.4.0' apply false
|
id 'org.jetbrains.kotlin.android' version "$KOTLIN_VERSION" apply false
|
||||||
id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false
|
id 'com.autonomousapps.dependency-analysis' version "$GRADLE_ANALYZE_VERSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
|
|||||||
@@ -42,4 +42,48 @@ platform :android do
|
|||||||
json_key: "google-play-key.json",
|
json_key: "google-play-key.json",
|
||||||
package_name: "com.appttude.h_mal.atlas_weather")
|
package_name: "com.appttude.h_mal.atlas_weather")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc "Capture screenshots on MonoWeather"
|
||||||
|
lane :screenGrabMonoWeather do
|
||||||
|
build_android_app(
|
||||||
|
task: 'assemble',
|
||||||
|
build_type: 'Debug',
|
||||||
|
flavor: 'MonoWeather',
|
||||||
|
)
|
||||||
|
build_android_app(
|
||||||
|
task: 'assemble',
|
||||||
|
build_type: 'AndroidTest',
|
||||||
|
flavor: 'MonoWeather',
|
||||||
|
)
|
||||||
|
screengrab(
|
||||||
|
app_package_name: "com.appttude.h_mal.monoWeather",
|
||||||
|
locales: ["en-UK"],
|
||||||
|
app_apk_path: "app/build/outputs/apk/monoWeather/debug/app-monoWeather-debug.apk",
|
||||||
|
tests_apk_path: "app/build/outputs/apk/androidTest/monoWeather/debug/app-monoWeather-debug-androidTest.apk",
|
||||||
|
test_instrumentation_runner: "com.appttude.h_mal.atlas_weather.application.TestRunner",
|
||||||
|
use_tests_in_packages: "com.appttude.h_mal.atlas_weather.snapshot"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Capture screenshots on AtlasWeather"
|
||||||
|
lane :screenGrabAtlasWeather do
|
||||||
|
build_android_app(
|
||||||
|
task: 'assemble',
|
||||||
|
build_type: 'Debug',
|
||||||
|
flavor: 'AtlasWeather',
|
||||||
|
)
|
||||||
|
build_android_app(
|
||||||
|
task: 'assemble',
|
||||||
|
build_type: 'AndroidTest',
|
||||||
|
flavor: 'AtlasWeather',
|
||||||
|
)
|
||||||
|
screengrab(
|
||||||
|
app_package_name: "com.appttude.h_mal.atlas_weather",
|
||||||
|
locales: ["en-UK"],
|
||||||
|
app_apk_path: "app/build/outputs/apk/atlasWeather/debug/app-atlasWeather-debug.apk",
|
||||||
|
tests_apk_path: "app/build/outputs/apk/androidTest/atlasWeather/debug/app-atlasWeather-debug-androidTest.apk",
|
||||||
|
test_instrumentation_runner: "com.appttude.h_mal.atlas_weather.application.TestRunner",
|
||||||
|
use_tests_in_packages: "com.appttude.h_mal.atlas_weather.snapshot"
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
56
fastlane/README.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
fastlane documentation
|
||||||
|
----
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Make sure you have the latest version of the Xcode command line tools installed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xcode-select --install
|
||||||
|
```
|
||||||
|
|
||||||
|
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||||
|
|
||||||
|
# Available Actions
|
||||||
|
|
||||||
|
## Android
|
||||||
|
|
||||||
|
### android deployMonoWeather
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane android deployMonoWeather
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy a new Mono Weather version to the Google Play
|
||||||
|
|
||||||
|
### android deployAtlasWeather
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane android deployAtlasWeather
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy a new Atlas Weather version to the Google Play
|
||||||
|
|
||||||
|
### android screenGrabMonoWeather
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane android screenGrabMonoWeather
|
||||||
|
```
|
||||||
|
|
||||||
|
Capture screenshots on MonoWeather
|
||||||
|
|
||||||
|
### android screenGrabAtlasWeather
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane android screenGrabAtlasWeather
|
||||||
|
```
|
||||||
|
|
||||||
|
Capture screenshots on AtlasWeather
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||||
|
|
||||||
|
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||||
|
|
||||||
|
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||||
@@ -9,11 +9,67 @@
|
|||||||
|
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
android.enableJetifier=true
|
|
||||||
android.useAndroidX=true
|
|
||||||
org.gradle.jvmargs=-Xmx1536m
|
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
|
android.nonTransitiveRClass=false
|
||||||
|
android.nonFinalResIds=false
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Plugin versions
|
||||||
|
ANDROID_CORE = 1.13.1
|
||||||
|
CUSTOM_VIEW = 1.1.0
|
||||||
|
CARD_VIEW = 1.0.0
|
||||||
|
FRAGMENT_VERSION = 1.8.0
|
||||||
|
MATERIAL_VERSION = 1.12.0
|
||||||
|
APP_COMPAT = 1.7.0
|
||||||
|
CONSTR_LAYOUT_VERSION = 2.1.4
|
||||||
|
ANDROID_LIFECYCLE = 2.8.2
|
||||||
|
RECYCLER_VIEW = 1.3.2
|
||||||
|
SWIPE_REFRESH = 1.1.0
|
||||||
|
PERMISSIONS_DISPATCHER = 4.9.2
|
||||||
|
TOMTOM_SEARCH = 2.4771
|
||||||
|
TOMTOM_MAP = 2.4807
|
||||||
|
NAVIGATION_VERSION = 2.7.7
|
||||||
|
PREFERENCES_VERSION = 1.2.1
|
||||||
|
RETROFIT_VERSION = 2.9.0
|
||||||
|
OKHTTP_VERSION = 4.9.0
|
||||||
|
MOKITO_INLINE_VERSION = 2.13.0
|
||||||
|
CORE_TEST_VERSION = 2.2.0
|
||||||
|
MOCKK_VERSION = 1.10.5
|
||||||
|
TEST_JUNIT_VERSION = 1.2.0
|
||||||
|
TEST_RUNNER_VERSION = 1.5.2
|
||||||
|
ESPRESSO_VERSION = 3.6.0
|
||||||
|
HAMCREST_VERSION = 2.2
|
||||||
|
JUNIT_VERSION = 4.13.2
|
||||||
|
KODEIN_VERSION = 6.2.1
|
||||||
|
ROOM_VERSION = 2.6.1
|
||||||
|
KOTLINX_COROUTINES = 1.6.1
|
||||||
|
TEST_KTX_VERSION = 1.6.0
|
||||||
|
ANDROIDX_TEST = 1.6.0
|
||||||
|
TEST_MONITOR = 1.7.0
|
||||||
|
GOOGLE_PLAY_SERVICE = 21.3.0
|
||||||
|
GOOGLE_SERVICES = 4.3.15
|
||||||
|
GSON = 2.10.1
|
||||||
|
GUAVA = 33.2.1-android
|
||||||
|
ANDROID_LIBRARY = 8.5.0
|
||||||
|
ANDROID_APPLICATION = 8.5.0
|
||||||
|
GRADLE_PLUGIN_VERSION = 8.5.0
|
||||||
|
KOTLIN_VERSION = 2.0.0
|
||||||
|
KOTLIN_GRADLE_PLUGIN = 1.6.21
|
||||||
|
GRADLE_ANALYZE_VERSION = 1.20.0
|
||||||
|
|
||||||
|
# Android configuration
|
||||||
|
TARGET_SDK_VERSION = 34
|
||||||
|
MIN_SDK_VERSION = 26
|
||||||
|
|
||||||
|
# Gradle parameters
|
||||||
|
org.gradle.jvmargs = -Xmx1536m
|
||||||
|
|
||||||
|
# AndroidX
|
||||||
|
android.useAndroidX = true
|
||||||
|
android.enableJetifier = true
|
||||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Wed Jul 26 10:14:37 BST 2023
|
#Tue Jun 25 17:02:21 BST 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
64
readme.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Weather-apps
|
||||||
|
|
||||||
|
Weather-apps contains two weather apps - Atlas weather and Mono weather. They are both simple and user-friendly Android applications that provides current weather information and forecasts. With a sleek design and accurate data, to keeps you updated on the latest weather conditions in your area.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Current Weather**: Get real-time weather updates including temperature, humidity, wind speed, and atmospheric pressure.
|
||||||
|
- **Forecast**: View detailed weather forecasts for the next 7 days.
|
||||||
|
- **Location-Based Updates**: Automatically fetch weather data based on your current location.
|
||||||
|
- **Search Functionality**: Search for weather information in different cities around the world.
|
||||||
|
- **Notifications**: Receive weather alerts and notifications for significant weather changes.
|
||||||
|
- **Customizable Settings**: Choose between Celsius and Fahrenheit, and set your preferred update frequency.
|
||||||
|
- **Customizable Home screen widget**: Add a home screen widget to give you regular updates on forecast.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
### Atlas Weather
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
### Mono Weather
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Upon launching the app, you will be prompted to allow location access. Grant the necessary permissions.
|
||||||
|
2. The home screen will display the current weather information for your location.
|
||||||
|
3. Swipe left or tap on the forecast tab to view the 7-day weather forecast.
|
||||||
|
4. Use the search icon to look up weather information for other cities.
|
||||||
|
5. Access the settings menu to customize your preferences.
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
The app requires the following permissions:
|
||||||
|
|
||||||
|
- **Location**: To provide accurate weather information based on your current location.
|
||||||
|
- **Internet**: To fetch weather data from the server.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
Weather-apps
|
||||||
|
- uses the [OpenWeatherMap API](https://openweathermap.org/api) to retrieve weather data.
|
||||||
|
- uses the [TomTom Search API](https://developer.tomtom.com/search-api/documentation/product-information/introduction) to retrieve geolocation data.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please follow these steps:
|
||||||
|
|
||||||
|
1. Fork the repository.
|
||||||
|
2. Create a new branch: `git checkout -b feature/your-feature-name`
|
||||||
|
3. Make your changes and commit them: `git commit -m 'Add some feature'`
|
||||||
|
4. Push to the branch: `git push origin feature/your-feature-name`
|
||||||
|
5. Create a pull request.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
- [OpenWeatherMap](https://openweathermap.org) for the weather data API.
|
||||||
|
- [TomTom Search API](https://developer.tomtom.com/search-api/documentation/product-information/introduction) for the geolocation API.
|
||||||
BIN
screenshots/atlas/forecast.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
screenshots/atlas/home.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
screenshots/atlas/settings.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
screenshots/mono/forecast.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
screenshots/mono/home.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
screenshots/mono/settings.png
Normal file
|
After Width: | Height: | Size: 12 KiB |