Add background notification with clickable action
This commit is contained in:
parent
dc461ad5dc
commit
16720a035a
3 changed files with 162 additions and 1 deletions
|
|
@ -0,0 +1,66 @@
|
|||
package com.mistral.chat.api
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.mistral.chat.R
|
||||
|
||||
class ApiForegroundService : Service() {
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "api_service_channel"
|
||||
const val NOTIFICATION_ID = 1002 // Different from AI response notification
|
||||
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, ApiForegroundService::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
val intent = Intent(context, ApiForegroundService::class.java)
|
||||
context.stopService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
createNotificationChannel()
|
||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("Mistral Chat")
|
||||
.setContentText("Получение ответа от AI...")
|
||||
.setSmallIcon(android.R.drawable.ic_menu_send)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"API Service",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = "Уведомление о работе API"
|
||||
}
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.mistral.chat.ui
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
|
|
@ -20,6 +24,7 @@ import android.widget.EditText
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -99,6 +104,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||
private var userScrolledAfterSend = false
|
||||
private var lastUserMessagePosition = -1
|
||||
private var apiKeyDialog: AlertDialog? = null
|
||||
private var leftAppDuringApiCall = false
|
||||
private var notificationSessionId: Long = -1L
|
||||
|
||||
companion object {
|
||||
private const val PREFS_NAME = "mistral_chat_prefs"
|
||||
|
|
@ -110,6 +117,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||
private const val KEY_DEFAULT_TIMEZONE = "default_timezone"
|
||||
private const val KEY_DEFAULT_CITY = "default_city"
|
||||
private const val MAX_PROFILES = 10
|
||||
private const val CHANNEL_ID_AI_RESPONSE = "ai_response_channel"
|
||||
private const val NOTIFICATION_ID_AI_RESPONSE = 1001
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -179,6 +188,18 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||
loadProfilesAndSessions()
|
||||
restoreCalDavConnection()
|
||||
|
||||
// Обработка session_id из уведомления
|
||||
intent?.getLongExtra("session_id", -1L)?.let { sessionId ->
|
||||
if (sessionId > 0) {
|
||||
lifecycleScope.launch {
|
||||
val session = database.sessionDao().getSessionById(sessionId)
|
||||
session?.let {
|
||||
selectSession(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputField.postDelayed({
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputField.requestFocus()
|
||||
|
|
@ -1290,6 +1311,10 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||
val selectedModel = selectedModelName
|
||||
val sessionIdAtStart = currentSessionId
|
||||
|
||||
// Сбрасываем флаг в начале нового запроса
|
||||
leftAppDuringApiCall = false
|
||||
notificationSessionId = currentSessionId ?: -1L
|
||||
|
||||
sendButton.isEnabled = false
|
||||
progressIndicator.isVisible = true
|
||||
|
||||
|
|
@ -1504,7 +1529,15 @@ if (!isActive) return@launch
|
|||
Toast.makeText(this@MainActivity, responseToShow, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Если пользователь ушел из приложения пока готовился ответ - показываем пуш
|
||||
if (leftAppDuringApiCall) {
|
||||
showBackgroundNotification(responseToShow)
|
||||
}
|
||||
|
||||
// Сбрасываем флаг после обработки
|
||||
leftAppDuringApiCall = false
|
||||
|
||||
sendButton.isEnabled = true
|
||||
progressIndicator.isVisible = false
|
||||
|
||||
|
|
@ -1693,4 +1726,65 @@ if (!isActive) return@launch
|
|||
else -> "❌ Произошла ошибка: $error"
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBackgroundNotification(response: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val hasPermission = checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||
if (!hasPermission) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID_AI_RESPONSE,
|
||||
"Ответы ИИ",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
description = "Уведомления о полученных ответах ИИ"
|
||||
enableVibration(true)
|
||||
enableLights(true)
|
||||
}
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val title = "Ответ ИИ получен"
|
||||
val body = if (response.length > 100) response.take(100) + "..." else response
|
||||
|
||||
val intent = Intent(applicationContext, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra("session_id", notificationSessionId)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
applicationContext,
|
||||
notificationSessionId.toInt(),
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID_AI_RESPONSE)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setContentTitle(title)
|
||||
.setContentText(body)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(response))
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID_AI_RESPONSE, notification)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
leftAppDuringApiCall = true
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
leftAppDuringApiCall = false
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue