notification on lower level android working

This commit is contained in:
2024-02-18 12:38:38 +00:00
parent 75c7fa364e
commit ba630d1d2c
14 changed files with 193 additions and 32 deletions

View File

@@ -30,7 +30,7 @@
<receiver <receiver
android:name=".service.notification.NotificationReceiver" android:name=".service.notification.NotificationReceiver"
android:exported="true"/> android:exported="false"/>
</application> </application>
</manifest> </manifest>

View File

@@ -0,0 +1,46 @@
package com.appttude.h_mal.atlas_weather.application
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.atlas_weather.data.WeatherSource
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
import com.appttude.h_mal.atlas_weather.service.notification.NotificationService
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
import com.appttude.h_mal.atlas_weather.viewmodel.SettingsViewModel
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
class ApplicationViewModelFactory(
private val application: Application,
private val locationProvider: LocationProvider,
private val source: WeatherSource,
private val settingsRepository: SettingsRepository,
private val notificationService: NotificationService
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
with(modelClass) {
return when {
isAssignableFrom(WorldViewModel::class.java) -> WorldViewModel(
locationProvider,
source
)
isAssignableFrom(MainViewModel::class.java) -> MainViewModel(
locationProvider,
source
)
isAssignableFrom(SettingsViewModel::class.java) -> SettingsViewModel(
application, locationProvider, source, settingsRepository, notificationService
)
else -> throw IllegalArgumentException("Unknown ViewModel class")
} as T
}
}
}

View File

@@ -4,6 +4,7 @@ import com.appttude.h_mal.atlas_weather.service.notification.NotificationHelper
import com.appttude.h_mal.atlas_weather.service.notification.NotificationService import com.appttude.h_mal.atlas_weather.service.notification.NotificationService
import org.kodein.di.generic.bind import org.kodein.di.generic.bind
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
import org.kodein.di.generic.provider
import org.kodein.di.generic.singleton import org.kodein.di.generic.singleton
@@ -20,12 +21,26 @@ open class AtlasApp : AppClass() {
} }
bind() from singleton { bind() from singleton {
NotificationService(this@AtlasApp).let { notificationService = it } NotificationService(this@AtlasApp).apply { notificationService = this }
}
bind() from provider {
ApplicationViewModelFactory(
this@AtlasApp,
instance(),
instance(),
instance(),
instance()
)
} }
} }
override fun onCreate() { // override fun onCreate() {
super.onCreate() // super.onCreate()
notificationService.schedulePushNotifications() // notificationService.schedulePushNotifications()
} // }
fun scheduleNotifications() = notificationService.schedulePushNotifications()
fun unscheduleNotifications() = notificationService.unschedulePushNotifications()
} }

View File

@@ -2,6 +2,7 @@ package com.appttude.h_mal.atlas_weather.service.notification
import android.Manifest import android.Manifest
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.TaskStackBuilder import android.app.TaskStackBuilder
@@ -10,8 +11,11 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Build
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.atlas_weather.utils.displayToast
@@ -30,9 +34,10 @@ import org.kodein.di.generic.instance
* Updated by h_mal on 27/11/2020 * Updated by h_mal on 27/11/2020
*/ */
const val NOTIFICATION_CHANNEL_ID = "my_notification_channel_1" const val NOTIFICATION_CHANNEL_ID = "my_notification_channel_1"
const val NOTIFICATION_ID = 505
class NotificationReceiver : BroadcastReceiver() { class NotificationReceiver : BroadcastReceiver() {
private val kodein = LateInitKodein() private val kodein = LateInitKodein()
private val helper: NotificationHelper by kodein.instance() private val helper: NotificationHelper by kodein.instance()
@@ -64,22 +69,51 @@ class NotificationReceiver : BroadcastReceiver() {
addNextIntent(notificationIntent) addNextIntent(notificationIntent)
} }
val pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) val pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
val bmp: Bitmap = runBlocking { Picasso.get().load(weather.current?.icon).get() } val bmp: Bitmap = runBlocking { Picasso.get().load(weather.current?.icon).get() }
val notification = builder.setContentTitle("Weather App") val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setContentText(weather.current?.main + "°C") .setSmallIcon(R.mipmap.ic_notif)
.setSmallIcon(R.mipmap.ic_notif) //change icon
.setLargeIcon(bmp) .setLargeIcon(bmp)
.setAutoCancel(true)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.build() .setAutoCancel(true)
builder.setChannelId(NOTIFICATION_CHANNEL_ID) .setContentTitle("My notification")
.setContentText("Much longer text that cannot fit one line...")
.setStyle(NotificationCompat.BigTextStyle()
.bigText("Much longer text that cannot fit one line..."))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
// Deliver notification // Create the NotificationChannel, but only on API 26+ because
val notificationManager = // the NotificationChannel class is not in the Support Library.
val name = context.getString(R.string.channel_name)
val descriptionText = context.getString(R.string.channel_description)
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance).apply {
description = descriptionText
}
// Register the channel with the system.
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(0, notification) notificationManager.createNotificationChannel(channel)
with(NotificationManagerCompat.from(context)) {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
// grantResults: IntArray)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return@with
}
// notificationId is a unique int for each notification that you must define.
notify(NOTIFICATION_ID, builder.build())
}
} }
} }
} }

