yayan 1 년 전
부모
커밋
258a7fe7c0

+ 2 - 2
app/build.gradle

@@ -77,10 +77,10 @@ dependencies {
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1"
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1"
 
-    implementation "com.google.dagger:hilt-android:2.48"
+    implementation "com.google.dagger:hilt-android:2.49"
     kapt "com.google.dagger:hilt-android-compiler:2.48"
 
-    implementation "androidx.hilt:hilt-navigation-compose:1.1.0"
+    implementation "androidx.hilt:hilt-navigation-compose:1.2.0"
 
 }
 

+ 3 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Chat.kt

@@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.KeyboardCapitalization
 import androidx.compose.ui.text.input.TextFieldValue
@@ -42,6 +43,7 @@ fun Chat(
     val messageModel: MessageViewModel = hiltViewModel()
     val list by messageModel.getOpposite(pin).observeAsState()
     val state = rememberLazyListState()
+    val context = LocalContext.current
     Column(
         modifier = Modifier
             .fillMaxSize()
@@ -131,6 +133,7 @@ fun Chat(
                         }
                         me.let {
                             messageModel.send(
+                                context,
                                 Message(
                                     message_id = System.nanoTime().toString(),
                                     f_pin = it.f_pin,

+ 64 - 59
app/src/main/java/io/nexilis/alpha/ui/main/Chats.kt

@@ -38,7 +38,7 @@ fun Chats(
     val chats by messageSummaryViewModel.all.observeAsState()
     val messageViewModel: MessageViewModel = hiltViewModel()
     val buddyViewModel: BuddyViewModel = hiltViewModel()
-    var selectedItem by remember { mutableStateOf(0) }
+    var selectedItem by remember { mutableIntStateOf(0) }
     LazyColumn(
         contentPadding = contentPadding,
     ) {
@@ -48,65 +48,70 @@ fun Chats(
                 chats?.get(it)?.l_pin ?: 0
             }
         ) { item ->
-            val chat = chats?.get(item)
-            val message by messageViewModel.get(chat?.message_id ?: "").observeAsState()
-            val buddy by buddyViewModel.getBuddy(chat?.l_pin ?: "").observeAsState()
-            ListItem(
-                modifier = Modifier.selectable(selected = selectedItem == item, onClick = {
-                    selectedItem = item
-                    navController.navigate(Screen.Chat.route + "/${chat?.l_pin}") {
-                        launchSingleTop = true
-                        restoreState = true
+            chats?.get(item)?.let { chat ->
+                val buddy by buddyViewModel.getBuddy(chat.l_pin).observeAsState()
+                ListItem(
+                    modifier = Modifier.selectable(selected = selectedItem == item, onClick = {
+                        selectedItem = item
+                        navController.navigate(Screen.Chat.route + "/${chat.l_pin}") {
+                            launchSingleTop = true
+                            restoreState = true
+                        }
+                    }),
+                    headlineContent = {
+                        buddy?.let {
+                            Text(
+                                "${it.first_name} ${it.last_name}".trim(),
+                                style = MaterialTheme.typography.titleSmall
+                            )
+                        }
+                    },
+                    supportingContent = {
+                        val message by messageViewModel.get(chat.message_id).observeAsState()
+                        message?.let {
+                            Text(
+                                it.message_text,
+                                style = MaterialTheme.typography.bodySmall,
+                                overflow = TextOverflow.Ellipsis,
+                                maxLines = 1
+                            )
+                        }
+                    },
+                    leadingContent = {
+                        buddy?.let {
+                            AsyncImage(
+                                model = ImageRequest.Builder(LocalContext.current)
+                                    .data("https://digixplatform.com/filepalio/image/${it.image_id}")
+                                    .addHeader("Cookie", "PHPSESSID=123;MOBILE=123")
+                                    .crossfade(true)
+                                    .build(),
+                                placeholder = rememberVectorPainter(image = Icons.Default.Person),
+                                contentDescription = "",
+                                contentScale = ContentScale.Crop,
+                                modifier = Modifier
+                                    .size(48.dp)
+                                    .clip(CircleShape)
+                                    .background(Color.LightGray),
+                                error = rememberVectorPainter(image = Icons.Default.Person),
+                            )
+                        }
+                    },
+                    trailingContent = {
+                        BadgedBox(
+                            badge = {
+                                Badge {
+                                    Text(
+                                        item.toString(),
+                                        modifier = Modifier.semantics {
+                                            contentDescription = "$item new notifications"
+                                        }
+                                    )
+                                }
+                            }) {
+                        }
                     }
-                }),
-                headlineContent = {
-                    Text(
-                        "${buddy?.first_name ?: "Bot"} ${buddy?.last_name ?: ""}".trim(),
-                        style = MaterialTheme.typography.titleSmall
-                    )
-                },
-                supportingContent = {
-                    Text(
-                        message?.message_text ?: "Secondary text",
-                        style = MaterialTheme.typography.bodySmall,
-                        overflow = TextOverflow.Ellipsis,
-                        maxLines = 1
-                    )
-                },
-                leadingContent = {
-                    AsyncImage(
-                        model = ImageRequest.Builder(LocalContext.current)
-                            .data("https://digixplatform.com/filepalio/image/${buddy?.image_id}")
-                            .addHeader("Cookie", "PHPSESSID=123;MOBILE=123")
-                            .crossfade(true)
-                            .build(),
-                        placeholder = rememberVectorPainter(image = Icons.Default.Person),
-                        contentDescription = "",
-                        contentScale = ContentScale.Crop,
-                        modifier = Modifier
-                            .size(48.dp)
-                            .clip(CircleShape)
-                            .background(Color.LightGray),
-//                            .padding(8.dp),
-                        error = rememberVectorPainter(image = Icons.Default.Person),
-//                        colorFilter = ColorFilter.tint(Color.White)
-                    )
-                },
-                trailingContent = {
-                    BadgedBox(
-                        badge = {
-                            Badge {
-                                Text(
-                                    item.toString(),
-                                    modifier = Modifier.semantics {
-                                        contentDescription = "$item new notifications"
-                                    }
-                                )
-                            }
-                        }) {
-                    }
-                }
-            )
+                )
+            }
         }
     }
 }

+ 2 - 1
cpaas-lite/build.gradle

@@ -53,10 +53,11 @@ dependencies {
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1"
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1"
 
-    implementation "com.google.dagger:hilt-android:2.48"
+    implementation "com.google.dagger:hilt-android:2.49"
     kapt "com.google.dagger:hilt-android-compiler:2.48"
 
     implementation "com.squareup.okhttp3:okhttp:4.12.0"
+    implementation "androidx.work:work-runtime-ktx:2.9.0"
 }
 
 kapt {

+ 18 - 0
cpaas-lite/src/main/java/io/nexilis/service/Service.kt

@@ -77,6 +77,24 @@ class Service {
             return null
         }
 
+        fun sendSync(string: String): Data? {
+            try {
+                Log.d(tag, "Service:sGetResponse >>> $string")
+                val response = nuSDKService.getInstance(pass)
+                    .sGetResponse(pass, string, DEFAULT_TIMEOUT, false)
+                    ?: return null
+                Log.d(tag, "Service:sGetResponse <<< $response")
+                val r = Data()
+                if (r.parse(response)) {
+                    return r
+                }
+            } catch (e: Exception) {
+                Log.e(tag, e.message, e)
+            }
+            return null
+        }
+
+
         fun sendAsync(data: Data): String? {
             try {
                 Log.d(tag, "Service:sSend:$data")

+ 0 - 5
cpaas-lite/src/main/java/io/nexilis/service/core/ApiModule.kt

@@ -87,11 +87,6 @@ object ApiModule {
         return database.messageSummaryDao()
     }
 
-    @Provides
-    fun provideOutgoingDao(database: ApiRoomDatabase): OutgoingDao {
-        return database.outgoingDao()
-    }
-
     @Provides
     fun providePrefsDao(database: ApiRoomDatabase): PrefsDao {
         return database.prefsDao()

+ 83 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/Outgoing.kt

@@ -0,0 +1,83 @@
+package io.nexilis.service.core
+
+import android.annotation.TargetApi
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.os.Build
+import androidx.annotation.StringRes
+import androidx.core.app.NotificationCompat
+import androidx.work.CoroutineWorker
+import androidx.work.ForegroundInfo
+import androidx.work.WorkManager
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import io.nexilis.service.R
+import io.nexilis.service.Service
+
+class Outgoing(context: Context, parameters: WorkerParameters) :
+    CoroutineWorker(context, parameters) {
+    override suspend fun doWork(): Result {
+        try {
+            inputData.getString("data")?.let {
+                Service.sendSync(it)?.let { data ->
+                    return Result.success(
+                        workDataOf(
+                            "message_id" to data.bodies["A18"],
+                            "opposite_pin" to data.bodies["A01"],
+                            "status" to data.bodies["A15"]?.toInt()
+                        )
+                    )
+                }
+            }
+        } catch (e: Exception) {
+            Result.failure()
+        }
+        return Result.retry()
+    }
+
+    private val notificationManager =
+        context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+    override suspend fun getForegroundInfo(): ForegroundInfo {
+        val notificationId = 112345
+        return ForegroundInfo(notificationId, createNotification())
+    }
+
+    private fun getString(@StringRes id: Int) = applicationContext.getString(id)
+
+    private fun createNotification(): Notification {
+        val channelId = getString(R.string.notification_channel_id)
+        val title = getString(R.string.notification_title)
+        val cancel = getString(R.string.cancel_processing)
+        val name = getString(R.string.channel_name)
+        // This PendingIntent can be used to cancel the Worker.
+        val intent = WorkManager.getInstance(applicationContext).createCancelPendingIntent(id)
+
+        val builder = NotificationCompat.Builder(applicationContext, channelId)
+            .setContentTitle(title)
+            .setTicker(title)
+            .setSmallIcon(applicationContext.applicationInfo.icon)
+            .setOngoing(true)
+            .addAction(R.drawable.baseline_cancel_24, cancel, intent)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            createNotificationChannel(channelId, name).also {
+                builder.setChannelId(it.id)
+            }
+        }
+        return builder.build()
+    }
+
+    @TargetApi(Build.VERSION_CODES.O)
+    private fun createNotificationChannel(
+        channelId: String,
+        name: String
+    ): NotificationChannel {
+        return NotificationChannel(
+            channelId, name, NotificationManager.IMPORTANCE_LOW
+        ).also { channel ->
+            notificationManager.createNotificationChannel(channel)
+        }
+    }
+}

+ 0 - 31
cpaas-lite/src/main/java/io/nexilis/service/data/daos/OutgoingDao.kt

@@ -1,31 +0,0 @@
-package io.nexilis.service.data.daos
-
-import androidx.lifecycle.LiveData
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
-import io.nexilis.service.data.entities.*
-
-@Dao
-interface OutgoingDao {
-
-    @Query("select * from Outgoing")
-    fun getAll(): LiveData<List<Outgoing>>
-
-    @Query("select * from Outgoing where id = :id or package_id = :id")
-    fun get(id: String): LiveData<List<Outgoing>>
-
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    suspend fun insert(entity: Outgoing)
-
-    @Query("delete from Outgoing")
-    suspend fun deleteAll()
-
-    @Query("delete from Outgoing where id = :id")
-    suspend fun deleteId(id: String)
-
-    @Query("delete from Outgoing where package_id = :id")
-    suspend fun deletePackageId(id: String)
-
-}

+ 0 - 16
cpaas-lite/src/main/java/io/nexilis/service/data/entities/Outgoing.kt

@@ -1,16 +0,0 @@
-package io.nexilis.service.data.entities
-
-import androidx.room.Entity
-import androidx.room.Index
-
-@Entity(
-    primaryKeys = ["id"],
-    indices = [
-        Index(value = ["package_id"], unique = true)
-    ]
-)
-data class Outgoing(
-    val id: String,
-    val package_id: String,
-    val message: String
-) : MainEntity

+ 51 - 16
cpaas-lite/src/main/java/io/nexilis/service/data/repositories/MessageRepository.kt

@@ -1,8 +1,16 @@
 package io.nexilis.service.data.repositories
 
+import android.content.Context
+import android.util.Log
+import androidx.activity.ComponentActivity
 import androidx.lifecycle.LiveData
-import io.nexilis.service.Service
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.OutOfQuotaPolicy
+import androidx.work.WorkManager
+import androidx.work.WorkRequest
+import androidx.work.workDataOf
 import io.nexilis.service.core.Data
+import io.nexilis.service.core.Outgoing
 import io.nexilis.service.core.toStupidString
 import io.nexilis.service.data.daos.GroupMemberDao
 import io.nexilis.service.data.daos.MessageDao
@@ -10,8 +18,12 @@ import io.nexilis.service.data.daos.MessageStatusDao
 import io.nexilis.service.data.entities.MainEntity
 import io.nexilis.service.data.entities.Message
 import io.nexilis.service.data.entities.MessageStatus
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.util.UUID
 import javax.inject.Inject
 
+
 class MessageRepository @Inject constructor(
     private val dao: MessageDao,
     private val messageStatusDao: MessageStatusDao,
@@ -32,7 +44,11 @@ class MessageRepository @Inject constructor(
         dao.insert(entity as Message)
     }
 
-    suspend fun send(entity: MainEntity) {
+    suspend fun send(
+        context: Context,
+        entity: MainEntity,
+        completion: (String, String, Int) -> Unit
+    ) {
         val message = entity as Message
         dao.insert(message)
         if (message.message_scope_id == "4") {
@@ -113,20 +129,39 @@ class MessageRepository @Inject constructor(
         if (message.broadcast_flag > 0) {
             bodies["B4"] = message.broadcast_flag.toString()
         }
-        Service.sendSync(
-            Data(
-                code = "S0",
-                status = message.message_id,
-                f_pin = message.f_pin,
-                bodies = bodies
-            )
-        )?.let {
-            messageStatusDao.updateStatus(
-                status = it.bodies["A15"]?.toInt() ?: 2, // delivering
-                messageId = message.message_id,
-                pin = message.opposite_pin
-            )
+        val data = Data(
+            code = "S0",
+            status = message.message_id,
+            f_pin = message.f_pin,
+            bodies = bodies
+        )
+        val id = UUID.randomUUID()
+        val workRequest: WorkRequest =
+            OneTimeWorkRequestBuilder<Outgoing>()
+                .setId(id)
+                .setInputData(
+                    workDataOf(
+                        "data" to data.toString()
+                    )
+                )
+                .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
+                .build()
+        WorkManager.getInstance(context).enqueue(workRequest)
+        withContext(Dispatchers.Main) {
+            WorkManager.getInstance(context).getWorkInfoByIdLiveData(id).observe(context as ComponentActivity) { workInfo ->
+                if (workInfo.state.isFinished) {
+                    Log.d(
+                        "SAPI",
+                        "update from work:${workInfo.outputData.getString("message_id")}:${
+                            workInfo.outputData.getString("opposite_pin")
+                        }:${workInfo.outputData.getInt("status", 0)}"
+                    )
+                    completion(workInfo.outputData.getString("message_id") ?: "",
+                        workInfo.outputData.getString("opposite_pin") ?: "",
+                        workInfo.outputData.getInt("status", 0)
+                        )
+                }
+            }
         }
-
     }
 }

+ 4 - 0
cpaas-lite/src/main/java/io/nexilis/service/data/repositories/MessageStatusRepository.kt

@@ -21,4 +21,8 @@ class MessageStatusRepository @Inject constructor(private val dao: MessageStatus
     override suspend fun insert(entity: MainEntity) {
         dao.insert(entity as MessageStatus)
     }
+
+    suspend fun updateStatus(status: Int, messageId: String, pin: String) {
+        dao.updateStatus(status = status, messageId = messageId, pin = pin)
+    }
 }

+ 0 - 2
cpaas-lite/src/main/java/io/nexilis/service/data/rooms/ApiRoomDatabase.kt

@@ -26,7 +26,6 @@ import io.nexilis.service.data.entities.*
         MessageFavorite::class,
         MessageStatus::class,
         MessageSummary::class,
-        Outgoing::class,
         Prefs::class,
         Pull::class,
         ServiceBank::class,
@@ -49,7 +48,6 @@ abstract class ApiRoomDatabase : RoomDatabase() {
     abstract fun messageDao(): MessageDao
     abstract fun messageStatusDao(): MessageStatusDao
     abstract fun messageSummaryDao(): MessageSummaryDao
-    abstract fun outgoingDao(): OutgoingDao
     abstract fun prefsDao(): PrefsDao
     abstract fun pullDao(): PullDao
     abstract fun serviceBankDao(): ServiceBankDao

+ 7 - 0
cpaas-lite/src/main/java/io/nexilis/service/data/viewmodels/MessageStatusViewModel.kt

@@ -2,9 +2,12 @@ package io.nexilis.service.data.viewmodels
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
 import dagger.hilt.android.lifecycle.HiltViewModel
 import io.nexilis.service.data.entities.MessageStatus
 import io.nexilis.service.data.repositories.MessageStatusRepository
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 import javax.inject.Inject
 
 @HiltViewModel
@@ -19,4 +22,8 @@ class MessageStatusViewModel @Inject constructor(private val repository: Message
     fun get(id: String, pin: String) : LiveData<MessageStatus> {
         return repository.get(id, pin)
     }
+
+    fun updateStatus(status: Int, messageId: String, pin: String) = viewModelScope.launch(Dispatchers.IO) {
+        repository.updateStatus(status = status, messageId = messageId, pin = pin)
+    }
 }

+ 23 - 6
cpaas-lite/src/main/java/io/nexilis/service/data/viewmodels/MessageViewModel.kt

@@ -1,9 +1,11 @@
 package io.nexilis.service.data.viewmodels
 
+import android.content.Context
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import dagger.hilt.android.lifecycle.HiltViewModel
+import io.nexilis.service.data.daos.MessageStatusDao
 import io.nexilis.service.data.entities.Message
 import io.nexilis.service.data.repositories.MessageRepository
 import kotlinx.coroutines.Dispatchers
@@ -11,16 +13,19 @@ import kotlinx.coroutines.launch
 import javax.inject.Inject
 
 @HiltViewModel
-class MessageViewModel @Inject constructor(private val repository: MessageRepository) :
+class MessageViewModel @Inject constructor(
+    private val repository: MessageRepository,
+    private val messageStatusDao: MessageStatusDao
+) :
     ViewModel() {
 
     val all: LiveData<List<Message>> = repository.all
 
-    fun get(id: String) : LiveData<Message> {
+    fun get(id: String): LiveData<Message> {
         return repository.get(id)
     }
 
-    fun getOpposite(pin: String) : LiveData<List<Message>> {
+    fun getOpposite(pin: String): LiveData<List<Message>> {
         return repository.getOpposite(pin)
     }
 
@@ -28,8 +33,20 @@ class MessageViewModel @Inject constructor(private val repository: MessageReposi
         repository.insert(entity)
     }
 
-    fun send(entity: Message) = viewModelScope.launch(Dispatchers.IO) {
-        repository.send(entity)
-    }
+    fun send(
+        context: Context,
+        entity: Message
+    ) =
+        viewModelScope.launch(Dispatchers.IO) {
+            repository.send(context, entity) { id, pin, status ->
+                viewModelScope.launch(Dispatchers.IO) {
+                    messageStatusDao.updateStatus(
+                        status,
+                        messageId = id,
+                        pin = pin
+                    )
+                }
+            }
+        }
 
 }

+ 5 - 0
cpaas-lite/src/main/res/drawable/baseline_cancel_24.xml

@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#000000"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
+</vector>

+ 7 - 0
cpaas-lite/src/main/res/values/string.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="notification_channel_id">123412</string>
+    <string name="notification_title">Sending data..</string>
+    <string name="cancel_processing">Cancel</string>
+    <string name="channel_name">Outgoing</string>
+</resources>