Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:name=".WeatherApplication"
android:allowBackup="true"
Expand All @@ -12,9 +15,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.WeatherApp">
<activity
android:name=".ForecastActivity"
android:exported="false" />
<service android:name=".NotificationService"/>
<activity
android:name=".MainActivity"
android:exported="true">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 0 additions & 3 deletions app/src/main/java/com/example/weatherapp/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,4 @@ class MainActivity : AppCompatActivity() {
super.onResume()

}



}
171 changes: 171 additions & 0 deletions app/src/main/java/com/example/weatherapp/NotificationService.kt
Original file line number Diff line number Diff line change
@@ -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<Drawable>(){
override fun onResourceReady(
resource: Drawable,
transition: Transition<in Drawable>?
) {
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)
}

}
Original file line number Diff line number Diff line change
@@ -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<CurrentConditions> = MutableLiveData()
val currentConditions: LiveData<CurrentConditions>
get() = _currentConditions

fun loadData(coordinate: Coordinate) = runBlocking{
launch { _currentConditions.value = api.getCurrentConditionsLatLon(coordinate.latitude, coordinate.longitude) }
}
}
42 changes: 38 additions & 4 deletions app/src/main/java/com/example/weatherapp/SearchFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ->
Expand All @@ -104,25 +128,26 @@ 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)
)
}
}

override fun onResume() {
super.onResume()
requestLocationUpdate()
updateNotificationButton()
}

private fun requestLocationUpdate(){
Expand All @@ -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"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.weatherapp

import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
Expand All @@ -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<CurrentConditions>
get() = _currentConditions

Expand Down Expand Up @@ -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
}


}
Loading