diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..603b1407
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 00000000..ae78c113
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 00000000..169fd0dd
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 00000000..7f68460d
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index b3fe3b4f..8cf1b4b8 100644
--- a/README.md
+++ b/README.md
@@ -1,84 +1,21 @@
-# Work sample - Application developer
+# Weather Application - Android
-## Assignment
+## About
+- Simple android to show weather for your current location.
+- App is developed in kotlin, demonstrating the use of RxJava2, Retrofit, Dagger2 and implementing MVVM with LiveData.
-- Build an awesome weather app that shows the weather at your current location.
-- Code it for the platform (Android, iOS, web) you applied for or the one you prefer.
-
-## Minimum requirements
-- Show the weather for your current location
-- Use data from provided API (see API DOCS)
-- Good usability
-- Be visually appealing and follow platform conventions
-- Budget: 8-12 hours
-
-## Assets
-You can find some icon assets at [https://github.com/erikflowers/weather-icons](https://github.com/erikflowers/weather-icons) that you can use if you please. You are more than welcome to use other assets that you feel are better fitted for your solution. We do not expect you to be a designer, but since application development on Daresay most often includes visual interfaces we do expect you to to be well aware of your platforms design guidelines and conventions.
-
-## Expectations
-###### User experience
-The feature of the app is simple but we expect you to deliver a solution with a high user experience. Imagine this application is to be used by real user with real needs. Maybe your application won’t be as feature-full as many other applications on the market, but at least make it interesting, fun and intuitive to use. Of course you are allowed to extend your applications functionality. We appreciate solutions that exceed our expectations.
-
-###### Code
-We expect that the code is of high quality and under source control. Expect the solution to be continuously worked on by other developers and should therefore be easy to understand, adjust and extend. True beauty starts on the inside!
-
-## Delivery
-Fork the repository, code in your fork and make a pull request when done. Also send us an e-mail to let us know when you are done!
-
-### Good luck!
-
----
-
-
-# API DOCS
+## General flow of data
+- Check if the current location is set for the app, if not set then retrieve lat-long of the user location.
+- If the location is set, show the weather for the current location.
+- Show weather forecast for the current day and forecast the entire week.
+- Show weather details for the selected day of the week.
## Base url
http://worksample-api.herokuapp.com
-
-
-http://worksample-api.herokuapp.com is a simple wrapper of some of the endpoints provided on http://openweathermap.org/.
-
-## Available Endpoints and Documentation
-- http://worksample-api.herokuapp.com/weather [documentation](http://openweathermap.org/current)
-- http://worksample-api.herokuapp.com/forecast [documentation](http://openweathermap.org/forecast5)
-- http://worksample-api.herokuapp.com/forecast/daily [documentation](http://openweathermap.org/forecast16)
-
-We always respond in JSON and metrics.
-We also don’t support these features of the OpenWeatherMap API:
-
-- Bulk downloading
-- Search Accuracy (like/accuracy)
-- Limitation of result
-- Units format
-- Multilingual support
-- Callback functions for javascript.
-
-
-## You can use these parameters
+- It is a simple wrapper of some of the endpoints provided on http://openweathermap.org/.
#### API key (mandatory)
-The API-key is required for all API calls. It should have been sent to you together with the instructions asking you to do the work sample.
-
-#### By city name:
-City name and country code divided by comma, use ISO 3166 country codes.
-
-`?q={city name},{country code}`
-
-`?q={city name}`
-
-#### By city id:
-List of city ID:s can be downloaded [here](http://bulk.openweathermap.org/sample/)
-
-`?id={id}`
-
-#### By geographic coordinates:
-Coordinates of the location of your interest
-
-`?lat={lat}&lon={lon}`
-
-#### By Zip Code
-`?zip={zip code},{country code}`
+The API-key is required for all API calls.
##An example request:
-
[http://worksample-api.herokuapp.com/weather?q=Stockholm,SE&key={API_KEY}](http://worksample-api.herokuapp.com/weather?q=Stockholm,SE&key={API_KEY})
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 00000000..25d5fb05
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,117 @@
+apply plugin: 'com.android.application'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'kotlin-kapt'
+
+android {
+ // uses the variables like compile_sdk_version to keep the dependencies same throughout the application
+ compileSdkVersion rootProject.compile_sdk_version
+ buildToolsVersion rootProject.build_tool_version
+
+ defaultConfig {
+ applicationId "com.sneha.weatherapp"
+ minSdkVersion rootProject.ext.min_sdk_version
+ targetSdkVersion rootProject.ext.target_sdk_version
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ multiDexEnabled true
+ }
+
+ buildTypes {
+ debug {
+ buildConfigField("String", "BASE_URL", "\"https://worksample-api.herokuapp.com/\"")
+ buildConfigField("String", "API_KEY", "\"\"")
+ }
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+// buildConfigField("String", "BASE_URL", "\"https://worksample-api.herokuapp.com/\"")
+// buildConfigField("String", "API_KEY", "\"HBNA19QVVAPP\"")
+ }
+ }
+}
+
+dependencies {
+ implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+ //JARs
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ // Android Support Library
+ implementation "androidx.appcompat:appcompat:$app_compat_version"
+
+ // Constraint Layout
+ implementation "androidx.constraintlayout:constraintlayout:$constraint_version"
+
+ // Material Design
+ implementation "com.google.android.material:material:$support_library_version"
+
+ // RecyclerView
+ implementation 'androidx.recyclerview:recyclerview:1.1.0'
+
+ // CardView
+ implementation "androidx.cardview:cardview:$support_library_version"
+
+ // ViewModel and LiveData
+ implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
+
+ // Lifecycle Annotations
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
+ kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
+
+ // Multidex
+ implementation "androidx.multidex:multidex:2.0.1"
+
+ // Dagger
+ implementation "com.google.dagger:dagger:$dagger_version"
+ kapt "com.google.dagger:dagger-compiler:$dagger_version"
+
+ // Database
+ implementation "androidx.room:room-runtime:$room_version"
+ kapt "androidx.room:room-compiler:$room_version"
+ implementation "androidx.room:room-rxjava2:$room_version"
+
+ // Networking
+ implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
+ implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
+ implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
+ implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
+ implementation "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0"
+
+ // Reactive
+ implementation "io.reactivex.rxjava2:rxjava:$rootProject.rxjava_version"
+ implementation "io.reactivex.rxjava2:rxandroid:$rootProject.rxandroid_version"
+
+ // logger
+ implementation "com.jakewharton.timber:timber:$timber_version"
+
+ // Parser
+ implementation "com.google.code.gson:gson:$gson_version"
+
+ //Gps
+ implementation 'com.google.android.gms:play-services-location:17.0.0'
+
+ // Local Unit tests
+ testImplementation "junit:junit:$junit_version"
+ testImplementation "org.mockito:mockito-core:$mockito_version"
+ kaptTest "com.google.dagger:dagger-compiler:$dagger_version"
+ testImplementation "android.arch.core:core-testing:$core_testing_version"
+ testImplementation "org.hamcrest:hamcrest-library:$hamcrest_version"
+
+ // UI Testing
+ androidTestImplementation "androidx.test:runner:$test_runner_version"
+ androidTestImplementation "androidx.test.ext:junit:$test_ext_runner_version"
+ androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
+ androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
+ androidTestImplementation "org.mockito:mockito-core:$mockito_version"
+ kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version"
+
+ implementation "androidx.swiperefreshlayout:swiperefreshlayout:$refresh_layout_version"
+ implementation "com.google.code.gson:gson:$gson_version"
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/com/sneha/weatherapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/sneha/weatherapp/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..495c32a9
--- /dev/null
+++ b/app/src/androidTest/java/com/sneha/weatherapp/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.sneha.weatherapp
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.sneha.weatherapp", appContext.packageName)
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..000853eb
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/WeatherApplication.kt b/app/src/main/java/com/sneha/weatherapp/WeatherApplication.kt
new file mode 100644
index 00000000..12e15044
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/WeatherApplication.kt
@@ -0,0 +1,23 @@
+package com.sneha.weatherapp
+
+import android.app.Application
+import com.sneha.weatherapp.di.component.ApplicationComponent
+import com.sneha.weatherapp.di.component.DaggerApplicationComponent
+import com.sneha.weatherapp.di.module.ApplicationModule
+
+class WeatherApplication : Application() {
+ lateinit var applicationComponent: ApplicationComponent
+
+ override fun onCreate() {
+ super.onCreate()
+ injectDependencies()
+ }
+
+ private fun injectDependencies() {
+ applicationComponent = DaggerApplicationComponent
+ .builder()
+ .applicationModule(ApplicationModule(this))
+ .build()
+ applicationComponent.inject(this)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/local/db/DatabaseService.kt b/app/src/main/java/com/sneha/weatherapp/data/local/db/DatabaseService.kt
new file mode 100644
index 00000000..b9cc589c
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/local/db/DatabaseService.kt
@@ -0,0 +1,20 @@
+package com.sneha.weatherapp.data.local.db
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import com.sneha.weatherapp.data.local.db.dao.DummyDao
+import com.sneha.weatherapp.data.local.db.entity.DummyEntity
+import javax.inject.Singleton
+
+@Singleton
+@Database(
+ entities = [
+ DummyEntity::class
+ ],
+ exportSchema = false,
+ version = 1
+)
+abstract class DatabaseService : RoomDatabase() {
+
+ abstract fun dummyDao(): DummyDao
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/local/db/dao/DummyDao.kt b/app/src/main/java/com/sneha/weatherapp/data/local/db/dao/DummyDao.kt
new file mode 100644
index 00000000..3962b06c
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/local/db/dao/DummyDao.kt
@@ -0,0 +1,20 @@
+package com.sneha.weatherapp.data.local.db.dao
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+import com.sneha.weatherapp.data.local.db.entity.DummyEntity
+
+@Dao
+interface DummyDao {
+
+ @Query("SELECT * FROM dummy_entity")
+ fun getAll(): List
+
+ @Insert
+ fun insert(entity: DummyEntity)
+
+ @Delete
+ fun delete(entity: DummyEntity)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/local/db/entity/DummyEntity.kt b/app/src/main/java/com/sneha/weatherapp/data/local/db/entity/DummyEntity.kt
new file mode 100644
index 00000000..108041dd
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/local/db/entity/DummyEntity.kt
@@ -0,0 +1,18 @@
+package com.sneha.weatherapp.data.local.db.entity
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import org.jetbrains.annotations.NotNull
+
+@Entity(tableName = "dummy_entity")
+data class DummyEntity(
+
+ @PrimaryKey(autoGenerate = true)
+ @NotNull
+ val id: Long,
+
+ @ColumnInfo(name = "time")
+ @NotNull
+ val name: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/local/prefs/UserPreferences.kt b/app/src/main/java/com/sneha/weatherapp/data/local/prefs/UserPreferences.kt
new file mode 100644
index 00000000..991c24c9
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/local/prefs/UserPreferences.kt
@@ -0,0 +1,66 @@
+package com.sneha.weatherapp.data.local.prefs
+
+import android.content.SharedPreferences
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class UserPreferences @Inject constructor(private val prefs: SharedPreferences) {
+
+ companion object {
+ const val KEY_USER_ID = "PREF_KEY_USER_ID"
+ const val KEY_USER_NAME = "PREF_KEY_USER_NAME"
+ const val KEY_USER_EMAIL = "PREF_KEY_USER_EMAIL"
+ const val KEY_ACCESS_TOKEN = "PREF_KEY_ACCESS_TOKEN"
+ const val KEY_LATITUDE = "PREF_KEY_LATITUDE"
+ const val KEY_LONGITUDE = "PREF_KEY_LONGITUDE"
+ }
+
+ fun getUserId(): String? =
+ prefs.getString(KEY_USER_ID, null)
+
+ fun setUserId(userId: String) =
+ prefs.edit().putString(KEY_USER_ID, userId).apply()
+
+ fun removeUserId() =
+ prefs.edit().remove(KEY_USER_ID).apply()
+
+ fun getUserName(): String? =
+ prefs.getString(KEY_USER_NAME, null)
+
+ fun setUserName(userName: String) =
+ prefs.edit().putString(KEY_USER_NAME, userName).apply()
+
+ fun removeUserName() =
+ prefs.edit().remove(KEY_USER_NAME).apply()
+
+ fun getUserEmail(): String? =
+ prefs.getString(KEY_USER_EMAIL, null)
+
+ fun setUserEmail(email: String) =
+ prefs.edit().putString(KEY_USER_EMAIL, email).apply()
+
+ fun removeUserEmail() =
+ prefs.edit().remove(KEY_USER_EMAIL).apply()
+
+ fun getAccessToken(): String? =
+ prefs.getString(KEY_ACCESS_TOKEN, null)
+
+ fun setAccessToken(token: String) =
+ prefs.edit().putString(KEY_ACCESS_TOKEN, token).apply()
+
+ fun removeAccessToken() =
+ prefs.edit().remove(KEY_ACCESS_TOKEN).apply()
+
+ fun getLatitude(): String? =
+ prefs.getString(KEY_LATITUDE, null)
+
+ fun setLatitude(latitude: String) =
+ prefs.edit().putString(KEY_LATITUDE, latitude).apply()
+
+ fun getLongitude(): String? =
+ prefs.getString(KEY_LONGITUDE, null)
+
+ fun setLongitude(longitude: String) =
+ prefs.edit().putString(KEY_LONGITUDE, longitude).apply()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/model/Forecast.kt b/app/src/main/java/com/sneha/weatherapp/data/model/Forecast.kt
new file mode 100644
index 00000000..b7625249
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/model/Forecast.kt
@@ -0,0 +1,27 @@
+package com.sneha.weatherapp.data.model
+
+import com.google.gson.annotations.SerializedName
+import java.io.Serializable
+
+data class Forecast(
+ @SerializedName("dt")
+ val dt: Long,
+
+ @SerializedName("weather")
+ val weather: List,
+
+ @SerializedName("temp")
+ val temp: Temp,
+
+ @SerializedName("humidity")
+ val humidity: Int,
+
+ @SerializedName("pressure")
+ val pressure: Int,
+
+ @SerializedName("sunrise")
+ val sunrise: Long,
+
+ @SerializedName("sunset")
+ val sunset: Long
+): Serializable
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/model/ForecastItem.kt b/app/src/main/java/com/sneha/weatherapp/data/model/ForecastItem.kt
new file mode 100644
index 00000000..61ae404e
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/model/ForecastItem.kt
@@ -0,0 +1,17 @@
+package com.sneha.weatherapp.data.model
+
+import com.google.gson.annotations.SerializedName
+
+data class ForecastItem(
+ @SerializedName("main")
+ val main: Weather.MainData,
+
+ @SerializedName("weather")
+ val weather: List,
+
+ @SerializedName("dt")
+ val dt: Long,
+
+ @SerializedName("dt_txt")
+ val dt_txt: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/model/LocationData.kt b/app/src/main/java/com/sneha/weatherapp/data/model/LocationData.kt
new file mode 100644
index 00000000..61952422
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/model/LocationData.kt
@@ -0,0 +1,6 @@
+package com.sneha.weatherapp.data.model
+
+data class LocationData(
+ val longitude: Double?,
+ val latitude: Double?
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/model/Temp.kt b/app/src/main/java/com/sneha/weatherapp/data/model/Temp.kt
new file mode 100644
index 00000000..42f14190
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/model/Temp.kt
@@ -0,0 +1,12 @@
+package com.sneha.weatherapp.data.model
+
+import com.google.gson.annotations.SerializedName
+
+
+data class Temp(
+ @SerializedName("min")
+ val min: Double,
+
+ @SerializedName("max")
+ val max: Double
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/model/User.kt b/app/src/main/java/com/sneha/weatherapp/data/model/User.kt
new file mode 100644
index 00000000..c20d4365
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/model/User.kt
@@ -0,0 +1,27 @@
+package com.sneha.weatherapp.data.model
+
+import com.google.gson.annotations.Expose
+import com.google.gson.annotations.SerializedName
+
+data class User(
+
+ @Expose
+ @SerializedName("userId")
+ val id: String,
+
+ @Expose
+ @SerializedName("userName")
+ val name: String,
+
+ @Expose
+ @SerializedName("userEmail")
+ val email: String,
+
+ @Expose
+ @SerializedName("accessToken")
+ val accessToken: String,
+
+ @Expose
+ @SerializedName("profilePicUrl")
+ val profilePicUrl: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/model/Weather.kt b/app/src/main/java/com/sneha/weatherapp/data/model/Weather.kt
new file mode 100644
index 00000000..cc0f8433
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/model/Weather.kt
@@ -0,0 +1,117 @@
+package com.sneha.weatherapp.data.model
+
+import com.google.gson.annotations.SerializedName
+
+data class Weather(
+ @SerializedName("coord")
+ val coordinates: Coordinates,
+
+ @SerializedName("weather")
+ val weather: List,
+
+ @SerializedName("base")
+ val base: String,
+
+ @SerializedName("main")
+ val main: MainData,
+
+ @SerializedName("visibility")
+ val visibility: Double,
+
+ @SerializedName("wind")
+ val wind: Wind,
+
+ @SerializedName("clouds")
+ val clouds: Clouds,
+
+ @SerializedName("dt")
+ val dt: Long,
+
+ @SerializedName("sys")
+ val sys: Sys,
+
+ @SerializedName("timezone")
+ val timezone: Double,
+
+ @SerializedName("id")
+ val id: Double,
+
+ @SerializedName("name")
+ val cityName: String,
+
+ @SerializedName("cod")
+ val cod: Int
+) {
+
+ data class Coordinates(
+ @SerializedName("lon")
+ val longitude: Double,
+
+ @SerializedName("lat")
+ val latitude: Double
+ )
+
+ data class WeatherData(
+ @SerializedName("id")
+ val id: Int,
+
+ @SerializedName("main")
+ val main: String,
+
+ @SerializedName("description")
+ val description: String,
+
+ @SerializedName("icon")
+ val icon: String
+ )
+
+ data class MainData(
+ @SerializedName("temp")
+ val temperature: Double,
+
+ @SerializedName("feels_like")
+ val feelsLike: Double,
+
+ @SerializedName("temp_min")
+ val temperatureMin: Double,
+
+ @SerializedName("temp_max")
+ val temperatureMax: Double,
+
+ @SerializedName("pressure")
+ val pressure: Int,
+
+ @SerializedName("humidity")
+ val humidity: Int
+ )
+
+ data class Wind(
+ @SerializedName("speed")
+ val speed: Double,
+
+ @SerializedName("deg")
+ val degree: Double
+ )
+
+ data class Sys(
+ @SerializedName("type")
+ val type: Int,
+
+ @SerializedName("id")
+ val id: Int,
+
+ @SerializedName("country")
+ val country: String,
+
+ @SerializedName("sunrise")
+ val sunrise: Long,
+
+ @SerializedName("sunset")
+ val sunset: Long
+ )
+
+ data class Clouds(
+ @SerializedName("all")
+ val all: Int
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/remote/Endpoints.kt b/app/src/main/java/com/sneha/weatherapp/data/remote/Endpoints.kt
new file mode 100644
index 00000000..c94e727d
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/remote/Endpoints.kt
@@ -0,0 +1,8 @@
+package com.sneha.weatherapp.data.remote
+
+object Endpoints {
+
+ const val WEATHER = "weather"
+ const val FORECAST = "forecast"
+ const val FORECAST_DAILY = "forecast/daily"
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/remote/NetworkService.kt b/app/src/main/java/com/sneha/weatherapp/data/remote/NetworkService.kt
new file mode 100644
index 00000000..64cf52f4
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/remote/NetworkService.kt
@@ -0,0 +1,36 @@
+package com.sneha.weatherapp.data.remote
+
+import com.sneha.weatherapp.data.model.Weather
+import com.sneha.weatherapp.data.remote.response.DailyForecastResponse
+import com.sneha.weatherapp.data.remote.response.TodayForecastResponse
+import io.reactivex.Single
+import retrofit2.http.GET
+import retrofit2.http.Query
+import javax.inject.Singleton
+
+@Singleton
+interface NetworkService {
+
+ @GET(Endpoints.WEATHER)
+ fun getWeatherData(
+ @Query("lat") latitude: String?,
+ @Query("lon") longitude: String?,
+ @Query(Networking.HEADER_API_KEY) apiKey: String = Networking.API_KEY // default value set when Networking create is called
+ ): Single
+
+ @GET(Endpoints.FORECAST)
+ fun getTodayForecast(
+ @Query("lat") latitude: String?,
+ @Query("lon") longitude: String?,
+ @Query("cnt") count: Int,
+ @Query(Networking.HEADER_API_KEY) apiKey: String = Networking.API_KEY
+ ): Single
+
+ @GET(Endpoints.FORECAST_DAILY)
+ fun getDailyForecast(
+ @Query("lat") latitude: String?,
+ @Query("lon") longitude: String?,
+ @Query("cnt") count: Int,
+ @Query(Networking.HEADER_API_KEY) apiKey: String = Networking.API_KEY
+ ): Single
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/remote/Networking.kt b/app/src/main/java/com/sneha/weatherapp/data/remote/Networking.kt
new file mode 100644
index 00000000..fa3a9deb
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/remote/Networking.kt
@@ -0,0 +1,43 @@
+package com.sneha.weatherapp.data.remote
+
+import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
+import com.sneha.weatherapp.BuildConfig
+import okhttp3.Cache
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import java.io.File
+import java.util.concurrent.TimeUnit
+
+object Networking {
+
+ const val HEADER_API_KEY = "key"
+
+ private const val NETWORK_CALL_TIMEOUT = 60
+ internal lateinit var API_KEY: String
+// const val HEADER_ACCESS_TOKEN = "x-access-token"
+// const val HEADER_USER_ID = "x-user-id"
+
+ fun create(apiKey: String, baseUrl: String, cacheDir: File, cacheSize: Long): NetworkService {
+ API_KEY = apiKey
+ return Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .client(
+ OkHttpClient.Builder()
+ .cache(Cache(cacheDir, cacheSize))
+ .addInterceptor(HttpLoggingInterceptor()
+ .apply {
+ level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY
+ else HttpLoggingInterceptor.Level.NONE
+ })
+ .readTimeout(NETWORK_CALL_TIMEOUT.toLong(), TimeUnit.SECONDS)
+ .writeTimeout(NETWORK_CALL_TIMEOUT.toLong(), TimeUnit.SECONDS)
+ .build()
+ )
+ .addConverterFactory(GsonConverterFactory.create())
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .build()
+ .create(NetworkService::class.java)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/remote/request/DummyRequest.kt b/app/src/main/java/com/sneha/weatherapp/data/remote/request/DummyRequest.kt
new file mode 100644
index 00000000..a8b948c0
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/remote/request/DummyRequest.kt
@@ -0,0 +1,10 @@
+package com.sneha.weatherapp.data.remote.request
+
+import com.google.gson.annotations.Expose
+import com.google.gson.annotations.SerializedName
+
+data class DummyRequest(
+ @Expose
+ @SerializedName("id")
+ var id: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/remote/response/DailyForecastResponse.kt b/app/src/main/java/com/sneha/weatherapp/data/remote/response/DailyForecastResponse.kt
new file mode 100644
index 00000000..73cca59b
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/remote/response/DailyForecastResponse.kt
@@ -0,0 +1,15 @@
+package com.sneha.weatherapp.data.remote.response
+
+import com.google.gson.annotations.SerializedName
+import com.sneha.weatherapp.data.model.Forecast
+
+/**
+ * Daily forecast response object
+ */
+data class DailyForecastResponse(
+ @SerializedName("cod")
+ val cod: Int,
+
+ @SerializedName("list")
+ val forecastList: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/remote/response/TodayForecastResponse.kt b/app/src/main/java/com/sneha/weatherapp/data/remote/response/TodayForecastResponse.kt
new file mode 100644
index 00000000..9380556d
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/remote/response/TodayForecastResponse.kt
@@ -0,0 +1,13 @@
+package com.sneha.weatherapp.data.remote.response
+
+import com.google.gson.annotations.SerializedName
+import com.sneha.weatherapp.data.model.ForecastItem
+
+/**
+ * Today forecast response object
+ */
+data class TodayForecastResponse(
+
+ @SerializedName("list")
+ val forecastList: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/repository/UserRepository.kt b/app/src/main/java/com/sneha/weatherapp/data/repository/UserRepository.kt
new file mode 100644
index 00000000..d0f3dd05
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/repository/UserRepository.kt
@@ -0,0 +1,56 @@
+package com.sneha.weatherapp.data.repository
+
+import com.sneha.weatherapp.data.local.prefs.UserPreferences
+import com.sneha.weatherapp.data.model.LocationData
+import com.sneha.weatherapp.data.model.User
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class UserRepository @Inject constructor(
+// private val networkService: NetworkService,
+// private val databaseService: DatabaseService,
+ private val userPreferences: UserPreferences
+) {
+
+ fun saveCurrentUser(user: User) {
+ userPreferences.setUserId(user.id)
+ userPreferences.setUserName(user.name)
+ userPreferences.setUserEmail(user.email)
+ userPreferences.setAccessToken(user.accessToken)
+ }
+
+ fun removeCurrentUser() {
+ userPreferences.removeUserId()
+ userPreferences.removeUserName()
+ userPreferences.removeUserEmail()
+ userPreferences.removeAccessToken()
+ }
+
+ fun getCurrentUser(): User? {
+
+ val userId = userPreferences.getUserId()
+ val userName = userPreferences.getUserName()
+ val userEmail = userPreferences.getUserEmail()
+ val accessToken = userPreferences.getAccessToken()
+
+ return if (userId !== null && userName != null && userEmail != null && accessToken != null)
+ User(userId, userName, userEmail, accessToken)
+ else
+ null
+ }
+
+ fun saveLocationData(locationData: LocationData){
+ userPreferences.setLatitude(locationData.latitude.toString())
+ userPreferences.setLongitude(locationData.longitude.toString())
+ }
+
+ fun getLocationData() : LocationData? {
+ val latitude = userPreferences.getLatitude()
+ val longitude = userPreferences.getLongitude()
+ return if(!latitude.isNullOrEmpty() && !longitude.isNullOrEmpty()) {
+ LocationData(longitude.toDouble(), latitude.toDouble())
+ }else
+ null
+ }
+ }
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/data/repository/WeatherRepository.kt b/app/src/main/java/com/sneha/weatherapp/data/repository/WeatherRepository.kt
new file mode 100644
index 00000000..39b6ece7
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/data/repository/WeatherRepository.kt
@@ -0,0 +1,30 @@
+package com.sneha.weatherapp.data.repository
+
+import com.sneha.weatherapp.data.local.db.DatabaseService
+import com.sneha.weatherapp.data.local.prefs.UserPreferences
+import com.sneha.weatherapp.data.model.LocationData
+import com.sneha.weatherapp.data.model.Weather
+import com.sneha.weatherapp.data.remote.NetworkService
+import com.sneha.weatherapp.data.remote.response.DailyForecastResponse
+import com.sneha.weatherapp.data.remote.response.TodayForecastResponse
+import io.reactivex.Single
+import javax.inject.Inject
+
+class WeatherRepository @Inject constructor(
+ private val networkService: NetworkService,
+ private val databaseService: DatabaseService,
+ private val userPreferences: UserPreferences
+) {
+
+ fun getLocationData () : LocationData = LocationData(userPreferences.getLongitude()?.toDouble(),
+ userPreferences.getLatitude()?.toDouble())
+
+ fun fetchWeatherData(): Single =
+ networkService.getWeatherData(userPreferences.getLatitude(), userPreferences.getLongitude())
+
+ fun fetchTodayForecast(): Single =
+ networkService.getTodayForecast(userPreferences.getLatitude(),userPreferences.getLongitude(), 7)
+
+ fun fetchDailyForecast(): Single =
+ networkService.getDailyForecast(userPreferences.getLatitude(),userPreferences.getLongitude(), 7)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/Qualifiers.kt b/app/src/main/java/com/sneha/weatherapp/di/Qualifiers.kt
new file mode 100644
index 00000000..0fa1212d
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/Qualifiers.kt
@@ -0,0 +1,11 @@
+package com.sneha.weatherapp.di
+
+import javax.inject.Qualifier
+
+@Qualifier
+@Retention(AnnotationRetention.SOURCE)
+annotation class ApplicationContext
+
+@Qualifier
+@Retention(AnnotationRetention.SOURCE)
+annotation class ActivityContext
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/Scopes.kt b/app/src/main/java/com/sneha/weatherapp/di/Scopes.kt
new file mode 100644
index 00000000..0358adb1
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/Scopes.kt
@@ -0,0 +1,16 @@
+package com.sneha.weatherapp.di
+
+import javax.inject.Scope
+
+@Scope
+@Retention(AnnotationRetention.SOURCE)
+annotation class ActivityScope
+
+
+@Scope
+@Retention(AnnotationRetention.SOURCE)
+annotation class FragmentScope
+
+@Scope
+@Retention(AnnotationRetention.SOURCE)
+annotation class ViewModelScope
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/component/ActivityComponent.kt b/app/src/main/java/com/sneha/weatherapp/di/component/ActivityComponent.kt
new file mode 100644
index 00000000..a01c018f
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/component/ActivityComponent.kt
@@ -0,0 +1,23 @@
+package com.sneha.weatherapp.di.component
+
+import com.sneha.weatherapp.di.ActivityScope
+import com.sneha.weatherapp.di.module.ActivityModule
+import com.sneha.weatherapp.ui.location.LocationActivity
+import com.sneha.weatherapp.ui.weather.WeatherActivity
+import com.sneha.weatherapp.ui.splash.SplashActivity
+import com.sneha.weatherapp.ui.weather.SelectedDayForecastActivity
+import dagger.Component
+
+@ActivityScope
+@Component(
+ dependencies = [ApplicationComponent::class],
+ modules = [ActivityModule::class]
+)
+interface ActivityComponent {
+
+ fun inject(activity: SplashActivity)
+
+ fun inject(activity: WeatherActivity)
+
+ fun inject(activity: SelectedDayForecastActivity)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/component/ApplicationComponent.kt b/app/src/main/java/com/sneha/weatherapp/di/component/ApplicationComponent.kt
new file mode 100644
index 00000000..954a179b
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/component/ApplicationComponent.kt
@@ -0,0 +1,64 @@
+package com.sneha.weatherapp.di.component
+
+import android.app.Application
+import android.content.Context
+import android.content.SharedPreferences
+import com.sneha.weatherapp.WeatherApplication
+import com.sneha.weatherapp.data.local.db.DatabaseService
+import com.sneha.weatherapp.data.remote.NetworkService
+import com.sneha.weatherapp.data.repository.UserRepository
+import com.sneha.weatherapp.data.repository.WeatherRepository
+import com.sneha.weatherapp.di.ApplicationContext
+import com.sneha.weatherapp.di.module.ApplicationModule
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import dagger.Component
+import io.reactivex.disposables.CompositeDisposable
+import javax.inject.Singleton
+
+
+@Singleton
+@Component(modules = [ApplicationModule::class])
+interface ApplicationComponent {
+
+ fun inject(app: WeatherApplication)
+
+ fun getApplication(): Application
+
+ @ApplicationContext
+ fun getContext(): Context
+
+ /**
+ * These methods are written in ApplicationComponent because the instance of
+ * NetworkService is singleton and is maintained in the ApplicationComponent's implementation by Dagger
+ * For NetworkService singleton instance to be accessible to say DummyActivity's DummyViewModel
+ * this ApplicationComponent must expose a method that returns NetworkService instance
+ * This method will be called when NetworkService is injected in DummyViewModel.
+ * Also, in ActivityComponent you can find dependencies = [ApplicationComponent::class] to link this relationship
+ */
+ fun getNetworkService(): NetworkService
+
+ fun getDatabaseService(): DatabaseService
+
+ fun getSharedPreferences(): SharedPreferences
+
+ fun getNetworkHelper(): NetworkHelper
+
+ /**---------------------------------------------------------------------------
+ * Dagger will internally create UserRepository instance using constructor injection.
+ * Dependency through constructor
+ * UserRepository ->
+ * [NetworkService -> Nothing is required],
+ * [DatabaseService -> Nothing is required],
+ * [UserPreferences -> [SharedPreferences -> provided by the function provideSharedPreferences in ApplicationModule class]]
+ * So, Dagger will be able to create an instance of UserRepository by its own using constructor injection
+ *---------------------------------------------------------------------------------
+ */
+ fun getUserRepository(): UserRepository
+
+ fun getWeatherRepository(): WeatherRepository
+
+ fun getSchedulerProvider(): SchedulerProvider
+
+ fun getCompositeDisposable(): CompositeDisposable
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/component/FragmentComponent.kt b/app/src/main/java/com/sneha/weatherapp/di/component/FragmentComponent.kt
new file mode 100644
index 00000000..9c31a50d
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/component/FragmentComponent.kt
@@ -0,0 +1,16 @@
+package com.sneha.weatherapp.di.component
+
+import com.sneha.weatherapp.di.FragmentScope
+import com.sneha.weatherapp.di.module.FragmentModule
+import com.sneha.weatherapp.ui.weather.fragment.WeatherFragment
+import dagger.Component
+
+@FragmentScope
+@Component(
+ dependencies = [ApplicationComponent::class],
+ modules = [FragmentModule::class]
+)
+interface FragmentComponent {
+
+ fun inject(fragment: WeatherFragment)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/component/ViewHolderComponent.kt b/app/src/main/java/com/sneha/weatherapp/di/component/ViewHolderComponent.kt
new file mode 100644
index 00000000..01710365
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/component/ViewHolderComponent.kt
@@ -0,0 +1,19 @@
+package com.sneha.weatherapp.di.component
+
+import com.sneha.weatherapp.di.ViewModelScope
+import com.sneha.weatherapp.di.module.ViewHolderModule
+import com.sneha.weatherapp.ui.weather.adapter.DailyForecastItemViewHolder
+import com.sneha.weatherapp.ui.weather.adapter.ForecastItemViewHolder
+import dagger.Component
+
+@ViewModelScope
+@Component(
+ dependencies = [ApplicationComponent::class],
+ modules = [ViewHolderModule::class]
+)
+interface ViewHolderComponent {
+
+ fun inject(viewHolder: ForecastItemViewHolder)
+
+ fun inject(viewHolder: DailyForecastItemViewHolder)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/module/ActivityModule.kt b/app/src/main/java/com/sneha/weatherapp/di/module/ActivityModule.kt
new file mode 100644
index 00000000..e621ed13
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/module/ActivityModule.kt
@@ -0,0 +1,72 @@
+package com.sneha.weatherapp.di.module
+
+import androidx.lifecycle.ViewModelProviders
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.sneha.weatherapp.data.local.prefs.UserPreferences
+import com.sneha.weatherapp.data.repository.WeatherRepository
+import com.sneha.weatherapp.ui.base.BaseActivity
+import com.sneha.weatherapp.ui.weather.WeatherViewModel
+import com.sneha.weatherapp.ui.splash.SplashViewModel
+import com.sneha.weatherapp.ui.weather.adapter.DailyForecastAdapter
+import com.sneha.weatherapp.ui.weather.adapter.ForecastAdapter
+import com.sneha.weatherapp.utils.ViewModelProviderFactory
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import dagger.Module
+import dagger.Provides
+import io.reactivex.disposables.CompositeDisposable
+import javax.inject.Named
+import kotlin.collections.ArrayList
+
+/**
+ * Kotlin Generics Reference: https://kotlinlang.org/docs/reference/generics.html
+ * Basically it means that we can pass any class that extends BaseActivity which take
+ * BaseViewModel subclass as parameter
+ */
+@Module
+class ActivityModule(private val activity: BaseActivity<*>) {
+
+ @Provides
+ fun provideSplashViewModel(
+ schedulerProvider: SchedulerProvider,
+ compositeDisposable: CompositeDisposable,
+ networkHelper: NetworkHelper,
+ weatherRepository: WeatherRepository
+ ): SplashViewModel = ViewModelProviders.of(
+ activity, ViewModelProviderFactory(SplashViewModel::class) {
+ SplashViewModel(schedulerProvider, compositeDisposable, networkHelper, weatherRepository)
+ //this lambda creates and return SplashViewModel
+ }).get(SplashViewModel::class.java)
+
+ @Provides
+ fun provideWeatherViewModel(
+ schedulerProvider: SchedulerProvider,
+ compositeDisposable: CompositeDisposable,
+ networkHelper: NetworkHelper,
+ weatherRepository: WeatherRepository
+ ): WeatherViewModel = ViewModelProviders.of(
+ activity, ViewModelProviderFactory(WeatherViewModel::class) {
+ WeatherViewModel(
+ schedulerProvider,
+ compositeDisposable,
+ networkHelper,
+ weatherRepository)
+ }).get(WeatherViewModel::class.java)
+
+ @Provides
+ fun provideForecastAdapter() = ForecastAdapter(activity.lifecycle, ArrayList())
+
+ @Provides
+ fun providesDailyForecastAdapter() = DailyForecastAdapter(activity.lifecycle, ArrayList())
+
+ @Provides
+ @Named("DAILY_FORECAST_LAYOUT_MANAGER")
+ fun provideDailyForecastLayoutManager(): LinearLayoutManager =
+ LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
+
+ @Provides
+ @Named("TODAY_FORECAST_LAYOUT_MANAGER")
+ fun provideTodayForecastLayoutManager(): LinearLayoutManager =
+ LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/module/ApplicationModule.kt b/app/src/main/java/com/sneha/weatherapp/di/module/ApplicationModule.kt
new file mode 100644
index 00000000..fa4922f4
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/module/ApplicationModule.kt
@@ -0,0 +1,73 @@
+package com.sneha.weatherapp.di.module
+
+import android.app.Application
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.room.Room
+import com.sneha.weatherapp.BuildConfig
+import com.sneha.weatherapp.WeatherApplication
+import com.sneha.weatherapp.data.local.db.DatabaseService
+import com.sneha.weatherapp.data.remote.NetworkService
+import com.sneha.weatherapp.data.remote.Networking
+import com.sneha.weatherapp.di.ApplicationContext
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.RxSchedulerProvider
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import dagger.Module
+import dagger.Provides
+import io.reactivex.disposables.CompositeDisposable
+import javax.inject.Singleton
+
+@Module
+class ApplicationModule(private val application: WeatherApplication) {
+
+ @Provides
+ @Singleton
+ fun provideApplication(): Application = application
+
+ @Provides
+ @Singleton
+ @ApplicationContext
+ fun provideContext(): Context = application
+
+ /**
+ * Since this function do not have @Singleton then each time CompositeDisposable is injected
+ * then a new instance of CompositeDisposable will be provided
+ */
+ @Provides
+ fun provideCompositeDisposable(): CompositeDisposable = CompositeDisposable()
+
+ @Provides
+ fun provideSchedulerProvider(): SchedulerProvider = RxSchedulerProvider()
+
+ @Provides
+ @Singleton
+ fun provideSharedPreferences(): SharedPreferences =
+ application.getSharedPreferences("weather-app-prefs", Context.MODE_PRIVATE)
+
+ /**
+ * We need to write @Singleton on the provide method if we are create the instance inside this method
+ * to make it singleton. Even if we have written @Singleton on the instance's class
+ */
+ @Provides
+ @Singleton
+ fun provideDatabaseService(): DatabaseService =
+ Room.databaseBuilder(
+ application, DatabaseService::class.java,
+ "weather-app-db"
+ ).build()
+
+ @Provides
+ @Singleton
+ fun provideNetworkService(): NetworkService =
+ Networking.create(
+ BuildConfig.API_KEY,
+ BuildConfig.BASE_URL,
+ application.cacheDir,
+ 10 * 1024 * 1024 // 10MB
+ )
+
+ @Singleton
+ @Provides
+ fun provideNetworkHelper(): NetworkHelper = NetworkHelper(application)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/module/FragmentModule.kt b/app/src/main/java/com/sneha/weatherapp/di/module/FragmentModule.kt
new file mode 100644
index 00000000..062c77aa
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/module/FragmentModule.kt
@@ -0,0 +1,36 @@
+package com.sneha.weatherapp.di.module
+
+import androidx.lifecycle.ViewModelProviders
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.sneha.weatherapp.data.repository.WeatherRepository
+import com.sneha.weatherapp.ui.base.BaseFragment
+import com.sneha.weatherapp.ui.weather.fragment.WeatherFragmentViewModel
+import com.sneha.weatherapp.utils.ViewModelProviderFactory
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import dagger.Module
+import dagger.Provides
+import io.reactivex.disposables.CompositeDisposable
+
+@Module
+class FragmentModule(private val fragment: BaseFragment<*>) {
+
+ @Provides
+ fun provideLinearLayoutManager(): LinearLayoutManager = LinearLayoutManager(fragment.context)
+
+ @Provides
+ fun provideWeatherFragmentViewModel(
+ schedulerProvider: SchedulerProvider,
+ compositeDisposable: CompositeDisposable,
+ networkHelper: NetworkHelper,
+ weatherRepository: WeatherRepository
+ ): WeatherFragmentViewModel =
+ ViewModelProviders.of(fragment,
+ ViewModelProviderFactory(WeatherFragmentViewModel::class) {
+ WeatherFragmentViewModel(schedulerProvider, compositeDisposable, networkHelper, weatherRepository)
+ }
+ ).get(WeatherFragmentViewModel::class.java)
+
+// @Provides
+// fun provideWeatherAdapter() = WeatherAdapter(fragment.lifecycle, ArrayList())
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/di/module/ViewHolderModule.kt b/app/src/main/java/com/sneha/weatherapp/di/module/ViewHolderModule.kt
new file mode 100644
index 00000000..af27cd1f
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/di/module/ViewHolderModule.kt
@@ -0,0 +1,15 @@
+package com.sneha.weatherapp.di.module
+
+import androidx.lifecycle.LifecycleRegistry
+import com.sneha.weatherapp.di.ViewModelScope
+import com.sneha.weatherapp.ui.base.BaseItemViewHolder
+import dagger.Module
+import dagger.Provides
+
+@Module
+class ViewHolderModule(private val viewHolder: BaseItemViewHolder<*, *>) {
+
+ @Provides
+ @ViewModelScope
+ fun provideLifecycleRegistry(): LifecycleRegistry = LifecycleRegistry(viewHolder)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/base/BaseActivity.kt b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseActivity.kt
new file mode 100644
index 00000000..f9186146
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseActivity.kt
@@ -0,0 +1,68 @@
+package com.sneha.weatherapp.ui.base
+
+import android.os.Bundle
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Observer
+import com.sneha.weatherapp.WeatherApplication
+import com.sneha.weatherapp.di.component.ActivityComponent
+import com.sneha.weatherapp.di.component.DaggerActivityComponent
+import com.sneha.weatherapp.di.module.ActivityModule
+import com.sneha.weatherapp.utils.display.Toaster
+import javax.inject.Inject
+
+/**
+ * Reference for generics: https://kotlinlang.org/docs/reference/generics.html
+ * Basically BaseActivity will take any class that extends BaseViewModel
+ */
+abstract class BaseActivity : AppCompatActivity() {
+
+ @Inject
+ lateinit var viewModel: VM
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ injectDependencies(buildActivityComponent())
+ super.onCreate(savedInstanceState)
+ setContentView(provideLayoutId())
+ setupObservers()
+ setupView(savedInstanceState)
+ viewModel.onCreate()
+ }
+
+ private fun buildActivityComponent() =
+ DaggerActivityComponent
+ .builder()
+ .applicationComponent((application as WeatherApplication).applicationComponent)
+ .activityModule(ActivityModule(this))
+ .build()
+
+ protected open fun setupObservers() {
+ viewModel.messageString.observe(this, Observer {
+ it.data?.run { showMessage(this) }
+ })
+
+ viewModel.messageStringId.observe(this, Observer {
+ it.data?.run { showMessage(this) }
+ })
+ }
+
+ fun showMessage(message: String) = Toaster.show(applicationContext, message)
+
+ fun showMessage(@StringRes resId: Int) = showMessage(getString(resId))
+
+ open fun goBack() = onBackPressed()
+
+ override fun onBackPressed() {
+ if (supportFragmentManager.backStackEntryCount > 0)
+ supportFragmentManager.popBackStackImmediate()
+ else super.onBackPressed()
+ }
+
+ @LayoutRes
+ protected abstract fun provideLayoutId(): Int
+
+ protected abstract fun injectDependencies(activityComponent: ActivityComponent)
+
+ protected abstract fun setupView(savedInstanceState: Bundle?)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/base/BaseAdapter.kt b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseAdapter.kt
new file mode 100644
index 00000000..f6a2be6f
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseAdapter.kt
@@ -0,0 +1,97 @@
+package com.sneha.weatherapp.ui.base
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+abstract class BaseAdapter>>(
+ parentLifecycle: Lifecycle,
+ private val dataList: ArrayList
+) : RecyclerView.Adapter() {
+
+ private var recyclerView: RecyclerView? = null
+
+ init {
+ parentLifecycle.addObserver(object : LifecycleObserver {
+ @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+ fun onParentDestroy() {
+ recyclerView?.run {
+ for (i in 0 until childCount) {
+ getChildAt(i)?.let {
+ (getChildViewHolder(it) as BaseItemViewHolder<*, *>)
+ .run {
+ onDestroy()
+ viewModel.onManualCleared()
+ }
+ }
+ }
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ fun onParentStop() {
+ recyclerView?.run {
+ for (i in 0 until childCount) {
+ getChildAt(i)?.let {
+ (getChildViewHolder(it) as BaseItemViewHolder<*, *>).onStop()
+ }
+ }
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ fun onParentStart() {
+ recyclerView?.run {
+ if (layoutManager is LinearLayoutManager) {
+ val first = (layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
+ val last = (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
+ if (first in 0..last)
+ for (i in first..last) {
+ findViewHolderForAdapterPosition(i)?.let {
+ (it as BaseItemViewHolder<*, *>).onStart()
+ }
+ }
+ }
+ }
+ }
+ })
+ }
+
+ override fun onViewAttachedToWindow(holder: VH) {
+ super.onViewAttachedToWindow(holder)
+ holder.onStart()
+ }
+
+ override fun onViewDetachedFromWindow(holder: VH) {
+ super.onViewDetachedFromWindow(holder)
+ holder.onStop()
+ }
+
+ override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
+ super.onAttachedToRecyclerView(recyclerView)
+ this.recyclerView = recyclerView
+ }
+
+ override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
+ super.onDetachedFromRecyclerView(recyclerView)
+ this.recyclerView = null
+ }
+
+ override fun getItemCount(): Int = dataList.size
+
+ override fun onBindViewHolder(holder: VH, position: Int) {
+ holder.bind(dataList[position])
+ }
+
+ fun appendData(dataList: List) {
+ val oldCount = itemCount
+ this.dataList.addAll(dataList)
+ val currentCount = itemCount
+ if (oldCount == 0 && currentCount > 0)
+ notifyDataSetChanged()
+ else if (oldCount in 1 until currentCount)
+ notifyItemRangeChanged(oldCount - 1, currentCount - oldCount)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/base/BaseFragment.kt b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseFragment.kt
new file mode 100644
index 00000000..e29d08ca
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseFragment.kt
@@ -0,0 +1,75 @@
+package com.sneha.weatherapp.ui.base
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import com.sneha.weatherapp.WeatherApplication
+import com.sneha.weatherapp.di.component.DaggerFragmentComponent
+import com.sneha.weatherapp.di.component.FragmentComponent
+import com.sneha.weatherapp.di.module.FragmentModule
+import com.sneha.weatherapp.utils.display.Toaster
+import javax.inject.Inject
+
+/**
+ * Reference for generics: https://kotlinlang.org/docs/reference/generics.html
+ * Basically BaseFragment will take any class that extends BaseViewModel
+ */
+abstract class BaseFragment : Fragment() {
+
+ @Inject
+ lateinit var viewModel: VM
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ injectDependencies(buildFragmentComponent())
+ super.onCreate(savedInstanceState)
+ setupObservers()
+ viewModel.onCreate()
+ }
+
+ private fun buildFragmentComponent() =
+ DaggerFragmentComponent
+ .builder()
+ .applicationComponent((context!!.applicationContext as WeatherApplication).applicationComponent)
+ .fragmentModule(FragmentModule(this))
+ .build()
+
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
+ inflater.inflate(provideLayoutId(), container, false)
+
+ protected open fun setupObservers() {
+ viewModel.messageString.observe(this, Observer {
+ it.data?.run { showMessage(this) }
+ })
+
+ viewModel.messageStringId.observe(this, Observer {
+ it.data?.run { showMessage(this) }
+ })
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupView(view)
+ }
+
+
+ fun showMessage(message: String) = context?.let { Toaster.show(it, message) }
+
+ fun showMessage(@StringRes resId: Int) = showMessage(getString(resId))
+
+ fun goBack() {
+ if (activity is BaseActivity<*>) (activity as BaseActivity<*>).goBack()
+ }
+
+ @LayoutRes
+ protected abstract fun provideLayoutId(): Int
+
+ protected abstract fun injectDependencies(fragmentComponent: FragmentComponent)
+
+ protected abstract fun setupView(view: View)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/base/BaseItemViewHolder.kt b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseItemViewHolder.kt
new file mode 100644
index 00000000..c805d72e
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseItemViewHolder.kt
@@ -0,0 +1,88 @@
+package com.sneha.weatherapp.ui.base
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.RecyclerView
+import com.sneha.weatherapp.WeatherApplication
+import com.sneha.weatherapp.di.component.DaggerViewHolderComponent
+import com.sneha.weatherapp.di.component.ViewHolderComponent
+import com.sneha.weatherapp.di.module.ViewHolderModule
+import com.sneha.weatherapp.utils.display.Toaster
+import javax.inject.Inject
+
+abstract class BaseItemViewHolder>(
+ @LayoutRes layoutId: Int, parent: ViewGroup
+) : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(layoutId, parent, false)),
+ LifecycleOwner {
+
+ init {
+ onCreate()
+ }
+
+ @Inject
+ lateinit var viewModel: VM
+
+ @Inject
+ lateinit var lifecycleRegistry: LifecycleRegistry
+
+ override fun getLifecycle(): Lifecycle = lifecycleRegistry
+
+ open fun bind(data: T) {
+ viewModel.updateData(data)
+ }
+
+ protected fun onCreate() {
+ injectDependencies(buildViewHolderComponent())
+ lifecycleRegistry.markState(Lifecycle.State.INITIALIZED)
+ lifecycleRegistry.markState(Lifecycle.State.CREATED)
+ setupObservers()
+ setupView(itemView)
+ }
+
+ fun onStart() {
+ lifecycleRegistry.markState(Lifecycle.State.STARTED)
+ lifecycleRegistry.markState(Lifecycle.State.RESUMED)
+ }
+
+ fun onStop() {
+ lifecycleRegistry.markState(Lifecycle.State.STARTED)
+ lifecycleRegistry.markState(Lifecycle.State.CREATED)
+ }
+
+ fun onDestroy() {
+ lifecycleRegistry.markState(Lifecycle.State.DESTROYED)
+ }
+
+ private fun buildViewHolderComponent() =
+ DaggerViewHolderComponent
+ .builder()
+ .applicationComponent((itemView.context.applicationContext as WeatherApplication).applicationComponent)
+ .viewHolderModule(ViewHolderModule(this))
+ .build()
+
+ fun showMessage(message: String) = Toaster.show(itemView.context, message)
+
+ fun showMessage(@StringRes resId: Int) = showMessage(itemView.context.getString(resId))
+
+ protected open fun setupObservers() {
+ viewModel.messageString.observe(this, Observer {
+ it.data?.run { showMessage(this) }
+ })
+
+ viewModel.messageStringId.observe(this, Observer {
+ it.data?.run { showMessage(this) }
+ })
+ }
+
+ protected abstract fun injectDependencies(viewHolderComponent: ViewHolderComponent)
+
+ abstract fun setupView(view: View)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/base/BaseItemViewModel.kt b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseItemViewModel.kt
new file mode 100644
index 00000000..c0429b99
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseItemViewModel.kt
@@ -0,0 +1,22 @@
+package com.sneha.weatherapp.ui.base
+
+import androidx.lifecycle.MutableLiveData
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import io.reactivex.disposables.CompositeDisposable
+
+
+abstract class BaseItemViewModel(
+ schedulerProvider: SchedulerProvider,
+ compositeDisposable: CompositeDisposable,
+ networkHelper: NetworkHelper
+) : BaseViewModel(schedulerProvider, compositeDisposable, networkHelper) {
+
+ val data: MutableLiveData = MutableLiveData()
+
+ fun onManualCleared() = onCleared()
+
+ fun updateData(data: T) {
+ this.data.postValue(data)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/base/BaseViewModel.kt b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseViewModel.kt
new file mode 100644
index 00000000..e2e13c6c
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/base/BaseViewModel.kt
@@ -0,0 +1,61 @@
+package com.sneha.weatherapp.ui.base
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.sneha.weatherapp.R
+import com.sneha.weatherapp.utils.common.Resource
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import io.reactivex.disposables.CompositeDisposable
+import javax.net.ssl.HttpsURLConnection
+
+
+abstract class BaseViewModel(
+ protected val schedulerProvider: SchedulerProvider,
+ protected val compositeDisposable: CompositeDisposable,
+ protected val networkHelper: NetworkHelper
+) : ViewModel() {
+
+ override fun onCleared() {
+ compositeDisposable.dispose()
+ super.onCleared()
+ }
+
+ val messageStringId: MutableLiveData> = MutableLiveData()
+ val messageString: MutableLiveData> = MutableLiveData()
+
+ protected fun checkInternetConnectionWithMessage(): Boolean =
+ if (networkHelper.isNetworkConnected()) {
+ true
+ } else {
+ messageStringId.postValue(Resource.error(R.string.network_connection_error))
+ false
+ }
+
+ protected fun checkInternetConnection(): Boolean = networkHelper.isNetworkConnected()
+
+ protected fun handleNetworkError(err: Throwable?) =
+ err?.let {
+ networkHelper.castToNetworkError(it).run {
+ when (status) {
+ -1 -> messageStringId.postValue(Resource.error(R.string.network_default_error))
+ 0 -> messageStringId.postValue(Resource.error(R.string.server_connection_error))
+ HttpsURLConnection.HTTP_UNAUTHORIZED -> {
+ forcedLogoutUser()
+ messageStringId.postValue(Resource.error(R.string.server_connection_error))
+ }
+ HttpsURLConnection.HTTP_INTERNAL_ERROR ->
+ messageStringId.postValue(Resource.error(R.string.network_internal_error))
+ HttpsURLConnection.HTTP_UNAVAILABLE ->
+ messageStringId.postValue(Resource.error(R.string.network_server_not_available))
+ else -> messageString.postValue(Resource.error(message))
+ }
+ }
+ }
+
+ protected open fun forcedLogoutUser() {
+ // do something
+ }
+
+ abstract fun onCreate()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/location/GpsStatusLiveData.kt b/app/src/main/java/com/sneha/weatherapp/ui/location/GpsStatusLiveData.kt
new file mode 100644
index 00000000..7da5dbf0
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/location/GpsStatusLiveData.kt
@@ -0,0 +1,65 @@
+package com.sneha.weatherapp.ui.location
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.location.LocationManager
+import android.os.Build
+import android.provider.Settings
+import android.provider.Settings.Secure.*
+import androidx.lifecycle.LiveData
+import com.sneha.weatherapp.R
+import timber.log.Timber
+
+/**
+ * Listens to Gps (location service) which is highly important for tracking to work and then
+ * responds with appropriate state specified in {@link GpsStatus}
+ */
+class GpsStatusLiveData(private val context: Context) : LiveData() {
+
+ private val gpsSwitchStateReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) = checkGpsAndReact()
+ }
+
+ override fun onInactive() = unregisterReceiver()
+
+ override fun onActive() {
+ registerReceiver()
+ checkGpsAndReact()
+ }
+
+ private fun checkGpsAndReact() = if (isLocationEnabled()) {
+ postValue(GpsStatus.Enabled())
+ } else {
+ postValue(GpsStatus.Disabled())
+ }
+
+ private fun isLocationEnabled() =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ context.getSystemService(LocationManager::class.java)
+ .isProviderEnabled(LocationManager.GPS_PROVIDER)
+ } else {
+ try {
+ getInt(context.contentResolver, LOCATION_MODE) != LOCATION_MODE_OFF
+ } catch (e: Settings.SettingNotFoundException) {
+ Timber.e(e)
+ false
+ }
+ }
+
+ /**
+ * Broadcast receiver to listen the Location button toggle state in Android.
+ */
+ private fun registerReceiver() = context.registerReceiver(
+ gpsSwitchStateReceiver,
+ IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
+ )
+
+ private fun unregisterReceiver() = context.unregisterReceiver(gpsSwitchStateReceiver)
+}
+
+sealed class GpsStatus {
+ data class Enabled(val message: Int = R.string.gps_status_enabled) : GpsStatus()
+ data class Disabled(val message: Int = R.string.gps_status_disabled) : GpsStatus()
+}
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/location/LocationActivity.kt b/app/src/main/java/com/sneha/weatherapp/ui/location/LocationActivity.kt
new file mode 100644
index 00000000..3f0d6623
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/location/LocationActivity.kt
@@ -0,0 +1,151 @@
+package com.sneha.weatherapp.ui.location
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.provider.Settings
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProviders
+import com.sneha.weatherapp.R
+import com.sneha.weatherapp.ui.weather.WeatherActivity
+import com.sneha.weatherapp.utils.ViewModelProviderFactory
+import kotlinx.android.synthetic.main.activity_location.*
+
+
+const val LOCATION_REQUEST = 100
+
+class LocationActivity : AppCompatActivity() {
+
+ lateinit var locationViewModel: LocationViewModel
+
+ private var isGPSEnabled = false
+
+ private val gpsObserver = Observer { status ->
+ status?.let {
+ when (status) {
+ is GpsStatus.Enabled -> {
+ isGPSEnabled = true
+ invokeLocationAction()
+ }
+
+ is GpsStatus.Disabled -> {
+ isGPSEnabled = false
+ invokeLocationAction()
+ }
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_location)
+
+ tv_locate.setOnClickListener {
+ when (tv_locate.text) {
+ getString(R.string.locate) -> ActivityCompat.requestPermissions(
+ this,
+ arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ),
+ LOCATION_REQUEST
+ )
+ getString(R.string.enable_gps) -> {
+ val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
+ startActivity(intent)
+ }
+ else -> {
+ //DO NOTHING
+ }
+ }
+ }
+ }
+
+ private fun startLocationUpdate() {
+ locationViewModel.getLocationData().observe(this, Observer {
+ // latLong.text = getString(R.string.latLong, it.longitude, it.latitude)
+ latLong.text = getString(R.string.loading_data)
+ finish()
+ startActivity(Intent(applicationContext, WeatherActivity::class.java))
+ })
+ }
+
+ private fun isPermissionsGranted() =
+ ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED &&
+ ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED
+
+
+ private fun shouldShowRequestPermissionRationale() =
+ ActivityCompat.shouldShowRequestPermissionRationale(
+ this,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) && ActivityCompat.shouldShowRequestPermissionRationale(
+ this,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ )
+
+ @SuppressLint("MissingPermission")
+ override fun onRequestPermissionsResult(
+ requestCode: Int, permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ when (requestCode) {
+ LOCATION_REQUEST -> {
+ invokeLocationAction()
+ }
+ }
+ }
+
+ private fun invokeLocationAction() {
+ when {
+ !isGPSEnabled -> {
+ tv_locate_city.text = getString(R.string.turn_gps_on)
+ tv_locate.text = getString(R.string.enable_gps)
+ latLong.text = getString(R.string.enable_gps_message)
+ }
+
+ isPermissionsGranted() -> startLocationUpdate()
+
+ shouldShowRequestPermissionRationale() -> {
+ tv_locate_city.text = getString(R.string.locate_current_city)
+ tv_locate.text = getString(R.string.locate)
+ latLong.text = getString(R.string.permission_request)
+ }
+
+ else -> {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ),
+ LOCATION_REQUEST
+ )
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ locationViewModel = ViewModelProviders.of(
+ this, ViewModelProviderFactory(LocationViewModel::class) {
+ LocationViewModel(application)
+ }).get(LocationViewModel::class.java)
+ locationViewModel.gpsStatusLiveData.observe(this, gpsObserver)
+
+ invokeLocationAction()
+ }
+}
+
+
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/location/LocationLiveData.kt b/app/src/main/java/com/sneha/weatherapp/ui/location/LocationLiveData.kt
new file mode 100644
index 00000000..f95ab148
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/location/LocationLiveData.kt
@@ -0,0 +1,73 @@
+package com.sneha.weatherapp.ui.location
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.content.Context
+import android.location.Location
+import androidx.lifecycle.LiveData
+import com.google.android.gms.location.*
+import com.sneha.weatherapp.data.local.prefs.UserPreferences
+import com.sneha.weatherapp.data.model.LocationData
+
+class LocationLiveData(context: Application) :
+ LiveData() {
+
+ private var fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
+ private var sharedPreference =
+ context.getSharedPreferences("weather-app-prefs", Context.MODE_PRIVATE)
+
+ override fun onInactive() {
+ super.onInactive()
+ fusedLocationClient.removeLocationUpdates(locationCallback)
+ }
+
+
+ @SuppressLint("MissingPermission")
+ override fun onActive() {
+ super.onActive()
+ fusedLocationClient.lastLocation
+ .addOnSuccessListener { location: Location? ->
+ location?.also {
+ setLocationData(it)
+ }
+ }
+ startLocationUpdates()
+ }
+
+ @SuppressLint("MissingPermission")
+ private fun startLocationUpdates() {
+ fusedLocationClient.requestLocationUpdates(
+ locationRequest,
+ locationCallback,
+ null
+ )
+ }
+
+ private val locationCallback = object : LocationCallback() {
+ override fun onLocationResult(locationResult: LocationResult?) {
+ locationResult ?: return
+ for (location in locationResult.locations) {
+ setLocationData(location)
+ }
+ }
+ }
+
+ private fun setLocationData(location: Location) {
+ value = LocationData(
+ longitude = location.longitude,
+ latitude = location.latitude
+ )
+
+ val userPreferences = UserPreferences(sharedPreference)
+ userPreferences.setLatitude(location.latitude.toString())
+ userPreferences.setLongitude(location.longitude.toString())
+ }
+
+ companion object {
+ val locationRequest: LocationRequest = LocationRequest.create().apply {
+ interval = 10000
+ fastestInterval = 5000
+ priority = LocationRequest.PRIORITY_HIGH_ACCURACY
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/location/LocationViewModel.kt b/app/src/main/java/com/sneha/weatherapp/ui/location/LocationViewModel.kt
new file mode 100644
index 00000000..f3d59e98
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/location/LocationViewModel.kt
@@ -0,0 +1,21 @@
+package com.sneha.weatherapp.ui.location
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import com.google.android.gms.location.FusedLocationProviderClient
+import com.google.android.gms.location.LocationRequest
+import com.sneha.weatherapp.data.local.prefs.UserPreferences
+import com.sneha.weatherapp.data.repository.UserRepository
+import javax.inject.Inject
+
+class LocationViewModel(
+ application: Application
+) : AndroidViewModel(application) {
+
+ private val locationData = LocationLiveData(application)
+
+ fun getLocationData() = locationData
+
+ val gpsStatusLiveData = GpsStatusLiveData(application)
+
+}
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/splash/SplashActivity.kt b/app/src/main/java/com/sneha/weatherapp/ui/splash/SplashActivity.kt
new file mode 100644
index 00000000..2edd906c
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/splash/SplashActivity.kt
@@ -0,0 +1,47 @@
+package com.sneha.weatherapp.ui.splash
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.lifecycle.Observer
+import com.sneha.weatherapp.R
+import com.sneha.weatherapp.di.component.ActivityComponent
+import com.sneha.weatherapp.ui.base.BaseActivity
+import com.sneha.weatherapp.ui.location.LocationActivity
+import com.sneha.weatherapp.ui.weather.WeatherActivity
+
+class SplashActivity : BaseActivity() {
+
+ companion object {
+ const val TAG = "SplashActivity"
+ }
+
+ override fun provideLayoutId(): Int = R.layout.activity_splash
+
+ override fun injectDependencies(activityComponent: ActivityComponent) {
+ activityComponent.inject(this)
+ }
+
+ override fun setupView(savedInstanceState: Bundle?) {
+
+ }
+
+ override fun setupObservers() {
+ // Event is used by the view model to tell the activity to launch another activity
+ // view model also provided the Bundle in the event that is needed for the Activity
+// viewModel.launchWeather.observe(this, Observer>> {
+// it.getIfNotHandled()?.run {
+// finish()
+// startActivity(Intent(applicationContext, LocationActivity::class.java))
+// }
+// })
+
+ viewModel.locationLiveData.observe(this, Observer {
+ finish()
+ if (it.latitude != null && it.longitude != null) {
+ startActivity(Intent(applicationContext, WeatherActivity::class.java))
+ } else {
+ startActivity(Intent(applicationContext, LocationActivity::class.java))
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/splash/SplashViewModel.kt b/app/src/main/java/com/sneha/weatherapp/ui/splash/SplashViewModel.kt
new file mode 100644
index 00000000..07bd43ca
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/splash/SplashViewModel.kt
@@ -0,0 +1,42 @@
+package com.sneha.weatherapp.ui.splash
+
+import androidx.lifecycle.MutableLiveData
+import com.sneha.weatherapp.data.local.prefs.UserPreferences
+import com.sneha.weatherapp.data.model.LocationData
+import com.sneha.weatherapp.data.repository.WeatherRepository
+import com.sneha.weatherapp.ui.base.BaseViewModel
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import io.reactivex.disposables.CompositeDisposable
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+
+class SplashViewModel(
+ schedulerProvider: SchedulerProvider,
+ compositeDisposable: CompositeDisposable,
+ networkHelper: NetworkHelper,
+ weatherRepository: WeatherRepository
+) : BaseViewModel(schedulerProvider, compositeDisposable, networkHelper) {
+
+ // Event is used by the view model to tell the activity to launch another Activity
+ // view model also provided the Bundle in the event that is needed for the Activity
+// val launchWeather: MutableLiveData>> = MutableLiveData()
+ val locationLiveData: MutableLiveData = MutableLiveData()
+
+ init {
+ GlobalScope.launch {
+ delay(3000)
+ }
+
+ locationLiveData.value = weatherRepository.getLocationData()
+ }
+
+ override fun onCreate() {
+ // Empty Bundle passed to Activity in Event that is needed by the other Activity
+ // Here in actual application we will decide which screen to open based on
+ // either the user is logged in or not
+// launchWeather.postValue(Event(emptyMap()))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/SelectedDayForecastActivity.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/SelectedDayForecastActivity.kt
new file mode 100644
index 00000000..acc05b1d
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/SelectedDayForecastActivity.kt
@@ -0,0 +1,44 @@
+package com.sneha.weatherapp.ui.weather
+
+import android.os.Bundle
+import com.sneha.weatherapp.R
+import com.sneha.weatherapp.di.component.ActivityComponent
+import com.sneha.weatherapp.ui.base.BaseActivity
+import com.sneha.weatherapp.utils.common.TimeUtils
+import com.sneha.weatherapp.utils.display.WeatherToImage
+import kotlinx.android.synthetic.main.activity_selected_day.*
+import kotlinx.android.synthetic.main.activity_selected_day.tv_day
+import kotlinx.android.synthetic.main.activity_selected_day.tv_feels_like
+import com.google.gson.Gson
+import com.sneha.weatherapp.data.model.Forecast
+
+
+class SelectedDayForecastActivity : BaseActivity() {
+
+ override fun injectDependencies(activityComponent: ActivityComponent) {
+ activityComponent.inject(this)
+ }
+
+ override fun setupView(savedInstanceState: Bundle?) {
+
+ Gson().fromJson(
+ intent.getStringExtra("forecast_data"),
+ Forecast::class.java
+ ).let {
+ tv_day.text = TimeUtils.getTimeInExpectedFormat(it.dt, "EEEE")
+ tv_feels_like.text = it.weather[0].description.capitalize()
+ tv_icon.setImageResource(WeatherToImage.getImageForCode(it.weather[0].icon))
+ rv_temp_min_max.text = getString(R.string.temperature).plus(" ")
+ .plus(it.temp.min.toInt()).plus("℃").plus("/")
+ .plus(it.temp.max.toInt()).plus("℃")
+ tv_humidity.text = it.humidity.toString().plus("%")
+ tv_pressure.text = it.pressure.toString().plus(" hpa")
+ tv_sunrise.text = TimeUtils.getTimeInExpectedFormat(it.sunrise, "HH:mm")
+ tv_sunset.text = TimeUtils.getTimeInExpectedFormat(it.sunset, "HH:mm")
+ }
+
+ cancel.setOnClickListener { finish() }
+ }
+
+ override fun provideLayoutId(): Int = R.layout.activity_selected_day
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/WeatherActivity.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/WeatherActivity.kt
new file mode 100644
index 00000000..7d32fdc6
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/WeatherActivity.kt
@@ -0,0 +1,117 @@
+package com.sneha.weatherapp.ui.weather
+
+import android.content.Intent
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.sneha.weatherapp.R
+import com.sneha.weatherapp.di.component.ActivityComponent
+import com.sneha.weatherapp.ui.base.BaseActivity
+import com.sneha.weatherapp.ui.weather.adapter.DailyForecastAdapter
+import com.sneha.weatherapp.ui.weather.adapter.ForecastAdapter
+import com.sneha.weatherapp.utils.common.TimeUtils
+import kotlinx.android.synthetic.main.activity_weather.*
+import kotlinx.android.synthetic.main.layout_weather_details.*
+import javax.inject.Inject
+import com.sneha.weatherapp.data.model.Weather
+import com.sneha.weatherapp.ui.weather.adapter.ClickListener
+import kotlinx.android.synthetic.main.activity_location.*
+import javax.inject.Named
+
+
+class WeatherActivity : BaseActivity() {
+
+ @Inject
+ @field:Named("DAILY_FORECAST_LAYOUT_MANAGER")
+ lateinit var dailyForecastLayoutManager: LinearLayoutManager
+
+ @Inject
+ @field:Named("TODAY_FORECAST_LAYOUT_MANAGER")
+ lateinit var todayForecastLayoutManager: LinearLayoutManager
+
+ @Inject
+ lateinit var forecastAdapter: ForecastAdapter
+
+ @Inject
+ lateinit var dailyForecastAdapter: DailyForecastAdapter
+
+ private val clickListener = object : ClickListener {
+ override fun onItemClicked(forecast: String) {
+ val intent = Intent(this@WeatherActivity, SelectedDayForecastActivity::class.java)
+ intent.putExtra("forecast_data", forecast)
+ startActivity(intent)
+ }
+ }
+
+ override fun provideLayoutId(): Int = R.layout.activity_weather
+
+ override fun injectDependencies(activityComponent: ActivityComponent) {
+ activityComponent.inject(this)
+ }
+
+ override fun setupObservers() {
+
+ //fetching weather data
+ viewModel.getWeatherData().observe(this, Observer {
+ it?.let { weather ->
+ setWeatherData(weather)
+ }
+ })
+
+ //get today's forecast
+ viewModel.getForecastData().observe(this, Observer {
+ it?.forecastList?.let {
+ tv_today_forecast_label.visibility = View.VISIBLE
+ forecastAdapter.appendData(it)
+ }
+ })
+
+ //get this week's forecast
+ viewModel.getDailyForecastData().observe(this, Observer {
+ it?.forecastList?.let { forecast ->
+ tv_week_forecast_label.visibility = View.VISIBLE
+ dailyForecastAdapter.appendData(forecast)
+ }
+ })
+ }
+
+ override fun setupView(savedInstanceState: Bundle?) {
+ rv_today_forecast.layoutManager = todayForecastLayoutManager
+ rv_today_forecast.adapter = forecastAdapter
+
+ dailyForecastAdapter.clickListener = clickListener
+ rv_daily_forecast.layoutManager = dailyForecastLayoutManager
+ rv_daily_forecast.adapter = dailyForecastAdapter
+
+ swipe_refresh.setOnRefreshListener {
+ viewModel.refreshData()
+ viewModel.getWeatherData().observe(this, Observer {
+ it?.let {
+ setWeatherData(it)
+ swipe_refresh.isRefreshing = false
+ }
+ })
+ }
+ }
+
+ private fun setWeatherData(weather: Weather) {
+ tv_city_name.visibility = View.VISIBLE
+ weather_details.visibility = View.VISIBLE
+ tv_today_weather_details.visibility = View.VISIBLE
+ tv_city_name.text = weather.cityName
+ tv_updated_at.text = getString(R.string.updated).plus(" ").plus(TimeUtils.getTimeAgo(weather.dt))
+ tv_pressure.text = weather.main.pressure.toString().plus(" ").plus("hpa")
+ tv_humidity.text = weather.main.humidity.toString().plus("%")
+ tv_wind.text = weather.wind.speed.toString().plus(" ").plus("mps")
+ tv_rain.text = weather.clouds.all.toString().plus("%")
+ tv_sunrise.text = TimeUtils.getTimeInExpectedFormat(weather.sys.sunrise, "HH:mm")
+ tv_sunset.text = TimeUtils.getTimeInExpectedFormat(weather.sys.sunset, "HH:mm")
+ tv_temperature.text = weather.main.temperature.toInt().toString().plus("\u00B0")
+ tv_weather.text = weather.weather[0].description.capitalize()
+ rv_temp_min_max.text = getString(R.string.expected).plus(" ")
+ .plus(weather.main.temperatureMin.toInt()).plus("℃").plus("/")
+ .plus(weather.main.temperatureMax.toInt()).plus("℃")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/WeatherViewModel.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/WeatherViewModel.kt
new file mode 100644
index 00000000..4ee55708
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/WeatherViewModel.kt
@@ -0,0 +1,97 @@
+package com.sneha.weatherapp.ui.weather
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
+import com.sneha.weatherapp.data.model.Weather
+import com.sneha.weatherapp.data.remote.response.DailyForecastResponse
+import com.sneha.weatherapp.data.remote.response.TodayForecastResponse
+import com.sneha.weatherapp.data.repository.WeatherRepository
+import com.sneha.weatherapp.ui.base.BaseViewModel
+import com.sneha.weatherapp.utils.common.Resource
+import com.sneha.weatherapp.utils.common.Status
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import io.reactivex.disposables.CompositeDisposable
+
+
+class WeatherViewModel(
+ schedulerProvider: SchedulerProvider,
+ compositeDisposable: CompositeDisposable,
+ networkHelper: NetworkHelper,
+ private val weatherRepository: WeatherRepository
+) : BaseViewModel(schedulerProvider, compositeDisposable, networkHelper) {
+
+ private val weatherLiveData: MutableLiveData> = MutableLiveData()
+ private val forecastLiveData: MutableLiveData> =
+ MutableLiveData()
+ private val dailyForecastLiveData: MutableLiveData> =
+ MutableLiveData()
+
+ fun getWeatherData(): LiveData =
+ Transformations.map(weatherLiveData) {
+ it.data
+ }
+
+ fun getForecastData(): LiveData = Transformations.map(forecastLiveData) {
+ it.data
+ }
+
+ fun getDailyForecastData(): LiveData =
+ Transformations.map(dailyForecastLiveData) {
+ it.data
+ }
+
+ fun isWeatherFetching(): LiveData =
+ Transformations.map(weatherLiveData) { it.status == Status.LOADING }
+
+ override fun onCreate() {
+ callWeatherApi()
+
+ if (forecastLiveData.value == null && checkInternetConnectionWithMessage()) {
+ forecastLiveData.postValue(Resource.loading())
+ compositeDisposable.add(
+ weatherRepository.fetchTodayForecast()
+ .subscribeOn(schedulerProvider.io())
+ .subscribe(
+ { forecastLiveData.postValue(Resource.success(it)) },
+ {
+ handleNetworkError(it)
+ forecastLiveData.postValue(Resource.error())
+ })
+ )
+ }
+
+ if (dailyForecastLiveData.value == null && checkInternetConnectionWithMessage()) {
+ dailyForecastLiveData.postValue(Resource.loading())
+ compositeDisposable.add(
+ weatherRepository.fetchDailyForecast()
+ .subscribeOn(schedulerProvider.io())
+ .subscribe(
+ { dailyForecastLiveData.postValue(Resource.success(it)) },
+ {
+ handleNetworkError(it)
+ dailyForecastLiveData.postValue(Resource.error())
+ })
+ )
+ }
+ }
+
+ private fun callWeatherApi() {
+ compositeDisposable.add(
+ weatherRepository.fetchWeatherData()
+ .subscribeOn(schedulerProvider.io())
+ .subscribe({
+ weatherLiveData.postValue(Resource.success(it))
+ },
+ {
+ handleNetworkError(it)
+ weatherLiveData.postValue(Resource.error())
+ })
+ )
+ }
+
+ fun refreshData() {
+ callWeatherApi()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/DailyForecastAdapter.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/DailyForecastAdapter.kt
new file mode 100644
index 00000000..d760b0cb
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/DailyForecastAdapter.kt
@@ -0,0 +1,19 @@
+package com.sneha.weatherapp.ui.weather.adapter
+
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import com.sneha.weatherapp.data.model.Forecast
+import com.sneha.weatherapp.ui.base.BaseAdapter
+
+interface ClickListener{
+ fun onItemClicked(forecast: String)
+}
+
+class DailyForecastAdapter(
+ parentLifecycle: Lifecycle,
+ forecasts: ArrayList,
+ var clickListener: ClickListener? = null
+) : BaseAdapter(parentLifecycle, forecasts) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = DailyForecastItemViewHolder(parent, clickListener)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/DailyForecastItemViewHolder.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/DailyForecastItemViewHolder.kt
new file mode 100644
index 00000000..3e9af84e
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/DailyForecastItemViewHolder.kt
@@ -0,0 +1,62 @@
+package com.sneha.weatherapp.ui.weather.adapter
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.Observer
+import com.sneha.weatherapp.R
+import com.sneha.weatherapp.di.component.ViewHolderComponent
+import com.sneha.weatherapp.ui.base.BaseItemViewHolder
+import kotlinx.android.synthetic.main.item_view_daily_forecast.view.*
+import kotlinx.android.synthetic.main.item_view_forecast.view.iv_image
+import java.util.*
+import com.google.gson.Gson
+import com.sneha.weatherapp.data.model.Forecast
+
+
+class DailyForecastItemViewHolder(parent: ViewGroup, var clickListener: ClickListener?) :
+ BaseItemViewHolder(
+ R.layout.item_view_daily_forecast, parent
+ ) {
+
+ var forecast: Forecast? = null
+
+ override fun injectDependencies(viewHolderComponent: ViewHolderComponent) {
+ viewHolderComponent.inject(this)
+ }
+
+ override fun setupObservers() {
+ super.setupObservers()
+ viewModel.time.observe(this, Observer {
+ itemView.tv_day.text = when (position) {
+ 0 -> itemView.context.getString(R.string.today)
+ 1 -> itemView.context.getString(R.string.tomorrow)
+ else -> it
+ }
+ })
+
+ viewModel.feelsLike.observe(this, Observer {
+ itemView.tv_feels_like.text = it
+ })
+
+ viewModel.icon.observe(this, Observer {
+ itemView.iv_image.setImageResource(it)
+ })
+
+ viewModel.temp.observe(this, Observer {
+ itemView.tv_temp.text = it
+ })
+
+ viewModel.forecastItem.observe(this, Observer {
+ forecast = it
+ })
+ }
+
+ override fun setupView(view: View) {
+ view.setOnClickListener {
+ clickListener?.onItemClicked(Gson().toJson(forecast))
+ }
+ val androidColors = this.itemView.resources.getIntArray(R.array.cardColors)
+ val randomAndroidColor = androidColors[Random().nextInt(androidColors.size)]
+ itemView.card_view.setBackgroundColor(randomAndroidColor)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/DailyForecastItemViewModel.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/DailyForecastItemViewModel.kt
new file mode 100644
index 00000000..9900ca01
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/DailyForecastItemViewModel.kt
@@ -0,0 +1,46 @@
+package com.sneha.weatherapp.ui.weather.adapter
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
+import com.sneha.weatherapp.data.model.Forecast
+import com.sneha.weatherapp.ui.base.BaseItemViewModel
+import com.sneha.weatherapp.utils.common.TimeUtils
+import com.sneha.weatherapp.utils.display.WeatherToImage
+import com.sneha.weatherapp.utils.log.Logger
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import io.reactivex.disposables.CompositeDisposable
+import javax.inject.Inject
+
+class DailyForecastItemViewModel @Inject constructor(
+ schedulerProvider: SchedulerProvider,
+ compositeDisposable: CompositeDisposable,
+ networkHelper: NetworkHelper
+) : BaseItemViewModel(
+ schedulerProvider,
+ compositeDisposable,
+ networkHelper
+) {
+
+ companion object {
+ const val TAG = "DailyForecastItemViewModel"
+ }
+
+ val forecastItem: LiveData = Transformations.map(data) {it}
+ val time: LiveData =
+ Transformations.map(data) { TimeUtils.getTimeInExpectedFormat(it.dt, "EEE") }
+ val feelsLike: LiveData = Transformations.map(data) { it.weather[0].description }
+ val icon: LiveData = Transformations.map(data) { WeatherToImage.getImageForCode(it.weather[0].icon) }
+ val temp: LiveData = Transformations.map(data) {
+ "${it.temp.min.toInt()}℃ / ${it.temp.max.toInt()}℃"
+ }
+
+ fun onItemClick(position: Int) {
+// messageString.postValue(Resource.success("onItemClick at $position of ${data.value?.temp?.max?.toInt()}"))
+// Logger.d(TAG, "onItemClick at $position")
+ }
+
+ override fun onCreate() {
+ Logger.d(TAG, "onCreate called")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/ForecastAdapter.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/ForecastAdapter.kt
new file mode 100644
index 00000000..ade1848c
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/ForecastAdapter.kt
@@ -0,0 +1,16 @@
+package com.sneha.weatherapp.ui.weather.adapter
+
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import com.sneha.weatherapp.data.model.ForecastItem
+import com.sneha.weatherapp.ui.base.BaseAdapter
+
+class ForecastAdapter(
+ parentLifecycle: Lifecycle,
+ private val forecasts: ArrayList
+) : BaseAdapter(parentLifecycle, forecasts) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ForecastItemViewHolder(parent)
+
+ override fun getItemCount(): Int = if(forecasts.size > 10) 10 else forecasts.size
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/ForecastItemViewHolder.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/ForecastItemViewHolder.kt
new file mode 100644
index 00000000..faa9d676
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/ForecastItemViewHolder.kt
@@ -0,0 +1,41 @@
+package com.sneha.weatherapp.ui.weather.adapter
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.Observer
+import com.sneha.weatherapp.R
+import com.sneha.weatherapp.data.model.ForecastItem
+import com.sneha.weatherapp.di.component.ViewHolderComponent
+import com.sneha.weatherapp.ui.base.BaseItemViewHolder
+import com.sneha.weatherapp.utils.display.WeatherToImage
+import kotlinx.android.synthetic.main.item_view_forecast.view.*
+
+class ForecastItemViewHolder(parent: ViewGroup) :
+ BaseItemViewHolder(R.layout.item_view_forecast, parent) {
+
+ override fun injectDependencies(viewHolderComponent: ViewHolderComponent) {
+ viewHolderComponent.inject(this)
+ }
+
+ override fun setupObservers() {
+ super.setupObservers()
+
+ viewModel.time.observe(this, Observer {
+ itemView.tv_time.text = it
+ })
+
+ viewModel.feelsLike.observe(this, Observer {
+ itemView.tv_feels_like.text = it
+ })
+
+ viewModel.icon.observe(this, Observer {
+ itemView.iv_image.setImageResource(WeatherToImage.getImageForCode(it))
+ })
+ }
+
+ override fun setupView(view: View) {
+ view.setOnClickListener {
+ viewModel.onItemClick(adapterPosition)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/ForecastItemViewModel.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/ForecastItemViewModel.kt
new file mode 100644
index 00000000..eeb9d752
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/adapter/ForecastItemViewModel.kt
@@ -0,0 +1,41 @@
+package com.sneha.weatherapp.ui.weather.adapter
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
+import com.sneha.weatherapp.data.model.ForecastItem
+import com.sneha.weatherapp.ui.base.BaseItemViewModel
+import com.sneha.weatherapp.utils.common.TimeUtils
+import com.sneha.weatherapp.utils.log.Logger
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import io.reactivex.disposables.CompositeDisposable
+import javax.inject.Inject
+
+class ForecastItemViewModel @Inject constructor(
+ schedulerProvider: SchedulerProvider,
+ compositeDisposable: CompositeDisposable,
+ networkHelper: NetworkHelper
+) : BaseItemViewModel(
+ schedulerProvider,
+ compositeDisposable,
+ networkHelper
+) {
+
+ companion object {
+ const val TAG = "ForecastItemViewModel"
+ }
+
+ val time: LiveData =
+ Transformations.map(data) { TimeUtils.changeDateToHourFormat(it.dt_txt) }
+ val feelsLike: LiveData = Transformations.map(data) { it.weather[0].description }
+ val icon: LiveData = Transformations.map(data) { it.weather[0].icon }
+
+ fun onItemClick(position: Int) {
+// messageString.postValue(Resource.success("onItemClick at $position of ${data.value?.time}"))
+// Logger.d(TAG, "onItemClick at $position")
+ }
+
+ override fun onCreate() {
+ Logger.d(TAG, "onCreate called")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/fragment/WeatherFragment.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/fragment/WeatherFragment.kt
new file mode 100644
index 00000000..faf983d1
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/fragment/WeatherFragment.kt
@@ -0,0 +1,37 @@
+package com.sneha.weatherapp.ui.weather.fragment
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.Observer
+import com.sneha.weatherapp.R
+import com.sneha.weatherapp.di.component.FragmentComponent
+import com.sneha.weatherapp.ui.base.BaseFragment
+
+class WeatherFragment : BaseFragment() {
+
+ companion object {
+
+ const val TAG = "WeatherFragment"
+
+ fun newInstance(): WeatherFragment {
+ val args = Bundle()
+ val fragment = WeatherFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+
+ override fun provideLayoutId(): Int = R.layout.fragment_weather
+
+ override fun injectDependencies(fragmentComponent: FragmentComponent) {
+ fragmentComponent.inject(this)
+ }
+
+ override fun setupObservers() {
+ viewModel.getWeatherData().observe(this, Observer {
+ })
+ }
+
+ override fun setupView(view: View) {
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/ui/weather/fragment/WeatherFragmentViewModel.kt b/app/src/main/java/com/sneha/weatherapp/ui/weather/fragment/WeatherFragmentViewModel.kt
new file mode 100644
index 00000000..dd15bd8a
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/ui/weather/fragment/WeatherFragmentViewModel.kt
@@ -0,0 +1,46 @@
+package com.sneha.weatherapp.ui.weather.fragment
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
+import com.sneha.weatherapp.data.model.Weather
+import com.sneha.weatherapp.data.repository.WeatherRepository
+import com.sneha.weatherapp.ui.base.BaseViewModel
+import com.sneha.weatherapp.utils.common.Resource
+import com.sneha.weatherapp.utils.common.Status
+import com.sneha.weatherapp.utils.network.NetworkHelper
+import com.sneha.weatherapp.utils.rx.SchedulerProvider
+import io.reactivex.disposables.CompositeDisposable
+
+
+class WeatherFragmentViewModel(
+ schedulerProvider: SchedulerProvider,
+ compositeDisposable: CompositeDisposable,
+ networkHelper: NetworkHelper,
+ private val weatherRepository: WeatherRepository
+) : BaseViewModel(schedulerProvider, compositeDisposable, networkHelper) {
+
+ private val weatherLiveData: MutableLiveData> = MutableLiveData()
+
+ fun getWeatherData(): LiveData =
+ Transformations.map(weatherLiveData) { it.data }
+
+ fun isWeatherFetching(): LiveData =
+ Transformations.map(weatherLiveData) { it.status == Status.LOADING }
+
+ override fun onCreate() {
+ if (weatherLiveData.value == null && checkInternetConnectionWithMessage()) {
+ weatherLiveData.postValue(Resource.loading())
+ compositeDisposable.add(
+ weatherRepository.fetchWeatherData()
+ .subscribeOn(schedulerProvider.io())
+ .subscribe(
+ { weatherLiveData.postValue(Resource.success(it)) },
+ {
+ handleNetworkError(it)
+ weatherLiveData.postValue(Resource.error())
+ })
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/ViewModelProviderFactory.kt b/app/src/main/java/com/sneha/weatherapp/utils/ViewModelProviderFactory.kt
new file mode 100644
index 00000000..7fd84883
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/ViewModelProviderFactory.kt
@@ -0,0 +1,32 @@
+package com.sneha.weatherapp.utils
+
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import javax.inject.Singleton
+import kotlin.reflect.KClass
+
+/**
+ * When ViewModel require parameters in the constructor then ViewModelProviders.of(activity).get(ViewModel.class) do not work
+ * In this case we need to provide our own ViewModelProvider's Factory.
+ * create method is called by Android Framework when it needs to create a ViewModel instance.
+ * NOTE: When activity rotates then create method is not called but earlier instance of ViewModel is returned.
+ * that is why creator is provided here so that Android Framework can create the ViewModel instance according to its need.
+ * @T: It says that the ViewModelProviderFactory works with variable of type SplashViewModel
+ * Example: T -> SplashViewModel,
+ *
+ */
+@Singleton
+class ViewModelProviderFactory(
+ private val kClass: KClass, // KClass is the holder of class of type ViewModel that needs to be inject
+ private val creator: () -> T // This is the Lambda function, this is provided be the ActivityModule/FragmentModule,
+ // when creator lambda is called then that module creates and return the instance of ViewModel
+) : ViewModelProvider.NewInstanceFactory() {
+
+ @Suppress("UNCHECKED_CAST")
+ @Throws(IllegalArgumentException::class)
+ override fun create(modelClass: Class): T {
+ if (modelClass.isAssignableFrom(kClass.java)) return creator() as T
+ throw IllegalArgumentException("Unknown class time")
+ }
+}
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/common/Constants.kt b/app/src/main/java/com/sneha/weatherapp/utils/common/Constants.kt
new file mode 100644
index 00000000..a325a2e9
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/common/Constants.kt
@@ -0,0 +1,5 @@
+package com.sneha.weatherapp.utils.common
+
+object Constants {
+ const val NULL_INDEX = -1L
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/common/Event.kt b/app/src/main/java/com/sneha/weatherapp/utils/common/Event.kt
new file mode 100644
index 00000000..0f127e61
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/common/Event.kt
@@ -0,0 +1,21 @@
+package com.sneha.weatherapp.utils.common
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * Used as a wrapper for data that is exposed via a LiveData that represents an event.
+ */
+data class Event(private val content: T) {
+
+ private var hasBeenHandled = AtomicBoolean(false)
+
+ /**
+ * Returns the content and prevents its use again.
+ */
+ fun getIfNotHandled(): T? = if (hasBeenHandled.getAndSet(true)) null else content
+
+ /**
+ * Returns the content, even if it's already been handled.
+ */
+ fun peek(): T = content
+}
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/common/LiveDataUtils.kt b/app/src/main/java/com/sneha/weatherapp/utils/common/LiveDataUtils.kt
new file mode 100644
index 00000000..b374a782
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/common/LiveDataUtils.kt
@@ -0,0 +1,46 @@
+package com.sneha.weatherapp.utils.common
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+
+
+/**
+ * This function creates a [LiveData] of a [Pair] of the two types provided. The resulting LiveData is updated whenever either input LiveData updates and both LiveData have updated at least once before.
+ *
+ * If the combination of A and B is C, and A and B are updated in this pattern: `AABA`, C would be updated twice (once with the second A value and first B value, and once with the third A value and first B value).
+ *
+ * @param a the first LiveData
+ * @param b the second LiveData
+ * @author Mitchell Skaggs https://gist.github.com/magneticflux-/044c9d7a3cea431aa0e4f4f4950a2898
+ */
+fun combineLatest(a: LiveData, b: LiveData): MutableLiveData> {
+ return MediatorLiveData>().apply {
+ var lastA: A? = null
+ var lastB: B? = null
+
+ fun update() {
+ val localLastA = lastA
+ val localLastB = lastB
+ if (localLastA != null && localLastB != null)
+ this.value = Pair(localLastA, localLastB)
+ }
+
+ addSource(a) {
+ lastA = it
+ update()
+ }
+ addSource(b) {
+ lastB = it
+ update()
+ }
+ }
+}
+
+/**
+ * This is merely an extension function for [combineLatest].
+ *
+ * @see combineLatest
+ * @author Mitchell Skaggs
+ */
+fun LiveData.combineLatestWith(b: LiveData): LiveData> = combineLatest(this, b)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/common/LoadMoreListener.kt b/app/src/main/java/com/sneha/weatherapp/utils/common/LoadMoreListener.kt
new file mode 100644
index 00000000..f27f68b9
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/common/LoadMoreListener.kt
@@ -0,0 +1,6 @@
+package com.sneha.weatherapp.utils.common
+
+interface LoadMoreListener {
+
+ fun onLoadMore()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/common/Resource.kt b/app/src/main/java/com/sneha/weatherapp/utils/common/Resource.kt
new file mode 100644
index 00000000..d1e669ee
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/common/Resource.kt
@@ -0,0 +1,14 @@
+package com.sneha.weatherapp.utils.common
+
+data class Resource private constructor(val status: Status, val data: T?) {
+
+ companion object {
+ fun success(data: T? = null): Resource = Resource(Status.SUCCESS, data)
+
+ fun error(data: T? = null): Resource = Resource(Status.ERROR, data)
+
+ fun loading(data: T? = null): Resource = Resource(Status.LOADING, data)
+
+ fun unknown(data: T? = null): Resource = Resource(Status.UNKNOWN, data)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/common/Status.kt b/app/src/main/java/com/sneha/weatherapp/utils/common/Status.kt
new file mode 100644
index 00000000..63d7b9e0
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/common/Status.kt
@@ -0,0 +1,8 @@
+package com.sneha.weatherapp.utils.common
+
+enum class Status {
+ SUCCESS,
+ ERROR,
+ LOADING,
+ UNKNOWN
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/common/TimeUtils.kt b/app/src/main/java/com/sneha/weatherapp/utils/common/TimeUtils.kt
new file mode 100644
index 00000000..54eed78a
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/common/TimeUtils.kt
@@ -0,0 +1,59 @@
+package com.sneha.weatherapp.utils.common
+
+import android.text.format.DateUtils
+import java.text.SimpleDateFormat
+import java.util.*
+
+object TimeUtils {
+
+ private const val SECOND_MILLIS = 1000
+ private const val MINUTE_MILLIS = 60 * SECOND_MILLIS
+ private const val HOUR_MILLIS = 60 * MINUTE_MILLIS
+ private const val DAY_MILLIS = 24 * HOUR_MILLIS
+
+ fun getTimeAgo(time: Long): String {
+
+ val time = Date(time * 1000L).time
+
+ val now = System.currentTimeMillis()
+ if (time > now || time <= 0) return ""
+
+ val diff = now - time
+ return if (diff < MINUTE_MILLIS) {
+ "just now"
+ } else if (diff < 2 * MINUTE_MILLIS) {
+ "a minute ago"
+ } else if (diff < 50 * MINUTE_MILLIS) {
+ "${diff / MINUTE_MILLIS} minutes ago"
+ } else if (diff < 90 * MINUTE_MILLIS) {
+ "an hour ago"
+ } else if (diff < 24 * HOUR_MILLIS) {
+ "${diff / HOUR_MILLIS} hours ago"
+ } else if (diff < 48 * HOUR_MILLIS) {
+ "yesterday"
+ } else {
+ "${diff / DAY_MILLIS} days ago"
+ }
+ }
+
+ fun getTimeInExpectedFormat(dt: Long, format: String): String {
+ val date = Date(dt * 1000L)
+ val sdf: SimpleDateFormat = when (format) {
+ "HH:mm" -> SimpleDateFormat("hh:mm", Locale.US)
+ "EEE" -> SimpleDateFormat("EEE", Locale.US)
+ "EEEE" -> SimpleDateFormat("EEEE", Locale.US)
+ else -> SimpleDateFormat("yyyy-MM-dd", Locale.US)
+ }
+ sdf.timeZone = TimeZone.getTimeZone("UTC")
+ return sdf.format(date)
+ }
+
+ fun changeDateToHourFormat(date: String): String {
+ val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
+ val expectedDate = sdf.parse(date)
+
+ val formatter = SimpleDateFormat("HH:mm", Locale.US)
+ val format = formatter.format(expectedDate)
+ return format
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/display/ScreenUtils.kt b/app/src/main/java/com/sneha/weatherapp/utils/display/ScreenUtils.kt
new file mode 100644
index 00000000..feb427da
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/display/ScreenUtils.kt
@@ -0,0 +1,10 @@
+package com.sneha.weatherapp.utils.display
+
+import android.content.res.Resources
+
+object ScreenUtils {
+
+ fun getScreenWidth() = Resources.getSystem().displayMetrics.widthPixels
+
+ fun getScreenHeight() = Resources.getSystem().displayMetrics.heightPixels
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/display/Toaster.kt b/app/src/main/java/com/sneha/weatherapp/utils/display/Toaster.kt
new file mode 100644
index 00000000..325da30a
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/display/Toaster.kt
@@ -0,0 +1,19 @@
+package com.sneha.weatherapp.utils.display
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import com.sneha.weatherapp.R
+
+object Toaster {
+ fun show(context: Context, text: CharSequence) {
+ val toast = android.widget.Toast.makeText(context, text, android.widget.Toast.LENGTH_SHORT)
+ toast.view.background.setColorFilter(
+ ContextCompat.getColor(context, R.color.white), PorterDuff.Mode.SRC_IN
+ )
+ val textView = toast.view.findViewById(android.R.id.message) as TextView
+ textView.setTextColor(ContextCompat.getColor(context, R.color.black))
+ toast.show()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/display/WeatherToImage.kt b/app/src/main/java/com/sneha/weatherapp/utils/display/WeatherToImage.kt
new file mode 100644
index 00000000..e2579a1a
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/display/WeatherToImage.kt
@@ -0,0 +1,41 @@
+package com.sneha.weatherapp.utils.display
+
+import com.sneha.weatherapp.R
+
+object WeatherToImage {
+ fun getImageForCode(code: String): Int = when (code) {
+ "01d" -> {
+ R.drawable.ic_clear_sky
+ }
+ "01n" -> {
+ R.drawable.ic_clear_sky_night
+ }
+ "02d", "02n" -> {
+ R.drawable.ic_few_clouds
+ }
+ "03d", "03n" -> {
+ R.drawable.ic_broken_cloud
+ }
+ "04d", "04n" -> {
+ R.drawable.ic_broken_cloud
+ }
+ "09d", "09n" -> {
+ R.drawable.ic_shower_rain
+ }
+ "10d", "10n" -> {
+ R.drawable.ic_rain
+ }
+ "11d", "11n" -> {
+ R.drawable.ic_thunderstorm
+ }
+ "13d", "13n" -> {
+ R.drawable.ic_snow
+ }
+ "50d", "50n" -> {
+ R.drawable.ic_mist
+ }
+ else -> {
+ R.drawable.ic_broken_cloud
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/log/Logger.kt b/app/src/main/java/com/sneha/weatherapp/utils/log/Logger.kt
new file mode 100644
index 00000000..ad36429d
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/log/Logger.kt
@@ -0,0 +1,36 @@
+package com.sneha.weatherapp.utils.log
+
+import com.sneha.weatherapp.BuildConfig
+import timber.log.Timber
+
+object Logger {
+
+ init {
+ if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
+ }
+
+ fun d(tag: String, s: String, vararg params: Any) =
+ Timber.tag(tag).d(s, params)
+
+ fun d(tag: String, throwable: Throwable, s: String, vararg params: Any) =
+ Timber.tag(tag).d(throwable, s, params)
+
+ fun i(tag: String, s: String, vararg params: Any) =
+ Timber.tag(tag).i(s, params)
+
+ fun i(tag: String, throwable: Throwable, s: String, vararg params: Any) =
+ Timber.tag(tag).i(throwable, s, params)
+
+ fun w(tag: String, s: String, vararg params: Any) =
+ Timber.tag(tag).w(s, params)
+
+ fun w(tag: String, throwable: Throwable, s: String, vararg params: Any) =
+ Timber.tag(tag).w(throwable, s, params)
+
+ fun e(tag: String, s: String, vararg params: Any) =
+ Timber.tag(tag).e(s, params)
+
+ fun e(tag: String, throwable: Throwable, s: String, vararg params: Any) =
+ Timber.tag(tag).e(throwable, s, params)
+
+}
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/network/NetworkError.kt b/app/src/main/java/com/sneha/weatherapp/utils/network/NetworkError.kt
new file mode 100644
index 00000000..85cf0c94
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/network/NetworkError.kt
@@ -0,0 +1,14 @@
+package com.sneha.weatherapp.utils.network
+
+import com.google.gson.annotations.Expose
+import com.google.gson.annotations.SerializedName
+
+data class NetworkError(
+ val status: Int = -1,
+ @Expose
+ @SerializedName("statusCode")
+ val statusCode: String = "-1",
+ @Expose
+ @SerializedName("message")
+ val message: String = "Something went wrong"
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/network/NetworkHelper.kt b/app/src/main/java/com/sneha/weatherapp/utils/network/NetworkHelper.kt
new file mode 100644
index 00000000..535e7e92
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/network/NetworkHelper.kt
@@ -0,0 +1,45 @@
+package com.sneha.weatherapp.utils.network
+
+import android.content.Context
+import android.net.ConnectivityManager
+import com.google.gson.GsonBuilder
+import com.google.gson.JsonSyntaxException
+import com.jakewharton.retrofit2.adapter.rxjava2.HttpException
+import com.sneha.weatherapp.utils.log.Logger
+import java.io.IOException
+import java.net.ConnectException
+import javax.inject.Singleton
+
+@Singleton
+class NetworkHelper constructor(private val context: Context) {
+
+ companion object {
+ private const val TAG = "NetworkHelper"
+ }
+
+ fun isNetworkConnected(): Boolean {
+ val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val activeNetwork = cm.activeNetworkInfo
+ return activeNetwork?.isConnected ?: false
+ }
+
+ fun castToNetworkError(throwable: Throwable): NetworkError {
+ val defaultNetworkError = NetworkError()
+ try {
+ if (throwable is ConnectException) return NetworkError(0, "0")
+ if (throwable !is HttpException) return defaultNetworkError
+ val error = GsonBuilder()
+ .excludeFieldsWithoutExposeAnnotation()
+ .create()
+ .fromJson(throwable.response().errorBody()?.string(), NetworkError::class.java)
+ return NetworkError(throwable.code(), error.statusCode, error.message)
+ } catch (e: IOException) {
+ Logger.e(TAG, e.toString())
+ } catch (e: JsonSyntaxException) {
+ Logger.e(TAG, e.toString())
+ } catch (e: NullPointerException) {
+ Logger.e(TAG, e.toString())
+ }
+ return defaultNetworkError
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/rx/RxSchedulerProvider.kt b/app/src/main/java/com/sneha/weatherapp/utils/rx/RxSchedulerProvider.kt
new file mode 100644
index 00000000..3f50435e
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/rx/RxSchedulerProvider.kt
@@ -0,0 +1,16 @@
+package com.sneha.weatherapp.utils.rx
+
+import io.reactivex.Scheduler
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import javax.inject.Singleton
+
+@Singleton
+class RxSchedulerProvider : SchedulerProvider {
+
+ override fun computation(): Scheduler = Schedulers.computation()
+
+ override fun io(): Scheduler = Schedulers.io()
+
+ override fun ui(): Scheduler = AndroidSchedulers.mainThread()
+}
diff --git a/app/src/main/java/com/sneha/weatherapp/utils/rx/SchedulerProvider.kt b/app/src/main/java/com/sneha/weatherapp/utils/rx/SchedulerProvider.kt
new file mode 100644
index 00000000..c2c5aa60
--- /dev/null
+++ b/app/src/main/java/com/sneha/weatherapp/utils/rx/SchedulerProvider.kt
@@ -0,0 +1,14 @@
+package com.sneha.weatherapp.utils.rx
+
+import io.reactivex.Scheduler
+import javax.inject.Singleton
+
+@Singleton
+interface SchedulerProvider {
+
+ fun computation(): Scheduler
+
+ fun io(): Scheduler
+
+ fun ui(): Scheduler
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..1f6bb290
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/border.xml b/app/src/main/res/drawable/border.xml
new file mode 100644
index 00000000..fbb881d7
--- /dev/null
+++ b/app/src/main/res/drawable/border.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/chip.xml b/app/src/main/res/drawable/chip.xml
new file mode 100644
index 00000000..e369b5b1
--- /dev/null
+++ b/app/src/main/res/drawable/chip.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_broken_cloud.xml b/app/src/main/res/drawable/ic_broken_cloud.xml
new file mode 100644
index 00000000..c9454ea4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_broken_cloud.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml
new file mode 100644
index 00000000..ef839937
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cancel.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_clear_sky.xml b/app/src/main/res/drawable/ic_clear_sky.xml
new file mode 100644
index 00000000..37a2afcd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_clear_sky.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_clear_sky_night.xml b/app/src/main/res/drawable/ic_clear_sky_night.xml
new file mode 100644
index 00000000..99c95921
--- /dev/null
+++ b/app/src/main/res/drawable/ic_clear_sky_night.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_few_clouds.xml b/app/src/main/res/drawable/ic_few_clouds.xml
new file mode 100644
index 00000000..d87fb31e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_few_clouds.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_humidity.xml b/app/src/main/res/drawable/ic_humidity.xml
new file mode 100644
index 00000000..aa7c4e27
--- /dev/null
+++ b/app/src/main/res/drawable/ic_humidity.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml
new file mode 100644
index 00000000..88037e0c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_location.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_mist.xml b/app/src/main/res/drawable/ic_mist.xml
new file mode 100644
index 00000000..33fed52d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_mist.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_pressure.xml b/app/src/main/res/drawable/ic_pressure.xml
new file mode 100644
index 00000000..3818bd94
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pressure.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_rain.xml b/app/src/main/res/drawable/ic_rain.xml
new file mode 100644
index 00000000..e70b422e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_rain.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_scattered_clouds.xml b/app/src/main/res/drawable/ic_scattered_clouds.xml
new file mode 100644
index 00000000..c9454ea4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_scattered_clouds.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_shower_rain.xml b/app/src/main/res/drawable/ic_shower_rain.xml
new file mode 100644
index 00000000..f111478e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shower_rain.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_snow.xml b/app/src/main/res/drawable/ic_snow.xml
new file mode 100644
index 00000000..86554d49
--- /dev/null
+++ b/app/src/main/res/drawable/ic_snow.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_sunrise.xml b/app/src/main/res/drawable/ic_sunrise.xml
new file mode 100644
index 00000000..cfa0fd29
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sunrise.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_sunset.xml b/app/src/main/res/drawable/ic_sunset.xml
new file mode 100644
index 00000000..3bc20543
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sunset.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_thunderstorm.xml b/app/src/main/res/drawable/ic_thunderstorm.xml
new file mode 100644
index 00000000..4ee43200
--- /dev/null
+++ b/app/src/main/res/drawable/ic_thunderstorm.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_wind.xml b/app/src/main/res/drawable/ic_wind.xml
new file mode 100644
index 00000000..0d6e29c0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wind.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/app/src/main/res/font/font_family.xml b/app/src/main/res/font/font_family.xml
new file mode 100644
index 00000000..6da3c75f
--- /dev/null
+++ b/app/src/main/res/font/font_family.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/font/proxima_nova_bold.ttf b/app/src/main/res/font/proxima_nova_bold.ttf
new file mode 100644
index 00000000..22380f5c
Binary files /dev/null and b/app/src/main/res/font/proxima_nova_bold.ttf differ
diff --git a/app/src/main/res/font/proxima_nova_light.ttf b/app/src/main/res/font/proxima_nova_light.ttf
new file mode 100644
index 00000000..4aa51238
Binary files /dev/null and b/app/src/main/res/font/proxima_nova_light.ttf differ
diff --git a/app/src/main/res/font/proxima_nova_medium.ttf b/app/src/main/res/font/proxima_nova_medium.ttf
new file mode 100644
index 00000000..865211d2
Binary files /dev/null and b/app/src/main/res/font/proxima_nova_medium.ttf differ
diff --git a/app/src/main/res/font/proxima_nova_regular.ttf b/app/src/main/res/font/proxima_nova_regular.ttf
new file mode 100644
index 00000000..bfbb06b6
Binary files /dev/null and b/app/src/main/res/font/proxima_nova_regular.ttf differ
diff --git a/app/src/main/res/layout/activity_location.xml b/app/src/main/res/layout/activity_location.xml
new file mode 100644
index 00000000..ed144af8
--- /dev/null
+++ b/app/src/main/res/layout/activity_location.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_selected_day.xml b/app/src/main/res/layout/activity_selected_day.xml
new file mode 100644
index 00000000..e54acc58
--- /dev/null
+++ b/app/src/main/res/layout/activity_selected_day.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml
new file mode 100644
index 00000000..4b85a3a7
--- /dev/null
+++ b/app/src/main/res/layout/activity_splash.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_weather.xml b/app/src/main/res/layout/activity_weather.xml
new file mode 100644
index 00000000..facb67cf
--- /dev/null
+++ b/app/src/main/res/layout/activity_weather.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_weather.xml b/app/src/main/res/layout/fragment_weather.xml
new file mode 100644
index 00000000..6be97ca2
--- /dev/null
+++ b/app/src/main/res/layout/fragment_weather.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_view_daily_forecast.xml b/app/src/main/res/layout/item_view_daily_forecast.xml
new file mode 100644
index 00000000..05a13535
--- /dev/null
+++ b/app/src/main/res/layout/item_view_daily_forecast.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_view_forecast.xml b/app/src/main/res/layout/item_view_forecast.xml
new file mode 100644
index 00000000..72a42d73
--- /dev/null
+++ b/app/src/main/res/layout/item_view_forecast.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_weather_details.xml b/app/src/main/res/layout/layout_weather_details.xml
new file mode 100644
index 00000000..5d1be646
--- /dev/null
+++ b/app/src/main/res/layout/layout_weather_details.xml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/progress_bar.xml b/app/src/main/res/layout/progress_bar.xml
new file mode 100644
index 00000000..2383a7e3
--- /dev/null
+++ b/app/src/main/res/layout/progress_bar.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..63ec933b
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..12936143
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..e905a115
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..c66e54d8
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..60bfdc09
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..448461e5
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..89da6aac
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..f33b4df1
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..910a995d
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..72bad8cb
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..7f6e2ab1
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,20 @@
+
+
+ #000000
+
+ #0093c4
+ #4fc3f7
+ #81d4fa
+ #00acc1
+ #00acc1
+ #616161
+ #e0e0e0
+ #FFFFFF
+
+
+ - @color/blueDark
+ - @color/blueMedium
+ - @color/cyanDark
+ - @color/cyanMedium
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..c7f648d5
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,38 @@
+
+ Weather
+
+ Internet connection lost, please retry again
+ Something wrong happened, please retry again
+ Our systems got an error, please retry again
+ Our services are under maintenance, please retry after some time
+
+ Not able to connect, please retry again
+ Please log in again
+ Forecast for 7 days
+ Forecast today
+ Today
+ Tomorrow
+ Clouds
+ Sunset
+ Sunrise
+ Wind speed
+ Humidity
+ Pressure
+ Stockholm, SE
+ Updated
+ Expected
+ Weather details
+ Temperature
+
+
+ Longitude: %.3f, Latitude: %.3f
+ The GPS signal is unavailable. Please turn Location on.
+ App requires location permission to show your city weather
+ locate
+ Enable GPS
+ LOCATE CURRENT CITY
+ Turn on GPS
+ GPS Enabled
+ GPS Disabled
+ Loading weather data for your city...
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..7d216760
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/com/sneha/weatherapp/ExampleUnitTest.kt b/app/src/test/java/com/sneha/weatherapp/ExampleUnitTest.kt
new file mode 100644
index 00000000..beeb361e
--- /dev/null
+++ b/app/src/test/java/com/sneha/weatherapp/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.sneha.weatherapp
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..d9a6cd5f
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,90 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ ext.kotlin_version = '1.3.61'
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.3'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+//task clean(type: Delete) {
+// delete rootProject.buildDir
+//}
+
+ext{
+ // Sdk and Tools
+ min_sdk_version = 21
+ target_sdk_version = 29
+ compile_sdk_version = 29
+
+ // Core
+ app_compat_version = '1.1.0'
+ support_library_version = '1.0.0'
+ build_tool_version = '29.0.2'
+ constraint_version = '1.1.3'
+
+ // Lifecycle
+ lifecycle_version = '2.0.0'
+
+ // Reactive
+ rxjava_version = '2.2.3'
+ rxandroid_version = '2.1.1'
+
+ // Parser
+ gson_version = '2.8.5'
+
+ // Logging
+ timber_version = '4.7.1'
+
+ // DB
+ room_version = '2.0.0'
+ debug_db_version = '1.0.6'
+
+ // Networking
+ retrofit_version = '2.4.0'
+ okhttp_version = '3.11.0'
+ retrofit_adapter_version = '1.1.0'
+
+ // DB
+ room_version = '2.0.0'
+ debug_db_version = '1.0.6'
+
+ // Dagger
+ dagger_version = '2.21'
+
+ //Multidex
+ retrofit_version = '2.0.1'
+
+ //RefreshLayout
+ refresh_layout_version = '1.0.0'
+
+ //Gson
+ gson_version = '2.8.5'
+
+ // Tests
+ junit_version = '4.12'
+ espresso_version = '3.1.0'
+ mockito_version = '2.7.1'
+ core_testing_version = '1.1.1'
+ test_runner_version = '1.1.1'
+ test_ext_runner_version = '1.1.0'
+ hamcrest_version = '2.1'
+
+
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..23339e0d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# 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
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..f6b961fd
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..2edaae85
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Jan 30 22:40:59 IST 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 00000000..cccdd3d5
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..e2b911f9
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name='WeatherApp'