diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..fd3ba31
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index bfb934f..2619b83 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -42,7 +42,9 @@ dependencies {
def lifecycle_version = "2.5.0-alpha03"
def fragment_version = '1.4.1'
def nav_version = '2.4.1'
+ def core_version = '1.6.0'
+ implementation "androidx.core:core:$core_version"
implementation "com.google.android.gms:play-services-location:19.0.1"
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 674e353..f0e6284 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,9 @@
+
+
+
-
+
diff --git a/app/src/main/java/com/example/weatherapp/ApplicationModule.kt b/app/src/main/java/com/example/weatherapp/ApplicationModule.kt
index 354171e..60d9db6 100644
--- a/app/src/main/java/com/example/weatherapp/ApplicationModule.kt
+++ b/app/src/main/java/com/example/weatherapp/ApplicationModule.kt
@@ -5,11 +5,12 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
+import dagger.hilt.android.components.ServiceComponent
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
@Module
-@InstallIn(ActivityComponent::class)
+@InstallIn(ActivityComponent::class, ServiceComponent::class)
object ApplicationModule {
@Provides
fun provideApi(): Api {
diff --git a/app/src/main/java/com/example/weatherapp/MainActivity.kt b/app/src/main/java/com/example/weatherapp/MainActivity.kt
index 6c2dc26..c8621c2 100644
--- a/app/src/main/java/com/example/weatherapp/MainActivity.kt
+++ b/app/src/main/java/com/example/weatherapp/MainActivity.kt
@@ -17,7 +17,4 @@ class MainActivity : AppCompatActivity() {
super.onResume()
}
-
-
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/weatherapp/NotificationService.kt b/app/src/main/java/com/example/weatherapp/NotificationService.kt
new file mode 100644
index 0000000..3d7b6fb
--- /dev/null
+++ b/app/src/main/java/com/example/weatherapp/NotificationService.kt
@@ -0,0 +1,171 @@
+package com.example.weatherapp
+
+import android.Manifest
+import android.app.AlertDialog
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.os.IBinder
+import android.os.Looper
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.graphics.drawable.toBitmap
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.transition.Transition
+import com.google.android.gms.location.*
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class NotificationService: Service() {
+ @Inject lateinit var viewModel: NotificationServiceViewModel
+ lateinit var locationRequest: LocationRequest
+ private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
+ private lateinit var locationCallback: LocationCallback
+ private val CHANNEL_ID = "channel1"
+ private val notificationId = 1
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int{
+ super.onStartCommand(intent, flags, startId)
+ Log.d("Service", "service Started")
+ val notificationId = 1
+ val name = "notification Channel"
+ val importance = NotificationManager.IMPORTANCE_DEFAULT
+ val channel = NotificationChannel(CHANNEL_ID, name, importance)
+ val notificationManager: NotificationManager =
+ getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.createNotificationChannel(channel)
+
+ fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
+
+ getLocation()
+
+ locationRequest = LocationRequest.create().apply {
+ setInterval(60000)
+ setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
+ }
+
+ locationCallback = object : LocationCallback(){
+ override fun onLocationResult(locationResults: LocationResult) {
+ super.onLocationResult(locationResults)
+ if(locationResults.lastLocation != null){
+ val coordinate = Coordinate(locationResults.lastLocation.latitude,
+ locationResults.lastLocation.longitude)
+ viewModel.loadData(coordinate)
+ sendNotification()
+ Log.d("Location Service", "Getting Locations")
+ }
+ }
+ }
+
+ startLocationUpdate()
+
+ return START_REDELIVER_INTENT
+ }
+
+ override fun onBind(p0: Intent?): IBinder? {
+ return null
+ }
+
+ override fun onTaskRemoved(rootIntent: Intent?) {
+ val restartServiceIntent = Intent(applicationContext, NotificationService::class.java )
+ restartServiceIntent.setPackage(packageName)
+ startService(restartServiceIntent)
+ super.onTaskRemoved(rootIntent)
+ }
+
+ private fun getLocation(){
+ if (ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return
+ }
+ fusedLocationProviderClient.lastLocation.addOnSuccessListener {
+ val coordinate = Coordinate(it.latitude, it.longitude)
+ viewModel.loadData(coordinate)
+ sendNotification()
+ Log.d("Location Service", "Got first location")
+ }
+ }
+
+ private fun startLocationUpdate(){
+ if (ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ // TODO: Consider calling
+ // ActivityCompat#requestPermissions
+ // here to request the missing permissions, and then overriding
+ // public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ // int[] grantResults)
+ // to handle the case where the user grants the permission. See the documentation
+ // for ActivityCompat#requestPermissions for more details.
+ return
+ }
+ fusedLocationProviderClient.requestLocationUpdates(
+ locationRequest,
+ locationCallback,
+ Looper.getMainLooper()
+ )
+ }
+
+ private fun sendNotification(){
+ val currentConditions = viewModel.currentConditions.value
+ val locationName = currentConditions!!.name
+ val currentTemp = currentConditions!!.main.temp
+ var icon: Bitmap? = null
+ val iconName = viewModel.currentConditions.value!!.weather.firstOrNull()?.icon
+ val iconURL = "https://openweathermap.org/img/wn/${iconName}@2x.png"
+ Glide.with(this)
+ .load(iconURL)
+ .into( object : CustomTarget(){
+ override fun onResourceReady(
+ resource: Drawable,
+ transition: Transition?
+ ) {
+ icon = resource.toBitmap()
+ }
+
+ override fun onLoadCleared(placeholder: Drawable?) {
+ }
+ })
+
+ var builder = NotificationCompat.Builder(this, CHANNEL_ID)
+ .setSmallIcon(R.drawable.common_google_signin_btn_icon_dark)
+ .setContentTitle("Weather App")
+ .setContentText("${locationName}, ${currentTemp}°")
+ .setLargeIcon(icon)
+
+ startForeground(notificationId, builder.build())
+
+ with(NotificationManagerCompat.from(this)) {
+ notify(notificationId, builder.build())
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ fusedLocationProviderClient.removeLocationUpdates(locationCallback)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/weatherapp/NotificationServiceViewModel.kt b/app/src/main/java/com/example/weatherapp/NotificationServiceViewModel.kt
new file mode 100644
index 0000000..dc85b14
--- /dev/null
+++ b/app/src/main/java/com/example/weatherapp/NotificationServiceViewModel.kt
@@ -0,0 +1,18 @@
+package com.example.weatherapp
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import javax.inject.Inject
+
+class NotificationServiceViewModel @Inject() constructor(private val api: Api) : ViewModel() {
+ private val _currentConditions: MutableLiveData = MutableLiveData()
+ val currentConditions: LiveData
+ get() = _currentConditions
+
+ fun loadData(coordinate: Coordinate) = runBlocking{
+ launch { _currentConditions.value = api.getCurrentConditionsLatLon(coordinate.latitude, coordinate.longitude) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/weatherapp/SearchFragment.kt b/app/src/main/java/com/example/weatherapp/SearchFragment.kt
index fef780b..fae4d13 100644
--- a/app/src/main/java/com/example/weatherapp/SearchFragment.kt
+++ b/app/src/main/java/com/example/weatherapp/SearchFragment.kt
@@ -2,6 +2,7 @@ package com.example.weatherapp
import android.Manifest
import android.app.AlertDialog
+import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
@@ -87,6 +88,29 @@ class SearchFragment :Fragment(R.layout.fragment_search){
}
}
+ binding.notificationButton.setOnClickListener {
+ requestLocation()
+ if (ActivityCompat.checkSelfPermission(
+ context!!,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
+ context!!,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+
+ }else {
+ if (viewModel.getNotificationStatus() == false) {
+ viewModel.setNotificationsToTrue()
+ requireActivity().startService(Intent(context, NotificationService::class.java))
+ } else {
+ viewModel.setNotificationsToFalse()
+ requireActivity().stopService(Intent(context, NotificationService::class.java))
+ }
+ }
+ updateNotificationButton()
+ }
+
locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
){permissions ->
@@ -104,18 +128,18 @@ class SearchFragment :Fragment(R.layout.fragment_search){
private fun requestLocation(){
- if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)){
+ if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)){
AlertDialog.Builder(context)
.setTitle(R.string.location_rationale)
.setNeutralButton("Ok"){_, _->
locationPermissionRequest.launch(
- arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
+ arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
)
}
.show()
}else {
locationPermissionRequest.launch(
- arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
+ arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
)
}
}
@@ -123,6 +147,7 @@ class SearchFragment :Fragment(R.layout.fragment_search){
override fun onResume() {
super.onResume()
requestLocationUpdate()
+ updateNotificationButton()
}
private fun requestLocationUpdate(){
@@ -140,8 +165,17 @@ class SearchFragment :Fragment(R.layout.fragment_search){
val locationProvider = LocationServices.getFusedLocationProviderClient(context!!)
locationProvider.lastLocation.addOnSuccessListener {
- viewModel.updateLatLon(it.latitude, it.longitude)
+ if(it != null) {
+ viewModel.updateLatLon(it.latitude, it.longitude)
+ }
}
}
+ private fun updateNotificationButton(){
+ if (!viewModel.getNotificationStatus()){
+ binding.notificationButton.text = "Turn Notifications On"
+ } else{
+ binding.notificationButton.text = "Turn Notifications Off"
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/weatherapp/SearchFragmentViewModel.kt b/app/src/main/java/com/example/weatherapp/SearchFragmentViewModel.kt
index b48c66f..dc53b6d 100644
--- a/app/src/main/java/com/example/weatherapp/SearchFragmentViewModel.kt
+++ b/app/src/main/java/com/example/weatherapp/SearchFragmentViewModel.kt
@@ -1,5 +1,6 @@
package com.example.weatherapp
+import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@@ -21,6 +22,8 @@ class SearchFragmentViewModel @Inject constructor(private val api: Api) : ViewMo
private val _showErrorDialog = MutableLiveData(false)
+ private var notificaitonStatus = false
+
val currentConditions: LiveData
get() = _currentConditions
@@ -58,5 +61,16 @@ class SearchFragmentViewModel @Inject constructor(private val api: Api) : ViewMo
launch{ _currentConditions.value = api.getCurrentConditionsLatLon(latitude, longitude)}
}
+ fun getNotificationStatus(): Boolean{
+ return notificaitonStatus
+ }
+
+ fun setNotificationsToTrue(){
+ notificaitonStatus = true
+ }
+ fun setNotificationsToFalse(){
+ notificaitonStatus = false
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml
index ee7baf0..721f795 100644
--- a/app/src/main/res/layout/fragment_search.xml
+++ b/app/src/main/res/layout/fragment_search.xml
@@ -19,7 +19,7 @@
+
\ No newline at end of file