1+ package com.troplo.privateuploader
2+
3+ import android.Manifest
4+ import android.app.NotificationChannel
5+ import android.app.NotificationManager
6+ import android.app.PendingIntent
7+ import android.app.Service
8+ import android.content.Context
9+ import android.content.Intent
10+ import android.content.pm.PackageManager
11+ import android.graphics.Bitmap
12+ import android.graphics.BitmapShader
13+ import android.graphics.Canvas
14+ import android.graphics.Paint
15+ import android.graphics.RectF
16+ import android.graphics.Shader
17+ import android.graphics.drawable.BitmapDrawable
18+ import android.os.IBinder
19+ import androidx.core.app.ActivityCompat
20+ import androidx.core.app.NotificationCompat
21+ import androidx.core.app.NotificationManagerCompat
22+ import androidx.core.app.Person
23+ import androidx.core.app.RemoteInput
24+ import androidx.core.graphics.drawable.IconCompat
25+ import coil.request.ImageRequest
26+ import coil.size.Size
27+ import com.troplo.privateuploader.api.SessionManager
28+ import com.troplo.privateuploader.api.SocketHandler
29+ import com.troplo.privateuploader.api.TpuFunctions
30+ import com.troplo.privateuploader.api.imageLoader
31+ import com.troplo.privateuploader.api.stores.UserStore
32+ import com.troplo.privateuploader.data.model.Message
33+ import com.troplo.privateuploader.data.model.MessageEvent
34+ import io.socket.client.Socket
35+ import io.socket.emitter.Emitter
36+ import kotlinx.coroutines.Dispatchers
37+ import org.json.JSONObject
38+ import java.net.URISyntaxException
39+
40+
41+ class ChatService : Service () {
42+ private var socket: Socket ? = SocketHandler .getSocket()
43+ private val messages = mutableMapOf<Int , MutableList <NotificationCompat .MessagingStyle .Message >>()
44+
45+ override fun onCreate () {
46+ super .onCreate()
47+ try {
48+ println (" [ChatService] Started" )
49+ if (socket == null || ! socket!! .connected()) {
50+ val token = SessionManager (this ).getAuthToken()
51+ if (! token.isNullOrBlank()) {
52+ SocketHandler .initializeSocket(token, this , " android_kotlin_background_service" )
53+ socket = SocketHandler .getSocket()
54+ }
55+ }
56+ } catch (e: URISyntaxException ) {
57+ e.printStackTrace()
58+ }
59+ socket?.on(" message" , onNewMessage)
60+ }
61+
62+ override fun onStartCommand (intent : Intent ? , flags : Int , startId : Int ): Int {
63+ // Handle incoming messages from Socket.io
64+ socket?.connect()
65+ return START_STICKY
66+ }
67+
68+ override fun onDestroy () {
69+ super .onDestroy()
70+ println (" [ChatService] Stopped" )
71+ socket?.disconnect()
72+ socket?.off(" message" , onNewMessage)
73+ }
74+
75+ private val onNewMessage: Emitter .Listener = object : Emitter .Listener {
76+ override fun call (vararg args : Any? ) {
77+ println (" [ChatService] Message received" )
78+ // Process the new message
79+ val jsonArray = args[0 ] as JSONObject
80+ val payload = jsonArray.toString()
81+ val messageEvent = SocketHandler .gson.fromJson(payload, MessageEvent ::class .java)
82+
83+ val message = messageEvent.message
84+
85+ // Send a notification using the Conversations API
86+ sendNotification(message)
87+ }
88+ }
89+
90+ private fun sendNotification (message : Message ? ) {
91+ println (" [ChatService] Sending notification, ${message == null || message.userId == UserStore .getUser()?.id} " )
92+ if (message == null ) return
93+
94+ // Add any additional configuration to the notification builder as needed
95+ if (ActivityCompat .checkSelfPermission(
96+ this ,
97+ Manifest .permission.POST_NOTIFICATIONS
98+ ) != PackageManager .PERMISSION_GRANTED
99+ ) {
100+ println (" [ChatService] No permission to post notifications" )
101+ // TODO: Consider calling
102+ // ActivityCompat#requestPermissions
103+ // here to request the missing permissions, and then overriding
104+ // public void onRequestPermissionsResult(int requestCode, String[] permissions,
105+ // int[] grantResults)
106+ // to handle the case where the user grants the permission. See the documentation
107+ // for ActivityCompat#requestPermissions for more details.
108+ return
109+ }
110+ asyncLoadIcon(message.user?.avatar, this ) {
111+ try {
112+ println (" [ChatService] Loaded icon" )
113+ val chatPartner = Person .Builder ().apply {
114+ setName(message.user?.username)
115+ setKey(message.user?.id.toString())
116+ setIcon(it)
117+ setImportant(false )
118+ }.build()
119+
120+ val notificationManager = NotificationManagerCompat .from(this )
121+ val channel = NotificationChannel (
122+ " communications" ,
123+ " Messages from Communications" ,
124+ NotificationManager .IMPORTANCE_HIGH
125+ )
126+ notificationManager.createNotificationChannel(channel)
127+ if (messages[message.chatId] == null ) messages[message.chatId] = mutableListOf ()
128+ messages[message.chatId]?.add(
129+ NotificationCompat .MessagingStyle .Message (
130+ message.content,
131+ TpuFunctions .getDate(message.createdAt)?.time ? : 0 ,
132+ chatPartner
133+ )
134+ )
135+
136+ val style = NotificationCompat .MessagingStyle (chatPartner)
137+ .setConversationTitle(" Conversation in ${message.chatId} " )
138+
139+ for (msg in messages[message.chatId]!! ) {
140+ style.addMessage(msg)
141+ }
142+
143+ val replyIntent = Intent (this , InlineNotificationActivity ::class .java)
144+ replyIntent.putExtra(" chatId" , message.chatId)
145+ val replyPendingIntent = PendingIntent .getBroadcast(this , 0 , replyIntent, PendingIntent .FLAG_MUTABLE )
146+
147+ val remoteInput = RemoteInput .Builder (" content" )
148+ .setLabel(" Reply" )
149+ .build()
150+
151+ val replyAction = NotificationCompat .Action .Builder (
152+ R .drawable.tpu_logo,
153+ " Reply" ,
154+ replyPendingIntent
155+ )
156+ .addRemoteInput(remoteInput)
157+ .setAllowGeneratedReplies(true )
158+ .build()
159+
160+ val builder: NotificationCompat .Builder = NotificationCompat .Builder (this , " communications" )
161+ .addPerson(chatPartner)
162+ .setStyle(style)
163+ .setContentText(message.content)
164+ .setContentTitle(message.user?.username)
165+ .setSmallIcon(R .drawable.tpu_logo)
166+ .setWhen(TpuFunctions .getDate(message.createdAt)?.time ? : 0 )
167+ .addAction(replyAction)
168+ val res = notificationManager.notify(message.chatId, builder.build())
169+ println (" [ChatService] Notification sent, $res " )
170+ } catch (e: Exception ) {
171+ println (" [ChatService] Error sending notification, ${e.printStackTrace()} " )
172+ }
173+ }
174+ }
175+
176+ override fun onBind (intent : Intent ? ): IBinder ? {
177+ return null
178+ }
179+ }
180+
181+ fun asyncLoadIcon (avatar : String? , context : Context , setIcon : (IconCompat ? ) -> Unit ) {
182+ if (avatar.isNullOrEmpty())
183+ setIcon(null )
184+ else {
185+ val request = ImageRequest .Builder (context)
186+ .dispatcher(Dispatchers .IO )
187+ .data(data = TpuFunctions .image(avatar, null ))
188+ .apply {
189+ size(Size .ORIGINAL )
190+ }
191+ .target { drawable ->
192+ try {
193+ val bitmap = (drawable as BitmapDrawable ).bitmap
194+ val roundedBitmap = createRoundedBitmap(bitmap)
195+
196+ val roundedIcon = IconCompat .createWithBitmap(roundedBitmap)
197+
198+ setIcon(roundedIcon)
199+ } catch (e: Exception ) {
200+ println (e)
201+ setIcon(null )
202+ }
203+ }
204+ .build()
205+ imageLoader(context).enqueue(request)
206+ }
207+ }
208+
209+ private fun createRoundedBitmap (bitmap : Bitmap ): Bitmap {
210+ return try {
211+ val output = Bitmap .createBitmap(bitmap.width, bitmap.height, Bitmap .Config .ARGB_8888 )
212+ val canvas = Canvas (output)
213+
214+ val paint = Paint ()
215+ paint.isAntiAlias = true
216+ paint.shader = BitmapShader (bitmap, Shader .TileMode .CLAMP , Shader .TileMode .CLAMP )
217+
218+ val rect = RectF (0f , 0f , bitmap.width.toFloat(), bitmap.height.toFloat())
219+ canvas.drawRoundRect(rect, bitmap.width.toFloat(), bitmap.height.toFloat(), paint)
220+
221+ output
222+ } catch (e: Exception ) {
223+ bitmap
224+ }
225+ }
0 commit comments