yayan 6 luni în urmă
părinte
comite
4ab28bdf34

+ 1 - 1
app/build.gradle.kts

@@ -22,7 +22,7 @@ android {
 
     defaultConfig {
         applicationId = "io.nexilis.alpha"
-        minSdk = 24
+        minSdk = 26
 //        targetSdk = 35
         versionCode = 1
         versionName = "1.0"

+ 0 - 7
app/src/main/AndroidManifest.xml

@@ -3,14 +3,7 @@
     xmlns:tools="http://schemas.android.com/tools">
 
     <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
-    <uses-permission
-        android:name="android.permission.READ_EXTERNAL_STORAGE"
-        android:maxSdkVersion="32" />
 
-    <uses-feature android:name="android.hardware.camera.any" />
-    <uses-feature android:name="android.hardware.microphone" />
 
     <application
         android:name=".AlphaApplication"

+ 1 - 0
app/src/main/java/io/nexilis/alpha/MainActivity.kt

@@ -21,6 +21,7 @@ class MainActivity : ComponentActivity() {
             }
         }
         Api.connect(
+            "OneApp",
             "38747683290F62E9667A018F490396EAE47BC16ADECD85B7E865C733E6DBD6A2",
             this@MainActivity
         )

+ 13 - 1
app/src/main/java/io/nexilis/alpha/ui/components/Menu.kt

@@ -1,14 +1,18 @@
 package io.nexilis.alpha.ui.components
 
+import android.content.Intent
+import android.util.Log
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Call
 import androidx.compose.material.icons.filled.MoreVert
 import androidx.compose.material.icons.filled.VideoCall
 import androidx.compose.material3.*
 import androidx.compose.runtime.*
+import androidx.compose.ui.platform.LocalContext
 import androidx.navigation.NavHostController
 import androidx.navigation.compose.currentBackStackEntryAsState
 import io.nexilis.alpha.ui.screen.Screen
+import io.nexilis.service.core.CallService
 
 @Composable
 fun ActionsMenu(navController: NavHostController, navMainController: NavHostController) {
@@ -64,6 +68,7 @@ fun ChatMenu(navController: NavHostController) {
     var isOpenMenu by remember {
         mutableStateOf(false)
     }
+    var context = LocalContext.current
     IconButton(onClick = { }) {
         Icon(
             imageVector = Icons.Default.VideoCall,
@@ -71,7 +76,14 @@ fun ChatMenu(navController: NavHostController) {
             tint = MaterialTheme.colorScheme.primary
         )
     }
-    IconButton(onClick = { }) {
+    IconButton(onClick = {
+        val pin = navController.currentBackStackEntry?.arguments?.getString("pin")
+        Log.d("Menu", "pin:$pin")
+        val intent = Intent(context, CallService::class.java)
+        intent.action = "CALL"
+        intent.putExtra("pin", navController.currentBackStackEntry?.arguments?.getString("pin"))
+        context.startForegroundService(intent)
+    }) {
         Icon(
             imageVector = Icons.Default.Call,
             contentDescription = "",

+ 68 - 59
app/src/main/java/io/nexilis/alpha/ui/main/Friend.kt

@@ -14,6 +14,7 @@ import androidx.compose.material3.ListItem
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.SearchBar
+import androidx.compose.material3.SearchBarDefaults
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TopAppBar
@@ -43,7 +44,8 @@ import org.json.JSONArray
 fun Friend(navController: NavHostController) {
     val viewModel: BuddyViewModel = hiltViewModel()
     var text by rememberSaveable { mutableStateOf("") }
-    var active by rememberSaveable { mutableStateOf(false) }
+    var expanded by rememberSaveable { mutableStateOf(false) }
+    var isSearchActive by remember { mutableStateOf(false) }
     val list = remember { mutableStateListOf<Buddy>() }
     val listSuggestions = remember { mutableStateListOf<Buddy>() }
     val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
@@ -68,70 +70,77 @@ fun Friend(navController: NavHostController) {
                 .imePadding()
         ) {
             SearchBar(
-                modifier = Modifier.align(Alignment.CenterHorizontally),
-                query = text,
-                onQueryChange = { text = it },
-                onSearch = {
-                    active = false
-                    if (text.isNotEmpty()) {
-                        viewModel.searchFriend(text) { r, d ->
-                            list.clear()
-                            if (r && d.isNotEmpty()) {
-                                val data = JSONArray(d)
-                                for (i in 0 until data.length()) {
-                                    val t = data.getJSONObject(i)
-                                    val pin = t.optString("A00", "")
-                                    if (pin.isNotEmpty()) {
-                                        list.add(
-                                            Buddy(
-                                                f_pin = t.optString("A00", ""),
-                                                first_name = t.optString("A02", ""),
-                                                last_name = t.optString("A03", ""),
-                                                image_id = t.optString("A74", ""),
-                                                official_account = t.optString("oacc", "0")
-                                            )
-                                        )
+                inputField = {
+                    SearchBarDefaults.InputField(
+                        query = text,
+                        onQueryChange = { text = it },
+                        onSearch = {
+                            expanded = false
+                            if (text.isNotEmpty()) {
+                                viewModel.searchFriend(text) { r, d ->
+                                    list.clear()
+                                    if (r && d.isNotEmpty()) {
+                                        val data = JSONArray(d)
+                                        for (i in 0 until data.length()) {
+                                            val t = data.getJSONObject(i)
+                                            val pin = t.optString("A00", "")
+                                            if (pin.isNotEmpty()) {
+                                                list.add(
+                                                    Buddy(
+                                                        f_pin = t.optString("A00", ""),
+                                                        first_name = t.optString("A02", ""),
+                                                        last_name = t.optString("A03", ""),
+                                                        image_id = t.optString("A74", ""),
+                                                        official_account = t.optString("oacc", "0")
+                                                    )
+                                                )
+                                            }
+                                        }
                                     }
                                 }
                             }
-                        }
-                    }
-                },
-                active = active,
-                onActiveChange = {
-                    active = it
-                    if (it) {
-                        viewModel.suggestFriend("0") { r, d ->
-                            list.clear()
-                            if (r && d.isNotEmpty()) {
-                                val data = JSONArray(d)
-                                for (i in 0 until data.length()) {
-                                    val t = data.getJSONObject(i)
-                                    val pin = t.optString("A00", "")
-                                    if (pin.isNotEmpty()) {
-                                        listSuggestions.add(
-                                            Buddy(
-                                                f_pin = t.optString("A00", ""),
-                                                first_name = t.optString("A02", ""),
-                                                last_name = t.optString("A03", ""),
-                                                image_id = t.optString("A74", ""),
-                                                official_account = t.optString("oacc", "0")
-                                            )
-                                        )
-                                    }
+                        },
+                        expanded = expanded,
+                        onExpandedChange = {
+//                            expanded = it
+                        },
+                        placeholder = { Text("Search name..") },
+                        leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
+                        trailingIcon = {
+                            if (expanded) {
+                                IconButton(onClick = { text = "" }) {
+                                    Icon(Icons.Default.Close, contentDescription = null)
                                 }
                             }
-                        }
-                    }
+                        })
                 },
-                placeholder = { Text("Search name..") },
-                leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
-                trailingIcon = {
-                    if (active) {
-                        IconButton(onClick = { text = "" }) {
-                            Icon(Icons.Default.Close, contentDescription = null)
-                        }
-                    }
+                modifier = Modifier.align(Alignment.CenterHorizontally),
+                expanded = expanded,
+                onExpandedChange = {
+                    expanded = it
+//                    if (it) {
+//                        viewModel.suggestFriend("0") { r, d ->
+//                            list.clear()
+//                            if (r && d.isNotEmpty()) {
+//                                val data = JSONArray(d)
+//                                for (i in 0 until data.length()) {
+//                                    val t = data.getJSONObject(i)
+//                                    val pin = t.optString("A00", "")
+//                                    if (pin.isNotEmpty()) {
+//                                        listSuggestions.add(
+//                                            Buddy(
+//                                                f_pin = t.optString("A00", ""),
+//                                                first_name = t.optString("A02", ""),
+//                                                last_name = t.optString("A03", ""),
+//                                                image_id = t.optString("A74", ""),
+//                                                official_account = t.optString("oacc", "0")
+//                                            )
+//                                        )
+//                                    }
+//                                }
+//                            }
+//                        }
+//                    }
                 },
             ) {
                 SearchResultList(viewModel = viewModel, list = listSuggestions)

+ 2 - 1
app/src/main/java/io/nexilis/alpha/ui/main/Sign.kt

@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.navigation.NavHostController
 import io.nexilis.service.core.api
+import io.nexilis.service.core.appName
 import io.nexilis.service.core.pref
 import io.nexilis.service.data.viewmodels.BuddyViewModel
 
@@ -177,7 +178,7 @@ fun SignIn(navController: NavHostController, completion: (Boolean) -> Unit) {
         )
         Spacer(modifier = Modifier.size(8.dp))
         Button(onClick = {
-            viewModel.signIn(context.pref().api, textUsername.text, textPassword.text) { r, pin ->
+            viewModel.signIn(context.pref().appName, context.pref().api, textUsername.text, textPassword.text) { r, pin ->
                 completion(r)
             }
         }) {

+ 1 - 1
cpaas-lite/build.gradle.kts

@@ -10,7 +10,7 @@ android {
     compileSdk = 35
 
     defaultConfig {
-        minSdk = 23
+        minSdk = 26
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles("consumer-rules.pro")
     }

BIN
cpaas-lite/libs/annotation.jar


+ 17 - 11
cpaas-lite/src/main/AndroidManifest.xml

@@ -4,23 +4,29 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission
+        android:name="android.permission.READ_EXTERNAL_STORAGE"
+        android:maxSdkVersion="32" />
+
+    <uses-feature android:name="android.hardware.camera.any" />
+    <uses-feature android:name="android.hardware.microphone" />
+
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
 
     <application android:enabled="true">
-        <receiver
-            android:name="io.newuniverse.SDK.nuSDKReceiver"
-            android:enabled="true"
-            android:exported="false">
-            <intent-filter>
-                <action android:name="android.intent.action.ACTION_SHUTDOWN" />
-            </intent-filter>
-        </receiver>
-
         <service
-            android:name="io.newuniverse.SDK.nuSDKService"
+            android:name=".core.CallService"
             android:enabled="true"
             android:exported="false"
-            android:foregroundServiceType="microphone|camera|mediaProjection" />
+            android:foregroundServiceType="remoteMessaging|microphone|camera|mediaProjection" />
+
         <service
             android:name=".FirebaseService"
             android:exported="false">

+ 37 - 22
cpaas-lite/src/main/java/io/nexilis/service/Api.kt

@@ -2,7 +2,6 @@ package io.nexilis.service
 
 import android.app.Activity
 import android.content.Context
-import android.content.Intent
 import android.os.Build
 import android.util.Base64
 import android.util.Log
@@ -16,12 +15,15 @@ import io.newuniverse.SDK.nuSDKService
 import io.nexilis.service.core.Data
 import io.nexilis.service.core.Network
 import io.nexilis.service.core.api
+import io.nexilis.service.core.appName
 import io.nexilis.service.core.decrypt
 import io.nexilis.service.core.domain
 import io.nexilis.service.core.isLogin
 import io.nexilis.service.core.pref
 import io.nexilis.service.core.pin
 import io.nexilis.service.core.session
+import io.nexilis.service.data.entities.Buddy
+import io.nexilis.service.data.rooms.ApiRoomDatabase
 import kotlinx.coroutines.launch
 import org.json.JSONObject
 import java.security.SecureRandom
@@ -38,12 +40,13 @@ class Api {
 
         internal var condition: Condition? = null
 
-        fun connect(account: String, activity: Activity) {
-            Log.d(tag, "connect:$account")
-            activity.startService(Intent(activity, nuSDKService::class.java))
+        fun connect(appName: String, account: String, activity: Activity) {
+            Log.d(tag, "connect:$appName:$account")
             Thread {
                 try {
-                    val urlData = Network().getSync("https://nexilis.io/dipp/NuN1v3rs3/Qm3r4i0/get_ip_domain?account=$account") ?: return@Thread
+                    val urlData =
+                        Network().getSync("https://nexilis.io/dipp/NuN1v3rs3/Qm3r4i0/get_ip_domain?account=$account")
+                            ?: return@Thread
                     Log.d(tag, "urlData:${urlData}")
                     val decode = Base64.decode(urlData, Base64.DEFAULT).toString(Charsets.UTF_8)
                     val json = JSONObject(decode.decrypt())
@@ -51,7 +54,7 @@ class Api {
                     val domain = json.getString("domain")
                     val address = arrayAddress.getJSONObject(0)
                     val ip = address.getString("ip")
-                    val port = address.getString("portA").toInt()
+                    val port = address.getString("portD").toInt()
                     activity.pref().domain = domain
                     Log.d(tag, "domain:${activity.pref().domain}")
                     val options = FirebaseOptions.Builder()
@@ -62,24 +65,29 @@ class Api {
                     try {
                         Firebase.app(FirebaseApp.DEFAULT_APP_NAME)
                         Firebase.initialize(activity.applicationContext, options, "nexilis")
-                    } catch (e: Exception) {
-                        Firebase.initialize(activity.applicationContext, options, FirebaseApp.DEFAULT_APP_NAME)
-                    }
-                    while (!nuSDKService.bServiceStarted()) {
-                        Thread.sleep(100)
+                    } catch (_: Exception) {
+                        Firebase.initialize(
+                            activity.applicationContext,
+                            options,
+                            FirebaseApp.DEFAULT_APP_NAME
+                        )
                     }
                     val preferences = activity.pref()
+                    preferences.appName = appName
                     preferences.api = account
                     val random =
-                        String.format(Locale.getDefault(), "%010d", SecureRandom().nextInt(Int.MAX_VALUE))
+                        String.format(
+                            Locale.getDefault(),
+                            "%010d",
+                            SecureRandom().nextInt(Int.MAX_VALUE)
+                        )
                     val session = preferences.session.ifEmpty { random }
                     Log.d(tag, "initConnection:$pass,$account,$activity,$session")
-                    nuSDKService.getInstance(pass).initConnection(
+                    nuSDKService.initConnection(
                         pass,
                         account,
                         activity,
-                        SdkCallback::class.java,
-                        SdkCallback(),
+                        SdkCallback(activity),
                         ip,
                         port,
                         session,
@@ -99,7 +107,11 @@ class Api {
                                 Log.d(tag, "wait:result:$await")
                                 if (!await) {
                                     activity.runOnUiThread {
-                                        Toast.makeText(activity, "Please check your internet connection", Toast.LENGTH_LONG).show()
+                                        Toast.makeText(
+                                            activity,
+                                            "Please check your internet connection",
+                                            Toast.LENGTH_LONG
+                                        ).show()
                                     }
                                     return@Thread
                                 }
@@ -119,13 +131,16 @@ class Api {
                                         val p = r.bodies["A00"] ?: ""
                                         Log.d(tag, "signup:put:pin:$p")
                                         preferences.pin = p
-                                        Log.d(tag, "signup:retrieve")
-                                        Service.sendAsync(
-                                            Data(
-                                                code = "A050",
-                                                status = System.nanoTime().toString(),
+                                        apiScope.launch {
+                                            ApiRoomDatabase.getDatabase(activity).buddyDao().insert(
+                                                Buddy(
+                                                    f_pin = p,
+                                                    first_name = "USR_12345",
+                                                    image_id = "",
+                                                    type = "1"
+                                                )
                                             )
-                                        )
+                                        }
                                     }
                                 }
                             }

+ 4 - 4
cpaas-lite/src/main/java/io/nexilis/service/SdkCallback.kt

@@ -1,14 +1,14 @@
 package io.nexilis.service
 
+import android.content.Context
 import android.util.Log
 import dagger.hilt.android.EntryPointAccessors
 import io.newuniverse.SDK.nuSDKCallBack
-import io.newuniverse.SDK.nuSDKService
 import io.nexilis.service.core.Data
 import io.nexilis.service.core.IncomingInterface
 import kotlin.concurrent.withLock
 
-class SdkCallback : nuSDKCallBack {
+class SdkCallback(private val context: Context) : nuSDKCallBack {
 
     private val tag = "SdkCallback"
 
@@ -41,6 +41,7 @@ class SdkCallback : nuSDKCallBack {
     }
 
     override fun callStateChanged(p0: Int, p1: String?): Int {
+        Log.d(tag, "callStateChanged:$p0:$p1")
         return 0
     }
 
@@ -74,8 +75,7 @@ class SdkCallback : nuSDKCallBack {
                 p0?.let { s ->
                     data.packetId = s
                 }
-                val entryPoint = EntryPointAccessors.fromApplication(
-                    nuSDKService.getInstance(pass).applicationContext,
+                val entryPoint = EntryPointAccessors.fromApplication(context,
                     IncomingInterface::class.java
                 )
                 entryPoint.incoming().process(data)

+ 70 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/CallNotification.kt

@@ -0,0 +1,70 @@
+package io.nexilis.service.core
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import androidx.core.app.NotificationCompat
+import androidx.core.app.Person
+import io.nexilis.service.R
+
+class CallNotification(private val context: Context) {
+
+    private fun createNotificationChannel(
+        channelId: String,
+        name: String
+    ): NotificationChannel {
+        return NotificationChannel(
+            channelId, name, NotificationManager.IMPORTANCE_DEFAULT
+        ).also { channel ->
+            val notificationManager =
+                context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+            notificationManager.createNotificationChannel(channel)
+        }
+    }
+
+    fun getIncomingNotification(
+        person: Person,
+        title: String,
+        text: String,
+        declineIntent: PendingIntent,
+        answerIntent: PendingIntent
+    ): Notification {
+        val channelId = context.getString(R.string.notification_call_channel_id)
+        val name = context.getString(R.string.channel_name_call)
+        val style =
+            NotificationCompat.CallStyle.forIncomingCall(person, declineIntent, answerIntent)
+        val builder = NotificationCompat.Builder(context, channelId)
+            .setContentTitle(title)
+            .setContentText(text)
+            .setSmallIcon(context.applicationInfo.icon)
+            .setStyle(style)
+        createNotificationChannel(channelId, name).also {
+            builder.setChannelId(it.id)
+        }
+        return builder.build()
+    }
+
+    fun getOutgoingNotification(
+        person: Person,
+        title: String,
+        text: String,
+        hangupIntent: PendingIntent
+    ): Notification {
+        val channelId = context.getString(R.string.notification_call_channel_id)
+        val name = context.getString(R.string.channel_name_call)
+        val style =
+            NotificationCompat.CallStyle.forOngoingCall(person, hangupIntent)
+        val builder = NotificationCompat.Builder(context, channelId)
+            .setContentTitle(title)
+            .setContentText(text)
+            .setSmallIcon(context.applicationInfo.icon)
+            .setStyle(style)
+        createNotificationChannel(channelId, name).also {
+            builder.setChannelId(it.id)
+        }
+        return builder.build()
+    }
+
+}

+ 68 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/CallService.kt

@@ -0,0 +1,68 @@
+package io.nexilis.service.core
+
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Intent
+import android.graphics.drawable.BitmapDrawable
+import android.os.IBinder
+import androidx.core.app.Person
+import androidx.core.graphics.drawable.IconCompat
+import coil.request.ImageRequest
+import coil.transform.CircleCropTransformation
+import io.newuniverse.SDK.nuSDKService
+import io.nexilis.service.apiScope
+import io.nexilis.service.data.rooms.ApiRoomDatabase
+import io.nexilis.service.pass
+import kotlinx.coroutines.launch
+
+class CallService : Service() {
+
+    override fun onCreate() {
+        super.onCreate()
+    }
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        intent?.let { callIntent ->
+            val action = callIntent.action
+            if (action == "CALL") {
+                callIntent.extras?.let {
+                    apiScope.launch {
+                        val pin = it.getString("pin") ?: ""
+                        val buddy = ApiRoomDatabase.getDatabase(this@CallService).buddyDao()
+                            .getBuddySync(pin) ?: return@launch
+                        val request = ImageRequest.Builder(this@CallService)
+                            .addHeader("User-Agent", "Mozilla/5.0")
+                            .addHeader("Cookie", "PHPSESSID=123;MOBILE=123")
+                            .data("${pref().domain}/filepalio/image/${buddy.image_id}")
+                            .transformations(CircleCropTransformation()).build()
+                        val drawable =
+                            getUnsafeImageLoader().execute(request).drawable as BitmapDrawable
+                        val user =
+                            Person.Builder().setIcon(IconCompat.createWithBitmap(drawable.bitmap))
+                                .setName("${buddy.first_name} ${buddy.last_name}".trim()).build()
+                        val hangupIntent = PendingIntent.getActivity(
+                            this@CallService,
+                            0,
+                            callIntent,
+                            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+                        )
+                        val notification =
+                            CallNotification(this@CallService).getOutgoingNotification(
+                                user,
+                                "Call",
+                                "",
+                                hangupIntent
+                            )
+                        startForeground(1, notification)
+                        nuSDKService.getInstance(pass).icc(pass, notification, buddy.f_pin)
+                    }
+                }
+            }
+        }
+        return START_NOT_STICKY
+    }
+
+    override fun onBind(p0: Intent?): IBinder? {
+        return null
+    }
+}

+ 4 - 11
cpaas-lite/src/main/java/io/nexilis/service/core/ChatNotification.kt

@@ -1,19 +1,16 @@
 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.net.Uri
-import android.os.Build
 import androidx.core.app.NotificationCompat
 import androidx.core.app.Person
 import io.nexilis.service.R
 
 class ChatNotification(private val context: Context) {
 
-    @TargetApi(Build.VERSION_CODES.O)
     private fun createNotificationChannel(
         channelId: String,
         name: String
@@ -50,10 +47,8 @@ class ChatNotification(private val context: Context) {
             .setContentText(text)
             .setSmallIcon(context.applicationInfo.icon)
             .setStyle(style)
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            createNotificationChannel(channelId, name).also {
-                builder.setChannelId(it.id)
-            }
+        createNotificationChannel(channelId, name).also {
+            builder.setChannelId(it.id)
         }
         return builder.build()
     }
@@ -70,10 +65,8 @@ class ChatNotification(private val context: Context) {
             .setContentText(text)
             .setSmallIcon(context.applicationInfo.icon)
             .setStyle(style)
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            createNotificationChannel(channelId, name).also {
-                builder.setChannelId(it.id)
-            }
+        createNotificationChannel(channelId, name).also {
+            builder.setChannelId(it.id)
         }
         return builder.build()
     }

+ 6 - 2
cpaas-lite/src/main/java/io/nexilis/service/core/Extension.kt

@@ -132,6 +132,10 @@ var SharedPreferences.pin
     get() = this.optString("pin", "")
     set(value) = this.put("pin", value)
 
+var SharedPreferences.appName
+    get() = this.optString("app_name", "")
+    set(value) = this.put("app_name", value)
+
 var SharedPreferences.api
     get() = this.optString("api", "")
     set(value) = this.put("api", value)
@@ -286,7 +290,7 @@ class Secret {
         val sDecrypt: String
         try {
             iRandom = str.substring(0, 1).toInt()
-        } catch (ignored: NumberFormatException) {
+        } catch (_: NumberFormatException) {
         }
         sDecrypt = getPalindrom(str.substring(1))
         arr = sDecrypt.toCharArray()
@@ -297,7 +301,7 @@ class Secret {
                 } else {
                     arr[i] = getBeforeChar(arr[i], iRandom)
                 }
-            } catch (exception: Exception) {
+            } catch (_: Exception) {
                 return "-1"
             }
         }

+ 2 - 7
cpaas-lite/src/main/java/io/nexilis/service/core/Outgoing.kt

@@ -1,11 +1,9 @@
 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 android.util.Log
 import androidx.annotation.StringRes
 import androidx.core.app.NotificationCompat
@@ -163,15 +161,12 @@ class Outgoing(private val context: Context, parameters: WorkerParameters) :
             .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)
-            }
+        createNotificationChannel(channelId, name).also {
+            builder.setChannelId(it.id)
         }
         return builder.build()
     }
 
-    @TargetApi(Build.VERSION_CODES.O)
     private fun createNotificationChannel(
         channelId: String,
         name: String

+ 3 - 1
cpaas-lite/src/main/java/io/nexilis/service/data/repositories/BuddyRepository.kt

@@ -68,6 +68,7 @@ class BuddyRepository @Inject constructor(private val context: Context, private
     }
 
     suspend fun signIn(
+        appName: String,
         api: String,
         username: String,
         password: String,
@@ -80,7 +81,8 @@ class BuddyRepository @Inject constructor(private val context: Context, private
                 bodies = mutableMapOf(
                     "A92" to username,
                     "Bm" to password,
-                    "Api" to api
+                    "Api" to api,
+                    "AAN" to appName
                 )
             )
         )?.let { data ->

+ 2 - 1
cpaas-lite/src/main/java/io/nexilis/service/data/viewmodels/BuddyViewModel.kt

@@ -32,13 +32,14 @@ class BuddyViewModel @Inject constructor(private val repository: BuddyRepository
         }
 
     fun signIn(
+        appName: String,
         api: String,
         username: String,
         password: String,
         completion: (Boolean, String) -> Unit
     ) =
         viewModelScope.launch(Dispatchers.IO) {
-            repository.signIn(api, username, password, completion)
+            repository.signIn(appName, api, username, password, completion)
         }
 
     fun addFriend(pin: String) = viewModelScope.launch(Dispatchers.IO) {

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

@@ -2,8 +2,10 @@
 <resources>
     <string name="notification_channel_id">123412</string>
     <string name="notification_chat_channel_id">123413</string>
+    <string name="notification_call_channel_id">123414</string>
     <string name="notification_title">Sending data..</string>
     <string name="cancel_processing">Cancel</string>
     <string name="channel_name">Outgoing</string>
     <string name="channel_name_chat">Chat</string>
+    <string name="channel_name_call">Call</string>
 </resources>

+ 4 - 4
gradle/libs.versions.toml

@@ -1,5 +1,5 @@
 [versions]
-agp = "8.7.1"
+agp = "8.8.0"
 kotlin = "2.0.21"
 ksp = "2.0.21-1.0.26"
 coreKtx = "1.15.0"
@@ -7,12 +7,12 @@ junit = "4.13.2"
 junitVersion = "1.2.1"
 espressoCore = "3.6.1"
 lifecycleRuntimeKtx = "2.8.7"
-activityCompose = "1.9.3"
-composeBom = "2024.10.01"
+activityCompose = "1.10.0"
+composeBom = "2025.01.00"
 hilt = "2.49"
 hiltNavigation = "1.2.0"
 gms = "4.3.15"
-navigation = "2.8.3"
+navigation = "2.8.5"
 coil = "2.6.0"
 constraintLayout = "1.1.0"
 paging = "3.3.2"

+ 1 - 1
gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
 #Thu Oct 31 14:13:46 WIB 2024
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists