diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9c577a1..e761afd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + + if (isGranted) { + Toast.makeText(this, "Notification permission granted", Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(this, "Notification permission denied", Toast.LENGTH_SHORT).show() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val splashScreen = installSplashScreen() @@ -41,24 +54,24 @@ class MainActivity : AppCompatActivity() { sharedPreferencesHelper = SharedPreferencesHelper(this) - val noInternetLiveData : MutableLiveData = MutableLiveData(false) + val noInternetLiveData : MutableLiveData = MutableLiveData(false) - coroutineExceptionHandler = CoroutineExceptionHandler{_, throwable -> - if(throwable is UnknownHostException) { + // Handle internet-related errors + coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> + if (throwable is UnknownHostException) { noInternetLiveData.postValue(true) } } - noInternetLiveData.observe(this) {noInternet -> - if(noInternet) { - Toast.makeText(this, getString(R.string.no_internet), Toast.LENGTH_LONG).show() - Thread.sleep(1000) - exitProcess(0) - } + + + // Request notification permission for Android 13+ (API 33+) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requestNotificationPermission() } val settingsModel = SharedPreferencesHelper(this).getSettings() - if(settingsModel?.isMusicOn != false) { + if (settingsModel?.isMusicOn != false) { val startMusicIntent = Intent(this, WeatherMusicService::class.java) startService(startMusicIntent) } @@ -69,6 +82,13 @@ class MainActivity : AppCompatActivity() { setContentView(binding.root) } + private fun requestNotificationPermission() { + if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + // Request notification permission + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } + private fun fetchData() { val weatherService = RetrofitHelper.getInstance().create(WeatherService::class.java) val weatherRepository = WeatherRepository(weatherService) @@ -76,8 +96,8 @@ class MainActivity : AppCompatActivity() { val geocodingData = sharedPreferencesHelper.getGeocodingData() - if(weatherViewModel.weatherLiveData.value == null) { - if(geocodingData == null) { + if (weatherViewModel.weatherLiveData.value == null) { + if (geocodingData == null) { weatherViewModel.getWeather(coroutineExceptionHandler) } else { weatherViewModel.getGeoWeather(coroutineExceptionHandler, geocodingData) @@ -85,12 +105,12 @@ class MainActivity : AppCompatActivity() { } weatherViewModel.weatherLiveData.observe(this) { weatherData -> - if(weatherData == null) { + if (weatherData == null) { Toast.makeText(this, getString(R.string.api_fetching_error), Toast.LENGTH_SHORT).show() Thread.sleep(1000) exitProcess(0) } else { - if(!settingsUpdated) { + if (!settingsUpdated) { createLocalDB(weatherData) } } @@ -103,7 +123,7 @@ class MainActivity : AppCompatActivity() { val weatherHelper = WeatherHelper(currentSettings, weatherData) val newWeatherData = weatherHelper.convertWeatherData() - if(newWeatherData != weatherData) { + if (newWeatherData != weatherData) { settingsUpdated = true weatherViewModel.updateWeatherData(newWeatherData) } @@ -112,6 +132,7 @@ class MainActivity : AppCompatActivity() { override fun onDestroy() { super.onDestroy() + weatherViewModel.weatherLiveData.removeObservers(this) weatherViewModel.viewModelScope.cancel("ActivityDestroying") } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/tangobee/weathernaut/services/WeatherMusicService.kt b/app/src/main/java/me/tangobee/weathernaut/services/WeatherMusicService.kt index 2f4a21a..1b70516 100644 --- a/app/src/main/java/me/tangobee/weathernaut/services/WeatherMusicService.kt +++ b/app/src/main/java/me/tangobee/weathernaut/services/WeatherMusicService.kt @@ -3,12 +3,13 @@ package me.tangobee.weathernaut.services import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.app.Service import android.content.Intent -import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK +import android.content.pm.ServiceInfo import android.media.MediaPlayer -import android.os.IBinder import android.os.Build +import android.os.IBinder import androidx.core.app.NotificationCompat import me.tangobee.weathernaut.R @@ -20,63 +21,68 @@ class WeatherMusicService : Service() { private val musicUrl = "https://weathernaut-backend.onrender.com/api/music" private val SERVICE_ID = 1 + private val CHANNEL_ID = "MUSIC_SERVICE_CHANNEL" override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { createNotificationChannel() - // Foreground notification - val notification: Notification = NotificationCompat.Builder(this, "MUSIC_SERVICE_CHANNEL") + // Check if the service was called with the "STOP_MUSIC" action to stop the music + if (intent?.action == "STOP_MUSIC") { + stopSelf() // Stop the service and the music + return START_NOT_STICKY + } + + // Intent for stopping the music through notification action + val stopMusicIntent = Intent(this, WeatherMusicService::class.java).apply { + action = "STOP_MUSIC" + } + val stopMusicPendingIntent = PendingIntent.getService( + this, 0, stopMusicIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + // Foreground notification with action to stop music + val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Playing Weather Music") .setContentText("Enjoy the music and your day's weather!") - .setSmallIcon(R.drawable.icon_music) + .setSmallIcon(R.drawable.icon_music) // Replace with your own icon + .addAction(R.drawable.baseline_stop_24, "Stop Music", stopMusicPendingIntent) // Action to stop music .build() + // Start the service in the foreground with the notification if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { startForeground(SERVICE_ID, notification) } else { - startForeground(SERVICE_ID, notification, - FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK) + startForeground(SERVICE_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK) } + // Initialize or restart the media player if (mediaPlayer == null) { mediaPlayer = MediaPlayer().apply { setDataSource(musicUrl) - prepareAsync() + prepareAsync() // Async preparation for streaming setOnPreparedListener { - it.start() + it.start() // Start playing music when ready } setOnCompletionListener { - start() + start() // Loop the music when it ends } setOnErrorListener { _, _, _ -> - stopSelf() + stopSelf() // Stop service on error false } } } else if (!mediaPlayer!!.isPlaying) { - mediaPlayer!!.start() + mediaPlayer!!.start() // Resume music if it's paused } return START_STICKY } - override fun onDestroy() { - super.onDestroy() - if (mediaPlayer != null) { - mediaPlayer?.stop() - mediaPlayer?.release() - mediaPlayer = null - } - } - - override fun onBind(intent: Intent?): IBinder? { - return null // We don't bind this service to an activity - } - + // Create notification channel for Android 8.0 and above private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val serviceChannel = NotificationChannel( - "MUSIC_SERVICE_CHANNEL", + CHANNEL_ID, "Music Playback Channel", NotificationManager.IMPORTANCE_LOW ) @@ -84,4 +90,15 @@ class WeatherMusicService : Service() { manager?.createNotificationChannel(serviceChannel) } } + + override fun onDestroy() { + super.onDestroy() + mediaPlayer?.stop() + mediaPlayer?.release() + mediaPlayer = null + } + + override fun onBind(intent: Intent?): IBinder? { + return null // We don't bind this service to an activity + } } diff --git a/app/src/main/res/drawable/baseline_stop_24.xml b/app/src/main/res/drawable/baseline_stop_24.xml new file mode 100644 index 0000000..817d57b --- /dev/null +++ b/app/src/main/res/drawable/baseline_stop_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8c45fef..0c20ec7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ Weathernaut - + Retry Something went wrong!\nDon\'t worry its not you, its us. Try later. No Internet Connection Search Cities Button