diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..410d5594 --- /dev/null +++ b/.gitignore @@ -0,0 +1,267 @@ + +# Created by https://www.gitignore.io/api/linux,macos,windows,android,androidstudio +# Edit at https://www.gitignore.io/?templates=linux,macos,windows,android,androidstudio + +### Android ### +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +### Android Patch ### +gen-external-apklibs +output.json + +# Replacement of .externalNativeBuild directories introduced +# with Android Studio 3.5. +.cxx/ + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files + +# Files for the ART/Dalvik VM + +# Java class files + +# Generated files + +# Gradle files +.gradle + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) + +# Proguard folder generated by Eclipse + +# Log Files + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +*.ipr +*.swp + +# Android Patch + +# External native build folder generated in Android Studio 2.2 and later + +# NDK +obj/ + +# IntelliJ IDEA +*.iws +/out/ + +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/scopes/scope_settings.xml +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# OS-specific files +.DS_Store? + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.gitignore.io/api/linux,macos,windows,android,androidstudio diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..681f41ae --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 00000000..7f68460d --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..9aab14e7 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'com.android.application' +apply plugin: 'org.greenrobot.greendao' +apply plugin: 'kotlin-android' + + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "com.midnight.weatherforecast" + minSdkVersion 16 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + dataBinding { + enabled = true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + +} +greendao { + schemaVersion 12 + targetGenDir 'src/main/java' + +} + + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.squareup.retrofit2:retrofit:2.6.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.0.2' + implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.google.code.gson:gson:2.8.2' + implementation 'org.greenrobot:greendao:3.2.2' + + implementation ("com.google.android.material:material:1.0.0") + implementation ("androidx.cardview:cardview:1.0.0") + implementation ("androidx.recyclerview:recyclerview:1.0.0") + implementation ("androidx.appcompat:appcompat:1.0.2") + + testImplementation 'org.robolectric:robolectric:4.3' + testImplementation 'junit:junit:4.12' + testImplementation 'androidx.test:core:1.2.0' + testImplementation "com.squareup.okhttp3:mockwebserver:3.6.0" + + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/midnight/weatherforecast/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/midnight/weatherforecast/ExampleInstrumentedTest.java new file mode 100644 index 00000000..affa957f --- /dev/null +++ b/app/src/androidTest/java/com/midnight/weatherforecast/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.midnight.weatherforecast; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.midnight.weatherforecast", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..983bd850 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/midnight/weatherforecast/controller/ControllerAPI.kt b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerAPI.kt new file mode 100644 index 00000000..cc34545c --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerAPI.kt @@ -0,0 +1,127 @@ +package com.midnight.weatherforecast.controller + +import com.midnight.weatherforecast.intersepts.GlobalIntersepts +import com.midnight.weatherforecast.interfaces.INFApi +import com.midnight.weatherforecast.models.modelsParam.ModelParamCurrentWeatherByGeo +import com.midnight.weatherforecast.models.modelsParam.ModelParamCurrentWeatherByName +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater +import com.midnight.weatherforecast.models.modelsResponse.ModelForecast +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory +import java.util.concurrent.TimeUnit + +/** + * + */ +class ControllerAPI { + val API_KEY="62fc4256-8f8c-11e5-8994-feff819cdc9f" + val API_ADDRESS="http://worksample-api.herokuapp.com" + + private var okHttpClient: OkHttpClient? = null + private var retrofitRegister: Retrofit? = null + private var serviceRegister: INFApi? = null + + private var callWeatherByName: Call? = null + private var callWeatherByGeo: Call? = null + + private var callWeatherByNameSync: Call? = null + private var callWeatherByGeoSync: Call? = null + + private var callForecastByName: Call? = null + + + private object Holder { val INSTANCE = ControllerAPI()} + companion object { + val instance: ControllerAPI by lazy { ControllerAPI.Holder.INSTANCE} + } + + /** + * + */ + init { + initOkHttp() + initBuilder() + initService() + } + + /** + * + */ + private fun initOkHttp() { + val interceptor = HttpLoggingInterceptor() + interceptor.level = HttpLoggingInterceptor.Level.BODY + okHttpClient = OkHttpClient.Builder() + .connectTimeout(120, TimeUnit.SECONDS) + .writeTimeout(120, TimeUnit.SECONDS) + .readTimeout(120, TimeUnit.SECONDS) + .addInterceptor(GlobalIntersepts()) + .addInterceptor(interceptor) + .build() + } + + /** + * + */ + private fun initBuilder() { + retrofitRegister = Retrofit.Builder() + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient!!) + .baseUrl(API_ADDRESS) + .build() + } + + /** + * + */ + private fun initService() { + serviceRegister = retrofitRegister!!.create(INFApi::class.java) + } + + /** + * + */ + fun getCurrentWeatherByName(paramCurrentWeatherByName: ModelParamCurrentWeatherByName, callback: Callback) { + callWeatherByName = serviceRegister!!.currentWeaterByCityName(paramCurrentWeatherByName.cityName,paramCurrentWeatherByName.key) + callWeatherByName!!.enqueue(callback) + } + + /** + * + */ + fun getCurrentWeatherByGPS(paramCurrentWeatherByGeo: ModelParamCurrentWeatherByGeo, callback: Callback) { + callWeatherByGeo = serviceRegister!!.currentWeaterByCityGeo(paramCurrentWeatherByGeo.lat,paramCurrentWeatherByGeo.lon,paramCurrentWeatherByGeo.key) + callWeatherByGeo!!.enqueue(callback) + } + + /** + * + */ + fun getCurrentWeatherByName(paramCurrentWeatherByName: ModelParamCurrentWeatherByName): ModelCurrentWeater? { + callWeatherByGeoSync = serviceRegister!!.currentWeaterByCityName(paramCurrentWeatherByName.cityName,paramCurrentWeatherByName.key) + return callWeatherByGeoSync?.execute()?.body() + } + + /** + * + */ + fun getCurrentWeatherByGPS(paramCurrentWeatherByGeo: ModelParamCurrentWeatherByGeo): ModelCurrentWeater? { + callWeatherByNameSync = serviceRegister!!.currentWeaterByCityGeo(paramCurrentWeatherByGeo.lat,paramCurrentWeatherByGeo.lon,paramCurrentWeatherByGeo.key) + return callWeatherByGeoSync?.execute()?.body() + } + + /** + * + */ + fun getForecastWeatherByName(paramCurrentWeatherByName: ModelParamCurrentWeatherByName, callback: Callback) { + callForecastByName = serviceRegister!!.forecastWeaterByCityName(paramCurrentWeatherByName.cityName,paramCurrentWeatherByName.key) + callForecastByName!!.enqueue(callback) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/midnight/weatherforecast/controller/ControllerDB.kt b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerDB.kt new file mode 100644 index 00000000..579d8ac2 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerDB.kt @@ -0,0 +1,53 @@ +package com.midnight.weatherforecast.controller + +import android.database.sqlite.SQLiteDatabase +import com.midnight.weatherforecast.core.Loader +import com.midnight.weatherforecast.models.modelsDb.DaoMaster +import com.midnight.weatherforecast.models.modelsDb.DaoSession +import com.midnight.weatherforecast.utils.AppConstant +import org.greenrobot.greendao.query.QueryBuilder + +/** + * + */ +class ControllerDB { + private var daoSession: DaoSession? = null + private var helper: DaoMaster.DevOpenHelper? = null + private var db: SQLiteDatabase? = null + private var daoMaster: DaoMaster? = null + + private object Holder { val INSTANCE=ControllerDB()} + companion object { + val instance: ControllerDB by lazy { Holder.INSTANCE } + } + + init { + helper = DaoMaster.DevOpenHelper(Loader.getInstanse().applicationContext, AppConstant.DB_NAME, null) + db = helper!!.writableDatabase + daoMaster = DaoMaster(db) + daoSession = daoMaster!!.newSession() + QueryBuilder.LOG_SQL = true + QueryBuilder.LOG_VALUES = true + } + + /** + * + */ + fun getDaoSession(): DaoSession? { + return daoSession + } + + /** + * + */ + fun getMaster(): DaoMaster{ + return this.daoMaster!! + } + + /** + * + */ + fun getDb():SQLiteDatabase{ + return db!! + } +} \ No newline at end of file diff --git a/app/src/main/java/com/midnight/weatherforecast/controller/ControllerData.kt b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerData.kt new file mode 100644 index 00000000..d31b6f85 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerData.kt @@ -0,0 +1,265 @@ +package com.midnight.weatherforecast.controller + +import android.util.Log +import com.midnight.weatherforecast.interfaces.INFForecast +import com.midnight.weatherforecast.interfaces.INFLoadData +import com.midnight.weatherforecast.interfaces.INFSearchCity +import com.midnight.weatherforecast.models.modelsDb.City +import com.midnight.weatherforecast.models.modelsDb.CityDao +import com.midnight.weatherforecast.models.modelsParam.ModelParamCurrentWeatherByGeo +import com.midnight.weatherforecast.models.modelsParam.ModelParamCurrentWeatherByName +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater +import com.midnight.weatherforecast.models.modelsResponse.ModelForecast +import com.midnight.weatherforecast.utils.DispatchQueue +import com.midnight.weatherforecast.utils.TaskUnit +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +/** + * + */ +class ControllerData { + var citiesWeater= arrayListOf() + private object Holder { val INSTANCE=ControllerData()} + companion object { + val instances : ControllerData by lazy { Holder.INSTANCE } + } + + fun getHandler(): DispatchQueue? { + return TaskUnit.controllerDataThread + } + + /** + * + */ + fun saveCity(model:ModelCurrentWeater,owner:Boolean):Boolean{ + var modelDaos=City() + modelDaos.cityName=model.name + modelDaos.cityId=model.id + if (owner) + modelDaos.owner=1 + else + modelDaos.owner=2 + ControllerDB.instance.getDaoSession()?.insertOrReplace(modelDaos) + return true + } + + /** + * + */ + fun addToCitiesList(model:ModelCurrentWeater){ + var found=false + for (i in 0 until citiesWeater.size){ + if (citiesWeater[i].id==model.id){ + citiesWeater[i]=model + found=true + break + } + } + if (!found){ + citiesWeater.add(model) + } + } + + /** + * + */ + fun removeACity(model:ModelCurrentWeater) { + val queryCity = ControllerDB.instance.getDaoSession()?.queryBuilder(City::class.java)?.where(CityDao.Properties.CityId.eq(model.id))?.build()?.unique() + if (queryCity != null ) { + ControllerDB.instance.getDaoSession()?.delete(queryCity) + } + + var index=0 + for (i in 0 until citiesWeater.size){ + if (citiesWeater[i].id==model.id){ + citiesWeater.removeAt(index) + break + } + index++ + } + } + + /** + * + */ + fun fetchCity(model:ModelParamCurrentWeatherByName, delegate: INFLoadData, cache:Boolean, async:Boolean, isMainCity:Boolean) { + if (async){ + ControllerAPI.instance.getCurrentWeatherByName(model, object : Callback { + override fun onResponse(call: Call, response: Response) { + addCityToList(response.body()!!,isMainCity,cache) + delegate.onSuccess() + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e("onFailure", "onFailure") + delegate.onFail() + } + }) + }else{ + getHandler()?.post { + val result=ControllerAPI.instance.getCurrentWeatherByName(model) + addCityToList(result!!,isMainCity,cache) + delegate.onSuccess() + + } + } + + } + + /** + * + */ + fun fetchCity(model:ModelParamCurrentWeatherByGeo, delegate: INFLoadData, cache:Boolean, async:Boolean, isMainCity:Boolean) { + if (async){ + ControllerAPI.instance.getCurrentWeatherByGPS(model, object : Callback { + override fun onResponse(call: Call, response: Response) { + addCityToList(response.body()!!,isMainCity,cache) + delegate.onSuccess() + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e("onFailure", "onFailure") + delegate.onFail() + } + }) + }else{ + getHandler()?.post { + val result=ControllerAPI.instance.getCurrentWeatherByGPS(model) + addCityToList(result!!,isMainCity,cache) + delegate.onSuccess() + } + } + + } + + /** + * + */ + fun loadData(delegate: INFLoadData){ + var modelLocalCity=getCachedLocalCity() + if (modelLocalCity!=null) + fetchCity(ModelParamCurrentWeatherByName(modelLocalCity!!.cityName,ControllerAPI.instance.API_KEY),delegate,false,false,true) + + var list=getCachedCity() + for (item in list!!){ + fetchCity(ModelParamCurrentWeatherByName(item.cityName,ControllerAPI.instance.API_KEY),delegate,false,false,false) + } + + } + + private fun addCityToList(model:ModelCurrentWeater,isMainCity:Boolean,isCache:Boolean){ + if (isMainCity){ + val queryList = ControllerDB.instance.getDaoSession()?.queryBuilder(City::class.java)?.where(CityDao.Properties.Owner.eq(1))?.build()?.list() + var mainId=-1 + if (queryList==null || queryList.size==0){ + if (isCache) + saveCity(model,isMainCity) + }else{ + val queryCity=queryList[0] + mainId= queryCity?.cityId!! + queryCity?.cityName=model.name + queryCity?.cityId=model.id + queryCity?.owner= 1 + ControllerDB.instance.getDaoSession()?.update(queryCity) + } + + var found=false + for (i in 0 until citiesWeater.size){ + if (citiesWeater[i].id==mainId){ + citiesWeater[i]=model + found=true + break + } + } + if (!found){ + citiesWeater.add(model) + } + + }else{ + val queryCity = ControllerDB.instance.getDaoSession()?.queryBuilder(City::class.java)?.where(CityDao.Properties.CityId.eq(model.id))?.build()?.list()?.get(0) + if (queryCity==null && isCache){ + saveCity(model,isMainCity) + }else{ + queryCity?.cityName=model.name + queryCity?.cityId=model.id + queryCity?.owner= 2 + ControllerDB.instance.getDaoSession()?.update(queryCity) + } + + var found=false + for (i in 0 until citiesWeater.size){ + if (citiesWeater[i].id==model.id){ + citiesWeater[i]=model + found=true + break + } + } + if (!found){ + citiesWeater.add(model) + } + } + + } + + /** + * + */ + fun searchCity(model:ModelParamCurrentWeatherByName,delegate: INFSearchCity) { + ControllerAPI.instance.getCurrentWeatherByName(model, object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.body()!!.id!=null) + delegate.onSuccess(response.body()!!) + else + delegate.onFail() + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e("onFailure", "onFailure") + delegate.onFail() + } + }) + + } + + /** + * + */ + fun fetchForecast(model:ModelParamCurrentWeatherByName,delegate: INFForecast) { + ControllerAPI.instance.getForecastWeatherByName(model, object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.body()?.list!!.size>0) + delegate.onSuccess(response.body()!!) + else + delegate.onFail() + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e("onFailure", "onFailure") + delegate.onFail() + } + }) + } + + /** + * + */ + fun getAllCachedCity(): MutableList? { + return ControllerDB.instance.getDaoSession()?.queryBuilder(City::class.java)?.build()?.list() + } + + /** + * + */ + fun getCachedCity(): MutableList? { + return ControllerDB.instance.getDaoSession()?.queryBuilder(City::class.java)?.where(CityDao.Properties.Owner.eq(2))?.build()?.list() + } + + /** + * + */ + fun getCachedLocalCity(): City? { + return ControllerDB.instance.getDaoSession()?.queryBuilder(City::class.java)?.where(CityDao.Properties.Owner.eq(1))?.build()?.unique() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/midnight/weatherforecast/controller/ControllerLocation.java b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerLocation.java new file mode 100644 index 00000000..84fb03a1 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerLocation.java @@ -0,0 +1,54 @@ +package com.midnight.weatherforecast.controller; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.util.Log; + +public class ControllerLocation implements LocationListener { + private LocationManager locationManager; + private int TIME_INTERVAL = 100; + private int DISTANCE_INTERVAL = 1; + private String TAG="ControllerLocation"; + public LocationManager getLocationManager() { + return locationManager; + } + private LocationListener delegate; + + public ControllerLocation(Activity activity,LocationListener delegate) { + this.locationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE); + this.delegate=delegate; + } + + @SuppressLint("MissingPermission") + public void requestUpdates(){ + this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, TIME_INTERVAL, DISTANCE_INTERVAL, this); + } + + @Override + public void onLocationChanged(Location location) { + Log.d(TAG, "Location found : " + location.getLatitude() + ", " + location.getLongitude() + (location.hasAccuracy() ? " : +- " + location.getAccuracy() + " meters" : "")); + this.delegate.onLocationChanged(location); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + Log.d(TAG, "Provided status changed : " + provider + " : status : " + status); + } + + @Override + public void onProviderEnabled(String provider) { + Log.d(TAG, "Provider enabled : " + provider); + this.delegate.onProviderEnabled(provider); + } + + @Override + public void onProviderDisabled(String provider) { + Log.d(TAG, "Provider disabled : " + provider); + this.delegate.onProviderDisabled(provider); + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/controller/ControllerPermission.kt b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerPermission.kt new file mode 100644 index 00000000..a73ad293 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/controller/ControllerPermission.kt @@ -0,0 +1,64 @@ +package com.midnight.weatherforecast.controller + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.annotation.IntRange +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.midnight.weatherforecast.R +import java.util.* + +class ControllerPermission { + val requestCodeLocation = 101 + private val permissionLocation = Manifest.permission.ACCESS_FINE_LOCATION + + private object Holder { val INSTANCE=ControllerPermission()} + companion object { + val instance : ControllerPermission by lazy { ControllerPermission.Holder.INSTANCE} + } + + /** + * + */ + fun getLocationPermission(context: Context,activity: Activity):Boolean{ + return if (!checkPermission(context,permissionLocation)){ + grantPermission(activity,requestCodeLocation,permissionLocation) + false + }else{ + true + } + } + + /** + * + */ + private fun checkPermission(context: Context,permission: String): Boolean { + return if (Build.VERSION.SDK_INT >= 23) { + ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED + } else { + true + } + } + + /** + * + */ + private fun grantPermission(activity: Activity, requestCode: Int, permission: String) { + executeGrantPermission(activity, permission, requestCode) + } + + /** + * + */ + private fun executeGrantPermission(activity: Activity, permissions: String, @IntRange(from = 0L) requestCode: Int) { + if (permissions.isNotEmpty()) { + val perm = arrayOfNulls(1) + perm[0]=permissions + ActivityCompat.requestPermissions(activity, perm, requestCode) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/midnight/weatherforecast/core/Loader.java b/app/src/main/java/com/midnight/weatherforecast/core/Loader.java new file mode 100644 index 00000000..c656de95 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/core/Loader.java @@ -0,0 +1,58 @@ +package com.midnight.weatherforecast.core; + +import android.app.Application; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Handler; + +import androidx.annotation.NonNull; + +public class Loader extends Application { + public static volatile Handler applicationHandler; + public static Loader ourInstance; + + + @Override + public void onCreate() { + super.onCreate(); + ourInstance=this; + applicationHandler = new Handler(getApplicationContext().getMainLooper()); + } + + /** + * @return + */ + public static Loader getInstanse() { + if (ourInstance == null) + ourInstance = new Loader(); + return ourInstance; + } + + /** + * @return + */ + public Context getApplicationContext() { + return Loader.this; + } + + + @Override + public void onLowMemory() { + super.onLowMemory(); + } + + @Override + public void onTerminate() { + super.onTerminate(); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/interfaces/INFApi.kt b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFApi.kt new file mode 100644 index 00000000..15c84023 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFApi.kt @@ -0,0 +1,28 @@ +package com.midnight.weatherforecast.interfaces + +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater +import com.midnight.weatherforecast.models.modelsResponse.ModelForecast +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Query + +interface INFApi { + @GET("weather") + abstract fun currentWeaterByCityName(@Query("q") cityName: String,@Query("key") token: String): Call + + @GET("weather") + abstract fun currentWeaterByCityId(@Query("id") cityId: String,@Query("key") token: String): Call + + @GET("weather") + abstract fun currentWeaterByCityGeo(@Query("lat") cityLat: String,@Query("lon") cityLon: String,@Query("key") token: String): Call + + @GET("weather") + abstract fun currentWeaterByCityZip(@Query("zip") cityZip: String,@Query("key") token: String): Call + + @GET("weather") + abstract fun currentWeaterByCitiesId(@Query("id") cityId: String,@Query("key") token: String): Call + + @GET("forecast") + abstract fun forecastWeaterByCityName(@Query("q") cityName: String,@Query("key") token: String): Call + +} \ No newline at end of file diff --git a/app/src/main/java/com/midnight/weatherforecast/interfaces/INFForecast.kt b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFForecast.kt new file mode 100644 index 00000000..cb17df46 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFForecast.kt @@ -0,0 +1,9 @@ +package com.midnight.weatherforecast.interfaces + +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater +import com.midnight.weatherforecast.models.modelsResponse.ModelForecast + +interface INFForecast { + fun onSuccess(model:ModelForecast) + fun onFail() +} \ No newline at end of file diff --git a/app/src/main/java/com/midnight/weatherforecast/interfaces/INFListViewClick.java b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFListViewClick.java new file mode 100644 index 00000000..ca8f6e78 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFListViewClick.java @@ -0,0 +1,6 @@ +package com.midnight.weatherforecast.interfaces; + +public interface INFListViewClick { + public void onClickListenner(int position); + public void onLongClick(int position); +} diff --git a/app/src/main/java/com/midnight/weatherforecast/interfaces/INFLoadData.java b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFLoadData.java new file mode 100644 index 00000000..08daef25 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFLoadData.java @@ -0,0 +1,6 @@ +package com.midnight.weatherforecast.interfaces; + +public interface INFLoadData { + public void onSuccess(); + public void onFail(); +} diff --git a/app/src/main/java/com/midnight/weatherforecast/interfaces/INFSearchCity.kt b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFSearchCity.kt new file mode 100644 index 00000000..a9454e82 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/interfaces/INFSearchCity.kt @@ -0,0 +1,8 @@ +package com.midnight.weatherforecast.interfaces + +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater + +interface INFSearchCity { + fun onSuccess(model:ModelCurrentWeater) + fun onFail() +} \ No newline at end of file diff --git a/app/src/main/java/com/midnight/weatherforecast/intersepts/GlobalIntersepts.java b/app/src/main/java/com/midnight/weatherforecast/intersepts/GlobalIntersepts.java new file mode 100644 index 00000000..f3ca653d --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/intersepts/GlobalIntersepts.java @@ -0,0 +1,22 @@ +package com.midnight.weatherforecast.intersepts; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class GlobalIntersepts implements Interceptor { + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + okhttp3.Response response = chain.proceed(request); + if (response.code() == 403) { + //We have to show some error + return response; + }else { + return response; + } + + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/City.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/City.java new file mode 100644 index 00000000..b23b924e --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/City.java @@ -0,0 +1,64 @@ +package com.midnight.weatherforecast.models.modelsDb; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Index; +import org.greenrobot.greendao.annotation.Property; +import org.greenrobot.greendao.annotation.Generated; + +@Entity(nameInDb = "CITY_LIST") +public class City { + @Id + private Long id; + @Property(nameInDb = "CITYID") + @Index(unique = true) + private int cityId; + @Property(nameInDb = "CITYNAME") + private String cityName; + @Property(nameInDb = "RECOWNER") + private int owner; + + @Generated(hash = 1705308826) + public City(Long id, int cityId, String cityName, int owner) { + this.id = id; + this.cityId = cityId; + this.cityName = cityName; + this.owner = owner; + } + + @Generated(hash = 750791287) + public City() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public int getCityId() { + return cityId; + } + + public void setCityId(int cityId) { + this.cityId = cityId; + } + + public String getCityName() { + return cityName; + } + + public void setCityName(String cityName) { + this.cityName = cityName; + } + + public int getOwner() { + return owner; + } + + public void setOwner(int owner) { + this.owner = owner; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/CityDao.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/CityDao.java new file mode 100644 index 00000000..97eed734 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/CityDao.java @@ -0,0 +1,142 @@ +package com.midnight.weatherforecast.models.modelsDb; + +import android.database.Cursor; +import android.database.sqlite.SQLiteStatement; + +import org.greenrobot.greendao.AbstractDao; +import org.greenrobot.greendao.Property; +import org.greenrobot.greendao.internal.DaoConfig; +import org.greenrobot.greendao.database.Database; +import org.greenrobot.greendao.database.DatabaseStatement; + +// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. +/** + * DAO for table "CITY_LIST". +*/ +public class CityDao extends AbstractDao { + + public static final String TABLENAME = "CITY_LIST"; + + /** + * Properties of entity City.
+ * Can be used for QueryBuilder and for referencing column names. + */ + public static class Properties { + public final static Property Id = new Property(0, Long.class, "id", true, "_id"); + public final static Property CityId = new Property(1, int.class, "cityId", false, "CITYID"); + public final static Property CityName = new Property(2, String.class, "cityName", false, "CITYNAME"); + public final static Property Owner = new Property(3, int.class, "owner", false, "RECOWNER"); + } + + + public CityDao(DaoConfig config) { + super(config); + } + + public CityDao(DaoConfig config, DaoSession daoSession) { + super(config, daoSession); + } + + /** Creates the underlying database table. */ + public static void createTable(Database db, boolean ifNotExists) { + String constraint = ifNotExists? "IF NOT EXISTS ": ""; + db.execSQL("CREATE TABLE " + constraint + "\"CITY_LIST\" (" + // + "\"_id\" INTEGER PRIMARY KEY ," + // 0: id + "\"CITYID\" INTEGER NOT NULL ," + // 1: cityId + "\"CITYNAME\" TEXT," + // 2: cityName + "\"RECOWNER\" INTEGER NOT NULL );"); // 3: owner + // Add Indexes + db.execSQL("CREATE UNIQUE INDEX " + constraint + "IDX_CITY_LIST_CITYID ON \"CITY_LIST\"" + + " (\"CITYID\" ASC);"); + } + + /** Drops the underlying database table. */ + public static void dropTable(Database db, boolean ifExists) { + String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"CITY_LIST\""; + db.execSQL(sql); + } + + @Override + protected final void bindValues(DatabaseStatement stmt, City entity) { + stmt.clearBindings(); + + Long id = entity.getId(); + if (id != null) { + stmt.bindLong(1, id); + } + stmt.bindLong(2, entity.getCityId()); + + String cityName = entity.getCityName(); + if (cityName != null) { + stmt.bindString(3, cityName); + } + stmt.bindLong(4, entity.getOwner()); + } + + @Override + protected final void bindValues(SQLiteStatement stmt, City entity) { + stmt.clearBindings(); + + Long id = entity.getId(); + if (id != null) { + stmt.bindLong(1, id); + } + stmt.bindLong(2, entity.getCityId()); + + String cityName = entity.getCityName(); + if (cityName != null) { + stmt.bindString(3, cityName); + } + stmt.bindLong(4, entity.getOwner()); + } + + @Override + public Long readKey(Cursor cursor, int offset) { + return cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0); + } + + @Override + public City readEntity(Cursor cursor, int offset) { + City entity = new City( // + cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id + cursor.getInt(offset + 1), // cityId + cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // cityName + cursor.getInt(offset + 3) // owner + ); + return entity; + } + + @Override + public void readEntity(Cursor cursor, City entity, int offset) { + entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0)); + entity.setCityId(cursor.getInt(offset + 1)); + entity.setCityName(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2)); + entity.setOwner(cursor.getInt(offset + 3)); + } + + @Override + protected final Long updateKeyAfterInsert(City entity, long rowId) { + entity.setId(rowId); + return rowId; + } + + @Override + public Long getKey(City entity) { + if(entity != null) { + return entity.getId(); + } else { + return null; + } + } + + @Override + public boolean hasKey(City entity) { + return entity.getId() != null; + } + + @Override + protected final boolean isEntityUpdateable() { + return true; + } + +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/DaoMaster.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/DaoMaster.java new file mode 100644 index 00000000..f56e9a24 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/DaoMaster.java @@ -0,0 +1,96 @@ +package com.midnight.weatherforecast.models.modelsDb; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.util.Log; + +import org.greenrobot.greendao.AbstractDaoMaster; +import org.greenrobot.greendao.database.StandardDatabase; +import org.greenrobot.greendao.database.Database; +import org.greenrobot.greendao.database.DatabaseOpenHelper; +import org.greenrobot.greendao.identityscope.IdentityScopeType; + + +// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. +/** + * Master of DAO (schema version 12): knows all DAOs. + */ +public class DaoMaster extends AbstractDaoMaster { + public static final int SCHEMA_VERSION = 12; + + /** Creates underlying database table using DAOs. */ + public static void createAllTables(Database db, boolean ifNotExists) { + CityDao.createTable(db, ifNotExists); + } + + /** Drops underlying database table using DAOs. */ + public static void dropAllTables(Database db, boolean ifExists) { + CityDao.dropTable(db, ifExists); + } + + /** + * WARNING: Drops all table on Upgrade! Use only during development. + * Convenience method using a {@link DevOpenHelper}. + */ + public static DaoSession newDevSession(Context context, String name) { + Database db = new DevOpenHelper(context, name).getWritableDb(); + DaoMaster daoMaster = new DaoMaster(db); + return daoMaster.newSession(); + } + + public DaoMaster(SQLiteDatabase db) { + this(new StandardDatabase(db)); + } + + public DaoMaster(Database db) { + super(db, SCHEMA_VERSION); + registerDaoClass(CityDao.class); + } + + public DaoSession newSession() { + return new DaoSession(db, IdentityScopeType.Session, daoConfigMap); + } + + public DaoSession newSession(IdentityScopeType type) { + return new DaoSession(db, type, daoConfigMap); + } + + /** + * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} - + */ + public static abstract class OpenHelper extends DatabaseOpenHelper { + public OpenHelper(Context context, String name) { + super(context, name, SCHEMA_VERSION); + } + + public OpenHelper(Context context, String name, CursorFactory factory) { + super(context, name, factory, SCHEMA_VERSION); + } + + @Override + public void onCreate(Database db) { + Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION); + createAllTables(db, false); + } + } + + /** WARNING: Drops all table on Upgrade! Use only during development. */ + public static class DevOpenHelper extends OpenHelper { + public DevOpenHelper(Context context, String name) { + super(context, name); + } + + public DevOpenHelper(Context context, String name, CursorFactory factory) { + super(context, name, factory); + } + + @Override + public void onUpgrade(Database db, int oldVersion, int newVersion) { + Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables"); + dropAllTables(db, true); + onCreate(db); + } + } + +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/DaoSession.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/DaoSession.java new file mode 100644 index 00000000..dd7d85f3 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsDb/DaoSession.java @@ -0,0 +1,48 @@ +package com.midnight.weatherforecast.models.modelsDb; + +import java.util.Map; + +import org.greenrobot.greendao.AbstractDao; +import org.greenrobot.greendao.AbstractDaoSession; +import org.greenrobot.greendao.database.Database; +import org.greenrobot.greendao.identityscope.IdentityScopeType; +import org.greenrobot.greendao.internal.DaoConfig; + +import com.midnight.weatherforecast.models.modelsDb.City; + +import com.midnight.weatherforecast.models.modelsDb.CityDao; + +// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. + +/** + * {@inheritDoc} + * + * @see org.greenrobot.greendao.AbstractDaoSession + */ +public class DaoSession extends AbstractDaoSession { + + private final DaoConfig cityDaoConfig; + + private final CityDao cityDao; + + public DaoSession(Database db, IdentityScopeType type, Map>, DaoConfig> + daoConfigMap) { + super(db); + + cityDaoConfig = daoConfigMap.get(CityDao.class).clone(); + cityDaoConfig.initIdentityScope(type); + + cityDao = new CityDao(cityDaoConfig, this); + + registerDao(City.class, cityDao); + } + + public void clear() { + cityDaoConfig.clearIdentityScope(); + } + + public CityDao getCityDao() { + return cityDao; + } + +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByCitiesId.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByCitiesId.java new file mode 100644 index 00000000..a59df740 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByCitiesId.java @@ -0,0 +1,40 @@ +package com.midnight.weatherforecast.models.modelsParam; + +import java.util.ArrayList; + +/** + * + */ +public class ModelParamCurrentWeatherByCitiesId { + private ArrayList ids=new ArrayList<>(); + private String key; + + public ModelParamCurrentWeatherByCitiesId(String cityId, String key) { + this.ids.add(cityId); + this.key = key; + } + + public String getCityId() { + String result=""; + synchronized (ids) { + for (int index = 0; index < ids.size(); index++) { + result = result + ids.get(index); + if (index < ids.size() - 1) + result = result + ","; + } + } + return result; + } + + public void setCityId(String cityId) { + this.ids.add(cityId); + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByGeo.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByGeo.java new file mode 100644 index 00000000..bc71a80e --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByGeo.java @@ -0,0 +1,40 @@ +package com.midnight.weatherforecast.models.modelsParam; + +/** + * + */ +public class ModelParamCurrentWeatherByGeo { + private String lat; + private String lon; + private String key; + + public ModelParamCurrentWeatherByGeo(String lat, String lon, String key) { + this.lat = lat; + this.lon = lon; + this.key = key; + } + + public String getLat() { + return lat; + } + + public void setLat(String lat) { + this.lat = lat; + } + + public String getLon() { + return lon; + } + + public void setLon(String lon) { + this.lon = lon; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherById.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherById.java new file mode 100644 index 00000000..ea461807 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherById.java @@ -0,0 +1,30 @@ +package com.midnight.weatherforecast.models.modelsParam; + +/** + * + */ +public class ModelParamCurrentWeatherById { + private String cityId; + private String key; + + public ModelParamCurrentWeatherById(String cityId, String key) { + this.cityId = cityId; + this.key = key; + } + + public String getCityId() { + return cityId; + } + + public void setCityId(String cityId) { + this.cityId = cityId; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByName.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByName.java new file mode 100644 index 00000000..347a32d8 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByName.java @@ -0,0 +1,30 @@ +package com.midnight.weatherforecast.models.modelsParam; + +/** + * + */ +public class ModelParamCurrentWeatherByName { + private String cityName; + private String key; + + public ModelParamCurrentWeatherByName(String cityName, String key) { + this.cityName = cityName; + this.key = key; + } + + public String getCityName() { + return cityName; + } + + public void setCityName(String cityName) { + this.cityName = cityName; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByZip.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByZip.java new file mode 100644 index 00000000..c944701d --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsParam/ModelParamCurrentWeatherByZip.java @@ -0,0 +1,30 @@ +package com.midnight.weatherforecast.models.modelsParam; + +/** + * + */ +public class ModelParamCurrentWeatherByZip { + private String zipCode; + private String key; + + public ModelParamCurrentWeatherByZip(String zipCode, String key) { + this.zipCode = zipCode; + this.key = key; + } + + public String getZipCode() { + return zipCode; + } + + public void setZipCode(String zipCode) { + this.zipCode = zipCode; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsResponse/ModelCurrentWeater.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsResponse/ModelCurrentWeater.java new file mode 100644 index 00000000..b62b16d1 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsResponse/ModelCurrentWeater.java @@ -0,0 +1,171 @@ +package com.midnight.weatherforecast.models.modelsResponse; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import com.midnight.weatherforecast.models.modelsSub.ModelCloud; +import com.midnight.weatherforecast.models.modelsSub.ModelCoord; +import com.midnight.weatherforecast.models.modelsSub.ModelMain; +import com.midnight.weatherforecast.models.modelsSub.ModelRain; +import com.midnight.weatherforecast.models.modelsSub.ModelSys; +import com.midnight.weatherforecast.models.modelsSub.ModelWeather; +import com.midnight.weatherforecast.models.modelsSub.ModelWind; + +import java.io.Serializable; +import java.util.List; + +public class ModelCurrentWeater implements Serializable { + @SerializedName("coord") + @Expose + private ModelCoord coord; + @SerializedName("weather") + @Expose + private List weather = null; + @SerializedName("base") + @Expose + private String base; + @SerializedName("main") + @Expose + private ModelMain main; + @SerializedName("visibility") + @Expose + private Integer visibility; + @SerializedName("wind") + @Expose + private ModelWind wind; + @SerializedName("rain") + @Expose + private ModelRain rain; + @SerializedName("clouds") + @Expose + private ModelCloud clouds; + @SerializedName("dt") + @Expose + private Integer dt; + @SerializedName("sys") + @Expose + private ModelSys sys; + @SerializedName("timezone") + @Expose + private Integer timezone; + @SerializedName("id") + @Expose + private Integer id; + @SerializedName("name") + @Expose + private String name; + @SerializedName("cod") + @Expose + private Integer cod; + + public ModelCoord getCoord() { + return coord; + } + + public void setCoord(ModelCoord coord) { + this.coord = coord; + } + + public List getWeather() { + return weather; + } + + public void setWeather(List weather) { + this.weather = weather; + } + + public String getBase() { + return base; + } + + public void setBase(String base) { + this.base = base; + } + + public ModelMain getMain() { + return main; + } + + public void setMain(ModelMain main) { + this.main = main; + } + + public Integer getVisibility() { + return visibility; + } + + public void setVisibility(Integer visibility) { + this.visibility = visibility; + } + + public ModelWind getWind() { + return wind; + } + + public void setWind(ModelWind wind) { + this.wind = wind; + } + + public ModelRain getRain() { + return rain; + } + + public void setRain(ModelRain rain) { + this.rain = rain; + } + + public ModelCloud getClouds() { + return clouds; + } + + public void setClouds(ModelCloud clouds) { + this.clouds = clouds; + } + + public Integer getDt() { + return dt; + } + + public void setDt(Integer dt) { + this.dt = dt; + } + + public ModelSys getSys() { + return sys; + } + + public void setSys(ModelSys sys) { + this.sys = sys; + } + + public Integer getTimezone() { + return timezone; + } + + public void setTimezone(Integer timezone) { + this.timezone = timezone; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getCod() { + return cod; + } + + public void setCod(Integer cod) { + this.cod = cod; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsResponse/ModelForecast.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsResponse/ModelForecast.java new file mode 100644 index 00000000..b11975fd --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsResponse/ModelForecast.java @@ -0,0 +1,53 @@ +package com.midnight.weatherforecast.models.modelsResponse; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public class ModelForecast { + @SerializedName("cod") + @Expose + private String cod; + @SerializedName("message") + @Expose + private Integer message; + @SerializedName("cnt") + @Expose + private Integer cnt; + @SerializedName("list") + @Expose + private java.util.List list = null; + + public String getCod() { + return cod; + } + + public void setCod(String cod) { + this.cod = cod; + } + + public Integer getMessage() { + return message; + } + + public void setMessage(Integer message) { + this.message = message; + } + + public Integer getCnt() { + return cnt; + } + + public void setCnt(Integer cnt) { + this.cnt = cnt; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelCloud.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelCloud.java new file mode 100644 index 00000000..0eb54a23 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelCloud.java @@ -0,0 +1,18 @@ +package com.midnight.weatherforecast.models.modelsSub; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class ModelCloud { + @SerializedName("all") + @Expose + private Integer all; + + public Integer getAll() { + return all; + } + + public void setAll(Integer all) { + this.all = all; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelCoord.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelCoord.java new file mode 100644 index 00000000..819ad94e --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelCoord.java @@ -0,0 +1,29 @@ +package com.midnight.weatherforecast.models.modelsSub; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class ModelCoord { + @SerializedName("lon") + @Expose + private Double lon; + @SerializedName("lat") + @Expose + private Double lat; + + public Double getLon() { + return lon; + } + + public void setLon(Double lon) { + this.lon = lon; + } + + public Double getLat() { + return lat; + } + + public void setLat(Double lat) { + this.lat = lat; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelMain.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelMain.java new file mode 100644 index 00000000..35ec589e --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelMain.java @@ -0,0 +1,62 @@ +package com.midnight.weatherforecast.models.modelsSub; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class ModelMain { + @SerializedName("temp") + @Expose + private Double temp; + @SerializedName("pressure") + @Expose + private Integer pressure; + @SerializedName("humidity") + @Expose + private Integer humidity; + @SerializedName("temp_min") + @Expose + private Double tempMin; + @SerializedName("temp_max") + @Expose + private Double tempMax; + + public Double getTemp() { + return temp; + } + + public void setTemp(Double temp) { + this.temp = temp; + } + + public Integer getPressure() { + return pressure; + } + + public void setPressure(Integer pressure) { + this.pressure = pressure; + } + + public Integer getHumidity() { + return humidity; + } + + public void setHumidity(Integer humidity) { + this.humidity = humidity; + } + + public Double getTempMin() { + return tempMin; + } + + public void setTempMin(Double tempMin) { + this.tempMin = tempMin; + } + + public Double getTempMax() { + return tempMax; + } + + public void setTempMax(Double tempMax) { + this.tempMax = tempMax; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelRain.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelRain.java new file mode 100644 index 00000000..3bb25bdf --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelRain.java @@ -0,0 +1,18 @@ +package com.midnight.weatherforecast.models.modelsSub; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class ModelRain { + @SerializedName("1h") + @Expose + private Double _1h; + + public Double get_1h() { + return _1h; + } + + public void set_1h(Double _1h) { + this._1h = _1h; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelSys.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelSys.java new file mode 100644 index 00000000..65fc9bfa --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelSys.java @@ -0,0 +1,62 @@ +package com.midnight.weatherforecast.models.modelsSub; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class ModelSys { + @SerializedName("type") + @Expose + private Integer type; + @SerializedName("id") + @Expose + private Integer id; + @SerializedName("country") + @Expose + private String country; + @SerializedName("sunrise") + @Expose + private Integer sunrise; + @SerializedName("sunset") + @Expose + private Integer sunset; + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public Integer getSunrise() { + return sunrise; + } + + public void setSunrise(Integer sunrise) { + this.sunrise = sunrise; + } + + public Integer getSunset() { + return sunset; + } + + public void setSunset(Integer sunset) { + this.sunset = sunset; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelWeather.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelWeather.java new file mode 100644 index 00000000..b5ef2b25 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelWeather.java @@ -0,0 +1,51 @@ +package com.midnight.weatherforecast.models.modelsSub; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class ModelWeather { + @SerializedName("id") + @Expose + private Integer id; + @SerializedName("main") + @Expose + private String main; + @SerializedName("description") + @Expose + private String description; + @SerializedName("icon") + @Expose + private String icon; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getMain() { + return main; + } + + public void setMain(String main) { + this.main = main; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelWind.java b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelWind.java new file mode 100644 index 00000000..caab2ab2 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/models/modelsSub/ModelWind.java @@ -0,0 +1,29 @@ +package com.midnight.weatherforecast.models.modelsSub; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class ModelWind { + @SerializedName("speed") + @Expose + private Double speed; + @SerializedName("deg") + @Expose + private Double deg; + + public Double getSpeed() { + return speed; + } + + public void setSpeed(Double speed) { + this.speed = speed; + } + + public Double getDeg() { + return deg; + } + + public void setDeg(Double deg) { + this.deg = deg; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/utils/AppConstant.java b/app/src/main/java/com/midnight/weatherforecast/utils/AppConstant.java new file mode 100644 index 00000000..517ed3ce --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/utils/AppConstant.java @@ -0,0 +1,118 @@ +package com.midnight.weatherforecast.utils; + +import com.midnight.weatherforecast.R; + +import java.util.HashMap; + +public class AppConstant { + public static final String Clouds="Clouds"; + public static final String Clear="Clear"; + public static final String Tornado="Tornado"; + public static final String Squall="Squall"; + public static final String Ash="Ash"; + public static final String Sand="Sand"; + public static final String Fog="Fog"; + public static final String Dust="Dust"; + public static final String Haze="Haze"; + public static final String Smoke="Smoke"; + public static final String Mist="Mist"; + public static final String Snow="Snow"; + public static final String Rain="Rain"; + public static final String Drizzle="Drizzle"; + public static final String Thunderstorm="Thunderstorm"; + + public static final HashMap iconList; + static { + iconList=new HashMap<>(); + iconList.put(Clouds, R.drawable.ic_wi_cloud); + iconList.put(Clear, R.drawable.ic_wi_day_sunny); + iconList.put(Tornado, R.drawable.ic_wi_lightning); + iconList.put(Squall, R.drawable.ic_wi_day_light_wind); + iconList.put(Ash, R.drawable.ic_wi_day_light_wind); + iconList.put(Sand, R.drawable.ic_wi_day_light_wind); + iconList.put(Fog, R.drawable.ic_wi_cloudy_windy); + iconList.put(Dust, R.drawable.ic_wi_cloudy_windy); + iconList.put(Haze, R.drawable.ic_wi_cloudy_windy); + iconList.put(Smoke, R.drawable.ic_wi_day_light_wind); + iconList.put(Mist, R.drawable.ic_wi_cloudy_windy); + iconList.put(Snow, R.drawable.ic_wi_day_snow_wind); + iconList.put(Rain, R.drawable.ic_wi_day_rain); + iconList.put(Drizzle, R.drawable.ic_wi_day_rain); + iconList.put(Thunderstorm, R.drawable.ic_wi_lightning); + } + + public static final HashMap iconListBlack; + static { + iconListBlack=new HashMap<>(); + iconListBlack.put(Clouds, R.drawable.ic_wi_cloud_black); + iconListBlack.put(Clear, R.drawable.ic_wi_day_sunny_black); + iconListBlack.put(Tornado, R.drawable.ic_wi_lightning_black); + iconListBlack.put(Squall, R.drawable.ic_wi_day_light_wind_black); + iconListBlack.put(Ash, R.drawable.ic_wi_day_light_wind_black); + iconListBlack.put(Sand, R.drawable.ic_wi_day_light_wind_black); + iconListBlack.put(Fog, R.drawable.ic_wi_cloudy_windy_black); + iconListBlack.put(Dust, R.drawable.ic_wi_cloudy_windy_black); + iconListBlack.put(Haze, R.drawable.ic_wi_cloudy_windy_black); + iconListBlack.put(Smoke, R.drawable.ic_wi_day_light_wind_black); + iconListBlack.put(Mist, R.drawable.ic_wi_cloudy_windy_black); + iconListBlack.put(Snow, R.drawable.ic_wi_day_snow_wind_black); + iconListBlack.put(Rain, R.drawable.ic_wi_day_rain_black); + iconListBlack.put(Drizzle, R.drawable.ic_wi_day_rain_black); + iconListBlack.put(Thunderstorm, R.drawable.ic_wi_lightning_black); + } + + public static final HashMap iconListMight; + static { + iconListMight=new HashMap<>(); + iconListMight.put(Clouds, R.drawable.ic_wi_night_cloudy); + iconListMight.put(Clear, R.drawable.ic_wi_night_clear); + iconListMight.put(Tornado, R.drawable.ic_wi_lightning); + iconListMight.put(Squall, R.drawable.ic_wi_night_alt_cloudy_windy); + iconListMight.put(Ash, R.drawable.ic_wi_night_alt_cloudy_windy); + iconListMight.put(Sand, R.drawable.ic_wi_night_alt_cloudy_windy); + iconListMight.put(Fog, R.drawable.ic_wi_cloudy_windy); + iconListMight.put(Dust, R.drawable.ic_wi_cloudy_windy); + iconListMight.put(Haze, R.drawable.ic_wi_cloudy_windy); + iconListMight.put(Smoke, R.drawable.ic_wi_night_alt_cloudy_windy); + iconListMight.put(Mist, R.drawable.ic_wi_night_alt_cloudy_windy); + iconListMight.put(Snow, R.drawable.ic_wi_night_alt_snow); + iconListMight.put(Rain, R.drawable.ic_wi_night_alt_rain); + iconListMight.put(Drizzle, R.drawable.ic_wi_night_cloudy); + iconListMight.put(Thunderstorm, R.drawable.ic_wi_lightning); + } + + + public static final HashMap colorList; + static { + colorList=new HashMap<>(); + colorList.put(Clouds, 0xFF6D5DF1); + colorList.put(Clear, 0xFFFFC107); + colorList.put(Tornado, 0xFF757575); + colorList.put(Squall, 0xFF757575); + colorList.put(Ash, 0xFF9E9E9E); + colorList.put(Sand, 0xFF795548); + colorList.put(Fog,0xFFF5F5F5); + colorList.put(Dust, 0xFF455A64); + colorList.put(Haze, 0xFFBDBDBD); + colorList.put(Smoke, 0xFFBDBDBD); + colorList.put(Mist, 0xFFBDBDBD); + colorList.put(Snow, 0xFFFFFFFF); + colorList.put(Rain, 0xFF0097A7); + colorList.put(Drizzle, 0xFF00BCD4); + colorList.put(Thunderstorm, 0xFF303F9F); + } + + public static final HashMap dayOfWeek; + static { + dayOfWeek=new HashMap<>(); + dayOfWeek.put(1, "Saturday"); + dayOfWeek.put(2, "Sunday"); + dayOfWeek.put(3, "Monday"); + dayOfWeek.put(4, "Tuesday"); + dayOfWeek.put(5, "Wednesday"); + dayOfWeek.put(6, "Thursday"); + dayOfWeek.put(7, "Friday"); + } + + public static String DB_NAME="weatherDB.db"; +} diff --git a/app/src/main/java/com/midnight/weatherforecast/utils/DispatchQueue.java b/app/src/main/java/com/midnight/weatherforecast/utils/DispatchQueue.java new file mode 100644 index 00000000..6b499c99 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/utils/DispatchQueue.java @@ -0,0 +1,96 @@ +package com.midnight.weatherforecast.utils; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import java.util.concurrent.CountDownLatch; + +public class DispatchQueue extends Thread { + + private volatile Handler handler = null; + private CountDownLatch syncLatch = new CountDownLatch(1); + + public DispatchQueue(final String threadName) { + setName(threadName); + start(); + } + + public void sendMessage(Message msg, int delay) { + try { + syncLatch.await(); + if (delay <= 0) { + handler.sendMessage(msg); + } else { + handler.sendMessageDelayed(msg, delay); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void cancelRunnable(Runnable runnable) { + try { + syncLatch.await(); + handler.removeCallbacks(runnable); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void post(Runnable runnable) { + postRunnable(runnable, 0); + } + + + public void postRunnable(Runnable runnable) { + postRunnable(runnable, 0); + } + + public void postRunnable(Runnable runnable, long delay) { + try { + syncLatch.await(); + } catch (Exception e) { + e.printStackTrace(); + } + if (delay <= 0) { + handler.post(runnable); + } else { + handler.postDelayed(runnable, delay); + } + } + + public void cleanupQueue() { + try { + syncLatch.await(); + handler.removeCallbacksAndMessages(null); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void handleMessage(Message inputMessage) { + + } + + public void recycle() { + handler.getLooper().quit(); + } + + @Override + public void run() { + Looper.prepare(); + handler = new Handler() { + @Override + public void handleMessage(Message msg) { + DispatchQueue.this.handleMessage(msg); + } + }; + syncLatch.countDown(); + Looper.loop(); + } + + public Handler getHandler() { + return handler; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/utils/TaskUnit.java b/app/src/main/java/com/midnight/weatherforecast/utils/TaskUnit.java new file mode 100644 index 00000000..18950a10 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/utils/TaskUnit.java @@ -0,0 +1,6 @@ +package com.midnight.weatherforecast.utils; + +public class TaskUnit { + public static volatile DispatchQueue controllerDataThread = new DispatchQueue("controllerData"); + +} diff --git a/app/src/main/java/com/midnight/weatherforecast/utils/Utils.java b/app/src/main/java/com/midnight/weatherforecast/utils/Utils.java new file mode 100644 index 00000000..6c8af8c0 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/utils/Utils.java @@ -0,0 +1,45 @@ +package com.midnight.weatherforecast.utils; + +import android.text.format.DateFormat; +import android.util.Log; + +import com.midnight.weatherforecast.core.Loader; + +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +public class Utils { + /** + * + * @param runnable + */ + public static void runOnUiThread(Runnable runnable){ + Loader.applicationHandler.post(runnable); + } + + public static int getHourOfDay(int date){ + Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + calendar.setTimeInMillis(Long.valueOf(date)*1000); + return calendar.get(Calendar.HOUR_OF_DAY); + + } + + public static String getDayOfWeek(int date){ + Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + calendar.setTimeInMillis(Long.valueOf(date)*1000); + return AppConstant.dayOfWeek.get(calendar.get(Calendar.DAY_OF_WEEK)); + + } + + public static String getDateFormat(int date){ + Log.e("date","->" + date); + Calendar cal = Calendar.getInstance(Locale.ENGLISH); + cal.setTimeInMillis(Long.valueOf(date)*1000); + return DateFormat.format("HH:mm", cal).toString(); + + } + +} diff --git a/app/src/main/java/com/midnight/weatherforecast/views/MainActivity.java b/app/src/main/java/com/midnight/weatherforecast/views/MainActivity.java new file mode 100644 index 00000000..f1cfb56d --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/views/MainActivity.java @@ -0,0 +1,93 @@ +package com.midnight.weatherforecast.views; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.databinding.DataBindingUtil; +import android.os.Bundle; +import android.view.MenuItem; + +import com.midnight.weatherforecast.R; +import com.midnight.weatherforecast.controller.ControllerPermission; +import com.midnight.weatherforecast.databinding.ActivityMainBinding; +import com.midnight.weatherforecast.views.fragments.FragmentDetail; +import com.midnight.weatherforecast.views.fragments.FragmentMain; +import com.midnight.weatherforecast.views.fragments.FragmentSearch; + +public class MainActivity extends AppCompatActivity { + private ActivityMainBinding binding; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding= DataBindingUtil.setContentView(this, R.layout.activity_main); + initView(); + + } + + /** + * + */ + private void initView(){ + getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame,FragmentMain.getInstance(),FragmentMain.TAG) + .addToBackStack(null) + .commit(); + setSupportActionBar(binding.toolbar); + binding.toolbar.setTitleTextColor(getResources().getColor(android.R.color.white)); + binding.toolbar.setNavigationIcon(R.drawable.ic_back); + if (getSupportActionBar() != null){ + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + } + } + + public void openAddCityFragment(){ + getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame,FragmentSearch.getInstance(), FragmentSearch.TAG) + .addToBackStack(FragmentSearch.TAG) + .commit(); + + } + + public void openDetailFragment(Bundle bundle){ + getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame, FragmentDetail.getInstance(bundle), FragmentSearch.TAG) + .addToBackStack(FragmentSearch.TAG) + .commit(); + + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (grantResults[0]==-1){ + ((FragmentMain)getSupportFragmentManager().getFragments().get(0)).handlerLoading(false); + return; + } + if (ControllerPermission.Companion.getInstance().getRequestCodeLocation()==requestCode){ + if (getSupportFragmentManager().getFragments().size()>0){ + if (getSupportFragmentManager().getFragments().get(0) instanceof FragmentMain){ + ((FragmentMain)getSupportFragmentManager().getFragments().get(0)).getLocation(); + } + } + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + onBackPressed(); + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() { + if (getSupportFragmentManager().getFragments().size()>0){ + if (getSupportFragmentManager().getFragments().get(0) instanceof FragmentDetail){ + getSupportFragmentManager().popBackStack(); + }else if (getSupportFragmentManager().getFragments().get(0) instanceof FragmentSearch){ + getSupportFragmentManager().popBackStack(); + }else{ + finishAffinity(); + } + } + } + + +} diff --git a/app/src/main/java/com/midnight/weatherforecast/views/adapters/AdapterForecast.java b/app/src/main/java/com/midnight/weatherforecast/views/adapters/AdapterForecast.java new file mode 100644 index 00000000..ee0f800a --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/views/adapters/AdapterForecast.java @@ -0,0 +1,161 @@ +package com.midnight.weatherforecast.views.adapters; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.midnight.weatherforecast.R; +import com.midnight.weatherforecast.controller.ControllerData; +import com.midnight.weatherforecast.databinding.AdapterDetailBinding; +import com.midnight.weatherforecast.databinding.AdapterForecastBinding; +import com.midnight.weatherforecast.databinding.AdapterMainBinding; +import com.midnight.weatherforecast.interfaces.INFListViewClick; +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater; +import com.midnight.weatherforecast.utils.AppConstant; +import com.midnight.weatherforecast.utils.Utils; + +import java.util.ArrayList; +import java.util.List; + +public class AdapterForecast extends RecyclerView.Adapter{ + private Context context; + private INFListViewClick delegate; + private ArrayList data=new ArrayList<>(); + public AdapterForecast(Context context, INFListViewClick delegate,ArrayList data) { + this.context = context; + this.delegate=delegate; + this.data=data; + } + + public AdapterForecast(Context context, INFListViewClick delegate) { + this.context = context; + this.delegate=delegate; + } + + @Override + public int getItemViewType(int position) { + if (position==0){ + return 0; + }else{ + return 1; + } + } + + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + switch (viewType){ + case 0: + return new cityWeatherDetailViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.adapter_detail, parent, false)); + case 1: + return new cityWeatherForecastViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.adapter_forecast, parent, false)); + default: + return new cityWeatherForecastViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.adapter_search, parent, false)); + } + } + + + @Override + public int getItemCount() { + return data.size(); + } + + + @Override + public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) { + if (viewHolder instanceof cityWeatherDetailViewHolder){ + ModelCurrentWeater model=data.get(position); + final cityWeatherDetailViewHolder holder = (cityWeatherDetailViewHolder) viewHolder; + holder.binding.tvCityName.setText(model.getName()); + holder.binding.tvCityTemp.setText(String.valueOf(Math.round(model.getMain().getTemp()))); + holder.binding.tvCityMinTemp.setText(String.format(context.getString(R.string.min_temp),String.valueOf(Math.round(model.getMain().getTempMin())))); + holder.binding.tvCityMaxTemp.setText(String.format(context.getString(R.string.max_temp),String.valueOf(Math.round(model.getMain().getTempMax())))); + holder.binding.tvCityPressure.setText(String.format(context.getString(R.string.pressure),String.valueOf(Math.round(model.getMain().getPressure())))); + holder.binding.tvCityStatus.setText(model.getWeather().get(0).getMain()); + holder.binding.tvCityStatusDesc.setText(model.getWeather().get(0).getDescription()); + holder.binding.tvCityHum.setText(String.format(context.getString(R.string.humidity),String.valueOf(model.getMain().getHumidity()))); + holder.binding.tvCityWind.setText(String.format(context.getString(R.string.wind),String.valueOf(model.getWind().getSpeed()))); + holder.binding.imgIcon.setImageResource(AppConstant.iconList.get(model.getWeather().get(0).getMain())); + holder.binding.cardview.setCardBackgroundColor(AppConstant.colorList.get(model.getWeather().get(0).getMain())); + + if ("Snow".equalsIgnoreCase(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getMain()) || "Fog".equalsIgnoreCase(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getMain())){ + holder.binding.tvCityName.setTextColor(0xFF000000); + holder.binding.tvCityTemp.setTextColor(0xFF000000); + holder.binding.tvCityStatus.setTextColor(0xFF000000); + holder.binding.tvCityStatusDesc.setTextColor(0xFF000000); + holder.binding.tvCityHum.setTextColor(0xFF000000); + holder.binding.tvCityWind.setTextColor(0xFF000000); + holder.binding.imgIcon.setImageResource(AppConstant.iconListBlack.get(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getMain())); + } + + + }else if (viewHolder instanceof cityWeatherForecastViewHolder){ + ModelCurrentWeater model=data.get(position); + final cityWeatherForecastViewHolder holder = (cityWeatherForecastViewHolder) viewHolder; + holder.binding.tvDay.setText(Utils.getDayOfWeek(model.getDt())); + holder.binding.tvDate.setText(Utils.getDateFormat(model.getDt())); + holder.binding.tvCityTemp.setText(String.valueOf(Math.round(model.getMain().getTemp()))); + holder.binding.tvCityStatus.setText(model.getWeather().get(0).getMain()); + holder.binding.imgIcon.setImageResource(AppConstant.iconList.get(model.getWeather().get(0).getMain())); + holder.binding.cardview.setCardBackgroundColor(AppConstant.colorList.get(model.getWeather().get(0).getMain())); + + if ("Snow".equalsIgnoreCase(model.getWeather().get(0).getMain()) || "Fog".equalsIgnoreCase(model.getWeather().get(0).getMain())){ + holder.binding.tvDay.setTextColor(0xFF000000); + holder.binding.tvDate.setTextColor(0xFF000000); + holder.binding.tvCityTemp.setTextColor(0xFF000000); + holder.binding.tvCityStatus.setTextColor(0xFF000000); + holder.binding.imgIcon.setImageResource(AppConstant.iconListBlack.get(model.getWeather().get(0).getMain())); + } + int hours= Utils.getHourOfDay(model.getDt()); + if (hours>19 || hours<6){ + holder.binding.cardview.setCardBackgroundColor(0xFF1A2636); + holder.binding.imgIcon.setImageResource(AppConstant.iconListMight.get(model.getWeather().get(0).getMain())); + } + + } + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + public ViewHolder(View v) { + super(v); + } + } + + /** + * + */ + public class cityWeatherForecastViewHolder extends ViewHolder{ + private AdapterForecastBinding binding; + + public cityWeatherForecastViewHolder(@NonNull AdapterForecastBinding itemView) { + super(itemView.getRoot()); + binding=itemView; + } + } + + public class cityWeatherDetailViewHolder extends ViewHolder{ + private AdapterDetailBinding binding; + + public cityWeatherDetailViewHolder(@NonNull AdapterDetailBinding itemView) { + super(itemView.getRoot()); + binding=itemView; + } + } + + public void addData(ModelCurrentWeater modelCurrentWeater,int position){ + this.data.add(position,modelCurrentWeater); + notifyDataSetChanged(); + } + + public void addData(List data){ + this.data.addAll(data); + notifyDataSetChanged(); + } + +} diff --git a/app/src/main/java/com/midnight/weatherforecast/views/adapters/AdapterMain.java b/app/src/main/java/com/midnight/weatherforecast/views/adapters/AdapterMain.java new file mode 100644 index 00000000..354efac2 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/views/adapters/AdapterMain.java @@ -0,0 +1,80 @@ +package com.midnight.weatherforecast.views.adapters; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.RecyclerView; +import com.midnight.weatherforecast.R; +import com.midnight.weatherforecast.controller.ControllerData; +import com.midnight.weatherforecast.databinding.AdapterMainBinding; +import com.midnight.weatherforecast.interfaces.INFListViewClick; +import com.midnight.weatherforecast.utils.AppConstant; +import com.midnight.weatherforecast.utils.Utils; + +public class AdapterMain extends RecyclerView.Adapter{ + private Context context; + private INFListViewClick delegate; + public AdapterMain(Context context,INFListViewClick delegate) { + this.context = context; + this.delegate=delegate; + } + + + @NonNull + @Override + public cityWeatherViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new cityWeatherViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.adapter_main, parent, false)); + } + + + @Override + public int getItemCount() { + return ControllerData.Companion.getInstances().getCitiesWeater().size(); + } + + + @Override + public void onBindViewHolder(@NonNull cityWeatherViewHolder holder, int position) { + holder.binding.tvCityName.setText(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getName()); + holder.binding.tvCityTemp.setText(String.valueOf(Math.round(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getMain().getTemp()))); + holder.binding.tvCityStatus.setText(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getMain()); + holder.binding.tvCityStatusDesc.setText(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getDescription()); + holder.binding.tvCityHum.setText(String.format(context.getString(R.string.humidity),String.valueOf(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getMain().getHumidity()))); + holder.binding.tvCityWind.setText(String.format(context.getString(R.string.wind),String.valueOf(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWind().getSpeed()))); + holder.binding.imgIcon.setImageResource(AppConstant.iconList.get(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getMain())); + holder.binding.cardview.setCardBackgroundColor(AppConstant.colorList.get(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getMain())); + + if ("Snow".equalsIgnoreCase(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getMain()) || "Fog".equalsIgnoreCase(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getMain())){ + holder.binding.tvCityName.setTextColor(0xFF000000); + holder.binding.tvCityTemp.setTextColor(0xFF000000); + holder.binding.tvCityStatus.setTextColor(0xFF000000); + holder.binding.tvCityStatusDesc.setTextColor(0xFF000000); + holder.binding.tvCityHum.setTextColor(0xFF000000); + holder.binding.tvCityWind.setTextColor(0xFF000000); + holder.binding.imgIcon.setImageResource(AppConstant.iconListBlack.get(ControllerData.Companion.getInstances().getCitiesWeater().get(position).getWeather().get(0).getMain())); + } + + holder.binding.cardview.setOnClickListener(v -> delegate.onClickListenner(position)); + + holder.binding.cardview.setOnLongClickListener(v -> { + delegate.onLongClick(position); + return true; + }); + } + + /** + * + */ + public class cityWeatherViewHolder extends RecyclerView.ViewHolder{ + private AdapterMainBinding binding; + + public cityWeatherViewHolder(@NonNull AdapterMainBinding itemView) { + super(itemView.getRoot()); + binding=itemView; + } + } + +} diff --git a/app/src/main/java/com/midnight/weatherforecast/views/adapters/AdapterSearch.java b/app/src/main/java/com/midnight/weatherforecast/views/adapters/AdapterSearch.java new file mode 100644 index 00000000..a83a6818 --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/views/adapters/AdapterSearch.java @@ -0,0 +1,84 @@ +package com.midnight.weatherforecast.views.adapters; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.midnight.weatherforecast.R; +import com.midnight.weatherforecast.controller.ControllerData; +import com.midnight.weatherforecast.databinding.AdapterSearchBinding; +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater; +import com.midnight.weatherforecast.utils.AppConstant; +import com.midnight.weatherforecast.utils.Utils; + +public class AdapterSearch extends RecyclerView.Adapter{ + private Context context; + private ModelCurrentWeater model; + + public AdapterSearch(Context context) { + this.context = context; + } + + public AdapterSearch(Context context, ModelCurrentWeater model) { + this.context = context; + this.model = model; + } + + @NonNull + @Override + public cityWeatherViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new cityWeatherViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.adapter_search, parent, false)); + } + + + @Override + public int getItemCount() { + if (model==null) + return 0; + else + return 1; + } + + + @Override + public void onBindViewHolder(@NonNull cityWeatherViewHolder holder, int position) { + holder.binding.tvCityName.setText(model.getName()); + holder.binding.tvCityTemp.setText(String.valueOf(Math.round(model.getMain().getTemp()))); + holder.binding.tvCityStatus.setText(model.getWeather().get(0).getMain()); + holder.binding.imgIcon.setImageResource(AppConstant.iconList.get(model.getWeather().get(0).getMain())); + holder.binding.cardview.setCardBackgroundColor(AppConstant.colorList.get(model.getWeather().get(0).getMain())); + + if ("Snow".equalsIgnoreCase(model.getWeather().get(0).getMain()) || "Fog".equalsIgnoreCase(model.getWeather().get(0).getMain())){ + holder.binding.tvCityName.setTextColor(0xFF000000); + holder.binding.tvCityTemp.setTextColor(0xFF000000); + holder.binding.tvCityStatus.setTextColor(0xFF000000); + holder.binding.imgIcon.setImageResource(AppConstant.iconListBlack.get(model.getWeather().get(0).getMain())); + } + + } + + /** + * + */ + public class cityWeatherViewHolder extends RecyclerView.ViewHolder{ + private AdapterSearchBinding binding; + + public cityWeatherViewHolder(@NonNull AdapterSearchBinding itemView) { + super(itemView.getRoot()); + binding=itemView; + } + } + + public void update(ModelCurrentWeater mode){ + this.model=mode; + notifyDataSetChanged(); + } + + public ModelCurrentWeater getModel() { + return model; + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/views/fragments/FragmentDetail.java b/app/src/main/java/com/midnight/weatherforecast/views/fragments/FragmentDetail.java new file mode 100644 index 00000000..f5a9612e --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/views/fragments/FragmentDetail.java @@ -0,0 +1,106 @@ +package com.midnight.weatherforecast.views.fragments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.midnight.weatherforecast.R; +import com.midnight.weatherforecast.controller.ControllerAPI; +import com.midnight.weatherforecast.controller.ControllerData; +import com.midnight.weatherforecast.databinding.FragmentDetailBinding; +import com.midnight.weatherforecast.interfaces.INFForecast; +import com.midnight.weatherforecast.interfaces.INFListViewClick; +import com.midnight.weatherforecast.models.modelsParam.ModelParamCurrentWeatherByName; +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater; +import com.midnight.weatherforecast.models.modelsResponse.ModelForecast; +import com.midnight.weatherforecast.utils.Utils; +import com.midnight.weatherforecast.views.adapters.AdapterForecast; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class FragmentDetail extends Fragment implements INFListViewClick { + public static String TAG=FragmentDetail.class.getName(); + private FragmentDetailBinding binding; + private AdapterForecast adapter; + public static FragmentDetail getInstance(Bundle bundle){ + FragmentDetail model=new FragmentDetail(); + model.setArguments(bundle); + return model; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initView(); + initClick(); + initData(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = DataBindingUtil.inflate(inflater, R.layout.fragment_detail, container, false); + return binding.getRoot(); + } + + private void initView(){ + + } + + private void initClick(){ + + } + + private void initData() { + Bundle bundle = this.getArguments(); + if (bundle != null) { + binding.pbLoading.setVisibility(View.VISIBLE); + ModelCurrentWeater modelCurrentWeater= (ModelCurrentWeater) bundle.getSerializable("current"); + adapter=new AdapterForecast(getContext(),this); + binding.rclMain.setLayoutManager(new LinearLayoutManager(getContext())); + binding.rclMain.setAdapter(adapter); + adapter.addData(modelCurrentWeater,0); + ControllerData.Companion.getInstances().fetchForecast(new ModelParamCurrentWeatherByName(modelCurrentWeater.getName(), ControllerAPI.Companion.getInstance().getAPI_KEY()), new INFForecast() { + @Override + public void onSuccess(@NotNull ModelForecast model) { + if (getView()!=null) + Utils.runOnUiThread(() -> { + binding.pbLoading.setVisibility(View.GONE); + adapter.addData(model.getList()); + }); + } + + @Override + public void onFail() { + if (getView()!=null) + Utils.runOnUiThread(() -> { + binding.pbLoading.setVisibility(View.GONE); + }); + + } + }); + } + + } + + @Override + public void onClickListenner(int position) { + + } + + @Override + public void onLongClick(int position) { + + } +} + diff --git a/app/src/main/java/com/midnight/weatherforecast/views/fragments/FragmentMain.java b/app/src/main/java/com/midnight/weatherforecast/views/fragments/FragmentMain.java new file mode 100644 index 00000000..11d4f3ed --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/views/fragments/FragmentMain.java @@ -0,0 +1,172 @@ +package com.midnight.weatherforecast.views.fragments; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.location.Location; +import android.location.LocationListener; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.midnight.weatherforecast.R; +import com.midnight.weatherforecast.controller.ControllerAPI; +import com.midnight.weatherforecast.controller.ControllerData; +import com.midnight.weatherforecast.controller.ControllerLocation; +import com.midnight.weatherforecast.controller.ControllerPermission; +import com.midnight.weatherforecast.databinding.FragmentMainBinding; +import com.midnight.weatherforecast.interfaces.INFListViewClick; +import com.midnight.weatherforecast.interfaces.INFLoadData; +import com.midnight.weatherforecast.models.modelsParam.ModelParamCurrentWeatherByGeo; +import com.midnight.weatherforecast.utils.Utils; +import com.midnight.weatherforecast.views.MainActivity; +import com.midnight.weatherforecast.views.adapters.AdapterMain; + +public class FragmentMain extends Fragment implements LocationListener, INFListViewClick { + public static String TAG=FragmentMain.class.getName(); + private FragmentMainBinding binding; + private AdapterMain adapterMain; + private ControllerLocation locationUtils; + public static FragmentMain getInstance(){ + return new FragmentMain(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initView(); + initClick(); + getPermission(); + initData(); + } + + private void initData(){ + handlerLoading(true); + ControllerData.Companion.getInstances().loadData(new INFLoadData() { + @Override + public void onSuccess() { + if (getView()!=null) + Utils.runOnUiThread(() -> { + handlerLoading(false); + adapterMain.notifyDataSetChanged(); + }); + } + + @Override + public void onFail() { + + } + }); + } + + /** + * + */ + private void initView(){ + adapterMain=new AdapterMain(getContext(),this); + binding.rclMain.setLayoutManager(new LinearLayoutManager(getContext())); + binding.rclMain.setAdapter(adapterMain); + } + + private void initClick(){ + binding.fabAdd.setOnClickListener(v -> ((MainActivity)getActivity()).openAddCityFragment()); + } + + + + /** + * + */ + private void getPermission(){ + if (ControllerPermission.Companion.getInstance().getLocationPermission(getContext(),getActivity())){ + getLocation(); + } + } + + /** + * + */ + public void getLocation(){ + locationUtils = new ControllerLocation(getActivity(),this); + locationUtils.requestUpdates(); + } + + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false); + return binding.getRoot(); + } + + @Override + public void onLocationChanged(Location location) { + ControllerData.Companion.getInstances().fetchCity(new ModelParamCurrentWeatherByGeo(String.valueOf(location.getLatitude()), String.valueOf(location.getLongitude()), ControllerAPI.Companion.getInstance().getAPI_KEY()), new INFLoadData() { + @Override + public void onSuccess() { + if (getView()!=null) + Utils.runOnUiThread(() -> adapterMain.notifyDataSetChanged()); + } + + @Override + public void onFail() { + + } + },true,true,true); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + + } + + @Override + public void onProviderEnabled(String provider) { + + } + + @Override + public void onProviderDisabled(String provider) { + + } + + @Override + public void onClickListenner(int position) { + Bundle bnd=new Bundle(); + bnd.putSerializable("current",ControllerData.Companion.getInstances().getCitiesWeater().get(position)); + ((MainActivity)getActivity()).openDetailFragment(bnd); + } + + @Override + public void onLongClick(int position) { + if (position==0) + return; + new AlertDialog.Builder(getContext()) + .setTitle(getString(R.string.dialog_delete_title)) + .setMessage(getString(R.string.dialog_delete_message)) + .setPositiveButton(android.R.string.yes, (dialog, which) -> { + ControllerData.Companion.getInstances().removeACity(ControllerData.Companion.getInstances().getCitiesWeater().get(position)); + adapterMain.notifyDataSetChanged(); + }) + .setNegativeButton(android.R.string.no, (dialog, which) -> { + + }) + .setNegativeButton(android.R.string.no, null) + .show(); + + } + + public void handlerLoading(boolean isShow){ + if (isShow){ + binding.pbLoading.setVisibility(View.VISIBLE); + }else { + binding.pbLoading.setVisibility(View.GONE); + } + } +} diff --git a/app/src/main/java/com/midnight/weatherforecast/views/fragments/FragmentSearch.java b/app/src/main/java/com/midnight/weatherforecast/views/fragments/FragmentSearch.java new file mode 100644 index 00000000..ccdfe16f --- /dev/null +++ b/app/src/main/java/com/midnight/weatherforecast/views/fragments/FragmentSearch.java @@ -0,0 +1,125 @@ +package com.midnight.weatherforecast.views.fragments; + +import android.os.Bundle; +import android.os.Handler; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.midnight.weatherforecast.R; +import com.midnight.weatherforecast.controller.ControllerAPI; +import com.midnight.weatherforecast.controller.ControllerData; +import com.midnight.weatherforecast.databinding.FragmentAddCityBinding; +import com.midnight.weatherforecast.interfaces.INFSearchCity; +import com.midnight.weatherforecast.models.modelsParam.ModelParamCurrentWeatherByName; +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater; +import com.midnight.weatherforecast.utils.Utils; +import com.midnight.weatherforecast.views.MainActivity; +import com.midnight.weatherforecast.views.adapters.AdapterSearch; + +import org.jetbrains.annotations.NotNull; + +import okhttp3.internal.Util; + +public class FragmentSearch extends Fragment { + public static String TAG= FragmentSearch.class.getName(); + private FragmentAddCityBinding binding; + private AdapterSearch adapterSearch; + private Handler typeingHandler; + private TextWatcher txtWatcher; + private String cityName=""; + private Runnable searchRunnable=new Runnable() { + @Override + public void run() { + binding.pbLoading.setVisibility(View.VISIBLE); + ControllerData.Companion.getInstances().searchCity(new ModelParamCurrentWeatherByName(cityName.toString(), ControllerAPI.Companion.getInstance().getAPI_KEY()), new INFSearchCity() { + @Override + public void onSuccess(@NotNull ModelCurrentWeater model) { + Log.d("Search","done : "+model.getName()); + if (getView()!=null && model!=null) + Utils.runOnUiThread(() -> { + binding.pbLoading.setVisibility(View.GONE); + adapterSearch.update(model); + }); + } + + @Override + public void onFail() { + Log.d("Search"," Fail "); + } + }); + } + }; + public static FragmentSearch getInstance(){ + return new FragmentSearch(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initView(); + initValue(); + } + + + /** + * + */ + private void initView(){ + adapterSearch=new AdapterSearch(getContext()); + binding.rclMain.setLayoutManager(new LinearLayoutManager(getContext())); + binding.rclMain.setAdapter(adapterSearch); + } + + private void initValue(){ + typeingHandler=new Handler(); + txtWatcher=new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + Log.d("Search","Typing "); + typeingHandler.removeCallbacks(searchRunnable); + cityName=s.toString(); + if (!cityName.isEmpty()) + typeingHandler.postDelayed(searchRunnable,1500); + } + + @Override + public void afterTextChanged(Editable s) { + + } + }; + binding.edtSearch.addTextChangedListener(txtWatcher); + + binding.fabAdd.setOnClickListener(v -> { + if (adapterSearch.getModel()!=null){ + ControllerData.Companion.getInstances().saveCity(adapterSearch.getModel(),false); + ControllerData.Companion.getInstances().addToCitiesList(adapterSearch.getModel()); + ((MainActivity)getActivity()).onBackPressed(); + } + }); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = DataBindingUtil.inflate(inflater, R.layout.fragment_add_city, container, false); + return binding.getRoot(); + } + + + +} diff --git a/app/src/main/res/drawable-hdpi/ic_action_add.png b/app/src/main/res/drawable-hdpi/ic_action_add.png new file mode 100644 index 00000000..565f2f6a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_add.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_name.png b/app/src/main/res/drawable-hdpi/ic_action_name.png new file mode 100644 index 00000000..f3b2d85b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_name.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_add.png b/app/src/main/res/drawable-mdpi/ic_action_add.png new file mode 100644 index 00000000..f4c62e5b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_add.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_name.png b/app/src/main/res/drawable-mdpi/ic_action_name.png new file mode 100644 index 00000000..39dcd42b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_name.png differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..1f6bb290 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/ic_action_add.png b/app/src/main/res/drawable-xhdpi/ic_action_add.png new file mode 100644 index 00000000..06323824 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_add.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_name.png b/app/src/main/res/drawable-xhdpi/ic_action_name.png new file mode 100644 index 00000000..0ebf9ba6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_name.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_add.png b/app/src/main/res/drawable-xxhdpi/ic_action_add.png new file mode 100644 index 00000000..bd6269d1 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_add.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_name.png b/app/src/main/res/drawable-xxhdpi/ic_action_name.png new file mode 100644 index 00000000..59d128d4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_name.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_add.png b/app/src/main/res/drawable-xxxhdpi/ic_action_add.png new file mode 100644 index 00000000..5dcf5188 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_add.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_name.png b/app/src/main/res/drawable-xxxhdpi/ic_action_name.png new file mode 100644 index 00000000..6b7593ad Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_name.png differ diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 00000000..16b6ed2d --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..0d025f9b --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_wi_celsius.xml b/app/src/main/res/drawable/ic_wi_celsius.xml new file mode 100644 index 00000000..fcc7b0ce --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_celsius.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_celsius_black.xml b/app/src/main/res/drawable/ic_wi_celsius_black.xml new file mode 100644 index 00000000..5d05632d --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_celsius_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_cloud.xml b/app/src/main/res/drawable/ic_wi_cloud.xml new file mode 100644 index 00000000..a9429a3d --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_cloud.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_cloud_black.xml b/app/src/main/res/drawable/ic_wi_cloud_black.xml new file mode 100644 index 00000000..f9e527cc --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_cloud_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_cloudy_gusts.xml b/app/src/main/res/drawable/ic_wi_cloudy_gusts.xml new file mode 100644 index 00000000..db629df7 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_cloudy_gusts.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_cloudy_gusts_black.xml b/app/src/main/res/drawable/ic_wi_cloudy_gusts_black.xml new file mode 100644 index 00000000..624ae9a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_cloudy_gusts_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_cloudy_windy.xml b/app/src/main/res/drawable/ic_wi_cloudy_windy.xml new file mode 100644 index 00000000..1569f43c --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_cloudy_windy.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_cloudy_windy_black.xml b/app/src/main/res/drawable/ic_wi_cloudy_windy_black.xml new file mode 100644 index 00000000..bcbb11c8 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_cloudy_windy_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_day_light_wind.xml b/app/src/main/res/drawable/ic_wi_day_light_wind.xml new file mode 100644 index 00000000..11c949a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_day_light_wind.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_day_light_wind_black.xml b/app/src/main/res/drawable/ic_wi_day_light_wind_black.xml new file mode 100644 index 00000000..a51cadd8 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_day_light_wind_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_day_rain.xml b/app/src/main/res/drawable/ic_wi_day_rain.xml new file mode 100644 index 00000000..90a6019d --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_day_rain.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_day_rain_black.xml b/app/src/main/res/drawable/ic_wi_day_rain_black.xml new file mode 100644 index 00000000..3c43e14a --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_day_rain_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_day_snow_wind.xml b/app/src/main/res/drawable/ic_wi_day_snow_wind.xml new file mode 100644 index 00000000..9d61385c --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_day_snow_wind.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_day_snow_wind_black.xml b/app/src/main/res/drawable/ic_wi_day_snow_wind_black.xml new file mode 100644 index 00000000..6c8dcb50 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_day_snow_wind_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_day_sunny.xml b/app/src/main/res/drawable/ic_wi_day_sunny.xml new file mode 100644 index 00000000..c8bee6d9 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_day_sunny.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_day_sunny_black.xml b/app/src/main/res/drawable/ic_wi_day_sunny_black.xml new file mode 100644 index 00000000..838787bf --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_day_sunny_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_lightning.xml b/app/src/main/res/drawable/ic_wi_lightning.xml new file mode 100644 index 00000000..564483e0 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_lightning.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_lightning_black.xml b/app/src/main/res/drawable/ic_wi_lightning_black.xml new file mode 100644 index 00000000..ddfac178 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_lightning_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_night_alt_cloudy_windy.xml b/app/src/main/res/drawable/ic_wi_night_alt_cloudy_windy.xml new file mode 100644 index 00000000..22abcbad --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_night_alt_cloudy_windy.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_night_alt_rain.xml b/app/src/main/res/drawable/ic_wi_night_alt_rain.xml new file mode 100644 index 00000000..d2787095 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_night_alt_rain.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_night_alt_snow.xml b/app/src/main/res/drawable/ic_wi_night_alt_snow.xml new file mode 100644 index 00000000..34fb55bf --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_night_alt_snow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_night_clear.xml b/app/src/main/res/drawable/ic_wi_night_clear.xml new file mode 100644 index 00000000..d72f637e --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_night_clear.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wi_night_cloudy.xml b/app/src/main/res/drawable/ic_wi_night_cloudy.xml new file mode 100644 index 00000000..69641e46 --- /dev/null +++ b/app/src/main/res/drawable/ic_wi_night_cloudy.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..e03e8bb2 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_detail.xml b/app/src/main/res/layout/adapter_detail.xml new file mode 100644 index 00000000..0b13e145 --- /dev/null +++ b/app/src/main/res/layout/adapter_detail.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_forecast.xml b/app/src/main/res/layout/adapter_forecast.xml new file mode 100644 index 00000000..26c3e826 --- /dev/null +++ b/app/src/main/res/layout/adapter_forecast.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_main.xml b/app/src/main/res/layout/adapter_main.xml new file mode 100644 index 00000000..cfad9dae --- /dev/null +++ b/app/src/main/res/layout/adapter_main.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_search.xml b/app/src/main/res/layout/adapter_search.xml new file mode 100644 index 00000000..4ebe6e4f --- /dev/null +++ b/app/src/main/res/layout/adapter_search.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_add_city.xml b/app/src/main/res/layout/fragment_add_city.xml new file mode 100644 index 00000000..7297cafc --- /dev/null +++ b/app/src/main/res/layout/fragment_add_city.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml new file mode 100644 index 00000000..952c3e36 --- /dev/null +++ b/app/src/main/res/layout/fragment_detail.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml new file mode 100644 index 00000000..497cc24f --- /dev/null +++ b/app/src/main/res/layout/fragment_main.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..898f3ed5 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..dffca360 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..64ba76f7 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..dae5e082 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..e5ed4659 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..14ed0af3 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..b0907cac Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..d8ae0315 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..2c18de9e Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..beed3cdd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..206c0614 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,30 @@ + + + #673AB7 + #512DA8 + #D1C4E9 + #FFC107 + #212121 + #757575 + #FFFFFF + #BDBDBD + + #FFFFFF + #000000 + + #6D5DF1 + #FFC107 + #757575 + #757575 + #9E9E9E + #795548 + #BDBDBD + #455A64 + #BDBDBD + #BDBDBD + #BDBDBD + #BDBDBD + #0097A7 + #00BCD4 + #303F9F + diff --git a/app/src/main/res/values/dimension.xml b/app/src/main/res/values/dimension.xml new file mode 100644 index 00000000..fb72bf74 --- /dev/null +++ b/app/src/main/res/values/dimension.xml @@ -0,0 +1,8 @@ + + + 16sp + 16sp + 14sp + 34sp + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..86d3d536 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,17 @@ + + weatherforecast + We need your location in order to show weather! + Humidity : %1$s + Wind : %1$s km/h + Pressure : %1$s + MinTemp : %1$s c + MaxTemp : %1$s c + + City + + Delete City + Are you sure you want to delete this city? + + Yes + No + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..15fa6861 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/test/java/com/midnight/weatherforecast/ConditionCheck.java b/app/src/test/java/com/midnight/weatherforecast/ConditionCheck.java new file mode 100644 index 00000000..68192c00 --- /dev/null +++ b/app/src/test/java/com/midnight/weatherforecast/ConditionCheck.java @@ -0,0 +1,11 @@ +package com.midnight.weatherforecast; + +/** + * Created by midnight on 2/13/18. + */ + +public interface ConditionCheck { + public boolean condition(); + + public void finalyAssert(); +} diff --git a/app/src/test/java/com/midnight/weatherforecast/Halt.java b/app/src/test/java/com/midnight/weatherforecast/Halt.java new file mode 100644 index 00000000..f762370c --- /dev/null +++ b/app/src/test/java/com/midnight/weatherforecast/Halt.java @@ -0,0 +1,61 @@ +package com.midnight.weatherforecast; + +/** + * Created by midnight on 2/13/18. + */ + +public class Halt { + long timeout, interval; + final long TIME_OUT, INTERVAL; + private ConditionCheck conditionCheck; + public Halt(long timeout, long interval) { + this.timeout = timeout; + this.interval = interval; + TIME_OUT = timeout; + INTERVAL = interval; + } + + + public void execHalt(ConditionCheck condition_Check) { + try { + conditionCheck=condition_Check; + while (timeout > 0 && conditionCheck.condition()) { + Thread.sleep(interval); + timeout = timeout - interval; + } + } catch (InterruptedException e) { + + } finally { + conditionCheck.finalyAssert(); + } + + } + + public void reset() { + this.timeout = TIME_OUT; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public long getInterval() { + return interval; + } + + public void setInterval(long interval) { + this.interval = interval; + } + + public ConditionCheck getConditionCheck() { + return conditionCheck; + } + + public void setConditionCheck(ConditionCheck conditionCheck) { + this.conditionCheck = conditionCheck; + } +} diff --git a/app/src/test/java/com/midnight/weatherforecast/MyNetworkSecurityPolicy.java b/app/src/test/java/com/midnight/weatherforecast/MyNetworkSecurityPolicy.java new file mode 100644 index 00000000..73078cd9 --- /dev/null +++ b/app/src/test/java/com/midnight/weatherforecast/MyNetworkSecurityPolicy.java @@ -0,0 +1,28 @@ +package com.midnight.weatherforecast; + +import android.security.NetworkSecurityPolicy; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(NetworkSecurityPolicy.class) +public class MyNetworkSecurityPolicy { + + public MyNetworkSecurityPolicy() { + } + + @Implementation + public static NetworkSecurityPolicy getInstance() { + try { + Class shadow = MyNetworkSecurityPolicy.class.forName("android.security.NetworkSecurityPolicy"); + return (NetworkSecurityPolicy) shadow.newInstance(); + } catch (Exception e) { + throw new AssertionError(); + } + } + + @Implementation + public boolean isCleartextTrafficPermitted() { + return true; + } +} diff --git a/app/src/test/java/com/midnight/weatherforecast/TestGetCurrentCityWeather.java b/app/src/test/java/com/midnight/weatherforecast/TestGetCurrentCityWeather.java new file mode 100644 index 00000000..eb1d2c1e --- /dev/null +++ b/app/src/test/java/com/midnight/weatherforecast/TestGetCurrentCityWeather.java @@ -0,0 +1,137 @@ +package com.midnight.weatherforecast; + +import android.os.Build; +import android.os.Looper; + +import com.midnight.weatherforecast.controller.ControllerAPI; +import com.midnight.weatherforecast.core.Loader; +import com.midnight.weatherforecast.interfaces.INFApi; +import com.midnight.weatherforecast.models.modelsParam.ModelParamCurrentWeatherByGeo; +import com.midnight.weatherforecast.models.modelsParam.ModelParamCurrentWeatherByName; +import com.midnight.weatherforecast.models.modelsResponse.ModelCurrentWeater; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.io.IOException; + +import okhttp3.mockwebserver.MockWebServer; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +import static org.robolectric.Shadows.shadowOf; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = Build.VERSION_CODES.M,application = Loader.class,shadows = MyNetworkSecurityPolicy.class) +public class TestGetCurrentCityWeather { + private String finalResult=""; + private MockWebServer mockWebServer=new MockWebServer(); + private INFApi apiService; + @Before + public void startTest(){ + try { + mockWebServer.start(); + apiService = new Retrofit.Builder() + .baseUrl(mockWebServer.url("/")) + .build() + .create(INFApi.class); + } catch (IOException e) { + e.printStackTrace(); + } + + } + @Test + public void getCurrentByName() { + Halt checker=new Halt(10000,1000); + shadowOf(Looper.getMainLooper()).idle(); + + ControllerAPI.Companion.getInstance().getCurrentWeatherByName(new ModelParamCurrentWeatherByName("Stockholm", "62fc4256-8f8c-11e5-8994-feff819cdc9f"), new Callback() { + @Override + public void onResponse(Call call, Response response) { + finalResult=response.body().getName(); + } + + @Override + public void onFailure(Call call, Throwable t) { + finalResult="-1"; + } + }); + + checker.execHalt(new ConditionCheck() { + @Override + public boolean condition() { + if (finalResult.isEmpty()){ + shadowOf(Looper.getMainLooper()).idle(); + return true; + } + shadowOf(Looper.getMainLooper()).idle(); + return false; + } + + @Override + public void finalyAssert() { + Assert.assertEquals("Stockholm",finalResult); + } + }); + + Robolectric.flushForegroundThreadScheduler(); + Robolectric.flushBackgroundThreadScheduler(); + } + + @Test + public void getCurrentByGeo() { + Halt checker=new Halt(10000,1000); + shadowOf(Looper.getMainLooper()).idle(); + + ControllerAPI.Companion.getInstance().getCurrentWeatherByGPS(new ModelParamCurrentWeatherByGeo("59.33","18.07", "62fc4256-8f8c-11e5-8994-feff819cdc9f"), new Callback() { + @Override + public void onResponse(Call call, Response response) { + finalResult=response.body().getName(); + } + + @Override + public void onFailure(Call call, Throwable t) { + finalResult="-1"; + } + }); + + checker.execHalt(new ConditionCheck() { + @Override + public boolean condition() { + if (finalResult.isEmpty()){ + shadowOf(Looper.getMainLooper()).idle(); + return true; + } + shadowOf(Looper.getMainLooper()).idle(); + return false; + } + + @Override + public void finalyAssert() { + Assert.assertEquals("Stockholm",finalResult); + } + }); + + Robolectric.flushForegroundThreadScheduler(); + Robolectric.flushBackgroundThreadScheduler(); + } + + @After + public void endTest(){ + try { + mockWebServer.shutdown(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..4567937a --- /dev/null +++ b/build.gradle @@ -0,0 +1,32 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext { + kotlin_version = '1.3.50' + } + repositories { + google() + jcenter() + + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.1' + classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..199d16ed --- /dev/null +++ b/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..f6b961fd Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..add10aa9 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Oct 14 12:13:30 CEST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..e95643d6 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..03890672 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name='weatherforecast'