diff --git a/app/build.gradle b/app/build.gradle index ec36b6a..d92dbf7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,17 +1,19 @@ apply plugin: 'com.android.application' +apply plugin: 'me.tatarka.retrolambda' android { - compileSdkVersion versions.compileSdk - buildToolsVersion versions.buildTools + compileSdkVersion 25 + buildToolsVersion "25.0.3" + defaultConfig { applicationId "com.ikholopov.yamblz.weather.weathermobilization" - minSdkVersion versions.minSdk - targetSdkVersion versions.targetSdk - versionCode versions.version - versionName versions.versionName - resValue "string", "api_key", (project.findProperty("API_KEY") ?: "") - resValue "string", "google_api_key", (project.findProperty("GOOGLE_API_KEY") ?: "") - resValue "string", "location", (project.findProperty("LOCATION") ?: "") + minSdkVersion 16 + targetSdkVersion 25 + versionCode 2 + versionName "2.0.0" + + buildConfigField "String", "GOOGLE_API_KEY", (project.findProperty("GOOGLE_API_KEY") ?: "") + buildConfigField "String", "OPENWEATHERMAP_API_KEY", (project.findProperty("OPENWEATHERMAP_API_KEY") ?: "") testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' } @@ -37,43 +39,82 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } +ext.versions = [ + supportLibs : "25.3.1", + constraintLayout : "1.0.2", + room : "1.0.0-alpha5", + + threetenabp : "1.0.5", + butterKnife : "8.7.0", + + toggleButtonGroup : "1.0.2", + dagger : "2.11", + + gson : "2.8.1", + rxjava : "2.1.1", + rxandroid : "2.0.1", + rxbinding : "2.0.0", + retrofit : "2.3.0", + + junit : "4.12", + mockito : "1.10.19", + assertj : "2.8.0", + jUnitParams : "1.1.0", + robolectric : "3.3.2", + espresso : "2.2.2", + + leakCanary : "1.5.1" +] + dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile libraries.supportAppCompat - compile libraries.supportDesign - compile libraries.supportPreference - compile libraries.supportConstraintLayout - compile libraries.support - - compile libraries.seekbar - - compile libraries.butterKnife - annotationProcessor libraries.butterKnifeCompiler - - compile libraries.dagger - annotationProcessor libraries.daggerCompiler - - debugCompile libraries.leakCanaryDebug - releaseCompile libraries.leakCanaryRelease - - testCompile libraries.junit - testCompile libraries.mockito - testCompile libraries.assertj - testCompile libraries.jUnitParams - testCompile libraries.robolectric - - compile libraries.gson - compile libraries.rxjava - compile libraries.rxandroid - compile libraries.rxbinding - compile libraries.retrofit - compile libraries.converterGson - compile libraries.adapterRxjava2 - - androidTestCompile libraries.assertj - androidTestCompile libraries.espresso, { + + compile "com.android.support:appcompat-v7:$versions.supportLibs" + compile "com.android.support:design:$versions.supportLibs" + compile "com.android.support.constraint:constraint-layout:$versions.constraintLayout" + compile "com.android.support:support-v4:$versions.supportLibs" + compile "com.android.support:cardview-v7:$versions.supportLibs" + + compile "android.arch.persistence.room:runtime:$versions.room" + compile "android.arch.persistence.room:rxjava2:$versions.room" + annotationProcessor "android.arch.persistence.room:compiler:$versions.room" + + compile "com.nex3z:toggle-button-group:$versions.toggleButtonGroup" + + compile "com.jakewharton.threetenabp:threetenabp:$versions.threetenabp" + compile "com.jakewharton:butterknife:$versions.butterKnife" + annotationProcessor "com.jakewharton:butterknife-compiler:$versions.butterKnife" + + compile "com.google.dagger:dagger:$versions.dagger" + annotationProcessor "com.google.dagger:dagger-compiler:$versions.dagger" + + compile "com.google.code.gson:gson:$versions.gson" + compile "io.reactivex.rxjava2:rxjava:$versions.rxjava" + compile "io.reactivex.rxjava2:rxandroid:$versions.rxandroid" + compile "com.jakewharton.rxbinding2:rxbinding:$versions.rxbinding" + compile "com.jakewharton.rxbinding2:rxbinding-support-v4:$versions.rxbinding" + compile "com.squareup.retrofit2:retrofit:$versions.retrofit" + compile "com.squareup.retrofit2:converter-gson:$versions.retrofit" + compile "com.squareup.retrofit2:adapter-rxjava2:$versions.retrofit" + + debugCompile "com.squareup.leakcanary:leakcanary-android:$versions.leakCanary" + releaseCompile "com.squareup.leakcanary:leakcanary-android-no-op:$versions.leakCanary" + + testCompile "junit:junit:$versions.junit" + testCompile "org.mockito:mockito-core:$versions.mockito" + testCompile "org.assertj:assertj-core:$versions.assertj" + testCompile "pl.pragmatists:JUnitParams:$versions.jUnitParams" + testCompile "org.robolectric:robolectric:$versions.robolectric" + + androidTestCompile "org.assertj:assertj-core:$versions.assertj" + androidTestCompile "com.android.support.test.espresso:espresso-core:$versions.espresso", { exclude group: 'com.android.support', module: 'support-annotations' } -} +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 2061ffe..7c99e48 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -23,11 +23,12 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile + -printmapping mapping.txt --keep class com.ikholopov.yamblz.weather.weathermobilization.data.dto.** { *; } --keep class com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeather { *; } --keep class com.ikholopov.yamblz.weather.weathermobilization.data.CityInfo { *; } --keep class com.ikholopov.yamblz.weather.weathermobilization.data.CityAutoComplete { *; } + +-keep class com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.** { *; } +-keep class com.ikholopov.yamblz.weather.weathermobilization.model.network.CityInfo { *; } +-keep class com.ikholopov.yamblz.weather.weathermobilization.model.* { *; } # Retrofit 2.X ## https://square.github.io/retrofit/ ## @@ -43,3 +44,10 @@ -dontwarn okio.** -dontwarn javax.annotation.** + +# Retrolambda +-dontwarn java.lang.invoke.* +-dontwarn **$$Lambda$* + +-dontwarn android.arch.util.paging.CountedDataSource +-dontwarn android.arch.persistence.room.paging.LimitOffsetDataSource \ No newline at end of file diff --git a/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainActivityTest.java b/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainActivityTest.java index 3a5f9dc..1a073dd 100644 --- a/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainActivityTest.java +++ b/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainActivityTest.java @@ -5,23 +5,13 @@ import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; -import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.ui.PageObjects.CitiesPage; +import com.ikholopov.yamblz.weather.weathermobilization.ui.PageObjects.SettingsPage; -import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withParent; -import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static org.hamcrest.Matchers.allOf; - @RunWith(AndroidJUnit4.class) @LargeTest public class MainActivityTest { @@ -29,37 +19,17 @@ public class MainActivityTest { @Rule public ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); - @After - public void tearDown() { - activityTestRule.getActivity().getSupportLoaderManager().destroyLoader(0); - } - @Test - public void showWeatherOnStartTest() throws Throwable { - onView(withId(R.id.weather_location)).check(matches(isDisplayed())); + public void showCitiesOnStartTest() throws Throwable { + CitiesPage.obtain().assertOn(); } @Test - public void navigateToAboutTest() throws Throwable { - onView(allOf(withContentDescription("Open navigation drawer"), - withParent(withId(R.id.toolbar)), isDisplayed())) - .perform(click()); - - onView(allOf(withId(R.id.design_menu_item_text), withText(R.string.nav_drawer_about), isDisplayed())) - .perform(click()); - - onView(withId(R.id.about_message)).check(matches(withText(R.string.about_message))); - } - - @Test - public void navigateToSettingsTest() throws Throwable { - onView(allOf(withContentDescription("Open navigation drawer"), - withParent(withId(R.id.toolbar)), isDisplayed())) - .perform(click()); - - onView(allOf(withId(R.id.design_menu_item_text), withText(R.string.nav_drawer_settings), isDisplayed())) - .perform(click()); + public void navigateToSettingAndBackTest() throws Throwable { + CitiesPage.obtain().clickSettings(); + SettingsPage.obtain().assertOn(); - onView(withId(R.id.unitsOption)).check(matches(isDisplayed())); + SettingsPage.obtain().clickBack(); + CitiesPage.obtain().assertOn(); } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/PageObjects/CitiesPage.java b/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/PageObjects/CitiesPage.java new file mode 100644 index 0000000..4e280e7 --- /dev/null +++ b/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/PageObjects/CitiesPage.java @@ -0,0 +1,37 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.PageObjects; + +import com.ikholopov.yamblz.weather.weathermobilization.R; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +/** + * Created by turist on 13.08.2017. + */ + +public class CitiesPage { + + private static CitiesPage instance; + + public static CitiesPage obtain() { + if (instance == null) + instance = new CitiesPage(); + return instance; + } + + public CitiesPage assertOn() { + onView(withId(R.id.search_edit)) + .check(matches(isDisplayed())); + onView(withId(R.id.cities_list)) + .check(matches(isDisplayed())); + return instance; + } + + public CitiesPage clickSettings() { + onView(withId(R.id.settings_button)).perform(click()); + return instance; + } +} diff --git a/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/PageObjects/SettingsPage.java b/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/PageObjects/SettingsPage.java new file mode 100644 index 0000000..e8155ae --- /dev/null +++ b/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/PageObjects/SettingsPage.java @@ -0,0 +1,41 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.PageObjects; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.TemperatureFormat; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static com.ikholopov.yamblz.weather.weathermobilization.model.preferences.TemperatureFormat.CELSIUS; + +/** + * Created by turist on 13.08.2017. + */ + +public class SettingsPage { + + private static SettingsPage instance; + + public static SettingsPage obtain() { + if (instance == null) + instance = new SettingsPage(); + return instance; + } + + public SettingsPage assertOn() { + onView(withId(R.id.temperature_unit_button)).check(matches(isDisplayed())); + return instance; + } + + public SettingsPage clickBack() { + onView(withId(R.id.back_button)).perform(click()); + return instance; + } + + public SettingsPage switchTemperature(@TemperatureFormat.Units int target) { + onView(withId(target == CELSIUS ? R.id.choice_c : R.id.choice_f)).perform(click()); + return instance; + } +} diff --git a/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/SettingsTest.java b/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/SettingsTest.java index 8d4a095..d666463 100644 --- a/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/SettingsTest.java +++ b/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/SettingsTest.java @@ -3,12 +3,12 @@ import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.LargeTest; -import com.ikholopov.yamblz.weather.weathermobilization.R; import com.ikholopov.yamblz.weather.weathermobilization.WeatherApplication; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.Metric; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.PreferencesProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.TemperatureFormat; +import com.ikholopov.yamblz.weather.weathermobilization.ui.PageObjects.CitiesPage; +import com.ikholopov.yamblz.weather.weathermobilization.ui.PageObjects.SettingsPage; import org.junit.After; import org.junit.Before; @@ -16,16 +16,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withChild; -import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withParent; -import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.assertj.core.api.Java6Assertions.assertThat; -import static org.hamcrest.Matchers.allOf; @RunWith(AndroidJUnit4.class) public class SettingsTest { @@ -37,14 +28,10 @@ public class SettingsTest { @Before public void setUp() { - preferences = WeatherApplication.get(activityTestRule.getActivity()).getComponent().getPreferencesProvider(); + preferences = WeatherApplication.get(activityTestRule.getActivity()) + .getComponent().getPreferencesProvider(); - onView(allOf(withContentDescription("Open navigation drawer"), - withParent(withId(R.id.toolbar)), isDisplayed())) - .perform(click()); - - onView(allOf(withId(R.id.design_menu_item_text), withText(R.string.nav_drawer_settings), isDisplayed())) - .perform(click()); + CitiesPage.obtain().clickSettings(); } @After @@ -54,23 +41,14 @@ public void tearDown() { @Test public void changeUnitsOptionTest() { - Metric old = preferences.getMetricFromPreference(); - - onView(allOf(withId(R.id.switchWidget), withParent(withChild(withId(R.id.unitsOption))), isDisplayed())).perform(click()); - - Metric new_ = preferences.getMetricFromPreference(); - - assertThat(old).isNotEqualTo(new_); - } - - @Test - public void changeAutoUpdateOptionTest() { - boolean old = preferences.getAutoupdateEnabledPreference(); - onView(allOf(withId(R.id.switchWidget), withParent(withParent(withChild(withChild(withText(R.string.background_update))))), isDisplayed())).perform(click()); + int old = preferences.getTemperatureFormat(); + int target = old == TemperatureFormat.CELSIUS + ? TemperatureFormat.FAHRENHEIT + : TemperatureFormat.CELSIUS; - boolean new_ = preferences.getAutoupdateEnabledPreference(); + SettingsPage.obtain().switchTemperature(target); - assertThat(old).isNotEqualTo(new_); + assertThat(old).isNotEqualTo(target); } -} +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/WeatherTest.java b/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/WeatherTest.java deleted file mode 100644 index 48f4c81..0000000 --- a/app/src/androidTest/java/com/ikholopov/yamblz/weather/weathermobilization/ui/WeatherTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.ui; - - -import android.content.Context; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; - -import com.ikholopov.yamblz.weather.weathermobilization.R; -import com.ikholopov.yamblz.weather.weathermobilization.WeatherApplication; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeather; -import com.ikholopov.yamblz.weather.weathermobilization.data.WeatherUtility; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.Metric; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.LoaderNetController; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; - -@RunWith(AndroidJUnit4.class) -public class WeatherTest { - - @Rule - public ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); - - static class CurrentWeatherMock extends CurrentWeather { - - @Override - public float getTemp() { - return 123.45f; - } - - @Override - public int getWeatherId() { - return 200; - } - } - - @Test - public void setWeatherWhenControllerLoadedTest() throws Throwable { - final Context context = activityTestRule.getActivity(); - final LoaderNetController controller = WeatherApplication.get(context).getComponent().getWeatherNetController(); - PreferencesProvider preferences = WeatherApplication.get(context).getComponent().getPreferencesProvider(); - - final CurrentWeather weather = new CurrentWeatherMock(); - - activityTestRule.runOnUiThread(new Runnable() { - public void run() { - controller.onLoadFinished(null, weather); - } - }); - - String temperature = WeatherUtility.formatTemperature(context, 123.45f, preferences.getMetricFromPreference() == Metric.CELSIUS); - - onView(withId(R.id.weather_message)).check(matches(withText(temperature))); - } -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 11931e1..adfd72d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,31 +4,27 @@ - - - - - - - + + + android:theme="@style/AppTheme.NoActionBar" + android:windowSoftInputMode="stateUnchanged"> - - @@ -36,10 +32,16 @@ - - + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/OnBoot.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/OnBoot.java index c4c5b07..00ac450 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/OnBoot.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/OnBoot.java @@ -4,8 +4,10 @@ import android.content.Context; import android.content.Intent; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.UpdateServiceController; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.PreferencesProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.UpdateServiceController; + +import org.threeten.bp.Duration; import javax.inject.Inject; @@ -15,19 +17,18 @@ public class OnBoot extends BroadcastReceiver { - @Inject - PreferencesProvider preferences; - @Inject - UpdateServiceController serviceController; + @Inject PreferencesProvider preferences; + @Inject UpdateServiceController serviceController; @Override public void onReceive(Context context, Intent intent) { WeatherApplication.get(context).getComponent().inject(this); - if(preferences.getAutoupdateEnabledPreference()) { - serviceController.enableService(preferences.getUpdateInterval()); - } else { + Duration interval = preferences.getAutoUpdateInterval(); + if(interval.isZero()) { serviceController.disableService(); + } else { + serviceController.enableService(interval); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/SchedulerProvider.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/SchedulerProvider.java new file mode 100644 index 0000000..ac09c90 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/SchedulerProvider.java @@ -0,0 +1,30 @@ +package com.ikholopov.yamblz.weather.weathermobilization; + +import javax.inject.Inject; + +import io.reactivex.Scheduler; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by turist on 13.08.2017. + */ + +public class SchedulerProvider { + + @Inject + public SchedulerProvider() { + } + + public Scheduler main() { + return AndroidSchedulers.mainThread(); + } + + public Scheduler computation() { + return Schedulers.computation(); + } + + public Scheduler io() { + return Schedulers.io(); + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/WeatherApplication.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/WeatherApplication.java index 79f8015..3cd9e0b 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/WeatherApplication.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/WeatherApplication.java @@ -5,8 +5,8 @@ import com.ikholopov.yamblz.weather.weathermobilization.di.component.ApplicationComponent; import com.ikholopov.yamblz.weather.weathermobilization.di.component.DaggerApplicationComponent; -import com.ikholopov.yamblz.weather.weathermobilization.di.module.ApplicationModule; -import com.ikholopov.yamblz.weather.weathermobilization.di.module.PreferencesProviderModule; +import com.ikholopov.yamblz.weather.weathermobilization.di.module.application.ApplicationModule; +import com.jakewharton.threetenabp.AndroidThreeTen; import com.squareup.leakcanary.LeakCanary; /** @@ -15,6 +15,7 @@ */ public class WeatherApplication extends Application { + protected ApplicationComponent applicationComponent; public static WeatherApplication get(Context context) { @@ -24,15 +25,18 @@ public static WeatherApplication get(Context context) { @Override public void onCreate() { super.onCreate(); + if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } + LeakCanary.install(this); + AndroidThreeTen.init(this); + applicationComponent = DaggerApplicationComponent.builder() .applicationModule(new ApplicationModule(this)) - .preferencesProviderModule(new PreferencesProviderModule(getApplicationContext())) .build(); applicationComponent.inject(this); } @@ -40,4 +44,4 @@ public void onCreate() { public ApplicationComponent getComponent() { return applicationComponent; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CityAutoComplete.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CityAutoComplete.java deleted file mode 100644 index 9022601..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CityAutoComplete.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.city.CityShortInfo; - -/** - * Created by turist on 28.07.2017. - */ - -public class CityAutoComplete { - public CityShortInfo[] predictions; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CityInfo.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CityInfo.java deleted file mode 100644 index 9a3b1d6..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CityInfo.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -/** - * Created by turist on 28.07.2017. - */ - -public class CityInfo { - - private String name; - private float lat; - private float lng; - - public CityInfo(String name, float lat, float lng) { - this.name = name; - this.lat = lat; - this.lng = lng; - } - - public float getLng() { - return lng; - } - - public float getLat() { - return lat; - } - - public String getName() { - return name; - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeather.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeather.java deleted file mode 100644 index fb75450..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeather.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather.Clouds; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather.Coordinates; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather.SystemInfo; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather.Weather; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather.WeatherInfo; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather.Wind; - -/** - * POJO for weather provided by OpenWeatherApi - * Created by igor on 7/16/17. - */ - -public class CurrentWeather { - - Coordinates coord; - Weather[] weather; - String base; - WeatherInfo main; - int visibility; - Wind wind; - Clouds clouds; - int dt; - SystemInfo sys; - int id; - String name; - int cod; - - public int getId() { - return id; - } - - public float getTemp() { - return main.temp; - } - - public String getLocationName() { - return name; - } - - public void setLocationName(String name) { - this.name = name; - } - - public int getWeatherId() { - return weather[0].id; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeatherCache.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeatherCache.java deleted file mode 100644 index b697623..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeatherCache.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import java.io.IOException; - -/** - * Created by turist on 25.07.2017. - */ - -public interface CurrentWeatherCache { - void save(String jsonString) throws IOException; - CurrentWeather load() throws IOException; - void clear() throws IOException; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeatherFileCache.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeatherFileCache.java deleted file mode 100644 index c4ad8ad..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeatherFileCache.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import android.content.Context; -import android.util.Log; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; - -/** - * Created by turist on 25.07.2017. - */ - -public class CurrentWeatherFileCache implements CurrentWeatherCache { - - private static final String CACHED_FILE_NAME = "CachedCurrentWeather.json"; - private static final String TAG = "CurrentWeatherFileCache"; - - private Context context; - private CurrentWeather currentWeather; - - public CurrentWeatherFileCache(Context context) { - this.context = context; - } - - @Override - public void save(String jsonString) throws IOException { - OutputStream file = null; - try { - file = new FileOutputStream(new File(context.getFilesDir(), CACHED_FILE_NAME)); - file.write(jsonString.getBytes()); - } - catch (FileNotFoundException e) { - Log.e(TAG, "Failed to create cache file"); - } - catch (IOException e) { - Log.e(TAG, "Failed to write to cache file"); - } - finally { - if(file != null) { - file.close(); - } - } - } - - @Override - public CurrentWeather load() throws IOException { - if(currentWeather == null) { - InputStream file = null; - try { - file = new FileInputStream(new File(context.getFilesDir(), CACHED_FILE_NAME)); - int size = file.available(); - byte[] buffer = new byte[size]; - if(file.read(buffer) == -1) { - return null; - } - - String jsonString = new String(buffer, "UTF-8"); - Gson gson = new GsonBuilder().create(); - currentWeather = gson.fromJson(jsonString, CurrentWeather.class); - } catch (FileNotFoundException ignored) { - } finally { - if(file != null) { - file.close(); - } - } - } - - return currentWeather; - } - - @Override - public void clear() throws IOException { - currentWeather = null; - File file = new File(context.getFilesDir(), CACHED_FILE_NAME); - if(file.exists()) { - file.delete(); - } - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeatherLoader.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeatherLoader.java deleted file mode 100644 index 90de876..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/CurrentWeatherLoader.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import android.content.Context; -import android.util.Log; - -import com.google.gson.JsonSyntaxException; -import com.ikholopov.yamblz.weather.weathermobilization.R; -import com.ikholopov.yamblz.weather.weathermobilization.data.http.HttpHelper; -import com.ikholopov.yamblz.weather.weathermobilization.data.http.UriHelper; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.ProtocolException; -import java.util.HashMap; - -import javax.inject.Inject; - -/** - * Handles updating weather and loading it from local file - * Created by igor on 7/16/17. - */ - -public class CurrentWeatherLoader extends WeatherLoader { - - private static final String TAG = "CurrentWeatherLoader"; - - private boolean forceNetLoad = false; - private PreferencesProvider preferencesProvider; - private CurrentWeatherCache weatherCache; - private HttpHelper httpHelper; - private UriHelper uriHelper; - - @Inject - public CurrentWeatherLoader(Context context, PreferencesProvider preferencesProvider, - CurrentWeatherCache weatherCache, HttpHelper httpHelper, - UriHelper uriHelper) { - super(context); - this.preferencesProvider = preferencesProvider; - this.weatherCache = weatherCache; - this.httpHelper = httpHelper; - this.uriHelper = uriHelper; - } - - @Override - public CurrentWeather loadInBackground() { - if(forceNetLoad) { - fetchAndSaveCurrentWeatherJson(); - } - - CurrentWeather currentWeather; - try { - currentWeather = weatherCache.load(); - } catch (IOException | JsonSyntaxException e) { - Log.e(TAG, "Failed to read cache file"); - e.printStackTrace(); - currentWeather = null; - } - - if(currentWeather == null) { - fetchAndSaveCurrentWeatherJson(); - try { - currentWeather = weatherCache.load(); - } catch (IOException | JsonSyntaxException e) { - Log.e(TAG, "Failed to read cache file"); - e.printStackTrace(); - currentWeather = null; - } - } - - forceNetLoad = false; - return currentWeather; - } - - //Call to update weather data from the net, rather than local file - @Override - public void forceNetLoad() { - forceNetLoad = true; - this.forceLoad(); - } - - //Must not be called on MainThread - private void fetchAndSaveCurrentWeatherJson() { - final String LAT_PARAM = "lat"; - final String LNG_PARAM = "lon"; - final String MODE_PARAM = "mode"; - final String UNITS_PARAM = "units"; - final String APPID_PARAM = "appid"; - - final String mode = "json"; - final String units = "metric"; - - HashMap params = new HashMap<>(); - params.put(LAT_PARAM, preferencesProvider.getCityLat()+""); - params.put(LNG_PARAM, preferencesProvider.getCityLng()+""); - params.put(MODE_PARAM, mode); - params.put(UNITS_PARAM, units); - params.put(APPID_PARAM, getContext().getString(R.string.api_key)); - String urlString = uriHelper.create(params); - - try { - String jsonStr = httpHelper.get(urlString); - - if(jsonStr != null) { - weatherCache.save(jsonStr); - } - } - catch (MalformedURLException e) { - Log.e(TAG, "Invalid url"); - } - catch (ProtocolException e) { - Log.e(TAG, "Protocol error"); - } - catch (IOException e) { - Log.e(TAG, "Url connection failed"); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/GooglePlacesService.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/GooglePlacesService.java deleted file mode 100644 index 6c88776..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/GooglePlacesService.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import android.content.Context; - -import com.ikholopov.yamblz.weather.weathermobilization.R; -import com.ikholopov.yamblz.weather.weathermobilization.data.http.GooglePlacesApi; - -import java.util.Locale; - -import javax.inject.Inject; - -import io.reactivex.Single; -import io.reactivex.annotations.NonNull; -import io.reactivex.functions.Function; - -/** - * Created by turist on 28.07.2017. - */ - -public class GooglePlacesService implements PlacesService { - - private String apiKey; - private GooglePlacesApi api; - - @Inject - public GooglePlacesService(Context context, GooglePlacesApi api) { - this.apiKey = context.getString(R.string.google_api_key); - this.api = api; - } - - @Override - public Single details(String placeId) { - return api.details(placeId, apiKey, getLang()) - .map(new Function() { - @Override - public CityInfo apply(@NonNull com.ikholopov.yamblz.weather.weathermobilization.data.dto.city.CityInfo cityInfo) throws Exception { - return new CityInfo(cityInfo.result.formatted_address, cityInfo.result.geometry.location.lat, cityInfo.result.geometry.location.lng); - } - }); - } - - @Override - public Single autocomplete(String data) { - return api.autoComplete(data, "(cities)", apiKey, getLang()); - } - - private String getLang() { - return Locale.getDefault().getLanguage(); - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/PlacesService.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/PlacesService.java deleted file mode 100644 index ef23406..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/PlacesService.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import io.reactivex.Single; - -/** - * Created by turist on 28.07.2017. - */ - -public interface PlacesService { - Single details(String placeId); - Single autocomplete(String data); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherLoader.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherLoader.java deleted file mode 100644 index a14c9ba..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherLoader.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import android.support.v4.content.AsyncTaskLoader; -import android.content.Context; - -/** - * Interface that allows force update from the net - * Created by igor on 7/16/17. - */ - -public abstract class WeatherLoader extends AsyncTaskLoader { - public WeatherLoader(Context context) { - super(context); - } - - abstract void forceNetLoad(); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherUtility.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherUtility.java deleted file mode 100644 index f36c134..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherUtility.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import android.content.Context; - -import com.ikholopov.yamblz.weather.weathermobilization.R; - -/** - * Utility for dealing with weather icons and units - * Created by igor on 7/16/17. - */ - -public class WeatherUtility { - - private static final double FARHENHEIT_SCALE = 9.0 / 5.0; - private static final double FARHENHEIT_BASE = 32.0; - - public static String formatTemperature(Context context, double temperature, boolean isMetric) { - - double temp; - String grad; - if ( !isMetric ) { - temp = FARHENHEIT_SCALE * temperature + FARHENHEIT_BASE; - grad = context.getString(R.string.farhenheit); - } else { - temp = temperature; - grad = context.getString(R.string.celsius); - } - return context.getString(R.string.format_temperature, temp, grad); - } - - public static int getImageIdForWeatherId(int weatherId) { - if (weatherId >= 200 && weatherId <= 232) { - return R.mipmap.thunderstorm; - } else if (weatherId >= 300 && weatherId <= 321) { - return R.mipmap.rain; - } else if (weatherId >= 500 && weatherId <= 504) { - return R.mipmap.rain; - } else if (weatherId == 511) { - return R.mipmap.snow; - } else if (weatherId >= 520 && weatherId <= 531) { - return R.mipmap.rain; - } else if (weatherId >= 600 && weatherId <= 622) { - return R.mipmap.snow; - } else if (weatherId >= 701 && weatherId <= 761) { - return R.mipmap.clouds; - } else if (weatherId == 761 || weatherId == 781) { - return R.mipmap.thunderstorm; - } else if (weatherId == 800) { - return R.mipmap.sun; - } else if (weatherId == 801) { - return R.mipmap.mixed; - } else if (weatherId == 802) { - return R.mipmap.mixed; - } else if (weatherId >= 803 && weatherId <= 804) { - return R.mipmap.clouds; - } - return -1; - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/CityInfo.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/CityInfo.java deleted file mode 100644 index ae29b92..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/CityInfo.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.city; - -/** - * Created by turist on 28.07.2017. - */ - -public class CityInfo { - public CityInfoResult result; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/CityInfoResult.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/CityInfoResult.java deleted file mode 100644 index 81e07bd..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/CityInfoResult.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.city; - -/** - * Created by turist on 28.07.2017. - */ - -public class CityInfoResult { - public String formatted_address; - public Geometry geometry; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/CityShortInfo.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/CityShortInfo.java deleted file mode 100644 index 68b573e..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/CityShortInfo.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.city; - -/** - * Created by turist on 28.07.2017. - */ - -public class CityShortInfo { - public String description; - public String place_id; - - @Override - public String toString() { - return description; - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/Geometry.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/Geometry.java deleted file mode 100644 index 24e45d9..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/Geometry.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.city; - -/** - * Created by turist on 28.07.2017. - */ - -public class Geometry { - public Location location; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/Location.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/Location.java deleted file mode 100644 index 6c40077..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/city/Location.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.city; - -/** - * Created by turist on 28.07.2017. - */ - -public class Location { - public float lat; - public float lng; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Clouds.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Clouds.java deleted file mode 100644 index 2c405c1..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Clouds.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather; - -public class Clouds { - int all; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Coordinates.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Coordinates.java deleted file mode 100644 index 5c8f9e3..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Coordinates.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather; - -public class Coordinates { - float lon; - float lat; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/SystemInfo.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/SystemInfo.java deleted file mode 100644 index 1f2ca71..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/SystemInfo.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather; - -public class SystemInfo { - int type; - int id; - float message; - String country; - int sunrise; - int sunset; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/WeatherInfo.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/WeatherInfo.java deleted file mode 100644 index 66fdd03..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/WeatherInfo.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather; - -public class WeatherInfo { - public float temp; - public float pressure; - public int humidity; - public float temp_min; - public float temp_max; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Wind.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Wind.java deleted file mode 100644 index d5044f1..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Wind.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather; - -public class Wind { - float speed; - float deg; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/GooglePlacesApi.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/GooglePlacesApi.java deleted file mode 100644 index 004f0d6..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/GooglePlacesApi.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.http; - -import com.ikholopov.yamblz.weather.weathermobilization.data.CityAutoComplete; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.city.CityInfo; - -import io.reactivex.Single; -import retrofit2.http.GET; -import retrofit2.http.Query; - -/** - * Created by turist on 28.07.2017. - */ - -public interface GooglePlacesApi { - - @GET("autocomplete/json") - Single autoComplete(@Query("input") String data, - @Query("types") String types, - @Query("key") String apiKey, - @Query("language") String language); - - @GET("details/json") - Single details(@Query("placeid") String placeid, - @Query("key") String apiKey, - @Query("language") String language); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/HttpHelper.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/HttpHelper.java deleted file mode 100644 index 8c14e54..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/HttpHelper.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.http; - -import java.io.IOException; -import java.net.MalformedURLException; - -/** - * Created by turist on 25.07.2017. - */ - -public interface HttpHelper { - String get(String uri) throws IOException; -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/HttpHelperImpl.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/HttpHelperImpl.java deleted file mode 100644 index a573b07..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/HttpHelperImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.http; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; - -/** - * Created by turist on 25.07.2017. - */ - -public class HttpHelperImpl implements HttpHelper { - - @Override - public String get(String urlString) throws IOException { - URL url = new URL(urlString); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - urlConnection.setRequestMethod("GET"); - urlConnection.setUseCaches(false); - urlConnection.connect(); - - InputStream inputStream = urlConnection.getInputStream(); - StringBuilder builder = new StringBuilder(); - if(inputStream == null) { - return null; - } - - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - String line; - while((line = reader.readLine()) != null) { - builder.append(line); - } - - if(builder.length() == 0) { - return null; - } - - return builder.toString(); - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/UriHelper.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/UriHelper.java deleted file mode 100644 index 3853809..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/UriHelper.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.http; - -import java.util.Map; - -/** - * Created by turist on 25.07.2017. - */ - -public interface UriHelper { - - String create(Map params); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/WeatherUriHelper.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/WeatherUriHelper.java deleted file mode 100644 index ce102ba..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/http/WeatherUriHelper.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.http; - -import android.net.Uri; - -import java.util.Map; - -/** - * Created by turist on 25.07.2017. - */ - -public class WeatherUriHelper implements UriHelper { - - private static final String FORECAST_BASE_URL = "http://api.openweathermap.org/data/2.5/weather"; - - @Override - public String create(Map params) { - Uri.Builder builder = Uri.parse(FORECAST_BASE_URL).buildUpon(); - for(Map.Entry keyValue : params.entrySet()) { - builder.appendQueryParameter(keyValue.getKey(), keyValue.getValue()); - } - - return builder.build().toString(); - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/PerView.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/PerActivity.java similarity index 89% rename from app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/PerView.java rename to app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/PerActivity.java index 1f0cfaa..fb7c3fe 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/PerView.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/PerActivity.java @@ -9,9 +9,7 @@ * Created by igor on 7/16/17. */ - @Scope @Retention(RetentionPolicy.RUNTIME) -public @interface PerView { - +public @interface PerActivity { } diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/StringQualifier.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/StringQualifier.java new file mode 100644 index 0000000..7a4a853 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/StringQualifier.java @@ -0,0 +1,17 @@ +package com.ikholopov.yamblz.weather.weathermobilization.di; + +import javax.inject.Qualifier; + +/** + * Created by turist on 08.08.2017. + */ + +@Qualifier +public @interface StringQualifier { + + int UNKNOWN = 0; + int GOOGLE_API_KEY = 1; + int OPEN_WEATHER_MAP_API_KEY = 2; + + int stringType() default UNKNOWN; +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ActivityComponent.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ActivityComponent.java new file mode 100644 index 0000000..baaf438 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ActivityComponent.java @@ -0,0 +1,28 @@ +package com.ikholopov.yamblz.weather.weathermobilization.di.component; + +import com.ikholopov.yamblz.weather.weathermobilization.di.PerActivity; +import com.ikholopov.yamblz.weather.weathermobilization.di.module.ActivityModule; +import com.ikholopov.yamblz.weather.weathermobilization.ui.MainActivity; +import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.CitiesFragment; +import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.SettingsFragment; +import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.WeatherFragment; + +import dagger.Subcomponent; + +/** + * Provides Activity and Presenter + */ + +@PerActivity +@Subcomponent(modules = { ActivityModule.class }) +public interface ActivityComponent { + void inject(MainActivity activity); + void inject(WeatherFragment fragment); + void inject(CitiesFragment fragment); + void inject(SettingsFragment fragment); + + @Subcomponent.Builder interface Builder { + Builder activityModule(ActivityModule activityModule); + ActivityComponent build(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ApplicationComponent.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ApplicationComponent.java index 76c1bf5..83e7441 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ApplicationComponent.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ApplicationComponent.java @@ -4,16 +4,17 @@ import com.ikholopov.yamblz.weather.weathermobilization.OnBoot; import com.ikholopov.yamblz.weather.weathermobilization.WeatherApplication; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherCache; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherLoader; -import com.ikholopov.yamblz.weather.weathermobilization.data.PlacesService; -import com.ikholopov.yamblz.weather.weathermobilization.di.module.ApplicationModule; -import com.ikholopov.yamblz.weather.weathermobilization.di.module.GoogleApiModule; -import com.ikholopov.yamblz.weather.weathermobilization.di.module.PreferencesProviderModule; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.LoaderNetController; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.UpdateServiceController; -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.SettingsFragment; +import com.ikholopov.yamblz.weather.weathermobilization.di.module.application.ApplicationModule; +import com.ikholopov.yamblz.weather.weathermobilization.di.module.application.GooglePlacesModule; +import com.ikholopov.yamblz.weather.weathermobilization.di.module.application.OpenWeatherMapModule; +import com.ikholopov.yamblz.weather.weathermobilization.di.module.application.PreferencesModule; +import com.ikholopov.yamblz.weather.weathermobilization.di.module.application.RoomModule; +import com.ikholopov.yamblz.weather.weathermobilization.di.module.application.UpdateModule; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.PlacesService; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.WeatherService; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.PreferencesProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.UpdateServiceController; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.WeatherUpdateService; import javax.inject.Singleton; @@ -25,18 +26,20 @@ */ @Singleton -@Component(modules = { ApplicationModule.class, - PreferencesProviderModule.class, GoogleApiModule.class }) +@Component(modules = { ApplicationModule.class, UpdateModule.class, + GooglePlacesModule.class, OpenWeatherMapModule.class, + PreferencesModule.class, RoomModule.class }) public interface ApplicationComponent { + + void inject(OnBoot onBoot); void inject(WeatherApplication application); - void inject(SettingsFragment fragment); - void inject(OnBoot receiver); + void inject(WeatherUpdateService updateService); PreferencesProvider getPreferencesProvider(); UpdateServiceController getServiceController(); - LoaderNetController getWeatherNetController(); - CurrentWeatherLoader getWeatherLoader(); PlacesService getPlacesService(); - CurrentWeatherCache getWeatherCache(); + WeatherService getWeatherService(); Context getContext(); + + ActivityComponent.Builder activityComponentBuilder(); } \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/CitySelectComponent.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/CitySelectComponent.java deleted file mode 100644 index a594243..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/CitySelectComponent.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.di.component; - -import com.ikholopov.yamblz.weather.weathermobilization.di.PerView; -import com.ikholopov.yamblz.weather.weathermobilization.ui.CitySelectActivity; - -import dagger.Component; - -/** - * Provides Activity, Composer for activity and presenter - */ - -@PerView -@Component(dependencies = ApplicationComponent.class) -public interface CitySelectComponent { - void inject(CitySelectActivity activity); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ViewComponent.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ViewComponent.java deleted file mode 100644 index 9c9620c..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/component/ViewComponent.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.di.component; - -import com.ikholopov.yamblz.weather.weathermobilization.di.PerView; -import com.ikholopov.yamblz.weather.weathermobilization.di.module.ActivityModule; -import com.ikholopov.yamblz.weather.weathermobilization.di.module.CurrentWeatherPresenterModule; -import com.ikholopov.yamblz.weather.weathermobilization.di.module.MainComposerModule; -import com.ikholopov.yamblz.weather.weathermobilization.ui.MainActivity; -import com.ikholopov.yamblz.weather.weathermobilization.ui.MainViewComposer; -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.WeatherFragment; - -import dagger.Component; - -/** - * Provides Activity, Composer for activity and presenter - */ - -@PerView -@Component(dependencies = ApplicationComponent.class, - modules = {ActivityModule.class, MainComposerModule.class, - CurrentWeatherPresenterModule.class }) -public interface ViewComponent { - void inject(MainActivity activity); - void inject(MainViewComposer mainViewComposer); - void inject(WeatherFragment fragment); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/ActivityModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/ActivityModule.java index c44be3e..ce49cf0 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/ActivityModule.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/ActivityModule.java @@ -1,6 +1,9 @@ package com.ikholopov.yamblz.weather.weathermobilization.di.module; -import com.ikholopov.yamblz.weather.weathermobilization.ui.NavigatableActivity; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.MainActivityRouter; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.MainRouter; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.TwoPaneRouter; +import com.ikholopov.yamblz.weather.weathermobilization.ui.MainActivity; import dagger.Module; import dagger.Provides; @@ -12,14 +15,24 @@ @Module public class ActivityModule { - private NavigatableActivity activity; - public ActivityModule(NavigatableActivity activity) { + private final MainActivity activity; + + public ActivityModule(MainActivity activity) { this.activity = activity; } @Provides - NavigatableActivity provideActivity() { + MainActivity provideActivity() { return activity; } -} + + @Provides + MainRouter provideFragmentManager() { + if(activity.inTwoPaneMode()) { + return new TwoPaneRouter(activity.getSupportFragmentManager(), activity); + } else { + return new MainActivityRouter(activity.getSupportFragmentManager()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/ApplicationModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/ApplicationModule.java deleted file mode 100644 index f0cc5d1..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/ApplicationModule.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.di.module; - -import android.app.Application; -import android.content.Context; -import android.support.annotation.NonNull; - -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherCache; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherFileCache; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherLoader; -import com.ikholopov.yamblz.weather.weathermobilization.data.http.HttpHelper; -import com.ikholopov.yamblz.weather.weathermobilization.data.http.HttpHelperImpl; -import com.ikholopov.yamblz.weather.weathermobilization.data.http.UriHelper; -import com.ikholopov.yamblz.weather.weathermobilization.data.http.WeatherUriHelper; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.LoaderNetController; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.UpdateServiceController; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.WeatherNetController; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.WeatherServiceController; - -import javax.inject.Singleton; - -import dagger.Module; -import dagger.Provides; - -/** - * Provides application - * Created by igor on 7/16/17. - */ - -@Module -public class ApplicationModule { - private final Application application; - - public ApplicationModule(@NonNull Application application) { - this.application = application; - } - - @Provides - Application provideApplication() { - return application; - } - - @Provides - Context provideContext() { - return application.getApplicationContext(); - } - - @Provides - UpdateServiceController provideServiceController() { - return new WeatherServiceController(application.getApplicationContext()); - } - - @Provides - @Singleton - CurrentWeatherCache provideWeatherCache() { - return new CurrentWeatherFileCache(application.getApplicationContext()); - } - - @Provides - UriHelper provideUriHelper() { - return new WeatherUriHelper(); - } - - @Provides - HttpHelper provideHttpHelper() { - return new HttpHelperImpl(); - } - - @Provides - @Singleton - LoaderNetController provideLoaderNetController(CurrentWeatherLoader loader) { - return new WeatherNetController(loader); - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/CurrentWeatherPresenterModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/CurrentWeatherPresenterModule.java deleted file mode 100644 index d7ac2a0..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/CurrentWeatherPresenterModule.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.di.module; - -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.CurrentWeatherPresenter; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.CurrentWeatherPresenterImpl; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.LoaderNetController; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.UpdateServiceController; - -import dagger.Module; -import dagger.Provides; - -/** - * Provides presenter - * Created by igor on 7/20/17. - */ - -@Module -public class CurrentWeatherPresenterModule { - @Provides - CurrentWeatherPresenter providePresenter(PreferencesProvider preferences, - UpdateServiceController serviceController, - LoaderNetController weatherNetController) { - return new CurrentWeatherPresenterImpl(preferences, serviceController, weatherNetController); - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/MainComposerModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/MainComposerModule.java index 9ee2d38..666aecb 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/MainComposerModule.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/MainComposerModule.java @@ -1,10 +1,6 @@ package com.ikholopov.yamblz.weather.weathermobilization.di.module; -import com.ikholopov.yamblz.weather.weathermobilization.ui.MainViewComposer; -import com.ikholopov.yamblz.weather.weathermobilization.ui.MainViewComposerImpl; - import dagger.Module; -import dagger.Provides; /** * Provides Composer for MainActivity @@ -14,14 +10,14 @@ @Module public class MainComposerModule { - private MainViewComposer composer; + //private MainViewComposer composer; - public MainComposerModule() { - this.composer = new MainViewComposerImpl(); - } + //public MainComposerModule() { + // this.composer = new MainViewComposerImpl(); + // } - @Provides - MainViewComposer provideMainViewComposer() { - return composer; - } +// @Provides +// MainViewComposer provideMainViewComposer() { +// return composer; +// } } diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/PreferencesProviderModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/PreferencesProviderModule.java deleted file mode 100644 index 2e532dc..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/PreferencesProviderModule.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.di.module; - -import android.content.Context; - -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; - -import dagger.Module; -import dagger.Provides; - -/** - * Provides preferences wrapper - * Created by igor on 7/20/17. - */ - -@Module -public class PreferencesProviderModule { - private final PreferencesProvider provider; - - public PreferencesProviderModule(Context context) { - provider = new PreferencesProvider(context); - } - - @Provides - PreferencesProvider providePreferencesProvider() { - return provider; - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/ApplicationModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/ApplicationModule.java new file mode 100644 index 0000000..9ad435a --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/ApplicationModule.java @@ -0,0 +1,48 @@ +package com.ikholopov.yamblz.weather.weathermobilization.di.module.application; + +import android.app.Application; +import android.content.Context; +import android.support.annotation.NonNull; + +import com.ikholopov.yamblz.weather.weathermobilization.di.component.ActivityComponent; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.LanguageProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.LocalLanguageProvider; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides application + * Created by igor on 7/16/17. + */ + +@Singleton +@Module(subcomponents = { ActivityComponent.class }) +public class ApplicationModule { + + private final Application application; + + public ApplicationModule(@NonNull Application application) { + this.application = application; + } + + @Provides + @Singleton + Application provideApplication() { + return application; + } + + @Provides + @Singleton + Context provideContext() { + return application.getApplicationContext(); + } + + @Provides + @Singleton + LanguageProvider provideLanguageProvider() { + return new LocalLanguageProvider(); + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/GoogleApiModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/GoogleApiModule.java similarity index 59% rename from app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/GoogleApiModule.java rename to app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/GoogleApiModule.java index 75d80ff..d979c02 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/GoogleApiModule.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/GoogleApiModule.java @@ -1,10 +1,10 @@ -package com.ikholopov.yamblz.weather.weathermobilization.di.module; +package com.ikholopov.yamblz.weather.weathermobilization.di.module.application; -import android.content.Context; +import com.ikholopov.yamblz.weather.weathermobilization.BuildConfig; +import com.ikholopov.yamblz.weather.weathermobilization.di.StringQualifier; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.api.GooglePlacesApi; -import com.ikholopov.yamblz.weather.weathermobilization.data.GooglePlacesService; -import com.ikholopov.yamblz.weather.weathermobilization.data.PlacesService; -import com.ikholopov.yamblz.weather.weathermobilization.data.http.GooglePlacesApi; +import javax.inject.Singleton; import dagger.Module; import dagger.Provides; @@ -12,16 +12,20 @@ import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; +import static com.ikholopov.yamblz.weather.weathermobilization.di.StringQualifier.GOOGLE_API_KEY; + /** * Created by turist on 28.07.2017. */ @Module +@Singleton public class GoogleApiModule { private static final String API_BASE_URL = "https://maps.googleapis.com/maps/api/place/"; @Provides + @Singleton GooglePlacesApi provideApi() { return new Retrofit.Builder() .baseUrl(API_BASE_URL) @@ -32,7 +36,9 @@ GooglePlacesApi provideApi() { } @Provides - PlacesService provideService(Context context, GooglePlacesApi api) { - return new GooglePlacesService(context, api); + @Singleton + @StringQualifier(stringType = GOOGLE_API_KEY) + String provideApiKey() { + return BuildConfig.GOOGLE_API_KEY; } } \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/GooglePlacesModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/GooglePlacesModule.java new file mode 100644 index 0000000..864227b --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/GooglePlacesModule.java @@ -0,0 +1,22 @@ +package com.ikholopov.yamblz.weather.weathermobilization.di.module.application; + +import com.ikholopov.yamblz.weather.weathermobilization.model.network.PlacesService; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.api.GooglePlacesService; + +import javax.inject.Singleton; + +import dagger.Binds; +import dagger.Module; + +/** + * Created by turist on 28.07.2017. + */ + +@Module(includes = GoogleApiModule.class) +@Singleton +public abstract class GooglePlacesModule { + + @Binds + @Singleton + abstract PlacesService provideService(GooglePlacesService service); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/OpenWeatherMapApiModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/OpenWeatherMapApiModule.java new file mode 100644 index 0000000..fa0069e --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/OpenWeatherMapApiModule.java @@ -0,0 +1,51 @@ +package com.ikholopov.yamblz.weather.weathermobilization.di.module.application; + +import com.ikholopov.yamblz.weather.weathermobilization.BuildConfig; +import com.ikholopov.yamblz.weather.weathermobilization.di.StringQualifier; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.api.ForecastMapperProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.api.OpenWeatherMapApi; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + +import static com.ikholopov.yamblz.weather.weathermobilization.di.StringQualifier.OPEN_WEATHER_MAP_API_KEY; + +/** + * Created by turist on 28.07.2017. + */ + +@Module +@Singleton +public class OpenWeatherMapApiModule { + + private static final String API_BASE_URL = "http://api.openweathermap.org/data/"; + + @Provides + @Singleton + OpenWeatherMapApi provideApi() { + return new Retrofit.Builder() + .baseUrl(API_BASE_URL) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(OpenWeatherMapApi.class); + } + + @Provides + @Singleton + @StringQualifier(stringType = OPEN_WEATHER_MAP_API_KEY) + String provideApiKey() { + return BuildConfig.OPENWEATHERMAP_API_KEY; + } + + @Provides + @Singleton + ForecastMapperProvider forecastMapperProvider() { + return new ForecastMapperProvider(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/OpenWeatherMapModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/OpenWeatherMapModule.java new file mode 100644 index 0000000..4f8bded --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/OpenWeatherMapModule.java @@ -0,0 +1,22 @@ +package com.ikholopov.yamblz.weather.weathermobilization.di.module.application; + +import com.ikholopov.yamblz.weather.weathermobilization.model.network.WeatherService; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.api.OpenWeatherMapService; + +import javax.inject.Singleton; + +import dagger.Binds; +import dagger.Module; + +/** + * Created by turist on 28.07.2017. + */ + +@Module(includes = OpenWeatherMapApiModule.class) +@Singleton +public abstract class OpenWeatherMapModule { + + @Binds + @Singleton + abstract WeatherService provideService(OpenWeatherMapService service); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/PreferencesModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/PreferencesModule.java new file mode 100644 index 0000000..cfa0376 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/PreferencesModule.java @@ -0,0 +1,26 @@ +package com.ikholopov.yamblz.weather.weathermobilization.di.module.application; + +import android.content.Context; +import android.preference.PreferenceManager; + +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.PreferencesProvider; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides preferences wrapper + * Created by igor on 7/20/17. + */ + +@Module +public class PreferencesModule { + + @Provides + @Singleton + PreferencesProvider providePreferencesProvider(Context context) { + return new PreferencesProvider(PreferenceManager.getDefaultSharedPreferences(context)); + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/RoomModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/RoomModule.java new file mode 100644 index 0000000..0fe16a3 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/RoomModule.java @@ -0,0 +1,38 @@ +package com.ikholopov.yamblz.weather.weathermobilization.di.module.application; + +import android.arch.persistence.room.Room; +import android.content.Context; + +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao.CityDao; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao.ForecastDao; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.db.AppDatabase; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * Created by turist on 06.08.2017. + */ + +@Module +public class RoomModule { + + @Provides + @Singleton + AppDatabase provideDatabase(Context context) { + return Room.databaseBuilder(context, AppDatabase.class, "weather.sqlite") + .build(); + } + + @Provides + CityDao provideCityDao(AppDatabase database) { + return database.cityDao(); + } + + @Provides + ForecastDao provideForecastDao(AppDatabase database) { + return database.forecastDao(); + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/UpdateModule.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/UpdateModule.java new file mode 100644 index 0000000..4a2a0b5 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/di/module/application/UpdateModule.java @@ -0,0 +1,28 @@ +package com.ikholopov.yamblz.weather.weathermobilization.di.module.application; + +import com.ikholopov.yamblz.weather.weathermobilization.model.services.NetWeatherUpdater; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.UpdateServiceController; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.WeatherUpdateServiceController; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.WeatherUpdater; + +import javax.inject.Singleton; + +import dagger.Binds; +import dagger.Module; + +/** + * Created by turist on 07.08.2017. + */ + +@Module +@Singleton +public abstract class UpdateModule { + + @Binds + @Singleton + abstract UpdateServiceController provideServiceController(WeatherUpdateServiceController controller); + + @Binds + @Singleton + abstract WeatherUpdater provideUpdater(NetWeatherUpdater updater); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/City.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/City.java new file mode 100644 index 0000000..dbcab34 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/City.java @@ -0,0 +1,79 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model; + +import android.arch.persistence.room.ColumnInfo; +import android.arch.persistence.room.Embedded; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.Ignore; +import android.arch.persistence.room.Index; +import android.arch.persistence.room.PrimaryKey; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Created by turist on 04.08.2017. + */ + +@Entity(tableName = "cities", indices={@Index(value="place_id", unique=true)}) +public class City implements Parcelable { + + @PrimaryKey(autoGenerate = true) + public long id; + + public final String name; + + @ColumnInfo(name = "place_id") + public final String placeId; + + @Embedded + public final Coordinates coordinates; + + public City(long id, String name, String placeId, Coordinates coordinates) { + this.id = id; + this.name = name; + this.placeId = placeId; + this.coordinates = coordinates; + } + + @Ignore + public City(String name, String placeId, Coordinates coordinates) { + this.name = name; + this.placeId = placeId; + this.coordinates = coordinates; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + dest.writeString(name); + dest.writeString(placeId); + dest.writeDouble(coordinates.lat); + dest.writeDouble(coordinates.lng); + } + + protected City(Parcel in) { + id = in.readLong(); + name = in.readString(); + placeId = in.readString(); + double lat = in.readDouble(); + double lng = in.readDouble(); + coordinates = new Coordinates(lat, lng); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public City createFromParcel(Parcel in) { + return new City(in); + } + + @Override + public City[] newArray(int size) { + return new City[size]; + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/Coordinates.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/Coordinates.java new file mode 100644 index 0000000..50cf1a4 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/Coordinates.java @@ -0,0 +1,16 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model; + +/** + * Created by turist on 13.08.2017. + */ + +public class Coordinates { + + public final double lat; + public final double lng; + + public Coordinates(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/Forecast.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/Forecast.java new file mode 100644 index 0000000..b7dda09 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/Forecast.java @@ -0,0 +1,60 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model; + +import android.arch.persistence.room.Embedded; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ColumnInfo; +import android.arch.persistence.room.ForeignKey; +import android.arch.persistence.room.Ignore; +import android.arch.persistence.room.Index; + +import org.threeten.bp.Instant; + +/** + * Created by turist on 04.08.2017. + */ + +@Entity(tableName = "forecasts", primaryKeys = {"date_time", "city_id"}, + indices = @Index(value = "city_id"), foreignKeys = @ForeignKey(entity = City.class, + onDelete = ForeignKey.CASCADE, parentColumns = "id", childColumns = "city_id")) +public class Forecast { + + @ColumnInfo(name = "creating_time") + public final Instant creatingTime; + + @ColumnInfo(name = "date_time") + public final Instant dateTime; + + @ColumnInfo(name = "temperature") + public final double celsiusTemperature; + + @ColumnInfo(name = "pressure") + public final double pressure; + + @ColumnInfo(name = "wind_speed") + public final double windSpeed; + + @ColumnInfo(name = "wind_degree") + public final double windDegree; + + @Embedded + public final WeatherState state; + + @ColumnInfo(name = "city_id") + public final long cityId; + + public Forecast(Instant creatingTime, Instant dateTime, double celsiusTemperature, double pressure, double windSpeed, double windDegree, WeatherState state, long cityId) { + this.creatingTime = creatingTime; + this.dateTime = dateTime; + this.celsiusTemperature = celsiusTemperature; + this.pressure = pressure; + this.windSpeed = windSpeed; + this.windDegree = windDegree; + this.state = state; + this.cityId = cityId; + } + + @Ignore + public Forecast(Instant dateTime, double celsiusTemperature, double pressure, double windSpeed, double windDegree, WeatherState state, City city) { + this(Instant.now(), dateTime, celsiusTemperature, pressure, windSpeed, windDegree, state, city.id); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/WeatherState.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/WeatherState.java new file mode 100644 index 0000000..5675127 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/WeatherState.java @@ -0,0 +1,37 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by turist on 04.08.2017. + */ + +public class WeatherState { + + public static final int SUNNY = 0; + public static final int CLOUDY = 1; + public static final int DRIZZLE = 2; + public static final int HAZE = 3; + public static final int MOSTLY_CLOUDY = 4; + public static final int SLIGHT_DRIZZLE = 5; + public static final int SNOW = 6; + public static final int THUNDERSTORMS = 7; + + /** @hide */ + @IntDef({SUNNY, CLOUDY, DRIZZLE, HAZE, MOSTLY_CLOUDY, SLIGHT_DRIZZLE, SNOW, THUNDERSTORMS}) + @Retention(RetentionPolicy.SOURCE) + public @interface States {} + + @WeatherState.States + public final int state; + + public final String description; + + public WeatherState(int state, String description) { + this.state = state; + this.description = description; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/CityInfo.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/CityInfo.java new file mode 100644 index 0000000..267d7a3 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/CityInfo.java @@ -0,0 +1,36 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network; + +/** + * Created by turist on 28.07.2017. + */ + +public class CityInfo { + + private final String name; + private final double lat; + private final double lng; + private final String placeId; + + public CityInfo(String name, double lat, double lng, String placeId) { + this.name = name; + this.lat = lat; + this.lng = lng; + this.placeId = placeId; + } + + public double getLng() { + return lng; + } + + public double getLat() { + return lat; + } + + public String getName() { + return name; + } + + public String getPlaceId() { + return placeId; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/PlacesService.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/PlacesService.java new file mode 100644 index 0000000..d8d4a38 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/PlacesService.java @@ -0,0 +1,15 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network; + +import com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places.CityShortInfo; + +import io.reactivex.Observable; +import io.reactivex.Single; + +/** + * Created by turist on 28.07.2017. + */ + +public interface PlacesService { + Single details(String placeId); + Single autocomplete(String data); +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/WeatherService.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/WeatherService.java new file mode 100644 index 0000000..f833df3 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/WeatherService.java @@ -0,0 +1,14 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.Forecast; + +import io.reactivex.Observable; + +/** + * Created by turist on 28.07.2017. + */ + +public interface WeatherService { + Observable forecasts(City city); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/ForecastMapperProvider.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/ForecastMapperProvider.java new file mode 100644 index 0000000..7f3b1b9 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/ForecastMapperProvider.java @@ -0,0 +1,61 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.api; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.Forecast; +import com.ikholopov.yamblz.weather.weathermobilization.model.WeatherState; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather.ForecastInfo; + +import org.threeten.bp.Instant; + +import io.reactivex.functions.Function; + +/** + * Created by igor on 7/16/17. + */ + +public class ForecastMapperProvider { + + @WeatherState.States + public static int mapWeatherState(int apiWeatherId) { + switch (apiWeatherId / 100){ + case 2: + return WeatherState.THUNDERSTORMS; + case 3: + return WeatherState.DRIZZLE; + case 5: + return WeatherState.SLIGHT_DRIZZLE; + case 6: + return WeatherState.SNOW; + case 7: + return WeatherState.HAZE; + case 8: + if(apiWeatherId == 800) { + return WeatherState.SUNNY; + } else if(apiWeatherId == 801) { + return WeatherState.MOSTLY_CLOUDY; + } else { + return WeatherState.CLOUDY; + } + + case 9: + return apiWeatherId >= 951 && apiWeatherId <= 957 + ? WeatherState.MOSTLY_CLOUDY + : WeatherState.THUNDERSTORMS; + default: + return WeatherState.MOSTLY_CLOUDY; + } + } + + Function provideMapper(final City city) { + return info -> new Forecast( + Instant.ofEpochSecond(info.utcDatetimeSeconds), + info.main.temp, + info.main.pressure, + info.wind.speed, + info.wind.deg, + new WeatherState( + mapWeatherState(info.weatherStates[0].id), + info.weatherStates[0].description), + city); + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/GooglePlacesApi.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/GooglePlacesApi.java new file mode 100644 index 0000000..aabbf73 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/GooglePlacesApi.java @@ -0,0 +1,26 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.api; + +import com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places.AutoCompleteResponse; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places.CityInfoResponse; + +import io.reactivex.Single; +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * Created by turist on 28.07.2017. + */ + +public interface GooglePlacesApi { + + @GET("autocomplete/json") + Single autoComplete(@Query("input") String data, + @Query("types") String types, + @Query("key") String apiKey, + @Query("language") String language); + + @GET("details/json") + Single details(@Query("placeid") String placeid, + @Query("key") String apiKey, + @Query("language") String language); +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/GooglePlacesService.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/GooglePlacesService.java new file mode 100644 index 0000000..eceef9e --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/GooglePlacesService.java @@ -0,0 +1,49 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.api; + +import com.ikholopov.yamblz.weather.weathermobilization.di.StringQualifier; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.LanguageProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.CityInfo; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.PlacesService; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places.CityShortInfo; + +import javax.inject.Inject; + +import io.reactivex.Single; + +import static com.ikholopov.yamblz.weather.weathermobilization.di.StringQualifier.GOOGLE_API_KEY; + +/** + * Created by turist on 28.07.2017. + */ + +public class GooglePlacesService implements PlacesService { + + private final String apiKey; + private final GooglePlacesApi api; + private final LanguageProvider languageProvider; + + @Inject + public GooglePlacesService(@StringQualifier(stringType = GOOGLE_API_KEY) String apiKey, + GooglePlacesApi api, + LanguageProvider languageProvider) { + this.apiKey = apiKey; + this.api = api; + this.languageProvider = languageProvider; + } + + @Override + public Single details(String placeId) { + return api.details(placeId, apiKey, languageProvider.getLanguageCode()) + .map(cityInfo -> new CityInfo( + cityInfo.result.formattedAddress, + cityInfo.result.geometry.location.lat, + cityInfo.result.geometry.location.lng, + placeId)); + } + + @Override + public Single autocomplete(String data) { + return api.autoComplete(data, "(cities)", apiKey, languageProvider.getLanguageCode()) + .map(response -> response.predictions); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/OpenWeatherMapApi.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/OpenWeatherMapApi.java new file mode 100644 index 0000000..cfa477c --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/OpenWeatherMapApi.java @@ -0,0 +1,21 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.api; + +import com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather.ForecastResponse; + +import io.reactivex.Single; +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * Created by turist on 28.07.2017. + */ + +public interface OpenWeatherMapApi { + + @GET("2.5/forecast") + Single forecast(@Query("appid") String apiKey, + @Query("lat") double lat, + @Query("lon") double lng, + @Query("units") String units, + @Query("lang") String lang); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/OpenWeatherMapService.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/OpenWeatherMapService.java new file mode 100644 index 0000000..3da4051 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/api/OpenWeatherMapService.java @@ -0,0 +1,45 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.api; + +import com.ikholopov.yamblz.weather.weathermobilization.di.StringQualifier; +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.Forecast; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.LanguageProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.WeatherService; + +import javax.inject.Inject; + +import io.reactivex.Observable; + +import static com.ikholopov.yamblz.weather.weathermobilization.di.StringQualifier.OPEN_WEATHER_MAP_API_KEY; + +/** + * Created by turist on 28.07.2017. + */ + +public class OpenWeatherMapService implements WeatherService { + + private static final String UNITS = "metric"; + + private final String apiKey; + private final OpenWeatherMapApi api; + private final LanguageProvider languageProvider; + private final ForecastMapperProvider weatherMapper; + + @Inject + public OpenWeatherMapService(@StringQualifier(stringType = OPEN_WEATHER_MAP_API_KEY) String apiKey, + OpenWeatherMapApi api, + LanguageProvider languageProvider, + ForecastMapperProvider weatherMapper) { + this.apiKey = apiKey; + this.api = api; + this.languageProvider = languageProvider; + this.weatherMapper = weatherMapper; + } + + @Override + public Observable forecasts(final City city) { + return api.forecast(apiKey, city.coordinates.lat, city.coordinates.lng, UNITS, languageProvider.getLanguageCode()) + .flatMapObservable(response -> Observable.fromArray(response.list)) + .map(weatherMapper.provideMapper(city)); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/AutoCompleteResponse.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/AutoCompleteResponse.java new file mode 100644 index 0000000..b563844 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/AutoCompleteResponse.java @@ -0,0 +1,14 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places; + +/** + * Created by turist on 28.07.2017. + */ + +public class AutoCompleteResponse { + + public final CityShortInfo[] predictions; + + public AutoCompleteResponse(CityShortInfo[] predictions) { + this.predictions = predictions; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/CityInfoResponse.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/CityInfoResponse.java new file mode 100644 index 0000000..7d5fe95 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/CityInfoResponse.java @@ -0,0 +1,14 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places; + +/** + * Created by turist on 28.07.2017. + */ + +public class CityInfoResponse { + + public final CityInfoResult result; + + public CityInfoResponse(CityInfoResult result) { + this.result = result; + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/CityInfoResult.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/CityInfoResult.java new file mode 100644 index 0000000..31db023 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/CityInfoResult.java @@ -0,0 +1,21 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by turist on 28.07.2017. + */ + +public class CityInfoResult { + + @SerializedName("formatted_address") + public final String formattedAddress; + + @SerializedName("geometry") + public final Geometry geometry; + + public CityInfoResult(String formattedAddress, Geometry geometry) { + this.formattedAddress = formattedAddress; + this.geometry = geometry; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/CityShortInfo.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/CityShortInfo.java new file mode 100644 index 0000000..53e4795 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/CityShortInfo.java @@ -0,0 +1,26 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by turist on 28.07.2017. + */ + +public class CityShortInfo { + + @SerializedName("description") + public final String description; + + @SerializedName("place_id") + public final String placeId; + + public CityShortInfo(String description, String placeId) { + this.description = description; + this.placeId = placeId; + } + + @Override + public String toString() { + return description; + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/Geometry.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/Geometry.java new file mode 100644 index 0000000..d67e07c --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/Geometry.java @@ -0,0 +1,14 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places; + +/** + * Created by turist on 28.07.2017. + */ + +public class Geometry { + + public final Location location; + + public Geometry(Location location) { + this.location = location; + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/Location.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/Location.java new file mode 100644 index 0000000..1de70e7 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/places/Location.java @@ -0,0 +1,16 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places; + +/** + * Created by turist on 28.07.2017. + */ + +public class Location { + + public final double lat; + public final double lng; + + public Location(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/City.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/City.java new file mode 100644 index 0000000..1cf27fe --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/City.java @@ -0,0 +1,12 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather; + +/** + * Created by turist on 04.08.2017. + */ + +public class City { + public long id; + public String name; + public Coordinates coord; + public String country; +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Clouds.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Clouds.java new file mode 100644 index 0000000..b4fd70e --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Clouds.java @@ -0,0 +1,5 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather; + +public class Clouds { + int all; +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Coordinates.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Coordinates.java new file mode 100644 index 0000000..3295454 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Coordinates.java @@ -0,0 +1,6 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather; + +public class Coordinates { + double lon; + double lat; +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/ForecastInfo.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/ForecastInfo.java new file mode 100644 index 0000000..dc971ea --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/ForecastInfo.java @@ -0,0 +1,21 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by turist on 04.08.2017. + */ + +public class ForecastInfo { + + @SerializedName("dt") + public long utcDatetimeSeconds; + + public Main main; + + @SerializedName("weather") + public Weather[] weatherStates; + + public Clouds clouds; + public Wind wind; +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/ForecastResponse.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/ForecastResponse.java new file mode 100644 index 0000000..471397e --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/ForecastResponse.java @@ -0,0 +1,10 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather; + +/** + * Created by turist on 04.08.2017. + */ + +public class ForecastResponse { + public ForecastInfo[] list; + public City city; +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Main.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Main.java new file mode 100644 index 0000000..e44b3a6 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Main.java @@ -0,0 +1,9 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather; + +public class Main { + public double temp; + public double temp_min; + public double temp_max; + public double pressure; + public int humidity; +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Weather.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Weather.java similarity index 59% rename from app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Weather.java rename to app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Weather.java index 7b9f1b8..0b3ee71 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/data/dto/weather/Weather.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Weather.java @@ -1,4 +1,4 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data.dto.weather; +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather; public class Weather { public int id; diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Wind.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Wind.java new file mode 100644 index 0000000..1c3bb7b --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/network/dto/weather/Wind.java @@ -0,0 +1,6 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.weather; + +public class Wind { + public double speed; + public double deg; +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/CityRepository.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/CityRepository.java new file mode 100644 index 0000000..9cdd03a --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/CityRepository.java @@ -0,0 +1,43 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.persistence; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao.CityDao; + +import javax.inject.Inject; + +import io.reactivex.Flowable; +import io.reactivex.Maybe; + +/** + * Created by turist on 13.08.2017. + */ + +public class CityRepository { + + private final CityDao cityDao; + + @Inject + public CityRepository(CityDao cityDao) { + this.cityDao = cityDao; + } + + public void insert(City city) { + cityDao.insert(city); + } + + public void delete(City city) { + cityDao.delete(city); + } + + public Maybe getByPlaceId(String placeId) { + return cityDao.getByPlaceId(placeId); + } + + public Flowable cities(){ + return cityDao.all(); + } + + public City[] blockingAll() { + return cityDao.blockingAll(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/WeatherRepository.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/WeatherRepository.java new file mode 100644 index 0000000..c264b88 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/WeatherRepository.java @@ -0,0 +1,40 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.persistence; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.Forecast; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao.ForecastDao; + +import org.threeten.bp.Instant; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; + +import io.reactivex.Flowable; + +/** + * Created by turist on 13.08.2017. + */ + +public class WeatherRepository { + + private final ForecastDao forecastDao; + + @Inject + public WeatherRepository(ForecastDao forecastDao) { + this.forecastDao = forecastDao; + } + + public Flowable> forCity(City city) { + return forecastDao.forCity(city.id); + } + + public void save(List forecasts) { + forecastDao.insert(forecasts); + } + + public void deleteForCity(City city) { + forecastDao.deleteForCity(city.id); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/dao/CityDao.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/dao/CityDao.java new file mode 100644 index 0000000..137e72a --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/dao/CityDao.java @@ -0,0 +1,37 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; + +import java.util.Collection; + +import io.reactivex.Flowable; +import io.reactivex.Maybe; + +/** + * Created by turist on 05.08.2017. + */ + +@Dao +public interface CityDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(City city); + + @Query("SELECT * FROM cities WHERE place_id = :placeId") + Maybe getByPlaceId(String placeId); + + @Delete + void delete(City city); + + @Query("SELECT * FROM cities") + Flowable all(); + + @Query("SELECT * FROM cities") + City[] blockingAll(); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/dao/ForecastDao.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/dao/ForecastDao.java new file mode 100644 index 0000000..f066618 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/dao/ForecastDao.java @@ -0,0 +1,32 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; + +import com.ikholopov.yamblz.weather.weathermobilization.model.Forecast; + +import java.util.List; + +import io.reactivex.Flowable; + +/** + * Created by turist on 05.08.2017. + */ + +@Dao +public interface ForecastDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(Forecast forecast); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(List forecast); + + @Query("SELECT * FROM forecasts WHERE city_id = :cityId") + Flowable> forCity(long cityId); + + @Query("DELETE FROM forecasts WHERE city_id = :cityId") + void deleteForCity(long cityId); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/db/AppDatabase.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/db/AppDatabase.java new file mode 100644 index 0000000..f04cc3f --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/db/AppDatabase.java @@ -0,0 +1,21 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.persistence.db; + +import android.arch.persistence.room.Database; +import android.arch.persistence.room.RoomDatabase; +import android.arch.persistence.room.TypeConverters; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.Forecast; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao.CityDao; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao.ForecastDao; + +/** + * Created by turist on 06.08.2017. + */ + +@Database(entities = {City.class, Forecast.class}, version = 1, exportSchema = false) +@TypeConverters({Converters.class}) +public abstract class AppDatabase extends RoomDatabase { + public abstract CityDao cityDao(); + public abstract ForecastDao forecastDao(); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/db/Converters.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/db/Converters.java new file mode 100644 index 0000000..a120e0f --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/db/Converters.java @@ -0,0 +1,22 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.persistence.db; + +import android.arch.persistence.room.TypeConverter; + +import org.threeten.bp.Instant; + +/** + * Created by turist on 06.08.2017. + */ + +public class Converters { + + @TypeConverter + public static Instant fromTimestamp(Long value) { + return Instant.ofEpochSecond(value); + } + + @TypeConverter + public static Long dateToTimestamp(Instant date) { + return date.getEpochSecond(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/LanguageProvider.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/LanguageProvider.java new file mode 100644 index 0000000..29d0ca9 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/LanguageProvider.java @@ -0,0 +1,9 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.preferences; + +/** + * Created by turist on 02.08.2017. + */ + +public interface LanguageProvider { + String getLanguageCode(); +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/LocalLanguageProvider.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/LocalLanguageProvider.java new file mode 100644 index 0000000..c204486 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/LocalLanguageProvider.java @@ -0,0 +1,14 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.preferences; + +import java.util.Locale; + +/** + * Created by turist on 07.08.2017. + */ + +public class LocalLanguageProvider implements LanguageProvider { + @Override + public String getLanguageCode() { + return Locale.getDefault().getLanguage(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/PreferencesProvider.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/PreferencesProvider.java new file mode 100644 index 0000000..6201094 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/PreferencesProvider.java @@ -0,0 +1,54 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.preferences; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +import org.threeten.bp.Duration; + +import javax.inject.Inject; + +/** + * Simple SharedPreferences wrapper + * Created by igor on 7/14/17. + */ + +public class PreferencesProvider { + + private static final String TEMPERATURE_FORMAT_KEY = "temperature_format"; + private static final String AUTO_UPDATE_INTERVAL_KEY = "auto_update_interval"; + + private final SharedPreferences sharedPreferences; + + @Inject + public PreferencesProvider(@NonNull SharedPreferences sharedPreferences) { + this.sharedPreferences = sharedPreferences; + } + + @TemperatureFormat.Units + public int getTemperatureFormat() { + @TemperatureFormat.Units + int value = sharedPreferences + .getInt(TEMPERATURE_FORMAT_KEY, TemperatureFormat.CELSIUS); + + return value; + } + + public void putTemperatureFormat(@TemperatureFormat.Units int unit) { + sharedPreferences + .edit() + .putInt(TEMPERATURE_FORMAT_KEY, unit) + .apply(); + } + + public Duration getAutoUpdateInterval() { + long minutes = sharedPreferences.getLong(AUTO_UPDATE_INTERVAL_KEY, 30); + return Duration.ofMinutes(minutes); + } + + public void putAutoUpdateInterval(Duration duration) { + sharedPreferences + .edit() + .putLong(AUTO_UPDATE_INTERVAL_KEY, duration.toMinutes()) + .apply(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/TemperatureFormat.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/TemperatureFormat.java new file mode 100644 index 0000000..de7c650 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/TemperatureFormat.java @@ -0,0 +1,59 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.preferences; + +import android.support.annotation.IntDef; +import android.support.annotation.StringRes; + +import com.ikholopov.yamblz.weather.weathermobilization.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public abstract class TemperatureFormat { + + public static final int CELSIUS = 0; + public static final int FAHRENHEIT = 1; + + @IntDef({CELSIUS, FAHRENHEIT}) + @Retention(RetentionPolicy.SOURCE) + public @interface Units {} + + @StringRes + public abstract int unitStringId(); + + public abstract double convert(double celsiusDegree); + + public static TemperatureFormat create(@Units int unit) { + return unit == CELSIUS ? new CelsiusFormat() : new FahrenheitFormat(); + } + + private static class CelsiusFormat extends TemperatureFormat { + + @Override + @StringRes + public int unitStringId() { + return R.string.celsius; + } + + @Override + public double convert(double celsiusDegree) { + return celsiusDegree; + } + } + + private static class FahrenheitFormat extends TemperatureFormat { + + private static final double FAHRENHEIT_SCALE = 9.0 / 5.0; + private static final double FAHRENHEIT_BASE = 32.0; + + @Override + @StringRes + public int unitStringId() { + return R.string.fahrenheit; + } + + @Override + public double convert(double celsiusDegree) { + return FAHRENHEIT_SCALE * celsiusDegree + FAHRENHEIT_BASE; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/NetWeatherUpdater.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/NetWeatherUpdater.java new file mode 100644 index 0000000..6d0e996 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/NetWeatherUpdater.java @@ -0,0 +1,61 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.services; + +import android.support.annotation.WorkerThread; +import android.util.Log; + +import com.ikholopov.yamblz.weather.weathermobilization.SchedulerProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.WeatherService; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.CityRepository; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.WeatherRepository; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.db.AppDatabase; + +import javax.inject.Inject; + +import io.reactivex.Observable; + +/** + * Created by turist on 13.08.2017. + */ + +public class NetWeatherUpdater implements WeatherUpdater { + + final WeatherRepository weatherRepository; + final CityRepository cityRepository; + final WeatherService weatherService; + final SchedulerProvider schedulers; + + @Inject + public NetWeatherUpdater(WeatherRepository weatherRepository, CityRepository cityRepository, + WeatherService weatherService, SchedulerProvider schedulers) { + this.weatherRepository = weatherRepository; + this.cityRepository = cityRepository; + this.weatherService = weatherService; + this.schedulers = schedulers; + } + + @Override + public void updateWeather() { + Observable.fromArray(cityRepository.blockingAll()) + .subscribeOn(schedulers.computation()) + .subscribe( + this::updateWeather, + throwable -> Log.e("NetWeatherUpdater", "updateWeather", throwable)); + } + + @Override + @WorkerThread + public void updateWeather(City city) { + weatherService.forecasts(city) + .toList() + .subscribe( + forecasts -> { + weatherRepository.deleteForCity(city); + weatherRepository.save(forecasts); + }, + throwable -> + Log.e("NetWeatherUpdater", "updateWeather(city)", throwable + ) + ); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/UpdateServiceController.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/UpdateServiceController.java new file mode 100644 index 0000000..2baed82 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/UpdateServiceController.java @@ -0,0 +1,13 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.services; + +import org.threeten.bp.Duration; + +/** + * Responsible for starting a service + * Created by igor on 7/21/17. + */ + +public interface UpdateServiceController { + void enableService(Duration updateInterval); + void disableService(); +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/WeatherUpdateService.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/WeatherUpdateService.java similarity index 73% rename from app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/WeatherUpdateService.java rename to app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/WeatherUpdateService.java index e56978b..4e8d75f 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/WeatherUpdateService.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/WeatherUpdateService.java @@ -1,4 +1,4 @@ -package com.ikholopov.yamblz.weather.weathermobilization; +package com.ikholopov.yamblz.weather.weathermobilization.model.services; import android.app.AlarmManager; import android.app.IntentService; @@ -8,40 +8,42 @@ import android.os.SystemClock; import android.support.annotation.Nullable; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherLoader; +import com.ikholopov.yamblz.weather.weathermobilization.WeatherApplication; + +import javax.inject.Inject; /** * Created by igor on 7/16/17. */ public class WeatherUpdateService extends IntentService { - /** - * Creates an IntentService. Invoked by your subclass's constructor. - * - * @param name Used to name the worker thread, important only for debugging. - */ private static final String SERVICE_NAME = "WEATHER_UPDATE_SERVICE"; private static final int SERVICE_ID = 0; + @Inject WeatherUpdater updater; + public WeatherUpdateService() { super(SERVICE_NAME); } @Override protected void onHandleIntent(@Nullable Intent intent) { - CurrentWeatherLoader loader = WeatherApplication.get(getApplicationContext()).getComponent().getWeatherLoader(); - loader.forceNetLoad(); + WeatherApplication.get(getApplicationContext()) + .getComponent().inject(this); + + updater.updateWeather(); } - public static void setServiceEnabled(Context applicationContext, int interval) { + + public static void setServiceEnabled(Context applicationContext, long intervalMillis) { Intent intent = new Intent(applicationContext, WeatherUpdateService.class); PendingIntent pendingIntent = PendingIntent.getService(applicationContext, SERVICE_ID, intent, 0); AlarmManager manager = (AlarmManager) applicationContext.getSystemService(ALARM_SERVICE); manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, - interval * 60 * 1000, pendingIntent); + intervalMillis, pendingIntent); } public static void setServiceDisabled(Context applicationContext) { @@ -53,4 +55,4 @@ public static void setServiceDisabled(Context applicationContext) { manager.cancel(pendingIntent); pendingIntent.cancel(); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/WeatherUpdateServiceController.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/WeatherUpdateServiceController.java new file mode 100644 index 0000000..c7372b8 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/WeatherUpdateServiceController.java @@ -0,0 +1,27 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.services; + +import android.content.Context; + +import org.threeten.bp.Duration; + +import javax.inject.Inject; + +public class WeatherUpdateServiceController implements UpdateServiceController { + + private final Context context; + + @Inject + public WeatherUpdateServiceController(Context context) { + this.context = context; + } + + @Override + public void enableService(Duration updateInterval) { + WeatherUpdateService.setServiceEnabled(context, updateInterval.toMillis()); + } + + @Override + public void disableService() { + WeatherUpdateService.setServiceDisabled(context); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/WeatherUpdater.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/WeatherUpdater.java new file mode 100644 index 0000000..3fb9198 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/model/services/WeatherUpdater.java @@ -0,0 +1,13 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.services; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; + +/** + * Created by turist on 13.08.2017. + */ + +public interface WeatherUpdater { + + void updateWeather(); + void updateWeather(City city); +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/preferences/Metric.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/preferences/Metric.java deleted file mode 100644 index 3e01da4..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/preferences/Metric.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.preferences; - -import com.ikholopov.yamblz.weather.weathermobilization.R; - -/** - * Enum for temperature metrics - */ - -public enum Metric { - FAHRENHEIT, CELSIUS; - - public int getStringId() { - if(this == Metric.FAHRENHEIT) { - return R.string.farhenheit; - } else { - return R.string.celsius; - } - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/preferences/PreferencesProvider.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/preferences/PreferencesProvider.java deleted file mode 100644 index d417f88..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/preferences/PreferencesProvider.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.preferences; - -import android.content.Context; -import android.content.SharedPreferences; -import android.support.annotation.NonNull; -import android.support.v7.preference.PreferenceManager; - -import com.ikholopov.yamblz.weather.weathermobilization.R; -import com.ikholopov.yamblz.weather.weathermobilization.data.CityInfo; - -/** - * Simple SharedPreferences wrapper - * Created by igor on 7/14/17. - */ - -public class PreferencesProvider { - - private Context context; - - public PreferencesProvider(@NonNull Context context) { - this.context = context; - } - - public Metric getMetricFromPreference(){ - if(PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.preference_key_metrics), true)) { - return Metric.CELSIUS; - } - return Metric.FAHRENHEIT; - } - - public boolean getAutoupdateEnabledPreference() { - return PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.preference_key_autoupdate), true); - } - - public int getUpdateInterval() { - return PreferenceManager.getDefaultSharedPreferences(context) - .getInt(context.getString(R.string.preference_key_update_interval), - context.getResources().getInteger(R.integer.default_update_interval)); - } - - public String getCityName() { - return PreferenceManager.getDefaultSharedPreferences(context) - .getString(context.getString(R.string.preference_key_city_name), - context.getString(R.string.default_city_name)); - } - - public float getCityLat() { - return PreferenceManager.getDefaultSharedPreferences(context) - .getFloat(context.getString(R.string.preference_key_city_lat), - Float.parseFloat(context.getString(R.string.default_city_lat))); - } - - public float getCityLng() { - return PreferenceManager.getDefaultSharedPreferences(context) - .getFloat(context.getString(R.string.preference_key_city_lng), - Float.parseFloat(context.getString(R.string.default_city_lng))); - } - - public void saveCity(CityInfo city) { - SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); - editor.putString(context.getString(R.string.preference_key_city_name), city.getName()); - editor.putFloat(context.getString(R.string.preference_key_city_lat), city.getLat()); - editor.putFloat(context.getString(R.string.preference_key_city_lng), city.getLng()); - editor.apply(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitiesPresenter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitiesPresenter.java new file mode 100644 index 0000000..7454e7f --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitiesPresenter.java @@ -0,0 +1,162 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import android.util.Log; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.SchedulerProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.Coordinates; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.PlacesService; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.CityRepository; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.CitiesView; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.CityWeather; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; + +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.SingleSource; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; + +/** + * Created by turist on 08.08.2017. + */ + +public class CitiesPresenter extends RxPresenter { + + final MainRouter router; + final PlacesService placesService; + final CityRepository repository; + final SchedulerProvider schedulers; + + private CitiesView view; + private Disposable subscriptionOnSavedCities; + + @Inject + public CitiesPresenter(MainRouter router, PlacesService placesService, + CityRepository repository, SchedulerProvider schedulers) { + this.router = router; + this.placesService = placesService; + this.repository = repository; + this.schedulers = schedulers; + } + + public void bind(CitiesView view) { + this.view = view; + + save(view.settingsRequests() + .subscribe(o -> router.showSettings())); + + Observable searches = view.citiesSearches() + .debounce(400, TimeUnit.MILLISECONDS) + .subscribeOn(schedulers.computation()); + + save(searches + .map(s -> s.length() > 0) + .observeOn(schedulers.main()) + .subscribe(view::showClearButton)); + + save(view.searchClears().subscribe(o -> view.clearSearch())); + + save(searches + .filter(str -> str.length() > 0) + .switchMapSingle(this::search) + .observeOn(schedulers.main()) + .doOnNext(o -> { + clearSubscriptionOnSavedCities(); + view.setDescription(R.string.search_result); + }) + .subscribe(view, throwable -> { + Log.e("autocomplete", throwable.getMessage(), throwable); + view.onError(); + })); + + save(searches.filter(str -> str.length() == 0) + .observeOn(schedulers.main()) + .subscribe(o -> save(subscriptionOnSavedCities = repository.cities() + .subscribeOn(schedulers.computation()) + .flatMapSingle(cities -> Observable.fromArray(cities) + .map(CityWeather::new) + .toList()) + .observeOn(schedulers.main()) + .doOnNext(a -> view.setDescription(R.string.saved_cities)) + .subscribe(view, throwable -> { + Log.e("cities from db", throwable.getMessage(), throwable); + view.onError(); + })))); + + save(view.citiesSelections() + .observeOn(schedulers.computation()) + .map(CityWeather::getPlaceId) + .switchMapSingle(placeId -> + repository.getByPlaceId(placeId) + .switchIfEmpty(placesService.details(placeId) + .map(d -> new City(d.getName(), d.getPlaceId(), + new Coordinates(d.getLat(), d.getLng()))) + .toMaybe()) + .toSingle() + ) + .observeOn(schedulers.main()) + .subscribe(router::showCityWeather, throwable -> { + Log.e("city select", throwable.getMessage(), throwable); + view.onError(); + })); + + save(view.citiesOptionSelections() + .observeOn(schedulers.main()) + .subscribe(this::askAction, throwable -> { + Log.e("city options", throwable.getMessage(), throwable); + view.onError(); + })); + } + + void clearSubscriptionOnSavedCities() { + if(subscriptionOnSavedCities != null && !subscriptionOnSavedCities.isDisposed()) { + subscriptionOnSavedCities.dispose(); + } + } + + Single getDetails(String placeId) { + return placesService.details(placeId) + .map(d -> new City(d.getName(), d.getPlaceId(), + new Coordinates(d.getLat(), d.getLng()))); + } + + @Override + public void unbind() { + super.unbind(); + view = null; + } + + void askAction(CityWeather city) { + if(city.inStorage()) { + save(view.ask(R.string.delete_city) + .observeOn(schedulers.io()) + .flatMap(o -> repository.getByPlaceId(city.getPlaceId())) + .subscribe(repository::delete, throwable -> { + Log.e("city delete", throwable.getMessage(), throwable); + view.onError(); + })); + } else { + save(view.ask(R.string.save_city) + .observeOn(schedulers.io()) + .flatMapSingle(o -> getDetails(city.getPlaceId())) + .subscribe(repository::insert, throwable -> { + Log.e("city save", throwable.getMessage(), throwable); + view.onError(); + })); + } + } + + SingleSource> search(@NonNull CharSequence charSequence) throws Exception { + return placesService.autocomplete(charSequence.toString()) + .flatMapObservable(Observable::fromArray) + .map(cityShortInfo -> new CityWeather(cityShortInfo.placeId, cityShortInfo.description)) + .toList() + .subscribeOn(schedulers.io()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitySelectPresenter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitySelectPresenter.java deleted file mode 100644 index 64e21d7..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitySelectPresenter.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -import android.util.Log; - -import com.ikholopov.yamblz.weather.weathermobilization.data.CityAutoComplete; -import com.ikholopov.yamblz.weather.weathermobilization.data.CityInfo; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherCache; -import com.ikholopov.yamblz.weather.weathermobilization.data.PlacesService; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.city.CityShortInfo; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.ui.CitySelectView; - -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; - -import io.reactivex.MaybeSource; -import io.reactivex.ObservableSource; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.annotations.NonNull; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; -import io.reactivex.functions.Predicate; -import io.reactivex.schedulers.Schedulers; - -/** - * Created by turist on 28.07.2017. - */ - -public class CitySelectPresenter { - - private CompositeDisposable disposables = new CompositeDisposable(); - private PlacesService service; - private PreferencesProvider preferencesProvider; - private CurrentWeatherCache cache; - - @Inject - public CitySelectPresenter(PlacesService service, PreferencesProvider preferencesProvider, - CurrentWeatherCache cache) { - this.service = service; - this.preferencesProvider = preferencesProvider; - this.cache = cache; - } - - public void bind(@NonNull final CitySelectView view) { - disposables.add( - view.textChanges() - .debounce(400, TimeUnit.MILLISECONDS) - .subscribeOn(Schedulers.computation()) - .filter(new Predicate() { - @Override - public boolean test(@NonNull CharSequence charSequence) throws Exception { - return charSequence.length() > 0; - } - }) - .switchMap(new Function>() { - @Override - public ObservableSource apply(@NonNull CharSequence charSequence) throws Exception { - return service.autocomplete(charSequence.toString()).toObservable().subscribeOn(Schedulers.io()); - } - }, 1) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(view, new Consumer() { - @Override - public void accept(@NonNull Throwable throwable) throws Exception { - Log.d("autocomplete", throwable.getMessage(), throwable); - view.onError(); - } - }) - ); - - disposables.add( - view.selection() - .firstElement() - .flatMap(new Function>() { - @Override - public MaybeSource apply(@NonNull CityShortInfo cityShortInfo) throws Exception { - return service.details(cityShortInfo.place_id).subscribeOn(Schedulers.io()).toMaybe(); - } - }) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Consumer() { - @Override - public void accept(@NonNull CityInfo cityInfo) throws Exception { - preferencesProvider.saveCity(cityInfo); - cache.clear(); - view.onSelected(); - } - }, new Consumer() { - @Override - public void accept(@NonNull Throwable throwable) throws Exception { - Log.d("details", throwable.getMessage(), throwable); - view.onError(); - } - }) - ); - } - - public void unbind() { - disposables.clear(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CurrentWeatherPresenter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CurrentWeatherPresenter.java deleted file mode 100644 index 96eb3dd..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CurrentWeatherPresenter.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.ForecastFragment; - -/** - * Presenter for main screen - * Created by igor on 7/16/17. - */ - -public interface CurrentWeatherPresenter { - void bind(ForecastFragment weatherFragment); - void forceReload(); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CurrentWeatherPresenterImpl.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CurrentWeatherPresenterImpl.java deleted file mode 100644 index c0ac251..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CurrentWeatherPresenterImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.ForecastFragment; - -import javax.inject.Inject; - -/** - * Created by igor on 7/16/17. - */ - -public class CurrentWeatherPresenterImpl implements CurrentWeatherPresenter { - - private PreferencesProvider preferences; - private UpdateServiceController serviceController; - private LoaderNetController weatherNetController; - - @Inject - public CurrentWeatherPresenterImpl(PreferencesProvider preferences, - UpdateServiceController serviceController, LoaderNetController weatherNetController){ - this.preferences = preferences; - this.serviceController = serviceController; - this.weatherNetController = weatherNetController; - } - - @Override - public void bind(ForecastFragment weatherFragment) { - weatherNetController.bindForecastFragment(weatherFragment); - if(preferences.getAutoupdateEnabledPreference()) { - serviceController.enableService(preferences.getUpdateInterval()); - } - } - - @Override - public void forceReload() { - weatherNetController.forceNetLoad(); - } -} - diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/LoaderNetController.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/LoaderNetController.java deleted file mode 100644 index 668ca3f..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/LoaderNetController.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -import android.support.annotation.NonNull; -import android.support.v4.app.LoaderManager; - -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeather; -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.ForecastFragment; - -/** - * Responsible for Loader initialization and handling requests to it - * Created by igor on 7/21/17. - */ - -public interface LoaderNetController extends LoaderManager.LoaderCallbacks { - void bindForecastFragment(@NonNull ForecastFragment fragment); - void forceNetLoad(); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/MainActivityRouter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/MainActivityRouter.java new file mode 100644 index 0000000..85ae01e --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/MainActivityRouter.java @@ -0,0 +1,76 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import android.support.annotation.IdRes; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.CitiesFragment; +import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.SettingsFragment; +import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.WeatherFragment; + +/** + * Created by turist on 09.08.2017. + */ + +public class MainActivityRouter implements MainRouter { + + static final String ONE_CITY_TAG = "city"; + private static final String CITIES_LIST_TAG = "cities"; + static final String SETTINGS_TAG = "settings"; + + private final FragmentManager manager; + + public MainActivityRouter(FragmentManager manager) { + this.manager = manager; + } + + @Override + public void showCityWeather(City city) { + final String tag = ONE_CITY_TAG + city.placeId; + if(needShow(tag)) { + placeBackFragment(WeatherFragment.create(city), R.id.container, tag); + } + } + + @Override + public void showCitiesList() { + if(needShow(CITIES_LIST_TAG)) { + replaceFragment(new CitiesFragment(), R.id.container, CITIES_LIST_TAG); + } + } + + @Override + public void showSettings() { + if(needShow(SETTINGS_TAG)) { + placeBackFragment(new SettingsFragment(), R.id.container, SETTINGS_TAG); + } + } + + boolean needShow(String tag) { + return manager.findFragmentByTag(tag) == null; + } + + void replaceFragment(Fragment fragment, @IdRes int containerId, String tag) { + manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); + + manager.beginTransaction() + .setCustomAnimations(R.anim.slide_in, R.anim.slide_out, + R.anim.slide_in_pop, R.anim.slide_out_pop) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) + .replace(containerId, fragment, tag) + .commit(); + } + + void placeBackFragment(Fragment fragment, @IdRes int containerId, String tag) { + manager.beginTransaction() + .setCustomAnimations(R.anim.slide_in, R.anim.slide_out, + R.anim.slide_in_pop, R.anim.slide_out_pop) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) + .replace(containerId, fragment, tag) + .addToBackStack(null) + .commit(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/MainPresenter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/MainPresenter.java new file mode 100644 index 0000000..adc553d --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/MainPresenter.java @@ -0,0 +1,25 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.MainView; + +import javax.inject.Inject; + +/** + * Created by turist on 09.08.2017. + */ + +public class MainPresenter extends RxPresenter { + + private final MainRouter router; + + @Inject + public MainPresenter(MainRouter router) { + this.router = router; + } + + public void bind(MainView view, boolean hasState) { + if(!hasState) { + router.showCitiesList(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/MainRouter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/MainRouter.java new file mode 100644 index 0000000..d17cb6d --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/MainRouter.java @@ -0,0 +1,13 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; + +/** + * Created by turist on 09.08.2017. + */ + +public interface MainRouter { + void showCityWeather(City city); + void showCitiesList(); + void showSettings(); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/RxPresenter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/RxPresenter.java new file mode 100644 index 0000000..340dd96 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/RxPresenter.java @@ -0,0 +1,31 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import android.support.annotation.CallSuper; + +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; + +/** + * Base presenter, containing utility functional for work with {@link io.reactivex} framework + * + * Save subscriptions on {@link io.reactivex.Observable} and unsubscribe its when unbinded + * + * Created by turist on 02.08.2017. + */ +public class RxPresenter implements Unbindable { + + private CompositeDisposable disposables = new CompositeDisposable(); + + /** + * Any subscription must be saved with this method + */ + protected void save(Disposable subscription) { + this.disposables.add(subscription); + } + + @Override + @CallSuper + public void unbind() { + disposables.clear(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/SettingsPresenter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/SettingsPresenter.java new file mode 100644 index 0000000..16417e8 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/SettingsPresenter.java @@ -0,0 +1,68 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.PreferencesProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.TemperatureFormat; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.UpdateServiceController; +import com.ikholopov.yamblz.weather.weathermobilization.ui.UpdateIntervalFormat; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.SettingsView; + +import org.threeten.bp.Duration; + +import javax.inject.Inject; + +import static com.ikholopov.yamblz.weather.weathermobilization.model.preferences.TemperatureFormat.CELSIUS; + +/** + * Created by turist on 08.08.2017. + */ + +public class SettingsPresenter extends RxPresenter { + + private final PreferencesProvider preferences; + private final UpdateIntervalFormat format; + private final UpdateServiceController serviceController; + private SettingsView view; + + @Inject + public SettingsPresenter(PreferencesProvider preferences, UpdateIntervalFormat format, + UpdateServiceController serviceController) { + this.preferences = preferences; + this.format = format; + this.serviceController = serviceController; + } + + public void bind(SettingsView view) { + this.view = view; + view.setTemperatureUnit(preferences.getTemperatureFormat() == CELSIUS); + view.setUpdateInterval(format.getString(preferences.getAutoUpdateInterval())); + + save(view.temperatureUnitsSwitches() + .subscribe(this::saveTemperatureUnit)); + + save(view.updateIntervalRequests() + .subscribe(o -> view.showUpdateIntervalDialog(this::saveInterval))); + } + + @Override + public void unbind() { + super.unbind(); + view = null; + } + + private void saveTemperatureUnit(boolean isCelsius) { + preferences.putTemperatureFormat( + isCelsius ? CELSIUS : TemperatureFormat.FAHRENHEIT); + } + + void saveInterval(int intervalId) { + Duration interval = format.getInterval(intervalId); + preferences.putAutoUpdateInterval(interval); + view.setUpdateInterval(format.getString(intervalId)); + + if(interval.isZero()) { + serviceController.disableService(); + } else { + serviceController.enableService(interval); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/TwoPaneRouter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/TwoPaneRouter.java new file mode 100644 index 0000000..5f02369 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/TwoPaneRouter.java @@ -0,0 +1,42 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import android.support.v4.app.FragmentManager; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.SettingsFragment; +import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.WeatherFragment; + +/** + * Created by turist on 10.08.2017. + */ + +public class TwoPaneRouter extends MainActivityRouter { + + public interface RightPanelReplaced { + void onReplaced(); + } + + private RightPanelReplaced rightPanelReplaced; + + public TwoPaneRouter(FragmentManager manager, RightPanelReplaced rightPanelReplaced) { + super(manager); + this.rightPanelReplaced = rightPanelReplaced; + } + + @Override + public void showCityWeather(City city) { + final String tag = ONE_CITY_TAG + city.id; + if(needShow(tag)) { + replaceFragment(WeatherFragment.create(city), R.id.right_container, tag); + rightPanelReplaced.onReplaced(); + } + } + + @Override + public void showSettings() { + if(needShow(SETTINGS_TAG)) { + placeBackFragment(new SettingsFragment(), R.id.root_container, SETTINGS_TAG); + } + } +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/Unbindable.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/Unbindable.java new file mode 100644 index 0000000..3d52974 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/Unbindable.java @@ -0,0 +1,9 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +/** + * Created by turist on 13.08.2017. + */ + +public interface Unbindable { + void unbind(); +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/UpdateServiceController.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/UpdateServiceController.java deleted file mode 100644 index 760b54c..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/UpdateServiceController.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -/** - * Responsible for starting a service - * Created by igor on 7/21/17. - */ - -public interface UpdateServiceController { - void enableService(int updateInterval); - void disableService(); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherNetController.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherNetController.java deleted file mode 100644 index 66117a9..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherNetController.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.content.Loader; - -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeather; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherLoader; -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.ForecastFragment; - -public class WeatherNetController implements LoaderNetController { - - private static final int CURRENT_WEATHER_LOADER_ID = 0; - - private ForecastFragment weatherFragment; - private CurrentWeatherLoader loader; - - public WeatherNetController(CurrentWeatherLoader loader) { - this.weatherFragment = null; - this.loader = loader; - } - - @Override - public void bindForecastFragment(@NonNull ForecastFragment fragment) { - this.weatherFragment = fragment; - this.weatherFragment.initLoader(CURRENT_WEATHER_LOADER_ID, null, this).forceLoad(); - } - - @Override - public void forceNetLoad() { - loader.forceNetLoad(); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - return loader; - } - - @Override - public void onLoadFinished(Loader loader, CurrentWeather data) { - if(weatherFragment == null) { - throw new IllegalStateException("WeatherNetController not binded!"); - } - weatherFragment.setWeather(data); - } - - @Override - public void onLoaderReset(Loader loader) { - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherPresenter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherPresenter.java new file mode 100644 index 0000000..e0da4be --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherPresenter.java @@ -0,0 +1,132 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import android.util.Log; + +import com.ikholopov.yamblz.weather.weathermobilization.SchedulerProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.Forecast; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.WeatherService; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.CityRepository; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.WeatherRepository; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.PreferencesProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.TemperatureFormat; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.WeatherUpdater; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.Weather; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.WeatherView; + +import org.threeten.bp.Instant; + +import java.util.List; + +import javax.inject.Inject; + +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.disposables.Disposable; + + +/** + * Created by igor on 7/16/17. + */ + +public class WeatherPresenter extends RxPresenter { + + final SchedulerProvider schedulers; + final WeatherRepository repository; + final CityRepository cityRepository; + final WeatherUpdater updater; + final WeatherService weatherService; + final PreferencesProvider preferences; + + WeatherView weatherView; + City city; + Disposable currentSubscription; + + @Inject + public WeatherPresenter(SchedulerProvider schedulers, WeatherRepository repository, + CityRepository cityRepository, WeatherUpdater updater, + WeatherService weatherService, PreferencesProvider preferences) { + this.schedulers = schedulers; + this.repository = repository; + this.cityRepository = cityRepository; + this.updater = updater; + this.weatherService = weatherService; + this.preferences = preferences; + } + + public void bind(final WeatherView weatherView, final City city) { + this.city = city; + this.weatherView = weatherView; + + weatherView.setLocation(city.name); + + subscribeOnStorage(); + updateWeather(); + + save(weatherView.refreshes() + .observeOn(schedulers.io()) + .subscribe(o -> updateWeather(), throwable -> { + weatherView.onError(); + Log.e("WeatherPresenter", "when updating", throwable); + })); + } + + @Override + public void unbind() { + super.unbind(); + weatherView = null; + } + + void subscribeOnStorage() { + save(currentSubscription = repository.forCity(city) + .subscribeOn(schedulers.io()) + .flatMapSingle(this::filterOldAndMapToView) + .observeOn(schedulers.main()) + .subscribe(weatherView, throwable -> { + weatherView.onError(); + Log.e("WeatherPresenter", "when get weather", throwable); + })); + } + + void updateWeather() { + cityRepository.getByPlaceId(city.placeId) + .subscribeOn(schedulers.io()) + .defaultIfEmpty(city) + .subscribe(byPlaceId -> { + if(byPlaceId.id > 0) { + + if(byPlaceId.id != city.id) { + currentSubscription.dispose(); + city = byPlaceId; + subscribeOnStorage(); + } + + updater.updateWeather(city); + + return; + } + + save(weatherService.forecasts(city).toList() + .flatMap(this::filterOldAndMapToView) + .observeOn(schedulers.main()) + .subscribe(weatherView, throwable -> { + weatherView.onError(); + Log.e("WeatherPresenter", "when get weather", throwable); + })); + + }, throwable -> { + weatherView.onError(); + Log.e("WeatherPresenter", "when check city", throwable); + }); + } + + Single> filterOldAndMapToView(List forecasts) { + Instant limit = Instant.now().minusSeconds(3600); + TemperatureFormat format = TemperatureFormat.create(preferences.getTemperatureFormat()); + return Observable.fromIterable(forecasts) + .filter(forecast -> forecast.dateTime.isAfter(limit)) + .map(forecast -> new Weather(forecast, format)) + .subscribeOn(schedulers.computation()) + .toList(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherServiceController.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherServiceController.java deleted file mode 100644 index 4a281fe..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherServiceController.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -import android.content.Context; - -import com.ikholopov.yamblz.weather.weathermobilization.WeatherUpdateService; - -import javax.inject.Inject; - -public class WeatherServiceController implements UpdateServiceController { - - private Context context; - - @Inject - public WeatherServiceController(Context context) { - this.context = context; - } - - @Override - public void enableService(int updateInterval) { - WeatherUpdateService.setServiceEnabled(context, updateInterval); - } - - @Override - public void disableService() { - WeatherUpdateService.setServiceDisabled(context); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/CitiesView.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/CitiesView.java new file mode 100644 index 0000000..49bc35c --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/CitiesView.java @@ -0,0 +1,32 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter.views; + +import android.support.annotation.StringRes; + +import com.jakewharton.rxbinding2.InitialValueObservable; + +import java.util.List; + +import io.reactivex.Maybe; +import io.reactivex.Observable; +import io.reactivex.functions.Consumer; + +/** + * Created by turist on 08.08.2017. + */ + +public interface CitiesView extends Consumer> { + + Observable searchClears(); + Observable settingsRequests(); + Observable citiesSearches(); + Observable citiesSelections(); + Observable citiesOptionSelections(); + + Maybe ask(@StringRes int message); + + void setDescription(@StringRes int value); + void showClearButton(boolean value); + void clearSearch(); + + void onError(); +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/CityWeather.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/CityWeather.java new file mode 100644 index 0000000..66d0711 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/CityWeather.java @@ -0,0 +1,69 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter.views; + +import android.support.annotation.DrawableRes; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; + +/** + * Created by turist on 02.08.2017. + */ + +public class CityWeather { + + private final long cityId; + private final String placeId; + + @DrawableRes + private final int weatherImageId; + private final String temperature; + private final String locationName; + + public CityWeather(String placeId, String locationName) { + this.placeId = placeId; + this.weatherImageId = 0; + this.temperature = null; + this.locationName = locationName; + this.cityId = 0; + } + + public CityWeather(City city) { + this(city, null, 0); + } + + public CityWeather(City city, String temperature, @DrawableRes int weatherImageId) { + this.cityId = city.id; + this.placeId = city.placeId; + this.locationName = city.name; + this.weatherImageId = weatherImageId; + this.temperature = temperature; + } + + public boolean isOnlyCity() { + return temperature == null; + } + + public boolean inStorage() { + return cityId > 0; + } + + public String getTemperature() { + return temperature; + } + + public String getLocationName() { + return locationName; + } + + @DrawableRes + public int getWeatherImageId() { + return weatherImageId; + } + + public String getPlaceId() { + return placeId; + } + + public long getCityId() { + return cityId; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/MainView.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/MainView.java new file mode 100644 index 0000000..f1f0a2a --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/MainView.java @@ -0,0 +1,11 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter.views; + + +/** + * Created by turist on 09.08.2017. + */ + +public interface MainView { + + boolean inTwoPaneMode(); +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/SettingsView.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/SettingsView.java new file mode 100644 index 0000000..f5b94ea --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/SettingsView.java @@ -0,0 +1,18 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter.views; + +import io.reactivex.Observable; +import io.reactivex.functions.Consumer; + +/** + * Created by turist on 08.08.2017. + */ + +public interface SettingsView { + + void setTemperatureUnit(boolean isCelsius); + void setUpdateInterval(String interval); + void showUpdateIntervalDialog(Consumer intervalListener); + + Observable temperatureUnitsSwitches(); + Observable updateIntervalRequests(); +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/Weather.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/Weather.java new file mode 100644 index 0000000..d7dc297 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/Weather.java @@ -0,0 +1,71 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter.views; + +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.model.Forecast; +import com.ikholopov.yamblz.weather.weathermobilization.model.WeatherState; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.TemperatureFormat; + +import org.threeten.bp.ZoneId; +import org.threeten.bp.format.DateTimeFormatter; + +/** + * Created by turist on 13.08.2017. + */ + +public class Weather { + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("dd MMM HH:mm"); + + public final String dateTime; + public final double temperature; + public final double pressure; + public final double windSpeed; + public final double windDegree; + public final String description; + + @StringRes + public final int temperatureUnit; + @DrawableRes + public final int weatherImageId; + + public Weather(Forecast forecast, TemperatureFormat format) { + this.dateTime = forecast.dateTime.atZone(ZoneId.systemDefault()) + .toLocalDateTime().format(DATE_TIME_FORMATTER); + + this.temperatureUnit = format.unitStringId(); + this.temperature = format.convert(forecast.celsiusTemperature); + this.pressure = forecast.pressure; + this.windSpeed = forecast.windSpeed; + this.windDegree = forecast.windDegree; + this.description = forecast.state.description; + this.weatherImageId = stateToImageId(forecast.state.state); + } + + @DrawableRes + int stateToImageId(@WeatherState.States int state) { + switch (state) { + case WeatherState.CLOUDY: + return R.drawable.cloudy; + case WeatherState.DRIZZLE: + return R.drawable.drizzle; + case WeatherState.HAZE: + return R.drawable.haze; + case WeatherState.MOSTLY_CLOUDY: + return R.drawable.mostly_cloudy; + case WeatherState.SLIGHT_DRIZZLE: + return R.drawable.slight_drizzle; + case WeatherState.SNOW: + return R.drawable.snow; + case WeatherState.SUNNY: + return R.drawable.sunny; + case WeatherState.THUNDERSTORMS: + return R.drawable.thunderstorms; + + default: + return R.drawable.cloudy; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/WeatherView.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/WeatherView.java new file mode 100644 index 0000000..3ce3b57 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/views/WeatherView.java @@ -0,0 +1,18 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter.views; + +import java.util.List; + +import io.reactivex.Observable; +import io.reactivex.functions.Consumer; + +/** + * Created by igor on 7/20/17. + */ + +public interface WeatherView extends Consumer> { + + Observable refreshes(); + void setLocation(String name); + + void onError(); +} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/CitySelectActivity.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/CitySelectActivity.java deleted file mode 100644 index c9e1b18..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/CitySelectActivity.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.ui; - -import android.content.Loader; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.design.widget.BaseTransientBottomBar; -import android.support.design.widget.Snackbar; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.MenuItem; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.ListView; - -import com.ikholopov.yamblz.weather.weathermobilization.R; -import com.ikholopov.yamblz.weather.weathermobilization.WeatherApplication; -import com.ikholopov.yamblz.weather.weathermobilization.data.CityAutoComplete; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeather; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherLoader; -import com.ikholopov.yamblz.weather.weathermobilization.data.WeatherLoader; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.city.CityShortInfo; -import com.ikholopov.yamblz.weather.weathermobilization.di.component.CitySelectComponent; -import com.ikholopov.yamblz.weather.weathermobilization.di.component.DaggerCitySelectComponent; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.CitySelectPresenter; -import com.jakewharton.rxbinding2.widget.RxAdapterView; -import com.jakewharton.rxbinding2.widget.RxTextView; - -import javax.inject.Inject; - -import butterknife.BindView; -import butterknife.ButterKnife; -import io.reactivex.Observable; -import io.reactivex.annotations.NonNull; -import io.reactivex.functions.Function; - -/** - * Created by turist on 28.07.2017. - */ - -public class CitySelectActivity extends AppCompatActivity implements CitySelectView { - - @BindView(R.id.toolbar) Toolbar toolbar; - @BindView(R.id.city_edit) EditText cityEdit; - @BindView(R.id.cities_list) ListView citiesList; - - @Inject CitySelectPresenter presenter; - - private CitySelectComponent selectComponent; - private ArrayAdapter adapter; - - public CitySelectComponent getViewComponent() { - if(selectComponent == null) { - selectComponent = DaggerCitySelectComponent.builder() - .applicationComponent(WeatherApplication.get(this).getComponent()) - .build(); - } - return selectComponent; - } - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getViewComponent().inject(this); - - setContentView(R.layout.activity_city); - ButterKnife.bind(this); - - setSupportActionBar(toolbar); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setDisplayShowHomeEnabled(true); - - adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1); - citiesList.setAdapter(adapter); - - presenter.bind(this); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - presenter.unbind(); - } - - @Override - public Observable textChanges() { - return RxTextView.textChanges(cityEdit).skipInitialValue(); - } - - @Override - public Observable selection() { - return RxAdapterView.itemClicks(citiesList).map(new Function() { - @Override - public CityShortInfo apply(@NonNull Integer integer) throws Exception { - return adapter.getItem(integer); - } - }); - } - - @Override - public void onSelected() { - finish(); - } - - @Override - public void onError() { - Snackbar.make(citiesList, R.string.error_has_been, BaseTransientBottomBar.LENGTH_LONG).show(); - } - - @Override - public void accept(CityAutoComplete cityAutoComplete) throws Exception { - adapter.clear(); - adapter.addAll(cityAutoComplete.predictions); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/CitySelectView.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/CitySelectView.java deleted file mode 100644 index 72761bf..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/CitySelectView.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.ui; - -import com.ikholopov.yamblz.weather.weathermobilization.data.CityAutoComplete; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.city.CityShortInfo; - -import io.reactivex.Observable; -import io.reactivex.functions.Consumer; - -/** - * Created by turist on 28.07.2017. - */ - -public interface CitySelectView extends Consumer { - Observable textChanges(); - Observable selection(); - void onSelected(); - void onError(); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainActivity.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainActivity.java index b42c8ec..9773cbf 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainActivity.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainActivity.java @@ -1,36 +1,42 @@ package com.ikholopov.yamblz.weather.weathermobilization.ui; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.view.GravityCompat; -import android.support.v4.widget.DrawerLayout; -import android.view.MenuItem; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; import com.ikholopov.yamblz.weather.weathermobilization.R; import com.ikholopov.yamblz.weather.weathermobilization.WeatherApplication; -import com.ikholopov.yamblz.weather.weathermobilization.di.component.ViewComponent; -import com.ikholopov.yamblz.weather.weathermobilization.di.component.DaggerViewComponent; -import com.ikholopov.yamblz.weather.weathermobilization.di.module.MainComposerModule; +import com.ikholopov.yamblz.weather.weathermobilization.di.component.ActivityComponent; +import com.ikholopov.yamblz.weather.weathermobilization.di.module.ActivityModule; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.MainPresenter; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.TwoPaneRouter; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.MainView; import javax.inject.Inject; import butterknife.BindView; import butterknife.ButterKnife; +import io.reactivex.annotations.Nullable; -public class MainActivity extends NavigatableActivity { +public class MainActivity extends AppCompatActivity + implements MainView, TwoPaneRouter.RightPanelReplaced { - @Inject - MainViewComposer composer; + @Inject ActivityComponent viewComponent; + @Inject MainPresenter presenter; - private ViewComponent viewComponent; + @Nullable + @BindView(R.id.right_container) FrameLayout detailsContainer; - @BindView(R.id.drawer_layout) DrawerLayout drawer; + @Nullable + @BindView(R.id.select_city_hint) TextView selectCityHint; - public ViewComponent getViewComponent() { + public ActivityComponent getViewComponent() { if(viewComponent == null) { - viewComponent = DaggerViewComponent.builder() - .applicationComponent(WeatherApplication.get(this).getComponent()) - .mainComposerModule(new MainComposerModule()) + viewComponent = WeatherApplication.get(this).getComponent() + .activityComponentBuilder() + .activityModule(new ActivityModule(this)) .build(); } return viewComponent; @@ -38,30 +44,29 @@ public ViewComponent getViewComponent() { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(R.style.AppTheme_NoActionBar); super.onCreate(savedInstanceState); - getViewComponent().inject(this); setContentView(R.layout.activity_main); ButterKnife.bind(this); - composer.bind(this); - composer.setUpView(savedInstanceState == null); + + getViewComponent().inject(this); + presenter.bind(this, savedInstanceState != null); } @Override - public void onBackPressed() { - if (drawer.isDrawerOpen(GravityCompat.START)) { - drawer.closeDrawer(GravityCompat.START); - } - else { - super.onBackPressed(); - composer.onBackPressed(); - } + protected void onDestroy() { + super.onDestroy(); + presenter.unbind(); } @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - int id = item.getItemId(); - composer.handleNavigation(id); - return true; + public boolean inTwoPaneMode() { + return detailsContainer != null; + } + + @Override + public void onReplaced() { + if(selectCityHint != null) { + selectCityHint.setVisibility(View.INVISIBLE); + } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainViewComposer.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainViewComposer.java deleted file mode 100644 index c4972f7..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainViewComposer.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.ui; - -import android.support.annotation.NonNull; - -/** - * Created by igor on 7/15/17. - */ - -public interface MainViewComposer { - - //Bind activity to be composed - void bind(@NonNull NavigatableActivity activity); - - //Activity set up - void setUpView(boolean initial); - - //Navigation handling - void handleNavigation(int navId); - - //On back button - void onBackPressed(); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainViewComposerImpl.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainViewComposerImpl.java deleted file mode 100644 index b2c68d6..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/MainViewComposerImpl.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.ui; - -import android.support.annotation.NonNull; -import android.support.design.widget.NavigationView; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.GravityCompat; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.widget.Toolbar; -import android.view.View; - -import com.ikholopov.yamblz.weather.weathermobilization.R; -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.AboutFragment; -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.SettingsFragment; -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.WeatherFragment; - -import butterknife.BindView; -import butterknife.ButterKnife; - -/** - * Created by igor on 7/15/17. - */ - -public class MainViewComposerImpl implements MainViewComposer { - - private NavigatableActivity activity = null; - private ActionBarDrawerToggle toggle; - - @BindView(R.id.toolbar) Toolbar toolbar; - @BindView(R.id.drawer_layout) DrawerLayout drawer; - @BindView(R.id.nav_view) NavigationView navigationView; - - @Override - public void bind(@NonNull NavigatableActivity activity) { - this.activity = activity; - ButterKnife.bind(this, activity); - } - - @Override - public void setUpView(@NonNull boolean initial) { - if(activity == null) { - return; - } - activity.setSupportActionBar(toolbar); - if(activity.findViewById(R.id.content_frame) != null && initial) { - WeatherFragment fragment = WeatherFragment.newInstance(); - fragment.setArguments(activity.getIntent().getExtras()); - activity.getSupportFragmentManager().beginTransaction() - .setCustomAnimations(R.anim.slide_in, R.anim.slide_out, - R.anim.slide_in, R.anim.slide_out) - .replace(R.id.content_frame, fragment) - .commit(); - } - Fragment current = activity.getSupportFragmentManager().findFragmentById(R.id.content_frame); - toggle = new ActionBarDrawerToggle( - activity, drawer, toolbar, R.string.navigation_drawer_open, - R.string.navigation_drawer_close); - toggle.syncState(); - toggle.setToolbarNavigationClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - activity.onBackPressed(); - } - }); - navigationView.setNavigationItemSelectedListener(activity); - updateToolbar(current); - } - - @Override - public void handleNavigation(int navId) { - if(activity == null) { - return; - } - switch(navId) { - case R.id.nav_weather: - if(shouldReplaceFragment(WeatherFragment.FRAGMENT_NAME_ID)) { - placeFragment(WeatherFragment.newInstance(), false); - } - break; - case R.id.nav_settings: - if(shouldReplaceFragment(SettingsFragment.FragmentNameId)) { - placeFragment(SettingsFragment.newInstance(), true); - } - break; - case R.id.nav_about: - if(shouldReplaceFragment(AboutFragment.FragmentNameId)) { - placeFragment(AboutFragment.newInstance(), true); - } - break; - } - drawer.closeDrawer(GravityCompat.START); - } - - @Override - public void onBackPressed() { - if(activity == null) { - return; - } - Fragment current = activity.getSupportFragmentManager().findFragmentById(R.id.content_frame); - updateToolbar(current); - } - - private boolean shouldReplaceFragment(int fragmentNameId) { - Named currentFragment = (Named)activity.getSupportFragmentManager() - .findFragmentById(R.id.content_frame); - return currentFragment == null || - currentFragment.getNameId() != fragmentNameId; - } - - private void placeFragment(@NonNull Fragment fragment, boolean addToStack) { - activity.getSupportFragmentManager() - .popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); - FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction() - .setCustomAnimations(R.anim.slide_in, R.anim.slide_out, - R.anim.slide_in, R.anim.slide_out) - .replace(R.id.content_frame, fragment); - if(addToStack) { - transaction = transaction.addToBackStack(null); - } - transaction.commit(); - updateToolbar(fragment); - } - - private void updateToolbar(Fragment current) { - if(current != null && current instanceof Named) { - activity.getSupportActionBar().setTitle( - activity.getString(((Named)current).getNameId())); - activity.getSupportFragmentManager().executePendingTransactions(); - int fragmentsInStack = activity.getSupportFragmentManager().getBackStackEntryCount(); - if(fragmentsInStack > 0) { - toggle.setDrawerIndicatorEnabled(false); - activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true); - activity.getSupportActionBar().setDisplayShowHomeEnabled(true); - } - else { - activity.getSupportActionBar().setDisplayHomeAsUpEnabled(false); - activity.getSupportActionBar().setDisplayShowHomeEnabled(false); - toggle.setDrawerIndicatorEnabled(true); - } - } - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/Named.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/Named.java deleted file mode 100644 index 1be3338..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/Named.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.ui; - -/** - * Interface for objects with resource name - */ - -public interface Named { - int getNameId(); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/NavigatableActivity.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/NavigatableActivity.java deleted file mode 100644 index a6935c1..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/NavigatableActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.ui; - -import android.support.design.widget.NavigationView; -import android.support.v7.app.AppCompatActivity; - -/** - * Created by igor on 7/15/17. - */ - -public abstract class NavigatableActivity extends AppCompatActivity - implements NavigationView.OnNavigationItemSelectedListener { - -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/UpdateIntervalFormat.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/UpdateIntervalFormat.java new file mode 100644 index 0000000..e460ed4 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/UpdateIntervalFormat.java @@ -0,0 +1,67 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui; + +import android.content.Context; + +import com.ikholopov.yamblz.weather.weathermobilization.R; + +import org.threeten.bp.Duration; + +import javax.inject.Inject; + +/** + * Created by turist on 11.08.2017. + */ + +public class UpdateIntervalFormat { + + private final Context context; + + @Inject + public UpdateIntervalFormat(Context context) { + this.context = context; + } + + public String getString(Duration interval) { + long minutes = interval.toMinutes(); + if(minutes < 10) { + return getString(0); + } + if(minutes < 20) { + return getString(1); + } + if(minutes < 50) { + return getString(2); + } + if(minutes < 80) { + return getString(3); + } + + return getString(4); + } + + public String getString(int intervalId) { + String[] intervals = context.getResources() + .getStringArray(R.array.update_intervals); + + if(intervalId < 5) { + return intervals[intervalId]; + } + + return intervals[4]; + } + + public Duration getInterval(int intervalId) { + switch (intervalId) { + case 0: + return Duration.ZERO; + case 1: + return Duration.ofMinutes(15); + case 2: + return Duration.ofMinutes(30); + case 3: + return Duration.ofHours(1); + default: + return Duration.ofHours(2); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/CitiesAdapter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/CitiesAdapter.java new file mode 100644 index 0000000..dc3a0dc --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/CitiesAdapter.java @@ -0,0 +1,86 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.adapters; + +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.CityWeather; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.annotations.NonNull; +import io.reactivex.functions.Consumer; + +/** + * Created by turist on 11.08.2017. + */ + +public class CitiesAdapter extends RecyclerView.Adapter + implements Consumer>, CityViewHolder.OnItemClickListener { + + public interface OnItemClickListener { + void onClick(CityWeather cityWeather); + } + + public interface OnItemLongClickListener { + void onLongClick(CityWeather cityWeather); + } + + private List list = new ArrayList<>(); + private OnItemClickListener clickListener; + private OnItemLongClickListener longClickListener; + + @Override + public CityViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_city, parent, false); + return new CityViewHolder(view, this); + } + + @Override + public void onBindViewHolder(CityViewHolder holder, int position) { + CityWeather cityWeather = list.get(position); + if(cityWeather.isOnlyCity()) { + holder.onlyCity(cityWeather.getLocationName()); + } else { + holder.withWeather(cityWeather); + } + } + + @Override + public int getItemCount() { + return list.size(); + } + + @Override + public void accept(@NonNull List cityWeather) throws Exception { + list.clear(); + list.addAll(cityWeather); + notifyDataSetChanged(); + } + + @Override + public void onClick(int position) { + if(clickListener != null) { + clickListener.onClick(list.get(position)); + } + } + + @Override + public void onLongClick(int position) { + if(longClickListener != null) { + longClickListener.onLongClick(list.get(position)); + } + } + + public void setClickListener(OnItemClickListener clickListener) { + this.clickListener = clickListener; + } + + public void setLongClickListener(OnItemLongClickListener longClickListener) { + this.longClickListener = longClickListener; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/CityViewHolder.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/CityViewHolder.java new file mode 100644 index 0000000..3f2b254 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/CityViewHolder.java @@ -0,0 +1,56 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.adapters; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.CityWeather; + +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * Created by turist on 11.08.2017. + */ + +public class CityViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.name) TextView name; + + public interface OnItemClickListener { + void onClick(int position); + void onLongClick(int position); + } + + public CityViewHolder(View itemView, OnItemClickListener clickListener) { + super(itemView); + + itemView.setOnClickListener(v -> { + int position = getAdapterPosition(); + if(position != RecyclerView.NO_POSITION) { + clickListener.onClick(position); + } + }); + + itemView.setOnLongClickListener(v -> { + int position = getAdapterPosition(); + if(position == RecyclerView.NO_POSITION) { + return false; + } + + clickListener.onLongClick(position); + return true; + }); + + ButterKnife.bind(this, itemView); + } + + void onlyCity(String cityName) { + name.setText(cityName); + } + + void withWeather(CityWeather weather) { + name.setText(weather.getLocationName()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/WeatherAdapter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/WeatherAdapter.java new file mode 100644 index 0000000..25041bc --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/WeatherAdapter.java @@ -0,0 +1,50 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.adapters; + +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.Weather; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.annotations.NonNull; +import io.reactivex.functions.Consumer; + +/** + * Created by turist on 11.08.2017. + */ + +public class WeatherAdapter extends RecyclerView.Adapter + implements Consumer> { + + private List list = new ArrayList<>(); + + @Override + public WeatherViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_weather, parent, false); + return new WeatherViewHolder(view); + } + + @Override + public void onBindViewHolder(WeatherViewHolder holder, int position) { + Weather weather = list.get(position); + holder.set(weather); + } + + @Override + public int getItemCount() { + return list.size(); + } + + @Override + public void accept(@NonNull List weather) throws Exception { + list.clear(); + list.addAll(weather); + notifyDataSetChanged(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/WeatherViewHolder.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/WeatherViewHolder.java new file mode 100644 index 0000000..1ce4203 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/adapters/WeatherViewHolder.java @@ -0,0 +1,40 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.adapters; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.Weather; + +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * Created by turist on 11.08.2017. + */ + +public class WeatherViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.state_image) ImageView state; + @BindView(R.id.time) TextView time; + @BindView(R.id.temperature) TextView temperature; + + public WeatherViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + void set(Weather weather) { + state.setImageResource(weather.weatherImageId); + time.setText(weather.dateTime); + + Context context = temperature.getContext(); + String unit = context.getString(weather.temperatureUnit); + String temperatureWithDegree = context + .getString(R.string.format_temperature, weather.temperature, unit); + temperature.setText(temperatureWithDegree); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/AboutFragment.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/AboutFragment.java deleted file mode 100644 index 050d19a..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/AboutFragment.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.ui.fragments; - - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.ikholopov.yamblz.weather.weathermobilization.R; -import com.ikholopov.yamblz.weather.weathermobilization.ui.Named; - - -/** - * {@link Named} {@link Fragment} with app information - */ -public class AboutFragment extends Fragment implements Named { - - public static final int FragmentNameId = R.string.nav_drawer_about; - - public AboutFragment() { - } - - public static AboutFragment newInstance() { - AboutFragment fragment = new AboutFragment(); - return fragment; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_about, container, false); - } - - @Override - public int getNameId() { - return FragmentNameId; - } -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/CitiesFragment.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/CitiesFragment.java new file mode 100644 index 0000000..fcc3681 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/CitiesFragment.java @@ -0,0 +1,138 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.fragments; + +import android.os.Bundle; +import android.support.annotation.StringRes; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.CitiesPresenter; +import com.ikholopov.yamblz.weather.weathermobilization.ui.MainActivity; +import com.ikholopov.yamblz.weather.weathermobilization.ui.adapters.CitiesAdapter; +import com.ikholopov.yamblz.weather.weathermobilization.ui.rx.RxCitiesAdapter; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.CitiesView; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.CityWeather; +import com.jakewharton.rxbinding2.InitialValueObservable; +import com.jakewharton.rxbinding2.view.RxView; +import com.jakewharton.rxbinding2.widget.RxTextView; + +import java.util.List; + +import javax.inject.Inject; + +import butterknife.BindView; +import io.reactivex.Maybe; +import io.reactivex.Observable; +import io.reactivex.annotations.NonNull; +import io.reactivex.subjects.MaybeSubject; + +/** + * Main {@link Fragment} with cities. + */ +public class CitiesFragment extends KnifeFragment implements CitiesView { + + @Inject CitiesPresenter presenter; + CitiesAdapter citiesAdapter; + + @BindView(R.id.search_edit) EditText searchEdit; + @BindView(R.id.description) TextView description; + @BindView(R.id.cities_list) RecyclerView citiesList; + @BindView(R.id.settings_button) FloatingActionButton settingsButton; + @BindView(R.id.clear_button) ImageView clearButton; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + ((MainActivity)getActivity()).getViewComponent().inject(this); + View view = bind(inflater.inflate(R.layout.fragment_cities, container, false)); + + clearButton.setScaleX(0.0f); + clearButton.setScaleY(0.0f); + + citiesAdapter = new CitiesAdapter(); + citiesList.setAdapter(citiesAdapter); + citiesList.setLayoutManager(new LinearLayoutManager(getContext())); + + presenter.bind(this); + save(presenter); + + return view; + } + + @Override + public void onError() { + Snackbar.make(citiesList, R.string.error_has_been, Snackbar.LENGTH_SHORT).show(); + } + + @Override + public Observable searchClears() { + return RxView.clicks(clearButton); + } + + @Override + public Observable settingsRequests() { + return RxView.clicks(settingsButton); + } + + @Override + public Observable citiesSearches() { + return RxTextView.textChanges(searchEdit); + } + + @Override + public Observable citiesSelections() { + return RxCitiesAdapter.clicks(citiesAdapter); + } + + @Override + public Observable citiesOptionSelections() { + return RxCitiesAdapter.longClicks(citiesAdapter); + } + + @Override + public Maybe ask(@StringRes int message) { + final MaybeSubject subject = MaybeSubject.create(); + + new AlertDialog.Builder(getContext()) + .setMessage(message) + .setPositiveButton(R.string.yes, (dialog, which) -> subject.onSuccess(true)) + .setNegativeButton(R.string.no, (dialog, which) -> {}) + .create() + .show(); + + return subject; + } + + @Override + public void setDescription(@StringRes int value) { + description.setText(value); + } + + @Override + public void showClearButton(boolean value) { + float scale = value ? 1.0f : 0.0f; + clearButton.animate() + .scaleY(scale).scaleX(scale) + .start(); + } + + @Override + public void clearSearch() { + searchEdit.setText(""); + } + + @Override + public void accept(@NonNull List cityWeathers) throws Exception { + citiesAdapter.accept(cityWeathers); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/ForecastFragment.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/ForecastFragment.java deleted file mode 100644 index 015a222..0000000 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/ForecastFragment.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.ui.fragments; - -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; - -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeather; - -/** - * Created by igor on 7/20/17. - */ - -public interface ForecastFragment { - - Loader initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks callbacks); - - void setWeather(@Nullable CurrentWeather weather); -} diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/KnifeFragment.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/KnifeFragment.java new file mode 100644 index 0000000..f5f062f --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/KnifeFragment.java @@ -0,0 +1,47 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.fragments; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.ikholopov.yamblz.weather.weathermobilization.presenter.Unbindable; + +import butterknife.ButterKnife; +import butterknife.Unbinder; + +/** + * Base fragment, containing utility functional for work with {@link ButterKnife} framework + * + * Bind views on ids (set with annotation), save bindings and unbind its when view destroyed + * + * Created by turist on 02.08.2017. + */ +public class KnifeFragment extends Fragment { + + private Unbinder unbinder; + private Unbindable unbindable; + + /** + * Need call from {{@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}} + */ + protected View bind(View view) { + unbinder = ButterKnife.bind(this, view); + return view; + } + + protected void save(Unbindable unbindable) { + this.unbindable = unbindable; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + + if(unbindable != null) { + unbindable.unbind(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/SettingsFragment.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/SettingsFragment.java index d7927ab..ca47670 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/SettingsFragment.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/SettingsFragment.java @@ -1,84 +1,87 @@ package com.ikholopov.yamblz.weather.weathermobilization.ui.fragments; - -import android.content.SharedPreferences; import android.os.Bundle; -import android.support.v7.preference.PreferenceFragmentCompat; -import android.support.v7.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import com.ikholopov.yamblz.weather.weathermobilization.R; -import com.ikholopov.yamblz.weather.weathermobilization.WeatherApplication; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.UpdateServiceController; -import com.ikholopov.yamblz.weather.weathermobilization.ui.Named; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.SettingsPresenter; +import com.ikholopov.yamblz.weather.weathermobilization.ui.MainActivity; +import com.ikholopov.yamblz.weather.weathermobilization.ui.rx.RxSingleToggleGroup; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.SettingsView; +import com.jakewharton.rxbinding2.view.RxView; +import com.nex3z.togglebuttongroup.SingleSelectToggleGroup; import javax.inject.Inject; +import butterknife.BindView; +import butterknife.OnClick; +import io.reactivex.Observable; +import io.reactivex.functions.Consumer; + /** - * {@link Named} {@link PreferenceFragmentCompat} with app preferences. + * {@link Fragment} with settings. */ +public class SettingsFragment extends KnifeFragment implements SettingsView { -public class SettingsFragment extends PreferenceFragmentCompat implements Named, - SharedPreferences.OnSharedPreferenceChangeListener { - - public static final int FragmentNameId = R.string.nav_drawer_settings; + @BindView(R.id.temperature_unit_button) SingleSelectToggleGroup temperatureUnitButton; + @BindView(R.id.update_interval_label) TextView updateIntervalButton; + @BindView(R.id.update_interval_value) TextView updateInterval; - @Inject - PreferencesProvider preferences; - - @Inject - UpdateServiceController serviceController; - - public static SettingsFragment newInstance() { - return new SettingsFragment(); - } + @Inject SettingsPresenter presenter; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - WeatherApplication.get(this.getContext()).getComponent().inject(this); - return super.onCreateView(inflater, container, savedInstanceState); + ((MainActivity)getActivity()).getViewComponent().inject(this); + View view = bind(inflater.inflate(R.layout.fragment_settings, container, false)); + presenter.bind(this); + return view; } @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.preferences); + public void setTemperatureUnit(boolean isCelsius) { + temperatureUnitButton.check(isCelsius ? R.id.choice_c : R.id.choice_f); } @Override - public void onResume() { - super.onResume(); - getPreferenceScreen().findPreference(getString(R.string.preference_key_city_name)) - .setSummary(preferences.getCityName()); - - PreferenceManager preferenceManager = getPreferenceManager(); - preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + public void setUpdateInterval(String interval) { + updateInterval.setText(interval); } @Override - public void onPause() { - super.onPause(); - PreferenceManager preferenceManager = getPreferenceManager(); - preferenceManager.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + public void showUpdateIntervalDialog(final Consumer intervalListener) { + new AlertDialog.Builder(getContext()) + .setTitle(R.string.update_interval) + .setItems(R.array.update_intervals, (dialog, which) -> { + try { + intervalListener.accept(which); + } catch (Exception e) { + Log.e("SettingsFragment", "save update interval " + which, e); + } + }) + .create() + .show(); } @Override - public int getNameId() { - return FragmentNameId; + public Observable temperatureUnitsSwitches() { + return RxSingleToggleGroup.selections(temperatureUnitButton) + .map(id -> id == R.id.choice_c); } @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if(key.equals(getString(R.string.preference_key_autoupdate)) - || key.equals(getString(R.string.preference_key_update_interval))){ - if(preferences.getAutoupdateEnabledPreference()) { - serviceController.enableService(preferences.getUpdateInterval()); - } else { - serviceController.disableService(); - } - } + public Observable updateIntervalRequests() { + return RxView.clicks(updateIntervalButton); + } + + @OnClick(R.id.back_button) + void navigateBack() { + getActivity().onBackPressed(); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/WeatherFragment.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/WeatherFragment.java index 6152193..a60de50 100644 --- a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/WeatherFragment.java +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/fragments/WeatherFragment.java @@ -1,12 +1,12 @@ package com.ikholopov.yamblz.weather.weathermobilization.ui.fragments; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -14,115 +14,148 @@ import android.widget.TextView; import com.ikholopov.yamblz.weather.weathermobilization.R; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeather; -import com.ikholopov.yamblz.weather.weathermobilization.data.WeatherUtility; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.Metric; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.CurrentWeatherPresenter; +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.WeatherPresenter; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.Weather; import com.ikholopov.yamblz.weather.weathermobilization.ui.MainActivity; -import com.ikholopov.yamblz.weather.weathermobilization.ui.Named; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.WeatherView; +import com.ikholopov.yamblz.weather.weathermobilization.ui.adapters.WeatherAdapter; +import com.jakewharton.rxbinding2.support.v4.widget.RxSwipeRefreshLayout; + +import java.util.List; import javax.inject.Inject; import butterknife.BindView; +import butterknife.BindViews; import butterknife.ButterKnife; +import io.reactivex.Observable; +import io.reactivex.annotations.NonNull; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; /** * Main {@link Fragment} with weather. - * {@link WeatherFragment#newInstance} fabric-method */ -public class WeatherFragment extends Fragment implements Named, ForecastFragment { +public class WeatherFragment extends KnifeFragment implements WeatherView { - public static final int FRAGMENT_NAME_ID = R.string.nav_drawer_weather; + private static final String CITY_KEY = "city_id"; - private String metric; - private CurrentWeather weather = null; + @Inject WeatherPresenter presenter; + private WeatherAdapter adapter; - @Inject CurrentWeatherPresenter presenter; - @Inject PreferencesProvider preferences; + @Nullable + @BindView(R.id.back_button) View backButton; + @BindView(R.id.weather_list) RecyclerView weatherList; @BindView(R.id.weather_location) TextView weatherLocation; @BindView(R.id.weather_message) TextView weatherMessage; - @BindView(R.id.weather_icon_view) ImageView weatherIcon; + @BindView(R.id.weather_icon) ImageView weatherIcon; @BindView(R.id.refresh_layout) SwipeRefreshLayout refreshLayout; - public static WeatherFragment newInstance() { - return new WeatherFragment(); + @BindView(R.id.temperature) TextView temperature; + @BindView(R.id.pressure) TextView pressure; + @BindView(R.id.updated) TextView updated; + @BindView(R.id.wind_speed) TextView windSpeed; + @BindView(R.id.wind_icon) ImageView windIcon; + + @BindViews({ R.id.weather_icon, R.id.pressure_label, R.id.forecast, R.id.updated_label, R.id.wind_label, R.id.wind_icon }) + List viewsToHide; + + static final ButterKnife.Setter VISIBLED = (view, value, index) -> view.setVisibility(value ? View.VISIBLE : View.INVISIBLE); + + public static WeatherFragment create(City city) { + WeatherFragment weatherFragment = new WeatherFragment(); + Bundle bundle = new Bundle(); + bundle.putParcelable(CITY_KEY, city); + weatherFragment.setArguments(bundle); + return weatherFragment; + } + + RecyclerView.LayoutManager getLayoutManager() { + if(backButton != null) { + return new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false); + } + + if(getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) { + return new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false); + } + + return new GridLayoutManager(getContext(), 2, LinearLayoutManager.HORIZONTAL, false); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ((MainActivity)getActivity()).getViewComponent().inject(this); - View rootView = inflater.inflate(R.layout.fragment_weather, container, false); - ButterKnife.bind(this, rootView); - presenter.bind(this); - weatherMessage.setText(weatherMessage.getText() + " " + metric); - refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - presenter.forceReload(); - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if(refreshLayout.isRefreshing()) { - refreshLayout.setRefreshing(false); - } - } - }, 3000); - } - }); - updateWeatherDisplay(); - return rootView; - } + View view = bind(inflater.inflate(R.layout.fragment_weather, container, false)); - @Override - public void onResume() { - super.onResume(); + adapter = new WeatherAdapter(); + weatherList.setAdapter(adapter); + weatherList.setLayoutManager(getLayoutManager()); + + ButterKnife.apply(viewsToHide, VISIBLED, false); - updateWeatherDisplay(); + City city = getArguments().getParcelable(CITY_KEY); + presenter.bind(this, city); + save(presenter); + + if(backButton != null) { + backButton.setOnClickListener(v -> getActivity().onBackPressed()); + } + + return view; } - @Override - public int getNameId() { - return FRAGMENT_NAME_ID; + private void setWeather(@NonNull Weather weather) { + ButterKnife.apply(viewsToHide, VISIBLED, true); + + weatherIcon.setImageResource(weather.weatherImageId); + weatherMessage.setText(weather.description); + updated.setText(weather.dateTime); + pressure.setText(getString(R.string.pressure_format, weather.pressure)); + windSpeed.setText(getString(R.string.speed_format, weather.windSpeed)); + windIcon.setRotation((float)weather.windDegree + 270f); + temperature.setText(getString(R.string.format_temperature, + weather.temperature, getString(weather.temperatureUnit))); } + @Override + public void onError() { + ButterKnife.apply(viewsToHide, VISIBLED, false); - //Update displayed weather - private void updateWeatherDisplay() { - metric = getString(preferences.getMetricFromPreference() - .getStringId()); - if(weather == null) { - weatherLocation.setText(getString(R.string.no_internet_connection)); - weatherIcon.setVisibility(View.INVISIBLE); - weatherMessage.setText(getString(R.string.swipe_down_to_refresh)); - return; - } - weatherIcon.setVisibility(View.VISIBLE); - weatherIcon.setImageResource(WeatherUtility.getImageIdForWeatherId(weather.getWeatherId())); - weatherLocation.setText(weather.getLocationName()); - weatherMessage.setText(WeatherUtility.formatTemperature(getContext(), weather.getTemp(), - preferences.getMetricFromPreference() == Metric.CELSIUS)); + updated.setText(""); + pressure.setText(""); + windSpeed.setText(""); + temperature.setText(""); + weatherMessage.setText(R.string.no_internet_swipe_down_to_refresh); + onStopRefreshing(); } @Override - public Loader initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks callbacks) { - return getActivity().getSupportLoaderManager() - .initLoader(id, args, callbacks); + public Observable refreshes() { + return RxSwipeRefreshLayout.refreshes(refreshLayout); } @Override - public void setWeather(@Nullable CurrentWeather weather) { - this.weather = weather; - if(weather != null) { - this.weather.setLocationName(preferences.getCityName()); - } + public void setLocation(String name) { + weatherLocation.setText(name); + } + protected void onStopRefreshing() { if(refreshLayout.isRefreshing()) { refreshLayout.setRefreshing(false); } - updateWeatherDisplay(); } -} + + @Override + public void accept(@NonNull List weathers) throws Exception { + if(weathers.size() > 0) { + setWeather(weathers.get(0)); + } + + adapter.accept(weathers); + onStopRefreshing(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/rx/RxCitiesAdapter.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/rx/RxCitiesAdapter.java new file mode 100644 index 0000000..c3a26ea --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/rx/RxCitiesAdapter.java @@ -0,0 +1,117 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.rx; + +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; + +import com.ikholopov.yamblz.weather.weathermobilization.ui.adapters.CitiesAdapter; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.CityWeather; + +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.android.MainThreadDisposable; + +import static com.jakewharton.rxbinding2.internal.Preconditions.checkMainThread; +import static com.jakewharton.rxbinding2.internal.Preconditions.checkNotNull; + +/** + * Created by turist on 11.08.2017. + */ + +public final class RxCitiesAdapter { + + @CheckResult + @NonNull + public static Observable clicks(@NonNull CitiesAdapter adapter) { + checkNotNull(adapter, "view == null"); + return new ClicksObservable(adapter); + } + + @CheckResult + @NonNull + public static Observable longClicks(@NonNull CitiesAdapter adapter) { + checkNotNull(adapter, "view == null"); + return new LongClicksObservable(adapter); + } + + private final static class ClicksObservable extends Observable { + private final CitiesAdapter adapter; + + ClicksObservable(@NonNull CitiesAdapter adapter) { + this.adapter = adapter; + } + + @Override + protected void subscribeActual(Observer observer) { + if (!checkMainThread(observer)) { + return; + } + ClicksObservable.Listener listener = new ClicksObservable.Listener(adapter, observer); + observer.onSubscribe(listener); + adapter.setClickListener(listener); + } + + static final class Listener extends MainThreadDisposable + implements CitiesAdapter.OnItemClickListener { + private final CitiesAdapter adapter; + private final Observer observer; + + Listener(CitiesAdapter adapter, Observer observer) { + this.adapter = adapter; + this.observer = observer; + } + + @Override + protected void onDispose() { + adapter.setClickListener(null); + } + + @Override + public void onClick(CityWeather cityWeather) { + if (!isDisposed()) { + observer.onNext(cityWeather); + } + } + } + } + + private final static class LongClicksObservable extends Observable { + private final CitiesAdapter adapter; + + LongClicksObservable(@NonNull CitiesAdapter adapter) { + this.adapter = adapter; + } + + @Override + protected void subscribeActual(Observer observer) { + if (!checkMainThread(observer)) { + return; + } + LongClicksObservable.Listener listener = new LongClicksObservable.Listener(adapter, observer); + observer.onSubscribe(listener); + adapter.setLongClickListener(listener); + } + + static final class Listener extends MainThreadDisposable + implements CitiesAdapter.OnItemLongClickListener { + private final CitiesAdapter adapter; + private final Observer observer; + + Listener(CitiesAdapter adapter, Observer observer) { + this.adapter = adapter; + this.observer = observer; + } + + @Override + protected void onDispose() { + adapter.setLongClickListener(null); + } + + @Override + public void onLongClick(CityWeather cityWeather) { + if (!isDisposed()) { + observer.onNext(cityWeather); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/rx/RxSingleToggleGroup.java b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/rx/RxSingleToggleGroup.java new file mode 100644 index 0000000..a733c44 --- /dev/null +++ b/app/src/main/java/com/ikholopov/yamblz/weather/weathermobilization/ui/rx/RxSingleToggleGroup.java @@ -0,0 +1,68 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui.rx; + +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; + +import com.nex3z.togglebuttongroup.SingleSelectToggleGroup; + +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.android.MainThreadDisposable; + +import static com.jakewharton.rxbinding2.internal.Preconditions.checkMainThread; +import static com.jakewharton.rxbinding2.internal.Preconditions.checkNotNull; + +/** + * Created by turist on 11.08.2017. + */ + +public final class RxSingleToggleGroup { + + @CheckResult + @NonNull + public static Observable selections(@NonNull SingleSelectToggleGroup view) { + checkNotNull(view, "view == null"); + return new ToggleGroupSelectionsObservable(view); + } + + private final static class ToggleGroupSelectionsObservable extends Observable { + private final SingleSelectToggleGroup view; + + ToggleGroupSelectionsObservable(SingleSelectToggleGroup view) { + this.view = view; + } + + @Override + protected void subscribeActual(Observer observer) { + if (!checkMainThread(observer)) { + return; + } + ToggleGroupSelectionsObservable.Listener listener = new ToggleGroupSelectionsObservable.Listener(view, observer); + observer.onSubscribe(listener); + view.setOnCheckedChangeListener(listener); + } + + static final class Listener extends MainThreadDisposable + implements SingleSelectToggleGroup.OnCheckedChangeListener { + private final SingleSelectToggleGroup view; + private final Observer observer; + + Listener(SingleSelectToggleGroup view, Observer observer) { + this.view = view; + this.observer = observer; + } + + @Override + protected void onDispose() { + view.setOnCheckedChangeListener(null); + } + + @Override + public void onCheckedChanged(SingleSelectToggleGroup group, int checkedId) { + if (!isDisposed()) { + observer.onNext(checkedId); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in.xml b/app/src/main/res/anim/slide_in.xml index 410ec4d..8b33fdd 100644 --- a/app/src/main/res/anim/slide_in.xml +++ b/app/src/main/res/anim/slide_in.xml @@ -2,9 +2,9 @@ + android:toXDelta="0%p" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out.xml b/app/src/main/res/anim/slide_out.xml index a030578..16ca389 100644 --- a/app/src/main/res/anim/slide_out.xml +++ b/app/src/main/res/anim/slide_out.xml @@ -2,9 +2,9 @@ + android:toXDelta="-100%p" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/cloudy.png b/app/src/main/res/drawable-hdpi/cloudy.png new file mode 100644 index 0000000..524b64f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/cloudy.png differ diff --git a/app/src/main/res/drawable-hdpi/drizzle.png b/app/src/main/res/drawable-hdpi/drizzle.png new file mode 100644 index 0000000..6827c1f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/drizzle.png differ diff --git a/app/src/main/res/drawable-hdpi/haze.png b/app/src/main/res/drawable-hdpi/haze.png new file mode 100644 index 0000000..1d086d9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/haze.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_back_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_arrow_back_black_24dp.png new file mode 100644 index 0000000..73f1dda Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_arrow_back_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_clear_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_clear_black_24dp.png new file mode 100644 index 0000000..1a9cd75 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_clear_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_menu_black_24dp.png new file mode 100644 index 0000000..3f8ebd6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_navigate_next_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_navigate_next_black_24dp.png new file mode 100644 index 0000000..3e98b52 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_navigate_next_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..c593e7a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_settings_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_settings_black_24dp.png new file mode 100644 index 0000000..acf1ddf Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_settings_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/mostly_cloudy.png b/app/src/main/res/drawable-hdpi/mostly_cloudy.png new file mode 100644 index 0000000..57cc219 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/mostly_cloudy.png differ diff --git a/app/src/main/res/drawable-hdpi/slight_drizzle.png b/app/src/main/res/drawable-hdpi/slight_drizzle.png new file mode 100644 index 0000000..4e06e37 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/slight_drizzle.png differ diff --git a/app/src/main/res/drawable-hdpi/snow.png b/app/src/main/res/drawable-hdpi/snow.png new file mode 100644 index 0000000..b387651 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/snow.png differ diff --git a/app/src/main/res/drawable-hdpi/sunny.png b/app/src/main/res/drawable-hdpi/sunny.png new file mode 100644 index 0000000..a777356 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sunny.png differ diff --git a/app/src/main/res/drawable-hdpi/thunderstorms.png b/app/src/main/res/drawable-hdpi/thunderstorms.png new file mode 100644 index 0000000..881ada2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/thunderstorms.png differ diff --git a/app/src/main/res/drawable-ldpi/cloudy.png b/app/src/main/res/drawable-ldpi/cloudy.png new file mode 100644 index 0000000..04044da Binary files /dev/null and b/app/src/main/res/drawable-ldpi/cloudy.png differ diff --git a/app/src/main/res/drawable-ldpi/drizzle.png b/app/src/main/res/drawable-ldpi/drizzle.png new file mode 100644 index 0000000..013129b Binary files /dev/null and b/app/src/main/res/drawable-ldpi/drizzle.png differ diff --git a/app/src/main/res/drawable-ldpi/haze.png b/app/src/main/res/drawable-ldpi/haze.png new file mode 100644 index 0000000..3bfc88d Binary files /dev/null and b/app/src/main/res/drawable-ldpi/haze.png differ diff --git a/app/src/main/res/drawable-ldpi/mostly_cloudy.png b/app/src/main/res/drawable-ldpi/mostly_cloudy.png new file mode 100644 index 0000000..fe9db8f Binary files /dev/null and b/app/src/main/res/drawable-ldpi/mostly_cloudy.png differ diff --git a/app/src/main/res/drawable-ldpi/slight_drizzle.png b/app/src/main/res/drawable-ldpi/slight_drizzle.png new file mode 100644 index 0000000..8226d94 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/slight_drizzle.png differ diff --git a/app/src/main/res/drawable-ldpi/snow.png b/app/src/main/res/drawable-ldpi/snow.png new file mode 100644 index 0000000..94a79c5 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/snow.png differ diff --git a/app/src/main/res/drawable-ldpi/sunny.png b/app/src/main/res/drawable-ldpi/sunny.png new file mode 100644 index 0000000..31f5b1c Binary files /dev/null and b/app/src/main/res/drawable-ldpi/sunny.png differ diff --git a/app/src/main/res/drawable-ldpi/thunderstorms.png b/app/src/main/res/drawable-ldpi/thunderstorms.png new file mode 100644 index 0000000..6468a8e Binary files /dev/null and b/app/src/main/res/drawable-ldpi/thunderstorms.png differ diff --git a/app/src/main/res/drawable-mdpi/cloudy.png b/app/src/main/res/drawable-mdpi/cloudy.png new file mode 100644 index 0000000..0efbad3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/cloudy.png differ diff --git a/app/src/main/res/drawable-mdpi/drizzle.png b/app/src/main/res/drawable-mdpi/drizzle.png new file mode 100644 index 0000000..234f328 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/drizzle.png differ diff --git a/app/src/main/res/drawable-mdpi/haze.png b/app/src/main/res/drawable-mdpi/haze.png new file mode 100644 index 0000000..a46f165 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/haze.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_back_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_arrow_back_black_24dp.png new file mode 100644 index 0000000..058d9d2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_arrow_back_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_clear_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_clear_black_24dp.png new file mode 100644 index 0000000..40a1a84 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_clear_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_menu_black_24dp.png new file mode 100644 index 0000000..ef2a48c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_menu_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_navigate_next_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_navigate_next_black_24dp.png new file mode 100644 index 0000000..c11a2a5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_navigate_next_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..6b16343 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_settings_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_settings_black_24dp.png new file mode 100644 index 0000000..c59419c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_settings_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/mostly_cloudy.png b/app/src/main/res/drawable-mdpi/mostly_cloudy.png new file mode 100644 index 0000000..73be20b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/mostly_cloudy.png differ diff --git a/app/src/main/res/drawable-mdpi/slight_drizzle.png b/app/src/main/res/drawable-mdpi/slight_drizzle.png new file mode 100644 index 0000000..353acd2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/slight_drizzle.png differ diff --git a/app/src/main/res/drawable-mdpi/snow.png b/app/src/main/res/drawable-mdpi/snow.png new file mode 100644 index 0000000..0125f4d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/snow.png differ diff --git a/app/src/main/res/drawable-mdpi/sunny.png b/app/src/main/res/drawable-mdpi/sunny.png new file mode 100644 index 0000000..49ee9d8 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/sunny.png differ diff --git a/app/src/main/res/drawable-mdpi/thunderstorms.png b/app/src/main/res/drawable-mdpi/thunderstorms.png new file mode 100644 index 0000000..600e82f Binary files /dev/null and b/app/src/main/res/drawable-mdpi/thunderstorms.png differ diff --git a/app/src/main/res/drawable-v21/ic_arrow_back_black_24px.xml b/app/src/main/res/drawable-v21/ic_arrow_back_black_24px.xml new file mode 100644 index 0000000..0c2c08a --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_arrow_back_black_24px.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_clear_black_24px.xml b/app/src/main/res/drawable-v21/ic_clear_black_24px.xml new file mode 100644 index 0000000..4ba3598 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_clear_black_24px.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_menu_black_24px.xml b/app/src/main/res/drawable-v21/ic_menu_black_24px.xml new file mode 100644 index 0000000..99e29d6 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_black_24px.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_menu_gallery.xml b/app/src/main/res/drawable-v21/ic_menu_gallery.xml deleted file mode 100644 index f6872c4..0000000 --- a/app/src/main/res/drawable-v21/ic_menu_gallery.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable-v21/ic_menu_manage.xml b/app/src/main/res/drawable-v21/ic_menu_manage.xml deleted file mode 100644 index c1be60b..0000000 --- a/app/src/main/res/drawable-v21/ic_menu_manage.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_menu_slideshow.xml b/app/src/main/res/drawable-v21/ic_menu_slideshow.xml deleted file mode 100644 index 209aa64..0000000 --- a/app/src/main/res/drawable-v21/ic_menu_slideshow.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable-v21/ic_navigate_next_black_24px.xml b/app/src/main/res/drawable-v21/ic_navigate_next_black_24px.xml new file mode 100644 index 0000000..3bd7f91 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_navigate_next_black_24px.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_search_black_24px.xml b/app/src/main/res/drawable-v21/ic_search_black_24px.xml new file mode 100644 index 0000000..f8349b3 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_search_black_24px.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_settings_black_24px.xml b/app/src/main/res/drawable-v21/ic_settings_black_24px.xml new file mode 100644 index 0000000..c2696b9 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_settings_black_24px.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/cloudy.png b/app/src/main/res/drawable-xhdpi/cloudy.png new file mode 100644 index 0000000..aabf69c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/cloudy.png differ diff --git a/app/src/main/res/drawable-xhdpi/drizzle.png b/app/src/main/res/drawable-xhdpi/drizzle.png new file mode 100644 index 0000000..a7ba72f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/drizzle.png differ diff --git a/app/src/main/res/drawable-xhdpi/haze.png b/app/src/main/res/drawable-xhdpi/haze.png new file mode 100644 index 0000000..f21d4e5 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/haze.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_back_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_arrow_back_black_24dp.png new file mode 100644 index 0000000..6073eec Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_arrow_back_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png new file mode 100644 index 0000000..6bc4372 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_menu_black_24dp.png new file mode 100644 index 0000000..4e0286b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_navigate_next_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_navigate_next_black_24dp.png new file mode 100644 index 0000000..23338b8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_navigate_next_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..6381902 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_settings_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_settings_black_24dp.png new file mode 100644 index 0000000..e84e188 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_settings_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/mostly_cloudy.png b/app/src/main/res/drawable-xhdpi/mostly_cloudy.png new file mode 100644 index 0000000..e6d6f4f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/mostly_cloudy.png differ diff --git a/app/src/main/res/drawable-xhdpi/slight_drizzle.png b/app/src/main/res/drawable-xhdpi/slight_drizzle.png new file mode 100644 index 0000000..069f71c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/slight_drizzle.png differ diff --git a/app/src/main/res/drawable-xhdpi/snow.png b/app/src/main/res/drawable-xhdpi/snow.png new file mode 100644 index 0000000..207a794 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/snow.png differ diff --git a/app/src/main/res/drawable-xhdpi/sunny.png b/app/src/main/res/drawable-xhdpi/sunny.png new file mode 100644 index 0000000..24e8b3c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sunny.png differ diff --git a/app/src/main/res/drawable-xhdpi/thunderstorms.png b/app/src/main/res/drawable-xhdpi/thunderstorms.png new file mode 100644 index 0000000..dcd04e3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/thunderstorms.png differ diff --git a/app/src/main/res/drawable-xxhdpi/cloudy.png b/app/src/main/res/drawable-xxhdpi/cloudy.png new file mode 100644 index 0000000..2cc3160 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/cloudy.png differ diff --git a/app/src/main/res/drawable-xxhdpi/drizzle.png b/app/src/main/res/drawable-xxhdpi/drizzle.png new file mode 100644 index 0000000..32c6045 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/drizzle.png differ diff --git a/app/src/main/res/drawable-xxhdpi/haze.png b/app/src/main/res/drawable-xxhdpi/haze.png new file mode 100644 index 0000000..4438f6e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/haze.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_back_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_back_black_24dp.png new file mode 100644 index 0000000..1f0559f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_back_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png new file mode 100644 index 0000000..51b4401 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_menu_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_menu_black_24dp.png new file mode 100644 index 0000000..7dae60b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_menu_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_navigate_next_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_navigate_next_black_24dp.png new file mode 100644 index 0000000..f97c51b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_navigate_next_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..3ae490e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_settings_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_settings_black_24dp.png new file mode 100644 index 0000000..3023ff8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_settings_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/mostly_cloudy.png b/app/src/main/res/drawable-xxhdpi/mostly_cloudy.png new file mode 100644 index 0000000..a62f164 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/mostly_cloudy.png differ diff --git a/app/src/main/res/drawable-xxhdpi/slight_drizzle.png b/app/src/main/res/drawable-xxhdpi/slight_drizzle.png new file mode 100644 index 0000000..a5fdc78 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/slight_drizzle.png differ diff --git a/app/src/main/res/drawable-xxhdpi/snow.png b/app/src/main/res/drawable-xxhdpi/snow.png new file mode 100644 index 0000000..ad5c121 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/snow.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sunny.png b/app/src/main/res/drawable-xxhdpi/sunny.png new file mode 100644 index 0000000..0412750 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sunny.png differ diff --git a/app/src/main/res/drawable-xxhdpi/thunderstorms.png b/app/src/main/res/drawable-xxhdpi/thunderstorms.png new file mode 100644 index 0000000..e781e05 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/thunderstorms.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/cloudy.png b/app/src/main/res/drawable-xxxhdpi/cloudy.png new file mode 100644 index 0000000..552585c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/cloudy.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/drizzle.png b/app/src/main/res/drawable-xxxhdpi/drizzle.png new file mode 100644 index 0000000..1637b51 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/drizzle.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/haze.png b/app/src/main/res/drawable-xxxhdpi/haze.png new file mode 100644 index 0000000..1b106c0 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/haze.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_black_24dp.png new file mode 100644 index 0000000..7726760 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clear_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_clear_black_24dp.png new file mode 100644 index 0000000..df42fee Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_clear_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_menu_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_menu_black_24dp.png new file mode 100644 index 0000000..5c747ed Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_menu_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_navigate_next_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_navigate_next_black_24dp.png new file mode 100644 index 0000000..7522b38 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_navigate_next_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..21be572 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_settings_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_settings_black_24dp.png new file mode 100644 index 0000000..476d5c9 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_settings_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/mostly_cloudy.png b/app/src/main/res/drawable-xxxhdpi/mostly_cloudy.png new file mode 100644 index 0000000..aa8f62a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/mostly_cloudy.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/slight_drizzle.png b/app/src/main/res/drawable-xxxhdpi/slight_drizzle.png new file mode 100644 index 0000000..a43c7fa Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/slight_drizzle.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/snow.png b/app/src/main/res/drawable-xxxhdpi/snow.png new file mode 100644 index 0000000..9d3b855 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/snow.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sunny.png b/app/src/main/res/drawable-xxxhdpi/sunny.png new file mode 100644 index 0000000..df0a125 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sunny.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/thunderstorms.png b/app/src/main/res/drawable-xxxhdpi/thunderstorms.png new file mode 100644 index 0000000..5c6b68e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/thunderstorms.png differ diff --git a/app/src/main/res/drawable/circle.xml b/app/src/main/res/drawable/circle.xml new file mode 100644 index 0000000..c3be75d --- /dev/null +++ b/app/src/main/res/drawable/circle.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml deleted file mode 100644 index dc6c1da..0000000 --- a/app/src/main/res/drawable/side_nav_bar.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/layout-large/activity_main.xml b/app/src/main/res/layout-large/activity_main.xml new file mode 100644 index 0000000..9eac58a --- /dev/null +++ b/app/src/main/res/layout-large/activity_main.xml @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/app/src/main/res/layout-large/fragment_weather.xml b/app/src/main/res/layout-large/fragment_weather.xml new file mode 100644 index 0000000..0acc671 --- /dev/null +++ b/app/src/main/res/layout-large/fragment_weather.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index aacf2ea..2bd9aa1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,26 +1,8 @@ - - - - - - - - + tools:context="com.ikholopov.yamblz.weather.weathermobilization.ui.MainActivity" /> diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml deleted file mode 100644 index 299664e..0000000 --- a/app/src/main/res/layout/app_bar_main.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/degree_switcher.xml b/app/src/main/res/layout/degree_switcher.xml deleted file mode 100644 index a55cb02..0000000 --- a/app/src/main/res/layout/degree_switcher.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_cities.xml b/app/src/main/res/layout/fragment_cities.xml new file mode 100644 index 0000000..7674f8c --- /dev/null +++ b/app/src/main/res/layout/fragment_cities.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index c8ea31a..e5c0a37 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -1,7 +1,97 @@ - + - + + + + + + + + + + + + + + + + + + + + + \ 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 index 46ddd20..8eff1cc 100644 --- a/app/src/main/res/layout/fragment_weather.xml +++ b/app/src/main/res/layout/fragment_weather.xml @@ -3,44 +3,129 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/refresh_layout" android:layout_width="match_parent" - android:layout_height="match_parent" - app:layout_behavior="@string/appbar_scrolling_view_behavior"> + android:layout_height="match_parent"> - + + + - + - + + + + + + + + + + + - + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent"/> - + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_city.xml b/app/src/main/res/layout/item_city.xml new file mode 100644 index 0000000..0273a1e --- /dev/null +++ b/app/src/main/res/layout/item_city.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_weather.xml b/app/src/main/res/layout/item_weather.xml new file mode 100644 index 0000000..bc95863 --- /dev/null +++ b/app/src/main/res/layout/item_weather.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml deleted file mode 100644 index e176cd1..0000000 --- a/app/src/main/res/layout/nav_header_main.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml deleted file mode 100644 index 741bbd8..0000000 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/mipmap-hdpi/clouds.png b/app/src/main/res/mipmap-hdpi/clouds.png deleted file mode 100644 index 8b8c141..0000000 Binary files a/app/src/main/res/mipmap-hdpi/clouds.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/mixed.png b/app/src/main/res/mipmap-hdpi/mixed.png deleted file mode 100644 index 9d451b3..0000000 Binary files a/app/src/main/res/mipmap-hdpi/mixed.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/rain.png b/app/src/main/res/mipmap-hdpi/rain.png deleted file mode 100644 index e291a24..0000000 Binary files a/app/src/main/res/mipmap-hdpi/rain.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/snow.png b/app/src/main/res/mipmap-hdpi/snow.png deleted file mode 100644 index ce5d86a..0000000 Binary files a/app/src/main/res/mipmap-hdpi/snow.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/sun.png b/app/src/main/res/mipmap-hdpi/sun.png deleted file mode 100644 index f01efa1..0000000 Binary files a/app/src/main/res/mipmap-hdpi/sun.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/thunderstorm.png b/app/src/main/res/mipmap-hdpi/thunderstorm.png deleted file mode 100644 index a996402..0000000 Binary files a/app/src/main/res/mipmap-hdpi/thunderstorm.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/clouds.png b/app/src/main/res/mipmap-mdpi/clouds.png deleted file mode 100644 index cc26665..0000000 Binary files a/app/src/main/res/mipmap-mdpi/clouds.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/mixed.png b/app/src/main/res/mipmap-mdpi/mixed.png deleted file mode 100644 index b47e56e..0000000 Binary files a/app/src/main/res/mipmap-mdpi/mixed.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/rain.png b/app/src/main/res/mipmap-mdpi/rain.png deleted file mode 100644 index 95ebc13..0000000 Binary files a/app/src/main/res/mipmap-mdpi/rain.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/snow.png b/app/src/main/res/mipmap-mdpi/snow.png deleted file mode 100644 index 87e4a20..0000000 Binary files a/app/src/main/res/mipmap-mdpi/snow.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/sun.png b/app/src/main/res/mipmap-mdpi/sun.png deleted file mode 100644 index 2a8f669..0000000 Binary files a/app/src/main/res/mipmap-mdpi/sun.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/thunderstorm.png b/app/src/main/res/mipmap-mdpi/thunderstorm.png deleted file mode 100644 index d18f744..0000000 Binary files a/app/src/main/res/mipmap-mdpi/thunderstorm.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/clouds.png b/app/src/main/res/mipmap-xhdpi/clouds.png deleted file mode 100644 index 57d1cd2..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/clouds.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/mixed.png b/app/src/main/res/mipmap-xhdpi/mixed.png deleted file mode 100644 index 610ebb2..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/mixed.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/rain.png b/app/src/main/res/mipmap-xhdpi/rain.png deleted file mode 100644 index c632cb4..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/rain.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/snow.png b/app/src/main/res/mipmap-xhdpi/snow.png deleted file mode 100644 index 885f54f..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/snow.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/sun.png b/app/src/main/res/mipmap-xhdpi/sun.png deleted file mode 100644 index 0406987..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/sun.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/thunderstorm.png b/app/src/main/res/mipmap-xhdpi/thunderstorm.png deleted file mode 100644 index 043f8ab..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/thunderstorm.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/clouds.png b/app/src/main/res/mipmap-xxhdpi/clouds.png deleted file mode 100644 index 92b622d..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/clouds.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/mixed.png b/app/src/main/res/mipmap-xxhdpi/mixed.png deleted file mode 100644 index beefa3a..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/mixed.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/rain.png b/app/src/main/res/mipmap-xxhdpi/rain.png deleted file mode 100644 index e056488..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/rain.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/snow.png b/app/src/main/res/mipmap-xxhdpi/snow.png deleted file mode 100644 index f4be6ce..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/snow.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/sun.png b/app/src/main/res/mipmap-xxhdpi/sun.png deleted file mode 100644 index 009a781..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/sun.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/thunderstorm.png b/app/src/main/res/mipmap-xxhdpi/thunderstorm.png deleted file mode 100644 index 42c8eba..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/thunderstorm.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/clouds.png b/app/src/main/res/mipmap-xxxhdpi/clouds.png deleted file mode 100644 index 2ffdd52..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/clouds.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/mixed.png b/app/src/main/res/mipmap-xxxhdpi/mixed.png deleted file mode 100644 index 03ed4fe..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/mixed.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/rain.png b/app/src/main/res/mipmap-xxxhdpi/rain.png deleted file mode 100644 index 85e6c13..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/rain.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/snow.png b/app/src/main/res/mipmap-xxxhdpi/snow.png deleted file mode 100644 index 0c5feaa..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/snow.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/sun.png b/app/src/main/res/mipmap-xxxhdpi/sun.png deleted file mode 100644 index e6925a1..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/sun.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/thunderstorm.png b/app/src/main/res/mipmap-xxxhdpi/thunderstorm.png deleted file mode 100644 index 5f18115..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/thunderstorm.png and /dev/null differ diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ecd49d3..ab60de9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,25 +1,43 @@ - Открыть панель навигации - Закрыть панель навигации + Погода "Погода в " - Погода - Настройки - О приложении - Еденицы измерения + %.1f м/с + %.1f гПа + + Формат температуры + Интервал обновления - Москва + + Выключить + 15 мин. + 30 мин. + 1 час + 2 часа + + + Город Разработано Игорем Холоповым в рамках проекта Яндекс Мобилизация\n Иконки сделаны Anindyanfitri - Freepik.com - Погода - Обновления погоды - Нет соединения - Потяните вниз, чтобы попробывать обновить - Интервал обновления - мин - Город + + Потяните вниз, чтобы обновить + Нет соединения\nПотяните вниз, чтобы попробывать ещё Произошла ошибка + Выбери город, чтобы увидеть подробности + Поиск города + Настройки + Удалить город из Избранного? + Сохранить город в Избранном? + Да + Нет + Сохранённые города + Результаты поиска + Обновлено + Ветер + Давление + Прогноз + \ No newline at end of file diff --git a/app/src/main/res/values-v21/drawables.xml b/app/src/main/res/values-v21/drawables.xml new file mode 100644 index 0000000..ff48854 --- /dev/null +++ b/app/src/main/res/values-v21/drawables.xml @@ -0,0 +1,8 @@ + + @drawable/ic_menu_black_24px + @drawable/ic_search_black_24px + @drawable/ic_settings_black_24px + @drawable/ic_navigate_next_black_24px + @drawable/ic_arrow_back_black_24px + @drawable/ic_clear_black_24px + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 43923c2..f4ff56c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,12 @@ #4286f4 #376096 #f2bc59 + #ff323232 + #ff707070 + #ffeaecef + #fffefefe + #24101000 + + @color/dark + @color/white diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 049f28f..e77982f 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,5 +1,4 @@ - 16dp 16dp 16dp diff --git a/app/src/main/res/values/drawables.xml b/app/src/main/res/values/drawables.xml deleted file mode 100644 index 129edbc..0000000 --- a/app/src/main/res/values/drawables.xml +++ /dev/null @@ -1,5 +0,0 @@ - - @android:drawable/ic_menu_gallery - @android:drawable/ic_menu_manage - @android:drawable/ic_menu_info_details - diff --git a/app/src/main/res/values/integer.xml b/app/src/main/res/values/integer.xml deleted file mode 100644 index bd4f2b8..0000000 --- a/app/src/main/res/values/integer.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 30 - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 66d84ca..5d2dabf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,37 +1,46 @@ Weather Mobilization - Open navigation drawer - Close navigation drawer - Showing weather in - Weather - Settings - About - Units - - °F + °F °C - Autoupdate weather + + %.1f m/s + %.1f hPa + + Temperature units Update interval - mins - City - PREF_KEY_METRICS - PREF_KEY_AUTOUPDATE - PREF_KEY_UPDATE_INTERVAL - PREF_KEY_CITY_NAME - PREF_KEY_CITY_LAT - PREF_KEY_CITY_LNG - Moscow - 55.755826 - 37.6173 + + Disable + 15 mins + 30 mins + 1 hour + 2 hours + + + City Developed by Igor Kholopov for Yandex Mobilization\n Icon vector created by Anindyanfitri - Freepik.com %1.0f%s + Swipe down to refresh - No Internet connection + No Internet connection\nSwipe down to refresh An error has occurred + Choose a city to see the details + City search + Settings + Delete city from Favorites? + Save city to Favorites? + Yes + No + Saved cities + Search results + Updated + Wind + Pressure + Forecast + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 259f5ba..05b324c 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,27 +1,9 @@ - - - - - - + + + + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml deleted file mode 100644 index 6f22e4d..0000000 --- a/app/src/main/res/xml/preferences.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/OnBootTest.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/OnBootTest.java index 301276a..1139f18 100644 --- a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/OnBootTest.java +++ b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/OnBootTest.java @@ -1,14 +1,15 @@ package com.ikholopov.yamblz.weather.weathermobilization; import com.ikholopov.yamblz.weather.weathermobilization.di.component.ApplicationComponent; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.presenter.UpdateServiceController; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.PreferencesProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.UpdateServiceController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.threeten.bp.Duration; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,17 +38,17 @@ public void setUp() throws Exception { @Test public void startUpdatingOnBootTest() throws Exception { - when(preferences.getAutoupdateEnabledPreference()).thenReturn(true); - when(preferences.getUpdateInterval()).thenReturn(5); + Duration duration = Duration.ofSeconds(5); + when(preferences.getAutoUpdateInterval()).thenReturn(duration); onBoot.onReceive(application, null); - verify(serviceController).enableService(5); + verify(serviceController).enableService(duration); } @Test public void stopUpdatingOnBootTest() throws Exception { - when(preferences.getAutoupdateEnabledPreference()).thenReturn(false); + when(preferences.getAutoUpdateInterval()).thenReturn(Duration.ZERO); onBoot.onReceive(application, null); diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/SchedulerProviderTest.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/SchedulerProviderTest.java new file mode 100644 index 0000000..caffa28 --- /dev/null +++ b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/SchedulerProviderTest.java @@ -0,0 +1,42 @@ +package com.ikholopov.yamblz.weather.weathermobilization; + +import org.junit.Test; + +import io.reactivex.android.plugins.RxAndroidPlugins; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.TestScheduler; + +import static org.junit.Assert.*; + +/** + * Created by turist on 14.08.2017. + */ +public class SchedulerProviderTest { + + SchedulerProvider provider = new SchedulerProvider(); + + @Test + public void main() throws Exception { + TestScheduler testScheduler = new TestScheduler(); + RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> testScheduler); + assertEquals(provider.main(), testScheduler); + RxAndroidPlugins.reset(); + } + + @Test + public void computation() throws Exception { + TestScheduler testScheduler = new TestScheduler(); + RxJavaPlugins.setComputationSchedulerHandler(scheduler -> testScheduler); + assertEquals(provider.computation(), testScheduler); + RxJavaPlugins.reset(); + } + + @Test + public void io() throws Exception { + TestScheduler testScheduler = new TestScheduler(); + RxJavaPlugins.setIoSchedulerHandler(scheduler -> testScheduler); + assertEquals(provider.io(), testScheduler); + RxJavaPlugins.reset(); + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/TestSchedulers.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/TestSchedulers.java new file mode 100644 index 0000000..537f2c0 --- /dev/null +++ b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/TestSchedulers.java @@ -0,0 +1,28 @@ +package com.ikholopov.yamblz.weather.weathermobilization; + +import io.reactivex.Scheduler; +import io.reactivex.schedulers.TestScheduler; + +/** + * Created by turist on 13.08.2017. + */ + +public class TestSchedulers extends SchedulerProvider { + + public TestScheduler scheduler = new TestScheduler(); + + @Override + public Scheduler main() { + return scheduler; + } + + @Override + public Scheduler computation() { + return scheduler; + } + + @Override + public Scheduler io() { + return scheduler; + } +} diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherFileCacheTests.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherFileCacheTests.java deleted file mode 100644 index 9e66fb0..0000000 --- a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherFileCacheTests.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import android.content.Context; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import java.io.File; - -import static org.assertj.core.api.Java6Assertions.assertThat; -import static org.mockito.Mockito.when; - -/** - * Created by turist on 24.07.2017. - */ - -@RunWith(MockitoJUnitRunner.class) -public class WeatherFileCacheTests { - - @Mock Context context; - - @Rule - public TemporaryFolder folder= new TemporaryFolder(); - - @Before - public void setUp() throws Exception { - File createdFolder = folder.newFolder("cache"); - when(context.getFilesDir()).thenReturn(createdFolder); - when(context.getApplicationContext()).thenReturn(context); - } - - @Test - public void loadAfterSaveTest() throws Exception { - CurrentWeatherFileCache currentWeatherFileCache = new CurrentWeatherFileCache(context); - - currentWeatherFileCache.save("{ \"id\" : 42 }"); - - CurrentWeather loaded = currentWeatherFileCache.load(); - - assertThat(loaded.getId()).isEqualTo(42); - } - - @Test - public void loadNullAfterClearTest() throws Exception { - CurrentWeatherFileCache currentWeatherFileCache = new CurrentWeatherFileCache(context); - - currentWeatherFileCache.save("{ \"id\" : 42 }"); - currentWeatherFileCache.clear(); - - CurrentWeather loaded = currentWeatherFileCache.load(); - - assertThat(loaded).isNull(); - } -} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherLoaderTests.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherLoaderTests.java deleted file mode 100644 index d5b002c..0000000 --- a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherLoaderTests.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import android.content.Context; - -import com.ikholopov.yamblz.weather.weathermobilization.data.http.HttpHelper; -import com.ikholopov.yamblz.weather.weathermobilization.data.http.UriHelper; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import static org.assertj.core.api.Java6Assertions.assertThat; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyMapOf; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.only; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * Created by turist on 24.07.2017. - */ - -@RunWith(MockitoJUnitRunner.class) -public class WeatherLoaderTests { - - @Mock Context context; - @Mock PreferencesProvider preferencesProvider; - @Mock CurrentWeatherFileCache cache; - @Mock HttpHelper httpHelper; - @Mock UriHelper uriHelper; - - private static final String urlString = "http://api.openweathermap.org/query123"; - - @Before - public void setUp() throws Exception { - when(context.getString(anyInt())).thenReturn("str"); - when(context.getApplicationContext()).thenReturn(context); - - when(preferencesProvider.getCityLat()).thenReturn(32.0f); - when(preferencesProvider.getCityLng()).thenReturn(31.0f); - when(httpHelper.get(urlString)).thenReturn("json"); - when(uriHelper.create(anyMapOf(String.class, String.class))).thenReturn(urlString); - } - - @Test - public void needNetLoadTest() throws Exception { - CurrentWeather currentWeather = new CurrentWeather(); - when(cache.load()).thenReturn(null, currentWeather); - - CurrentWeatherLoader loader = new CurrentWeatherLoader(context, preferencesProvider, cache, httpHelper, uriHelper); - CurrentWeather loaded = loader.loadInBackground(); - - assertThat(loaded).isEqualTo(currentWeather); - verify(httpHelper, only()).get(urlString); - verify(cache).save("json"); - } - - @Test - public void loadFromCacheTest() throws Exception { - CurrentWeather currentWeather = new CurrentWeather(); - when(cache.load()).thenReturn(currentWeather); - - CurrentWeatherLoader loader = new CurrentWeatherLoader(context, preferencesProvider, cache, httpHelper, uriHelper); - CurrentWeather loaded = loader.loadInBackground(); - - assertThat(loaded).isEqualTo(currentWeather); - verify(httpHelper, never()).get(urlString); - verify(cache, never()).save(anyString()); - } -} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherUtilityTests.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherUtilityTests.java deleted file mode 100644 index ebe6942..0000000 --- a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/data/WeatherUtilityTests.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.data; - -import android.content.Context; - -import com.ikholopov.yamblz.weather.weathermobilization.R; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; - -import static org.assertj.core.api.Java6Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Created by turist on 24.07.2017. - */ - -@RunWith(JUnitParamsRunner.class) -public class WeatherUtilityTests { - - @Test - public void formatTemperatureForCelsius() throws Exception { - Context context = mock(Context.class); - when(context.getString(R.string.celsius)).thenReturn("c"); - when(context.getString(R.string.format_temperature, 5.0, "c")).thenReturn("cel"); - - String cel = WeatherUtility.formatTemperature(context, 5.0, true); - - assertThat(cel).isEqualTo("cel"); - } - - @Test - public void formatTemperatureForFarhenheit() throws Exception { - Context context = mock(Context.class); - when(context.getString(R.string.farhenheit)).thenReturn("f"); - when(context.getString(R.string.format_temperature, 41.0, "f")).thenReturn("far"); - - String far = WeatherUtility.formatTemperature(context, 5.0, false); - - assertThat(far).isEqualTo("far"); - } - - static Object[] imageForWeather() { - return new Object[]{ - new Object[]{201, R.mipmap.thunderstorm}, - new Object[]{301, R.mipmap.rain}, - new Object[]{501, R.mipmap.rain}, - new Object[]{511, R.mipmap.snow}, - new Object[]{520, R.mipmap.rain}, - new Object[]{600, R.mipmap.snow}, - new Object[]{701, R.mipmap.clouds}, - new Object[]{800, R.mipmap.sun}, - new Object[]{801, R.mipmap.mixed}, - new Object[]{802, R.mipmap.mixed}, - new Object[]{803, R.mipmap.clouds}, - new Object[]{1000, -1} - }; - } - - @Test - @Parameters(method = "imageForWeather") - public void getImageIdForWeatherId(int weather, int image) throws Exception { - assertThat(WeatherUtility.getImageIdForWeatherId(weather)).isEqualTo(image); - } -} diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/CityRepositoryTest.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/CityRepositoryTest.java new file mode 100644 index 0000000..7db4b36 --- /dev/null +++ b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/CityRepositoryTest.java @@ -0,0 +1,60 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.persistence; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao.CityDao; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import io.reactivex.Maybe; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by turist on 13.08.2017. + */ + +@RunWith(MockitoJUnitRunner.class) +public class CityRepositoryTest { + + @Mock CityDao dao; + CityRepository repository; + + @Before + public void setUp() { + repository = new CityRepository(dao); + } + + @Test + public void insert() throws Exception { + City city = mock(City.class); + + repository.insert(city); + + dao.insert(city); + } + + @Test + public void delete() throws Exception { + City city = mock(City.class); + + repository.delete(city); + + dao.delete(city); + } + + @Test + public void getByPlaceId() throws Exception { + City city = mock(City.class); + when(dao.getByPlaceId("id")).thenReturn(Maybe.just(city)); + + City res = repository.getByPlaceId("id").blockingGet(); + + assertEquals(res, city); + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/WeatherRepositoryTest.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/WeatherRepositoryTest.java new file mode 100644 index 0000000..dba39c5 --- /dev/null +++ b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/model/persistence/WeatherRepositoryTest.java @@ -0,0 +1,55 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.persistence; + +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.Forecast; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.dao.ForecastDao; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.Flowable; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Created by turist on 13.08.2017. + */ + +@RunWith(MockitoJUnitRunner.class) +public class WeatherRepositoryTest { + + @Mock ForecastDao dao; + WeatherRepository repository; + + @Before + public void setUp() { + repository = new WeatherRepository(dao); + } + + @Test + public void forCity() throws Exception { + ArrayList list = new ArrayList<>(); + when(dao.forCity(123)).thenReturn(Flowable.just(list)); + + City city = new City(123, "", "", null); + List forecasts = repository.forCity(city).blockingFirst(); + + assertEquals(forecasts, list); + } + + @Test + public void deleteForCity() throws Exception { + City city = new City(123, "", "", null); + repository.deleteForCity(city); + + verify(dao).deleteForCity(123); + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/TemperatureFormatTest.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/TemperatureFormatTest.java new file mode 100644 index 0000000..dbb9ff4 --- /dev/null +++ b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/model/preferences/TemperatureFormatTest.java @@ -0,0 +1,22 @@ +package com.ikholopov.yamblz.weather.weathermobilization.model.preferences; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Created by turist on 14.08.2017. + */ +public class TemperatureFormatTest { + @Test + public void celsiusFormat() throws Exception { + assertEquals(TemperatureFormat.create(TemperatureFormat.CELSIUS) + .convert(1.0), 1.0, 0.0005); + } + + @Test + public void fahrenheitFormat() throws Exception { + assertEquals(TemperatureFormat.create(TemperatureFormat.FAHRENHEIT) + .convert(5.0), 41.0, 0.0005); + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitiesPresenterTests.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitiesPresenterTests.java new file mode 100644 index 0000000..d80f341 --- /dev/null +++ b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitiesPresenterTests.java @@ -0,0 +1,151 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import com.ikholopov.yamblz.weather.weathermobilization.R; +import com.ikholopov.yamblz.weather.weathermobilization.SchedulerProvider; +import com.ikholopov.yamblz.weather.weathermobilization.TestSchedulers; +import com.ikholopov.yamblz.weather.weathermobilization.model.City; +import com.ikholopov.yamblz.weather.weathermobilization.model.Coordinates; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.CityInfo; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.PlacesService; +import com.ikholopov.yamblz.weather.weathermobilization.model.network.dto.places.CityShortInfo; +import com.ikholopov.yamblz.weather.weathermobilization.model.persistence.CityRepository; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.CitiesView; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.CityWeather; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Flowable; +import io.reactivex.Maybe; +import io.reactivex.Observable; +import io.reactivex.Single; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyListOf; +import static org.mockito.Matchers.isNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Created by turist on 28.07.2017. + */ + +@RunWith(MockitoJUnitRunner.class) +public class CitiesPresenterTests { + + @Mock MainRouter router; + @Mock PlacesService placesService; + @Mock CityRepository repository; + @Mock SchedulerProvider schedulers; + @Mock CitiesView view; + + CitiesPresenter presenter; + TestSchedulers testSchedulers = new TestSchedulers(); + + @Before + public void setUp() { + when(view.citiesSearches()).thenReturn(Observable.empty()); + when(view.citiesSelections()).thenReturn(Observable.empty()); + when(view.settingsRequests()).thenReturn(Observable.empty()); + when(view.searchClears()).thenReturn(Observable.empty()); + when(view.citiesOptionSelections()).thenReturn(Observable.empty()); + + presenter = new CitiesPresenter(router, placesService, repository, testSchedulers); + } + + @Test + public void searchReactionTest() throws Exception { + when(view.citiesSearches()).thenReturn(Observable.just("city")); + when(placesService.autocomplete("city")).thenReturn(Single.just(new CityShortInfo[0])); + + presenter.bind(view); + + testSchedulers.scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + testSchedulers.scheduler.triggerActions(); + + verify(view).accept(anyListOf(CityWeather.class)); + verify(view).showClearButton(true); + verify(view).setDescription(R.string.search_result); + } + + @Test + public void clearSearchReactionTest() throws Exception { + when(view.searchClears()).thenReturn(Observable.just(new Object())); + + presenter.bind(view); + + verify(view).clearSearch(); + } + + @Test + public void searchClearedReactionTest() throws Exception { + when(view.citiesSearches()).thenReturn(Observable.just("as", "")); + when(repository.cities()).thenReturn(Flowable.just(new City[]{ new City("as", "id", new Coordinates(12.0, 13.0)) })); + + presenter.bind(view); + + testSchedulers.scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + testSchedulers.scheduler.triggerActions(); + + verify(view).accept(anyListOf(CityWeather.class)); + verify(view).showClearButton(false); + verify(view).setDescription(R.string.saved_cities); + } + + @Test + public void showSettingsTest() throws Exception { + when(view.settingsRequests()).thenReturn(Observable.just(new Object())); + + presenter.bind(view); + + verify(router).showSettings(); + } + + @Test + public void citySelectionTest() throws Exception { + City city = new City("n", "id", new Coordinates(1.0, 2.0)); + when(view.citiesSelections()).thenReturn(Observable.just(new CityWeather(city))); + when(repository.getByPlaceId("id")).thenReturn(Maybe.just(city)); + when(placesService.details("id")).thenReturn(Single.just(new CityInfo("n", 1.0, 2.0, "id"))); + + presenter.bind(view); + testSchedulers.scheduler.triggerActions(); + + verify(router).showCityWeather(city); + } + + @Test + public void citySelectionNoStorageTest() throws Exception { + City city = new City("n", "id", new Coordinates(1.0, 2.0)); + when(view.citiesSelections()).thenReturn(Observable.just(new CityWeather(city))); + when(repository.getByPlaceId("id")).thenReturn(Maybe.empty()); + when(placesService.details("id")).thenReturn(Single.just(new CityInfo("n", 1.0, 2.0, "id"))); + + presenter.bind(view); + testSchedulers.scheduler.triggerActions(); + + verify(router).showCityWeather(isNotNull(City.class)); + } + + @Test + public void cityOptionDeleteTest() throws Exception { + CityWeather cityWeather = mock(CityWeather.class); + City city = mock(City.class); + + when(cityWeather.inStorage()).thenReturn(true); + when(view.ask(R.string.delete_city)).thenReturn(Maybe.just(true)); + when(view.citiesOptionSelections()).thenReturn(Observable.just(cityWeather)); + when(repository.getByPlaceId(any())).thenReturn(Maybe.just(city)); + + presenter.bind(view); + testSchedulers.scheduler.triggerActions(); + + verify(repository).delete(city); + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitySelectPresenterTests.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitySelectPresenterTests.java deleted file mode 100644 index 8c01d21..0000000 --- a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/CitySelectPresenterTests.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -import com.ikholopov.yamblz.weather.weathermobilization.data.CityAutoComplete; -import com.ikholopov.yamblz.weather.weathermobilization.data.CityInfo; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherCache; -import com.ikholopov.yamblz.weather.weathermobilization.data.PlacesService; -import com.ikholopov.yamblz.weather.weathermobilization.data.dto.city.CityShortInfo; -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; -import com.ikholopov.yamblz.weather.weathermobilization.ui.CitySelectView; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - -import io.reactivex.Observable; -import io.reactivex.Scheduler; -import io.reactivex.Single; -import io.reactivex.android.plugins.RxAndroidPlugins; -import io.reactivex.annotations.NonNull; -import io.reactivex.functions.Function; -import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.schedulers.TestScheduler; - -import static org.mockito.Matchers.isNotNull; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * Created by turist on 28.07.2017. - */ - -@RunWith(MockitoJUnitRunner.class) -public class CitySelectPresenterTests { - - @Mock PlacesService placesService; - @Mock PreferencesProvider preferences; - @Mock CurrentWeatherCache cache; - @Mock CitySelectView view; - - private CitySelectPresenter presenter; - - @Before - public void setUp() { - RxAndroidPlugins.setInitMainThreadSchedulerHandler(new Function, Scheduler>() { - @Override - public Scheduler apply(@NonNull Callable schedulerCallable) throws Exception { - return Schedulers.trampoline(); - } - }); - - presenter = new CitySelectPresenter(placesService, preferences, cache); - } - - @Test - public void setAutocompleteToViewTest() throws Exception { - CityAutoComplete autoComplete = new CityAutoComplete(); - when(view.textChanges()).thenReturn(Observable.just("city")); - when(view.selection()).thenReturn(Observable.empty()); - when(placesService.autocomplete("city")).thenReturn(Single.just(autoComplete)); - - final TestScheduler testScheduler = new TestScheduler(); - RxJavaPlugins.setComputationSchedulerHandler(new Function() { - @Override - public Scheduler apply(@NonNull Scheduler scheduler) throws Exception { - return testScheduler; - } - }); - - RxJavaPlugins.setIoSchedulerHandler(new Function() { - @Override - public Scheduler apply(@NonNull Scheduler scheduler) throws Exception { - return testScheduler; - } - }); - - presenter.bind(view); - testScheduler.triggerActions(); - - verify(view).accept(autoComplete); - RxJavaPlugins.reset(); - } - - @Test - public void allDoneWhenSelectCityTest() throws Exception { - CityShortInfo cityShortInfo = new CityShortInfo(); - CityInfo cityInfo = new CityInfo("my city", 12.3f, 45.6f); - cityShortInfo.place_id = "cityid"; - when(view.textChanges()).thenReturn(Observable.empty()); - when(view.selection()).thenReturn(Observable.just(cityShortInfo)); - when(placesService.details("cityid")).thenReturn(Single.just(cityInfo)); - - final TestScheduler testScheduler = new TestScheduler(); - RxJavaPlugins.setComputationSchedulerHandler(new Function() { - @Override - public Scheduler apply(@NonNull Scheduler scheduler) throws Exception { - return testScheduler; - } - }); - - RxJavaPlugins.setIoSchedulerHandler(new Function() { - @Override - public Scheduler apply(@NonNull Scheduler scheduler) throws Exception { - return testScheduler; - } - }); - - presenter.bind(view); - testScheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - testScheduler.triggerActions(); - - verify(preferences).saveCity(cityInfo); - verify(cache).clear(); - verify(view).onSelected(); - - RxJavaPlugins.reset(); - } -} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/SettingsPresenterTest.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/SettingsPresenterTest.java new file mode 100644 index 0000000..a3cc0c8 --- /dev/null +++ b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/SettingsPresenterTest.java @@ -0,0 +1,74 @@ +package com.ikholopov.yamblz.weather.weathermobilization.presenter; + +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.PreferencesProvider; +import com.ikholopov.yamblz.weather.weathermobilization.model.preferences.TemperatureFormat; +import com.ikholopov.yamblz.weather.weathermobilization.model.services.UpdateServiceController; +import com.ikholopov.yamblz.weather.weathermobilization.presenter.views.SettingsView; +import com.ikholopov.yamblz.weather.weathermobilization.ui.UpdateIntervalFormat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.threeten.bp.Duration; + +import io.reactivex.Observable; + +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Created by turist on 14.08.2017. + */ + +@RunWith(MockitoJUnitRunner.class) +public class SettingsPresenterTest { + + @Mock PreferencesProvider preferences; + @Mock UpdateIntervalFormat format; + @Mock UpdateServiceController updateService; + @Mock SettingsView view; + + SettingsPresenter presenter; + + @Before + public void setUp() { + when(view.temperatureUnitsSwitches()).thenReturn(Observable.empty()); + when(view.updateIntervalRequests()).thenReturn(Observable.empty()); + presenter = new SettingsPresenter(preferences, format, updateService); + } + + @Test + public void changeTemperatureUnit() throws Exception { + when(view.temperatureUnitsSwitches()).thenReturn(Observable.just(true, false)); + + presenter.bind(view); + + verify(preferences).putTemperatureFormat(TemperatureFormat.CELSIUS); + verify(preferences).putTemperatureFormat(TemperatureFormat.FAHRENHEIT); + } + + @Test + public void showDialogForUpdateInterval() throws Exception { + when(view.updateIntervalRequests()).thenReturn(Observable.just(0)); + + presenter.bind(view); + + verify(view).showUpdateIntervalDialog(anyObject()); + } + + @Test + public void changeUpdateInterval() throws Exception { + Duration duration = Duration.ofSeconds(12); + when(format.getInterval(12)).thenReturn(duration); + when(format.getString(12)).thenReturn("12"); + + presenter.bind(view); + presenter.saveInterval(12); + + verify(updateService).enableService(duration); + verify(view).setUpdateInterval("12"); + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherNetControllerTest.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherNetControllerTest.java deleted file mode 100644 index 5a42062..0000000 --- a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherNetControllerTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeather; -import com.ikholopov.yamblz.weather.weathermobilization.data.CurrentWeatherLoader; -import com.ikholopov.yamblz.weather.weathermobilization.ui.fragments.ForecastFragment; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * Created by turist on 26.07.2017. - */ -@RunWith(MockitoJUnitRunner.class) -public class WeatherNetControllerTest { - - @Mock CurrentWeatherLoader loader; - @Mock ForecastFragment fragment; - - @Test - public void forceNetLoadTest() throws Exception { - WeatherNetController controller = new WeatherNetController(loader); - - controller.forceNetLoad(); - - verify(loader).forceNetLoad(); - } - - @Test - public void startLoadingWhenBindTest() throws Exception { - WeatherNetController controller = new WeatherNetController(loader); - when(fragment.initLoader(0, null, controller)).thenReturn(loader); - - controller.bindForecastFragment(fragment); - - verify(loader).forceLoad(); - } - - @Test - public void setWeatherWhenLoadedTest() throws Exception { - WeatherNetController controller = new WeatherNetController(loader); - when(fragment.initLoader(0, null, controller)).thenReturn(loader); - controller.bindForecastFragment(fragment); - - CurrentWeather weather = new CurrentWeather(); - controller.onLoadFinished(loader, weather); - - verify(fragment).setWeather(weather); - } -} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherPresenterTests.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherPresenterTests.java deleted file mode 100644 index 465619a..0000000 --- a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/presenter/WeatherPresenterTests.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.ikholopov.yamblz.weather.weathermobilization.presenter; - -import com.ikholopov.yamblz.weather.weathermobilization.preferences.PreferencesProvider; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * Created by turist on 24.07.2017. - */ - -@RunWith(MockitoJUnitRunner.class) -public class WeatherPresenterTests { - - @Mock PreferencesProvider preferences; - @Mock UpdateServiceController serviceController; - @Mock LoaderNetController weatherNetController; - - @Test - public void callLoadingForceTest() throws Exception { - CurrentWeatherPresenterImpl presenter = new CurrentWeatherPresenterImpl(preferences, serviceController, weatherNetController); - - presenter.forceReload(); - - verify(weatherNetController).forceNetLoad(); - } - - @Test - public void startUpdatingWhenBindTest() throws Exception { - when(preferences.getAutoupdateEnabledPreference()).thenReturn(true); - when(preferences.getUpdateInterval()).thenReturn(2); - - CurrentWeatherPresenterImpl presenter = new CurrentWeatherPresenterImpl(preferences, serviceController, weatherNetController); - - presenter.bind(null); - - verify(serviceController).enableService(2); - } - - @Test - public void notStartUpdatingWhenBindTest() throws Exception { - when(preferences.getAutoupdateEnabledPreference()).thenReturn(false); - - CurrentWeatherPresenterImpl presenter = new CurrentWeatherPresenterImpl(preferences, serviceController, weatherNetController); - - presenter.bind(null); - - verify(serviceController, never()).enableService(anyInt()); - } -} \ No newline at end of file diff --git a/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/ui/UpdateIntervalFormatTest.java b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/ui/UpdateIntervalFormatTest.java new file mode 100644 index 0000000..481a4d5 --- /dev/null +++ b/app/src/test/java/com/ikholopov/yamblz/weather/weathermobilization/ui/UpdateIntervalFormatTest.java @@ -0,0 +1,55 @@ +package com.ikholopov.yamblz.weather.weathermobilization.ui; + +import android.content.Context; +import android.content.res.Resources; + +import com.ikholopov.yamblz.weather.weathermobilization.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.threeten.bp.Duration; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by turist on 14.08.2017. + */ + +@RunWith(MockitoJUnitRunner.class) +public class UpdateIntervalFormatTest { + + @Mock Context context; + + UpdateIntervalFormat format; + + @Before + public void setUp() { + Resources resources = mock(Resources.class); + when(context.getResources()).thenReturn(resources); + when(resources.getStringArray(R.array.update_intervals)).thenReturn(new String[]{"0", "1", "2", "3", "4"}); + format = new UpdateIntervalFormat(context); + } + + @Test + public void getString() throws Exception { + assertEquals(format.getString(Duration.ZERO), "0"); + assertEquals(format.getString(Duration.ofMinutes(15)), "1"); + assertEquals(format.getString(Duration.ofMinutes(30)), "2"); + assertEquals(format.getString(Duration.ofHours(1)), "3"); + assertEquals(format.getString(Duration.ofHours(2)), "4"); + } + + @Test + public void getInterval() throws Exception { + assertEquals(format.getInterval(0), Duration.ZERO); + assertEquals(format.getInterval(1), Duration.ofMinutes(15)); + assertEquals(format.getInterval(2), Duration.ofMinutes(30)); + assertEquals(format.getInterval(3), Duration.ofHours(1)); + assertEquals(format.getInterval(4), Duration.ofHours(2)); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 0ccc78d..4a0663f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,20 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. -apply from: 'dependencies.gradle' - buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'me.tatarka:gradle-retrolambda:3.7.0' } } allprojects { repositories { jcenter() + maven { url 'https://maven.google.com' } + maven { url "https://jitpack.io" } } } diff --git a/dependencies.gradle b/dependencies.gradle deleted file mode 100644 index a0e5c71..0000000 --- a/dependencies.gradle +++ /dev/null @@ -1,69 +0,0 @@ -ext.versions = [ - version :1, - versionname :"1.0", - - compileSdk :25, - buildTools :"25.0.3", - - minSdk :19, - targetSdk :26, - - supportLibs :"25.3.1", - supportFix :"25.3.1.1", - constraintLayout :"1.0.2", - - seekbar :"2.3.0", - - butterKnife :"8.7.0", - - dagger :"2.11", - - junit :"4.12", - mockito :"1.10.19", - assertj :"2.8.0", - jUnitParams :"1.1.0", - robolectric :"3.3.2", - espresso :"2.2.2", - - gson :"2.8.1", - rxjava :"2.1.1", - rxandroid :"2.0.1", - rxbinding :"2.0.0", - retrofit :"2.3.0", - - leakCanary :"1.5.1" -] - -ext.libraries = [ - supportAppCompat :"com.android.support:appcompat-v7:$versions.supportLibs", - supportDesign :"com.android.support:design:$versions.supportLibs", - supportPreference :"com.takisoft.fix:preference-v7:$versions.supportFix", - supportConstraintLayout :"com.android.support.constraint:constraint-layout:$versions.constraintLayout", - support :"com.android.support:support-v4:$versions.supportLibs", - - seekbar :"com.pavelsikun:material-seekbar-preference:$versions.seekbar", - - butterKnife :"com.jakewharton:butterknife:$versions.butterKnife", - butterKnifeCompiler :"com.jakewharton:butterknife-compiler:$versions.butterKnife", - - dagger :"com.google.dagger:dagger:$versions.dagger", - daggerCompiler :"com.google.dagger:dagger-compiler:$versions.dagger", - - junit :"junit:junit:$versions.junit", - mockito :"org.mockito:mockito-core:$versions.mockito", - assertj :"org.assertj:assertj-core:$versions.assertj", - jUnitParams :"pl.pragmatists:JUnitParams:$versions.jUnitParams", - robolectric :"org.robolectric:robolectric:$versions.robolectric", - espresso :"com.android.support.test.espresso:espresso-core:$versions.espresso", - - gson :"com.google.code.gson:gson:$versions.gson", - rxjava :"io.reactivex.rxjava2:rxjava:$versions.rxjava", - rxandroid :"io.reactivex.rxjava2:rxandroid:$versions.rxandroid", - rxbinding :"com.jakewharton.rxbinding2:rxbinding:$versions.rxbinding", - retrofit :"com.squareup.retrofit2:retrofit:$versions.retrofit", - converterGson :"com.squareup.retrofit2:converter-gson:$versions.retrofit", - adapterRxjava2 :"com.squareup.retrofit2:adapter-rxjava2:$versions.retrofit", - - leakCanaryDebug :"com.squareup.leakcanary:leakcanary-android:$versions.leakCanary", - leakCanaryRelease :"com.squareup.leakcanary:leakcanary-android:$versions.leakCanary" -] \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 29a9b23..a5ed086 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,6 +15,5 @@ org.gradle.jvmargs=-Xmx1536m # 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 -API_KEY=ab18a306892069efe62fdcc99b61d1a0 -GOOGLE_API_KEY=AIzaSyDrKU-H2n4xrymGB9U07qoxQCXL_qoKrjk -LOCATION="moscow,ru" \ No newline at end of file +OPENWEATHERMAP_API_KEY="ab18a306892069efe62fdcc99b61d1a0" +GOOGLE_API_KEY="AIzaSyDrKU-H2n4xrymGB9U07qoxQCXL_qoKrjk" \ No newline at end of file