View File

@@ -32,6 +32,8 @@ class NotificationService(context: Context) {
AlarmManager.INTERVAL_DAY, AlarmManager.INTERVAL_DAY,
alarmPendingIntent alarmPendingIntent
) )
// alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(calendar.timeInMillis, alarmPendingIntent), alarmPendingIntent)
} }
fun unschedulePushNotifications() { fun unschedulePushNotifications() {

View File

@@ -1,7 +1,9 @@
package com.appttude.h_mal.atlas_weather.ui.home package com.appttude.h_mal.atlas_weather.ui.home
import android.Manifest.permission.ACCESS_COARSE_LOCATION import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.POST_NOTIFICATIONS
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
@@ -10,9 +12,8 @@ import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.Navigation.findNavController import androidx.navigation.Navigation.findNavController
import androidx.navigation.ui.onNavDestinationSelected import androidx.navigation.ui.onNavDestinationSelected
import androidx.recyclerview.widget.LinearLayoutManager
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST import com.appttude.h_mal.atlas_weather.application.AtlasApp
import com.appttude.h_mal.atlas_weather.base.BaseFragment import com.appttude.h_mal.atlas_weather.base.BaseFragment
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
@@ -56,6 +57,8 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
}) })
forecast_listview.adapter = recyclerAdapter forecast_listview.adapter = recyclerAdapter
scheduleNotification()
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@@ -99,6 +102,14 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
onRequestPermissionsResult(requestCode, grantResults) onRequestPermissionsResult(requestCode, grantResults)
} }
fun scheduleNotification() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
sendNotification()
} else {
(requireActivity().application as AtlasApp).scheduleNotifications()
}
}
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@NeedsPermission(ACCESS_COARSE_LOCATION) @NeedsPermission(ACCESS_COARSE_LOCATION)
fun showLocation() { fun showLocation() {
@@ -123,4 +134,29 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
fun onLocationNeverAskAgain() { fun onLocationNeverAskAgain() {
displayToast("Location permissions have been to never ask again") displayToast("Location permissions have been to never ask again")
} }
@SuppressLint("MissingPermission")
@NeedsPermission(POST_NOTIFICATIONS)
fun sendNotification() {
(requireActivity().application as AtlasApp).scheduleNotifications()
}
@OnShowRationale(POST_NOTIFICATIONS)
fun showRationaleForNotification(request: PermissionRequest) {
// PermissionsDeclarationDialog(requireContext()).showDialog({
// request.proceed()
// }, {
// request.cancel()
// })
}
@OnPermissionDenied(POST_NOTIFICATIONS)
fun onNotificationDenied() {
displayToast("Notification permissions have been denied")
}
@OnNeverAskAgain(POST_NOTIFICATIONS)
fun onNotificationNeverAskAgain() {
displayToast("Notification permissions have been to never ask again")
}
} }

View File

