From c2a6331accbf49c7806596f1532cff77a078061b Mon Sep 17 00:00:00 2001 From: AlexanderTorres3 Date: Thu, 28 Apr 2022 21:22:45 -0500 Subject: [PATCH 1/2] assignment 8 --- .idea/deploymentTargetDropDown.xml | 17 ++ app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 7 +- .../example/weatherapp/ApplicationModule.kt | 3 +- .../com/example/weatherapp/MainActivity.kt | 3 - .../example/weatherapp/NotificationService.kt | 171 ++++++++++++++++++ .../NotificationServiceViewModel.kt | 18 ++ .../com/example/weatherapp/SearchFragment.kt | 42 ++++- .../weatherapp/SearchFragmentViewModel.kt | 14 ++ app/src/main/res/layout/fragment_search.xml | 12 +- 10 files changed, 276 insertions(+), 13 deletions(-) create mode 100644 .idea/deploymentTargetDropDown.xml create mode 100644 app/src/main/java/com/example/weatherapp/NotificationService.kt create mode 100644 app/src/main/java/com/example/weatherapp/NotificationServiceViewModel.kt 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 @@