@@ -9,14 +9,12 @@ import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
import com.google.gson.Gson import com.google.gson.Gson
import org.kodein.di.Kodein import org.kodein.di.Kodein
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.x.androidXModule import org.kodein.di.android.x.androidXModule
import org.kodein.di.generic.bind import org.kodein.di.generic.bind
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
import org.kodein.di.generic.provider
import org.kodein.di.generic.singleton import org.kodein.di.generic.singleton
abstract class BaseAppClass : Application(), KodeinAware { abstract class BaseAppClass : Application(), KodeinAware {
@@ -40,7 +38,6 @@ abstract class BaseAppClass : Application(), KodeinAware {
bind() from singleton { SettingsRepositoryImpl(instance()) } bind() from singleton { SettingsRepositoryImpl(instance()) }
bind() from singleton { ServicesHelper(instance(), instance(), instance()) } bind() from singleton { ServicesHelper(instance(), instance(), instance()) }
bind() from singleton { WeatherSource(instance(), instance()) } bind() from singleton { WeatherSource(instance(), instance()) }
bind() from provider { ApplicationViewModelFactory(this@BaseAppClass, instance(), instance(),instance()) }
} }
open val flavourModule = Kodein.Module("Flavour") { open val flavourModule = Kodein.Module("Flavour") {

View File

@@ -8,8 +8,6 @@ import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInt
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
const val LOCATION_PERMISSION_REQUEST = 505
open class AppClass : BaseAppClass() { open class AppClass : BaseAppClass() {
override fun createNetworkModule(): WeatherApi { override fun createNetworkModule(): WeatherApi {

View File

@@ -8,7 +8,7 @@ import androidx.fragment.app.createViewModelLazy
import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseViewModel import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseViewModel
import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt
import com.appttude.h_mal.atlas_weather.model.ViewState import com.appttude.h_mal.atlas_weather.model.ViewState
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory import com.appttude.h_mal.atlas_weather.application.ApplicationViewModelFactory
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.x.kodein import org.kodein.di.android.x.kodein
import org.kodein.di.generic.instance import org.kodein.di.generic.instance

View File

@@ -6,11 +6,10 @@ import androidx.annotation.XmlRes
import androidx.fragment.app.createViewModelLazy import androidx.fragment.app.createViewModelLazy
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseAndroidViewModel import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseAndroidViewModel
import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt
import com.appttude.h_mal.atlas_weather.model.ViewState import com.appttude.h_mal.atlas_weather.model.ViewState
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory import com.appttude.h_mal.atlas_weather.application.ApplicationViewModelFactory
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.x.kodein import org.kodein.di.android.x.kodein
import org.kodein.di.generic.instance import org.kodein.di.generic.instance

View File

@@ -34,6 +34,8 @@
<string name="unit_key">Units</string> <string name="unit_key">Units</string>
<string name="widget_black_background">widget_black_background</string> <string name="widget_black_background">widget_black_background</string>
<string name="weather_units">Weather units</string> <string name="weather_units">Weather units</string>
<string name="channel_name">channel name</string>
<string name="channel_description">channel description</string>
<string-array name="units"> <string-array name="units">
<item>Metric</item> <item>Metric</item>

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.viewmodel package com.appttude.h_mal.atlas_weather.application
import android.app.Application import android.app.Application
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -6,6 +6,9 @@ import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.atlas_weather.data.WeatherSource import com.appttude.h_mal.atlas_weather.data.WeatherSource
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
import com.appttude.h_mal.atlas_weather.viewmodel.SettingsViewModel
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
class ApplicationViewModelFactory( class ApplicationViewModelFactory(

View File

@@ -1,6 +1,20 @@
import com.appttude.h_mal.atlas_weather.application.AppClass
import com.appttude.h_mal.atlas_weather.application.ApplicationViewModelFactory
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.provider
import org.kodein.di.generic.singleton
open class MonoApp : AppClass() { open class MonoApp : AppClass() {
override val flavourModule = super.flavourModule.copy { override val flavourModule = super.flavourModule.copy {
bind() from provider {
ApplicationViewModelFactory(
this@MonoApp,
instance(),
instance(),
instance(),
)
}
} }
} }

View File

@@ -13,7 +13,6 @@ import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseAndroidViewModel
import com.appttude.h_mal.atlas_weather.data.WeatherSource import com.appttude.h_mal.atlas_weather.data.WeatherSource
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
import com.appttude.h_mal.atlas_weather.service.notification.NotificationHelper
import com.appttude.h_mal.atlas_weather.widget.NewAppWidget import com.appttude.h_mal.atlas_weather.widget.NewAppWidget
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -24,12 +23,28 @@ class SettingsViewModel(
application: Application, application: Application,
private val locationProvider: LocationProvider, private val locationProvider: LocationProvider,
private val weatherSource: WeatherSource, private val weatherSource: WeatherSource,
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository
private val notificationHelper: NotificationHelper
) : BaseAndroidViewModel(application) { ) : BaseAndroidViewModel(application) {
private fun getContext() = getApplication<BaseAppClass>().applicationContext private fun getContext() = getApplication<BaseAppClass>().applicationContext
fun updateWidget() {
val context = getContext()
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
val widgetManager = AppWidgetManager.getInstance(context)
val ids =
widgetManager.getAppWidgetIds(
ComponentName(
context,
NewAppWidget::class.java
)
)
AppWidgetManager.getInstance(context)
.notifyAppWidgetViewDataChanged(ids, R.id.whole_widget_view)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
context.sendBroadcast(intent)
}
fun refreshWeatherData() { fun refreshWeatherData() {
onStart() onStart()
job = CoroutineScope(Dispatchers.IO).launch { job = CoroutineScope(Dispatchers.IO).launch {