Yayan D Wicaksono 1 rok temu
commit
441da3440b
100 zmienionych plików z 4233 dodań i 0 usunięć
  1. 15 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 89 0
      app/build.gradle
  4. 21 0
      app/proguard-rules.pro
  5. 22 0
      app/src/androidTest/java/io/nexilis/alpha/ExampleInstrumentedTest.kt
  6. 37 0
      app/src/main/AndroidManifest.xml
  7. 9 0
      app/src/main/java/io/nexilis/alpha/AlphaApplication.kt
  8. 26 0
      app/src/main/java/io/nexilis/alpha/MainActivity.kt
  9. 170 0
      app/src/main/java/io/nexilis/alpha/ui/main/Chat.kt
  10. 110 0
      app/src/main/java/io/nexilis/alpha/ui/main/Chats.kt
  11. 88 0
      app/src/main/java/io/nexilis/alpha/ui/main/Contact.kt
  12. 54 0
      app/src/main/java/io/nexilis/alpha/ui/main/Friend.kt
  13. 66 0
      app/src/main/java/io/nexilis/alpha/ui/main/Home.kt
  14. 163 0
      app/src/main/java/io/nexilis/alpha/ui/main/Main.kt
  15. 113 0
      app/src/main/java/io/nexilis/alpha/ui/main/Menu.kt
  16. 122 0
      app/src/main/java/io/nexilis/alpha/ui/main/Profile.kt
  17. 50 0
      app/src/main/java/io/nexilis/alpha/ui/main/Root.kt
  18. 188 0
      app/src/main/java/io/nexilis/alpha/ui/main/Sign.kt
  19. 23 0
      app/src/main/java/io/nexilis/alpha/ui/screen/Screen.kt
  20. 11 0
      app/src/main/java/io/nexilis/alpha/ui/theme/Color.kt
  21. 64 0
      app/src/main/java/io/nexilis/alpha/ui/theme/Theme.kt
  22. 34 0
      app/src/main/java/io/nexilis/alpha/ui/theme/Type.kt
  23. 20 0
      app/src/main/java/io/nexilis/alpha/ui/viewmodel/MainViewModel.kt
  24. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  25. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  26. 13 0
      app/src/main/res/drawable/ic_placeholder.xml
  27. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  28. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  29. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.webp
  30. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  31. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.webp
  32. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  33. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  34. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  35. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  36. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  37. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  38. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  39. 10 0
      app/src/main/res/values/colors.xml
  40. 4 0
      app/src/main/res/values/strings.xml
  41. 5 0
      app/src/main/res/values/themes.xml
  42. 13 0
      app/src/main/res/xml/backup_rules.xml
  43. 19 0
      app/src/main/res/xml/data_extraction_rules.xml
  44. 6 0
      app/src/main/res/xml/network_security_config.xml
  45. 16 0
      app/src/test/java/io/nexilis/alpha/ExampleUnitTest.kt
  46. 10 0
      build.gradle
  47. 1 0
      cpaas-lite/.gitignore
  48. 62 0
      cpaas-lite/build.gradle
  49. 0 0
      cpaas-lite/consumer-rules.pro
  50. BIN
      cpaas-lite/libs/annotation.jar
  51. 21 0
      cpaas-lite/proguard-rules.pro
  52. 22 0
      cpaas-lite/src/androidTest/java/io/nexilis/service/ExampleInstrumentedTest.kt
  53. 32 0
      cpaas-lite/src/main/AndroidManifest.xml
  54. 102 0
      cpaas-lite/src/main/java/io/nexilis/service/Api.kt
  55. 101 0
      cpaas-lite/src/main/java/io/nexilis/service/SdkCallback.kt
  56. 141 0
      cpaas-lite/src/main/java/io/nexilis/service/Service.kt
  57. 111 0
      cpaas-lite/src/main/java/io/nexilis/service/core/ApiModule.kt
  58. 179 0
      cpaas-lite/src/main/java/io/nexilis/service/core/Data.kt
  59. 332 0
      cpaas-lite/src/main/java/io/nexilis/service/core/Extension.kt
  60. 350 0
      cpaas-lite/src/main/java/io/nexilis/service/core/Incoming.kt
  61. 28 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/BuddyDao.kt
  62. 22 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/CallCenterHistoryDao.kt
  63. 22 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/DiscussionForumDao.kt
  64. 22 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/FollowDao.kt
  65. 22 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/FormDao.kt
  66. 22 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/FormItemDao.kt
  67. 25 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/GroupDao.kt
  68. 25 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/GroupMemberDao.kt
  69. 22 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/InquiryDao.kt
  70. 22 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/LinkPreviewDao.kt
  71. 28 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/MessageDao.kt
  72. 25 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/MessageStatusDao.kt
  73. 25 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/MessageSummaryDao.kt
  74. 31 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/OutgoingDao.kt
  75. 25 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/PrefsDao.kt
  76. 25 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/PullDao.kt
  77. 25 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/ServiceBankDao.kt
  78. 25 0
      cpaas-lite/src/main/java/io/nexilis/service/data/daos/WorkingAreaDao.kt
  79. 82 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/Buddy.kt
  80. 16 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/CallCenterHistory.kt
  81. 26 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/DiscussionForum.kt
  82. 8 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/Follow.kt
  83. 12 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/Form.kt
  84. 13 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/FormItem.kt
  85. 27 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/Group.kt
  86. 18 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/GroupMember.kt
  87. 10 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/Inquiry.kt
  88. 17 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/LinkPreview.kt
  89. 5 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/MainEntity.kt
  90. 67 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/Message.kt
  91. 8 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/MessageFavorite.kt
  92. 24 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/MessageStatus.kt
  93. 10 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/MessageSummary.kt
  94. 16 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/Outgoing.kt
  95. 9 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/Prefs.kt
  96. 12 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/Pull.kt
  97. 12 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/ServiceBank.kt
  98. 11 0
      cpaas-lite/src/main/java/io/nexilis/service/data/entities/WorkingArea.kt
  99. 116 0
      cpaas-lite/src/main/java/io/nexilis/service/data/repositories/BuddyRepository.kt
  100. 17 0
      cpaas-lite/src/main/java/io/nexilis/service/data/repositories/DiscussionForumRepository.kt

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 89 - 0
app/build.gradle

@@ -0,0 +1,89 @@
+plugins {
+    id 'com.android.application'
+    id 'org.jetbrains.kotlin.android'
+    id 'com.google.dagger.hilt.android'
+    id 'kotlin-kapt'
+    id 'com.google.devtools.ksp'
+}
+
+android {
+    namespace 'io.nexilis.alpha'
+    compileSdk 34
+
+    defaultConfig {
+        applicationId "io.nexilis.alpha"
+        minSdk 23
+        targetSdk 34
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        vectorDrawables {
+            useSupportLibrary true
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+    buildFeatures {
+        compose true
+    }
+    composeOptions {
+        kotlinCompilerExtensionVersion '1.5.3'
+    }
+    packagingOptions {
+        resources {
+            excludes += '/META-INF/{AL2.0,LGPL2.1}'
+        }
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.core:core-ktx:1.12.0'
+    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
+    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0"
+    implementation 'androidx.activity:activity-compose:1.8.2'
+    implementation project(path: ':cpaas-lite')
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+
+    implementation 'androidx.navigation:navigation-compose:2.7.6'
+    implementation 'io.coil-kt:coil-compose:2.2.2'
+
+    implementation platform('androidx.compose:compose-bom:2022.10.00')
+    implementation "androidx.compose.ui:ui"
+    implementation "androidx.compose.ui:ui-tooling-preview"
+    implementation 'androidx.compose.material3:material3'
+    implementation "androidx.compose.material:material-icons-extended"
+    implementation "androidx.compose.runtime:runtime"
+    implementation "androidx.compose.runtime:runtime-livedata"
+    debugImplementation "androidx.compose.ui:ui-tooling"
+    debugImplementation "androidx.compose.ui:ui-test-manifest"
+    androidTestImplementation "androidx.compose.ui:ui-test-junit4"
+
+    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"
+    kapt "com.google.dagger:hilt-android-compiler:2.48"
+
+    implementation "androidx.hilt:hilt-navigation-compose:1.1.0"
+
+}
+
+kapt {
+    correctErrorTypes = true
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 22 - 0
app/src/androidTest/java/io/nexilis/alpha/ExampleInstrumentedTest.kt

@@ -0,0 +1,22 @@
+package io.nexilis.alpha
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("io.nexilis.alpha", appContext.packageName)
+    }
+}

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

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+        android:name=".AlphaApplication"
+        android:allowBackup="false"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:networkSecurityConfig="@xml/network_security_config"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.Alpha"
+        tools:targetApi="31">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:label="@string/app_name"
+            android:theme="@style/Theme.Alpha"
+            android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.app.lib_name"
+                android:value="" />
+        </activity>
+    </application>
+
+</manifest>

+ 9 - 0
app/src/main/java/io/nexilis/alpha/AlphaApplication.kt

@@ -0,0 +1,9 @@
+package io.nexilis.alpha
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class AlphaApplication : Application() {
+
+}

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

@@ -0,0 +1,26 @@
+package io.nexilis.alpha
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import dagger.hilt.android.AndroidEntryPoint
+import io.nexilis.alpha.ui.main.Root
+import io.nexilis.alpha.ui.theme.AlphaTheme
+import io.nexilis.service.Api
+
+@AndroidEntryPoint
+class MainActivity : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            AlphaTheme {
+                Root()
+            }
+        }
+        Api.connect(
+            "38747683290F62E9667A018F490396EAE47BC16ADECD85B7E865C733E6DBD6A2",
+            this@MainActivity
+        )
+    }
+}

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

@@ -0,0 +1,170 @@
+package io.nexilis.alpha.ui.main
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Face
+import androidx.compose.material.icons.filled.Send
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import io.nexilis.alpha.ui.viewmodel.MainViewModel
+import io.nexilis.service.data.entities.Message
+import io.nexilis.service.data.viewmodels.BuddyViewModel
+import io.nexilis.service.data.viewmodels.MessageViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Chat(navController: NavHostController, pin: String, mainViewModel: MainViewModel) {
+    var textInput by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
+    }
+    val buddyViewModel: BuddyViewModel = hiltViewModel()
+    val me by buddyViewModel.me.observeAsState()
+    val buddy by buddyViewModel.getBuddy(pin).observeAsState()
+    val messageModel: MessageViewModel = hiltViewModel()
+    val list by messageModel.getOpposite(pin).observeAsState()
+    LaunchedEffect(buddy) {
+        buddy?.first_name?.let {
+            mainViewModel.setTitle(it)
+        }
+    }
+    val state = rememberLazyListState()
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .padding(start = 8.dp, end = 8.dp, bottom = 8.dp)
+    ) {
+        LazyColumn(
+            modifier = Modifier.weight(1.0f),
+            state = state,
+            reverseLayout = true
+        ) {
+            items(count = list?.size ?: 0) {
+                val message = list?.let { l ->
+                    val m = l[it]
+                    m
+                }
+                ListItem(modifier = Modifier.fillMaxWidth(), headlineText = {
+                    Row(
+                        modifier = Modifier.fillMaxWidth(),
+                        horizontalArrangement = if (message?.f_pin == me?.f_pin) Arrangement.End else Arrangement.Start
+                    ) {
+                        if (message?.f_pin == me?.f_pin) {
+                            Spacer(modifier = Modifier.weight(0.2f))
+                            Text(
+                                text = list?.get(it)?.message_text ?: "",
+                                style = MaterialTheme.typography.bodyMedium,
+                                modifier = Modifier
+                                    .graphicsLayer {
+                                        shadowElevation = 2.dp.toPx()
+                                        shape = RoundedCornerShape(16.dp)
+                                        clip = true
+                                    }
+                                    .weight(weight = 0.8f, fill = false)
+                                    .background(color = MaterialTheme.colorScheme.primaryContainer)
+                                    .padding(8.dp)
+                            )
+                        } else {
+                            Text(
+                                text = list?.get(it)?.message_text ?: "",
+                                style = MaterialTheme.typography.bodyMedium,
+                                modifier = Modifier
+                                    .graphicsLayer {
+                                        shadowElevation = 2.dp.toPx()
+                                        shape = RoundedCornerShape(16.dp)
+                                        clip = true
+                                    }
+                                    .weight(weight = 0.8f, fill = false)
+                                    .background(color = MaterialTheme.colorScheme.surface)
+                                    .padding(8.dp)
+                            )
+                            Spacer(modifier = Modifier.weight(0.2f))
+                        }
+                    }
+                })
+            }
+        }
+        TextField(
+            modifier = Modifier
+                .fillMaxWidth()
+                .graphicsLayer {
+                    shadowElevation = 2.dp.toPx()
+                    shape = CircleShape
+                    clip = true
+                }
+                .imePadding(),
+            value = textInput,
+            onValueChange = { textInput = it },
+            label = null,
+            placeholder = {
+                Text(text = "Typing here...")
+            },
+            leadingIcon = {
+                IconButton(onClick = { /*TODO*/ }) {
+                    Icon(imageVector = Icons.Default.Face, contentDescription = "")
+                }
+            },
+            trailingIcon = {
+                Row {
+                    IconButton(onClick = { /*TODO*/ }) {
+                        Icon(imageVector = Icons.Default.Add, contentDescription = "")
+                    }
+                    IconButton(onClick = {
+                        if (pin.trim().isEmpty()) {
+                            return@IconButton
+                        }
+                        if (textInput.text.trim().isEmpty()) {
+                            return@IconButton
+                        }
+                        me?.let {
+                            messageModel.send(
+                                Message(
+                                    message_id = System.nanoTime().toString(),
+                                    f_pin = it.f_pin,
+                                    l_pin = pin,
+                                    message_scope_id = "3", // 3: chat, 15: sms, 16: email
+                                    server_date = System.currentTimeMillis(),
+                                    status = "1",
+                                    message_text = textInput.text,
+                                    opposite_pin = pin,
+                                    f_display_name = it.first_name + " " + it.last_name,
+                                    message_large_text = textInput.text,
+                                    message_text_plain = textInput.text
+                                )
+                            )
+                            textInput = TextFieldValue("")
+                        }
+                    }) {
+                        Icon(imageVector = Icons.Default.Send, contentDescription = "")
+                    }
+                }
+            },
+            colors = TextFieldDefaults.textFieldColors(
+                containerColor = MaterialTheme.colorScheme.primaryContainer,
+                focusedIndicatorColor = Color.Transparent,
+                unfocusedIndicatorColor = Color.Transparent,
+                disabledIndicatorColor = Color.Transparent,
+                errorIndicatorColor = Color.Transparent
+            ),
+            keyboardOptions = KeyboardOptions(KeyboardCapitalization.Sentences),
+            maxLines = 3
+        )
+    }
+}

+ 110 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Chats.kt

@@ -0,0 +1,110 @@
+package io.nexilis.alpha.ui.main
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import coil.compose.AsyncImage
+import coil.request.ImageRequest
+import io.nexilis.alpha.ui.screen.Screen
+import io.nexilis.alpha.ui.viewmodel.MainViewModel
+import io.nexilis.service.data.viewmodels.BuddyViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Chats(
+    navController: NavHostController,
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    mainViewModel: MainViewModel
+) {
+    LaunchedEffect(Unit) {
+        mainViewModel.setTitle("Chats")
+    }
+    val viewModel: BuddyViewModel = hiltViewModel()
+    val all by viewModel.all.observeAsState()
+    var selectedItem by remember { mutableStateOf(0) }
+    LazyColumn(
+        contentPadding = contentPadding,
+    ) {
+        items(
+            count = all?.size ?: 0,
+            key = {
+                all?.get(it)?.f_pin ?: 0
+            }
+        ) { item ->
+            val buddy = all?.get(item)
+            ListItem(
+                modifier = Modifier.selectable(selected = selectedItem == item, onClick = {
+                    selectedItem = item
+                    navController.navigate(Screen.Chat.route + "/${buddy?.f_pin}") {
+                        launchSingleTop = true
+                        restoreState = true
+                    }
+                }),
+                headlineText = {
+                    Text(
+                        buddy?.first_name ?: "First text",
+                        style = MaterialTheme.typography.titleSmall
+                    )
+                },
+                supportingText = {
+                    Text(
+                        buddy?.image_id ?: "Secondary text",
+                        style = MaterialTheme.typography.bodySmall
+                    )
+                },
+                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"
+                                    }
+                                )
+                            }
+                        }) {
+                    }
+                }
+            )
+        }
+    }
+}

+ 88 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Contact.kt

@@ -0,0 +1,88 @@
+package io.nexilis.alpha.ui.main
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import coil.compose.AsyncImage
+import coil.request.ImageRequest
+import io.nexilis.alpha.ui.screen.Screen
+import io.nexilis.alpha.ui.viewmodel.MainViewModel
+import io.nexilis.service.data.viewmodels.BuddyViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Contact(navController: NavHostController, mainViewModel: MainViewModel) {
+    LaunchedEffect(Unit) {
+        mainViewModel.setTitle("Contact")
+    }
+    val viewModel: BuddyViewModel = hiltViewModel()
+    val all by viewModel.all.observeAsState()
+    var selectedItem by remember { mutableStateOf(0) }
+    LazyColumn {
+        items(
+            count = all?.size ?: 0,
+            key = {
+                all?.get(it)?.f_pin ?: 0
+            }
+        ) { item ->
+            val product = all?.get(item)
+            ListItem(
+                modifier = Modifier.selectable(selected = selectedItem == item, onClick = {
+                    selectedItem = item
+                    navController.navigate(Screen.Chat.route + "/${product?.f_pin}") {
+                        launchSingleTop = true
+                        restoreState = true
+                    }
+                }),
+                headlineText = {
+                    Text(
+                        product?.first_name ?: "",
+                        style = MaterialTheme.typography.titleSmall
+                    )
+                },
+                supportingText = {
+                    Text(
+                        "",
+                        style = MaterialTheme.typography.bodySmall
+                    )
+                },
+                leadingContent = {
+                    AsyncImage(
+                        model = ImageRequest.Builder(LocalContext.current)
+                            .data(product?.image_id)
+                            .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)
+                    )
+                }
+            )
+        }
+    }
+}

+ 54 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Friend.kt

@@ -0,0 +1,54 @@
+package io.nexilis.alpha.ui.main
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import io.nexilis.service.data.viewmodels.BuddyViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Friend(navController: NavHostController) {
+    val buddyViewModel: BuddyViewModel = hiltViewModel()
+    var textUsername by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
+    }
+    Column(
+        modifier = Modifier
+            .padding(16.dp)
+            .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.End
+    ) {
+        OutlinedTextField(
+            modifier = Modifier
+                .fillMaxWidth()
+                .imePadding(),
+            value = textUsername,
+            onValueChange = { textUsername = it },
+            label = {
+                Text(text = "pin")
+            },
+            maxLines = 1
+        )
+        Spacer(modifier = Modifier.size(8.dp))
+        Button(onClick = {
+            buddyViewModel.addFriend(textUsername.text)
+        }) {
+            Text(text = "Add")
+        }
+    }
+}

+ 66 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Home.kt

@@ -0,0 +1,66 @@
+package io.nexilis.alpha.ui.main
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import coil.compose.AsyncImage
+import coil.request.ImageRequest
+import io.nexilis.alpha.R
+import io.nexilis.alpha.ui.viewmodel.MainViewModel
+import io.nexilis.service.data.viewmodels.BuddyViewModel
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun Home(
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    mainViewModel: MainViewModel
+) {
+    LaunchedEffect(Unit) {
+        mainViewModel.setTitle("Home")
+    }
+    val viewModel: BuddyViewModel = hiltViewModel()
+    val allProduct by viewModel.all.observeAsState()
+    LazyVerticalGrid(
+        columns = GridCells.Fixed(3),
+        contentPadding = contentPadding,
+        horizontalArrangement = Arrangement.spacedBy(2.dp),
+        verticalArrangement = Arrangement.spacedBy(2.dp),
+        modifier = Modifier
+            .fillMaxWidth()
+            .fillMaxHeight()
+    ) {
+        items(
+            count = allProduct?.size ?: 0,
+            key = {
+                allProduct?.get(it)?.f_pin ?: 0
+            }
+        ) { item ->
+            val product = allProduct?.get(item)
+            val mod = Modifier
+                .animateItemPlacement()
+                .aspectRatio(1f)
+                .fillMaxWidth()
+            AsyncImage(
+                model = ImageRequest.Builder(LocalContext.current)
+                    .data("http://192.168.0.31/image/0298dc5a9f-18498F3634E.webp")
+                    .crossfade(true)
+                    .build(),
+                placeholder = painterResource(R.drawable.ic_placeholder),
+                contentDescription = "",
+                contentScale = ContentScale.Crop,
+                modifier = mod
+            )
+        }
+    }
+}

+ 163 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Main.kt

@@ -0,0 +1,163 @@
+package io.nexilis.alpha.ui.main
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Modifier
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavDestination.Companion.hierarchy
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.NavHostController
+import androidx.navigation.NavType
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navArgument
+import androidx.navigation.navigation
+import io.nexilis.alpha.ui.screen.Screen
+import io.nexilis.alpha.ui.viewmodel.MainViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Main(navController: NavHostController = rememberNavController()) {
+    val coroutineScope = rememberCoroutineScope()
+    val hostState = remember { SnackbarHostState() }
+    val navBackStackEntry by navController.currentBackStackEntryAsState()
+    val currentDestination = navBackStackEntry?.destination
+    var isHome = true
+    currentDestination?.route.let {
+        it?.let {
+            isHome =
+                it == Screen.Home.route || it == Screen.Chats.route || it == Screen.Profile.route
+        }
+    }
+    val mainViewModel: MainViewModel = hiltViewModel()
+    val title by mainViewModel.title.observeAsState("")
+    Scaffold(
+        modifier = Modifier.fillMaxSize(),
+        topBar = {
+            TopAppBar(
+                title = { Text(text = title) },
+                navigationIcon = {
+                    NavigationIcon(navController = navController, isHome = isHome)
+                },
+                actions = {
+                    Actions(navController = navController)
+                }
+            )
+        },
+        bottomBar = {
+            BottomBar(navController = navController, isHome = isHome)
+        },
+        snackbarHost = { SnackbarHost(hostState = hostState) },
+        floatingActionButton = {
+            FloatingActionButton(
+                navController = navController,
+                isHome = isHome
+            )
+        }
+    ) { contentPadding ->
+        NavHost(
+            navController = navController,
+            startDestination = Screen.Home.route,
+            modifier = Modifier.padding(contentPadding),
+            route = Graph.home
+        ) {
+            composable(Screen.Home.route) { Home(mainViewModel = mainViewModel) }
+            composable(Screen.Chats.route) {
+                Chats(
+                    navController = navController,
+                    mainViewModel = mainViewModel
+                )
+            }
+            composable(Screen.Profile.route) { Profile(mainViewModel = mainViewModel) }
+            navigation(startDestination = Screen.Contact.route, route = Graph.child) {
+                composable(Screen.Contact.route) {
+                    Contact(navController = navController, mainViewModel = mainViewModel)
+                }
+                composable(
+                    route = Screen.Chat.route + "/{pin}",
+                    arguments = listOf(navArgument("pin") { type = NavType.StringType })
+                ) {
+                    it.arguments?.getString("pin")
+                        ?.let { pin ->
+                            Chat(
+                                navController = navController,
+                                pin = pin,
+                                mainViewModel = mainViewModel
+                            )
+                        }
+                }
+                composable(Screen.Friend.route) {
+                    Friend(navController = navController)
+                }
+            }
+        }
+    }
+}
+
+@Composable
+fun NavigationIcon(navController: NavHostController, isHome: Boolean) {
+    if (!isHome) IconButton(onClick = {
+        navController.navigateUp()
+    }) {
+        Icon(
+            imageVector = Icons.Default.ArrowBack,
+            contentDescription = ""
+        )
+    }
+}
+
+@Composable
+fun Actions(navController: NavHostController) {
+    HomeMenu(navController = navController)
+}
+
+@Composable
+fun BottomBar(navController: NavHostController, isHome: Boolean) {
+    val navBackStackEntry by navController.currentBackStackEntryAsState()
+    val currentDestination = navBackStackEntry?.destination
+    val barItems = listOf(
+        Screen.Home,
+        Screen.Chats,
+        Screen.Profile
+    )
+    if (isHome) NavigationBar {
+        barItems.forEachIndexed { index, screen ->
+            NavigationBarItem(
+                icon = { Icon(screen.imageVector, contentDescription = screen.title) },
+                label = { Text(screen.title) },
+                selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
+                onClick = {
+                    navController.navigate(screen.route) {
+                        popUpTo(navController.graph.findStartDestination().id) {
+                            saveState = true
+                        }
+                        launchSingleTop = true
+                        restoreState = true
+                    }
+                }
+            )
+        }
+    }
+}
+
+@Composable
+fun FloatingActionButton(
+    navController: NavHostController,
+    isHome: Boolean
+) {
+    if (isHome) Button(onClick = {
+        navController.navigate(Screen.Contact.route) {
+            launchSingleTop = true
+            restoreState = true
+        }
+    }) {
+        Icon(imageVector = Icons.Default.Chat, contentDescription = "")
+    }
+}

+ 113 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Menu.kt

@@ -0,0 +1,113 @@
+package io.nexilis.alpha.ui.main
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Mic
+import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.NavHostController
+import io.nexilis.alpha.ui.screen.Screen
+
+@Composable
+fun HomeMenu(navController: NavHostController) {
+    var isOpenMenu by remember {
+        mutableStateOf(false)
+    }
+    IconButton(onClick = { }) {
+        Icon(
+            imageVector = Icons.Default.Mic,
+            contentDescription = ""
+        )
+    }
+    IconButton(onClick = { isOpenMenu = !isOpenMenu }) {
+        Icon(imageVector = Icons.Default.MoreVert, contentDescription = "")
+    }
+    DropdownMenu(
+        expanded = isOpenMenu,
+        onDismissRequest = { isOpenMenu = false }) {
+        DropdownMenuItem(
+            text = { Text(text = "New group") },
+            onClick = { /*TODO*/ })
+        DropdownMenuItem(
+            text = { Text(text = "Invite friend") },
+            onClick = {
+                isOpenMenu = false
+                navController.navigate(Screen.Friend.route) {
+//                    popUpTo(Screen.Chats.route) {
+//                        saveState = true
+//                    }
+                    launchSingleTop = true
+                    restoreState = true
+                }
+            })
+        DropdownMenuItem(
+            text = { Text(text = "Sign up") },
+            onClick = {
+                isOpenMenu = false
+                navController.navigate(Screen.SignUp.route) {
+                    popUpTo(navController.graph.findStartDestination().id) {
+                        saveState = true
+                    }
+                    launchSingleTop = true
+                    restoreState = true
+                }
+            })
+        DropdownMenuItem(
+            text = { Text(text = "Favorite messages") },
+            onClick = { /*TODO*/ })
+        DropdownMenuItem(
+            text = { Text(text = "Settings") },
+            onClick = { /*TODO*/ })
+    }
+}
+
+@Composable
+fun ContactMenu(navController: NavHostController) {
+    var isOpenMenu by remember {
+        mutableStateOf(false)
+    }
+    IconButton(onClick = { }) {
+        Icon(imageVector = Icons.Default.Search, contentDescription = "")
+    }
+    IconButton(onClick = { isOpenMenu = !isOpenMenu }) {
+        Icon(imageVector = Icons.Default.MoreVert, contentDescription = "")
+    }
+    DropdownMenu(
+        expanded = isOpenMenu,
+        onDismissRequest = { isOpenMenu = false }) {
+        DropdownMenuItem(
+            text = { Text(text = "Add friend") },
+            onClick = {
+                isOpenMenu = false
+                navController.navigate(Screen.Friend.route) {
+                    popUpTo(navController.graph.findStartDestination().id) {
+                        saveState = true
+                    }
+                    launchSingleTop = true
+                    restoreState = true
+                }
+            })
+    }
+}
+
+@Composable
+fun ChatMenu(navController: NavHostController) {
+    var isOpenMenu by remember {
+        mutableStateOf(false)
+    }
+    IconButton(onClick = { }) {
+        Icon(imageVector = Icons.Default.Search, contentDescription = "")
+    }
+    IconButton(onClick = { isOpenMenu = !isOpenMenu }) {
+        Icon(imageVector = Icons.Default.MoreVert, contentDescription = "")
+    }
+    DropdownMenu(
+        expanded = isOpenMenu,
+        onDismissRequest = { isOpenMenu = false }) {
+        DropdownMenuItem(
+            text = { Text(text = "Clear chat") },
+            onClick = { /*TODO*/ })
+    }
+}

+ 122 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Profile.kt

@@ -0,0 +1,122 @@
+package io.nexilis.alpha.ui.main
+
+import android.graphics.Bitmap
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.result.launch
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import coil.compose.rememberAsyncImagePainter
+import coil.request.ImageRequest
+import io.nexilis.alpha.ui.viewmodel.MainViewModel
+import io.nexilis.service.data.viewmodels.BuddyViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Profile(contentPadding: PaddingValues = PaddingValues(0.dp), mainViewModel: MainViewModel) {
+    LaunchedEffect(Unit) {
+        mainViewModel.setTitle("Profile")
+    }
+    var imageUri by remember { mutableStateOf<Bitmap?>(null) }
+    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) {
+        imageUri = it
+    }
+    val viewModel: BuddyViewModel = hiltViewModel()
+    val me by viewModel.me.observeAsState()
+    Column(
+        modifier = Modifier
+            .padding(contentPadding)
+            .padding(start = 16.dp, end = 16.dp)
+            .fillMaxWidth()
+    ) {
+        Box(
+            Modifier.align(Alignment.CenterHorizontally),
+            contentAlignment = Alignment.BottomEnd,
+        ) {
+            val painter: Painter = imageUri?.let {
+                BitmapPainter(it.asImageBitmap())
+            } ?: run {
+                rememberAsyncImagePainter(
+                    model = ImageRequest.Builder(LocalContext.current)
+                        .data("https://digixplatform.com/filepalio/image/${me?.image_id}")
+                        .addHeader("Cookie" , "PHPSESSID=123;MOBILE=123")
+                        .crossfade(true)
+                        .build()
+                )
+            }
+            Image(
+                painter = painter, contentDescription = "", modifier = Modifier
+                    .padding(top = 8.dp, end = 16.dp, bottom = 8.dp)
+                    .clip(CircleShape)
+                    .size(150.dp),
+                contentScale = ContentScale.Crop
+            )
+            IconButton(onClick = {
+                launcher.launch()
+            }) {
+                Icon(
+                    imageVector = Icons.Default.AddCircle,
+                    contentDescription = "",
+                    tint = MaterialTheme.colorScheme.primary
+                )
+            }
+        }
+        Spacer(modifier = Modifier.size(16.dp))
+        ListItem(
+            headlineText = { Text("Name", style = MaterialTheme.typography.bodySmall) },
+            supportingText = {
+                me?.first_name?.let {
+                    Text(
+                        it,
+                        style = MaterialTheme.typography.titleMedium
+                    )
+                }
+            },
+            leadingContent = {
+                Icon(imageVector = Icons.Default.Person, contentDescription = "")
+            }
+        )
+        ListItem(
+            headlineText = { Text("About", style = MaterialTheme.typography.bodySmall) },
+            supportingText = { Text("Yahoo", style = MaterialTheme.typography.titleMedium) },
+            leadingContent = {
+                Icon(imageVector = Icons.Default.Info, contentDescription = "")
+            }
+        )
+        ListItem(
+            headlineText = { Text("Phone", style = MaterialTheme.typography.bodySmall) },
+            supportingText = { Text("08112345678", style = MaterialTheme.typography.titleMedium) },
+            leadingContent = {
+                Icon(imageVector = Icons.Default.Call, contentDescription = "")
+            }
+        )
+        ListItem(
+            headlineText = { Text("Email", style = MaterialTheme.typography.bodySmall) },
+            supportingText = {
+                Text(
+                    "yayandw@gmail.com",
+                    style = MaterialTheme.typography.titleMedium
+                )
+            },
+            leadingContent = {
+                Icon(imageVector = Icons.Default.Email, contentDescription = "")
+            }
+        )
+    }
+}

+ 50 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Root.kt

@@ -0,0 +1,50 @@
+package io.nexilis.alpha.ui.main
+
+import android.util.Log
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navigation
+import io.nexilis.alpha.ui.screen.Screen
+import io.nexilis.service.data.viewmodels.BuddyViewModel
+
+@Composable
+fun Root(navController: NavHostController = rememberNavController()) {
+    val viewModel: BuddyViewModel = hiltViewModel()
+    val me by viewModel.me.observeAsState()
+    val start = if (me == null) Graph.authentication else Graph.home
+    NavHost(navController = navController, route = Graph.root, startDestination = start) {
+        navigation(route = Graph.authentication, startDestination = Screen.SignIn.route) {
+            composable(route = Screen.SignIn.route) {
+                SignIn(navController = navController) {
+                    if (it) {
+                        Log.d("SAPI", ">>>>> got to home")
+                        navController.navigate(Graph.home)
+                    }
+                }
+            }
+            composable(route = Screen.SignUp.route) {
+                SignUp(navController = navController) {
+                    if (it) {
+                        navController.navigate(Graph.home)
+                    }
+                }
+            }
+        }
+        composable(route = Graph.home) {
+            Main()
+        }
+    }
+}
+
+object Graph {
+    const val root = "root_graph"
+    const val authentication = "auth_graph"
+    const val home = "home_graph"
+    const val child = "child_graph"
+}

+ 188 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Sign.kt

@@ -0,0 +1,188 @@
+package io.nexilis.alpha.ui.main
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import io.nexilis.alpha.ui.viewmodel.MainViewModel
+import io.nexilis.service.data.viewmodels.BuddyViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SignUp(
+    navController: NavHostController,
+    mainViewModel: MainViewModel = hiltViewModel(),
+    completion: (Boolean) -> Unit
+) {
+    LaunchedEffect(Unit) {
+        mainViewModel.setTitle("Sign Up")
+    }
+    var textUsername by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
+    }
+    var textPassword by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
+    }
+    var textConfirmPassword by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
+    }
+    var isVisiblePassword by remember {
+        mutableStateOf(false)
+    }
+    var isVisibleConfirmPassword by remember {
+        mutableStateOf(false)
+    }
+    val viewModel: BuddyViewModel = hiltViewModel()
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .padding(start = 16.dp, end = 16.dp)
+            .verticalScroll(
+                rememberScrollState()
+            ),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center
+    ) {
+        OutlinedTextField(
+            modifier = Modifier
+                .fillMaxWidth()
+                .imePadding(),
+            value = textUsername,
+            onValueChange = { textUsername = it },
+            label = {
+                Text(text = "Username")
+            },
+            maxLines = 1
+        )
+        OutlinedTextField(
+            modifier = Modifier
+                .fillMaxWidth()
+                .imePadding(),
+            value = textPassword,
+            onValueChange = { textPassword = it },
+            label = {
+                Text(text = "Password")
+            },
+            visualTransformation = if (isVisiblePassword) VisualTransformation.None
+            else PasswordVisualTransformation(),
+            trailingIcon = {
+                IconButton(onClick = { isVisiblePassword = !isVisiblePassword }) {
+                    Icon(
+                        imageVector = if (isVisiblePassword) Icons.Default.Visibility
+                        else Icons.Default.VisibilityOff,
+                        contentDescription = ""
+                    )
+                }
+            },
+            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
+            maxLines = 1
+        )
+        OutlinedTextField(
+            modifier = Modifier
+                .fillMaxWidth()
+                .imePadding(),
+            value = textConfirmPassword,
+            onValueChange = { textConfirmPassword = it },
+            label = {
+                Text(text = "Confirm password")
+            },
+            visualTransformation = if (isVisibleConfirmPassword) VisualTransformation.None
+            else PasswordVisualTransformation(),
+            trailingIcon = {
+                IconButton(onClick = { isVisibleConfirmPassword = !isVisibleConfirmPassword }) {
+                    Icon(
+                        imageVector = if (isVisibleConfirmPassword) Icons.Default.Visibility
+                        else Icons.Default.VisibilityOff,
+                        contentDescription = ""
+                    )
+                }
+            },
+            maxLines = 1
+        )
+        Spacer(modifier = Modifier.size(8.dp))
+        Button(onClick = {
+            viewModel.signUp(textUsername.text, textConfirmPassword.text, completion)
+        }) {
+            Text(text = "Submit")
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SignIn(navController: NavHostController, completion: (Boolean) -> Unit) {
+    val viewModel: BuddyViewModel = hiltViewModel()
+    var textUsername by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
+    }
+    var textPassword by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
+    }
+    var isVisiblePassword by remember {
+        mutableStateOf(false)
+    }
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .padding(16.dp),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center
+    ) {
+        OutlinedTextField(
+            modifier = Modifier
+                .fillMaxWidth()
+                .imePadding(),
+            value = textUsername,
+            onValueChange = { textUsername = it },
+            label = {
+                Text(text = "Username")
+            },
+            maxLines = 1
+        )
+        OutlinedTextField(
+            modifier = Modifier
+                .fillMaxWidth()
+                .imePadding(),
+            value = textPassword,
+            onValueChange = { textPassword = it },
+            label = {
+                Text(text = "Password")
+            },
+            visualTransformation = if (isVisiblePassword) VisualTransformation.None
+            else PasswordVisualTransformation(),
+            trailingIcon = {
+                IconButton(onClick = { isVisiblePassword = !isVisiblePassword }) {
+                    Icon(
+                        imageVector = if (isVisiblePassword) Icons.Default.Visibility
+                        else Icons.Default.VisibilityOff,
+                        contentDescription = ""
+                    )
+                }
+            },
+            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
+            maxLines = 1
+        )
+        Spacer(modifier = Modifier.size(8.dp))
+        Button(onClick = {
+            viewModel.signIn(textUsername.text, textPassword.text, completion)
+        }) {
+            Text(text = "Submit")
+        }
+    }
+}

+ 23 - 0
app/src/main/java/io/nexilis/alpha/ui/screen/Screen.kt

@@ -0,0 +1,23 @@
+package io.nexilis.alpha.ui.screen
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Chat
+import androidx.compose.material.icons.filled.Email
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.ui.graphics.vector.ImageVector
+
+sealed class Screen(
+    val route: String,
+    val imageVector: ImageVector,
+    val title: String
+) {
+    object Home : Screen("home", Icons.Filled.Home, "Home")
+    object Chats : Screen("chats", Icons.Filled.Chat, "Chats")
+    object Profile : Screen("profile", Icons.Filled.Person, "Profile")
+    object Contact : Screen("contact", Icons.Filled.Email, "Contact")
+    object Chat : Screen("chat", Icons.Filled.Email, "Chat")
+    object SignIn : Screen("sign_in", Icons.Filled.Email, "Sign In")
+    object SignUp : Screen("sign_up", Icons.Filled.Email, "Sign Up")
+    object Friend : Screen("friend", Icons.Filled.Person, "Friend")
+}

+ 11 - 0
app/src/main/java/io/nexilis/alpha/ui/theme/Color.kt

@@ -0,0 +1,11 @@
+package io.nexilis.alpha.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)

+ 64 - 0
app/src/main/java/io/nexilis/alpha/ui/theme/Theme.kt

@@ -0,0 +1,64 @@
+package io.nexilis.alpha.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.ViewCompat
+
+private val DarkColorScheme = darkColorScheme(
+    primary = Purple80,
+    secondary = PurpleGrey80,
+    tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+    primary = Purple40,
+    secondary = PurpleGrey40,
+    tertiary = Pink40
+
+    /* Other default colors to override
+    background = Color(0xFFFFFBFE),
+    surface = Color(0xFFFFFBFE),
+    onPrimary = Color.White,
+    onSecondary = Color.White,
+    onTertiary = Color.White,
+    onBackground = Color(0xFF1C1B1F),
+    onSurface = Color(0xFF1C1B1F),
+    */
+)
+
+@Composable
+fun AlphaTheme(
+    darkTheme: Boolean = isSystemInDarkTheme(),
+    // Dynamic color is available on Android 12+
+    dynamicColor: Boolean = true,
+    content: @Composable () -> Unit
+) {
+    val colorScheme = when {
+        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+            val context = LocalContext.current
+            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+        }
+        darkTheme -> DarkColorScheme
+        else -> LightColorScheme
+    }
+    val view = LocalView.current
+    if (!view.isInEditMode) {
+        SideEffect {
+            (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
+            ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
+        }
+    }
+
+    MaterialTheme(
+        colorScheme = colorScheme,
+        typography = Typography,
+        content = content
+    )
+}

+ 34 - 0
app/src/main/java/io/nexilis/alpha/ui/theme/Type.kt

@@ -0,0 +1,34 @@
+package io.nexilis.alpha.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+    bodyLarge = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 16.sp,
+        lineHeight = 24.sp,
+        letterSpacing = 0.5.sp
+    )
+    /* Other default text styles to override
+    titleLarge = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 22.sp,
+        lineHeight = 28.sp,
+        letterSpacing = 0.sp
+    ),
+    labelSmall = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Medium,
+        fontSize = 11.sp,
+        lineHeight = 16.sp,
+        letterSpacing = 0.5.sp
+    )
+    */
+)

+ 20 - 0
app/src/main/java/io/nexilis/alpha/ui/viewmodel/MainViewModel.kt

@@ -0,0 +1,20 @@
+package io.nexilis.alpha.ui.viewmodel
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class MainViewModel @Inject constructor() : ViewModel() {
+
+    private val _title = MutableLiveData("")
+
+    val title: LiveData<String> get() = _title
+
+    fun setTitle(title: String) {
+        _title.value = title
+    }
+
+}

+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 13 - 0
app/src/main/res/drawable/ic_placeholder.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="400dp"
+    android:height="400dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:name="box"
+        android:pathData="M0.0,0.0 L24.0,0.0"
+        android:strokeWidth="48"
+        android:strokeColor="#CACACA"
+        android:strokeLineCap="square" />
+</vector>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


+ 10 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,4 @@
+<resources>
+    <string name="app_name">Alpha</string>
+    <string name="text_fox">The quick brown fox jumps over the lazy dog</string>
+</resources>

+ 5 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <style name="Theme.Alpha" parent="android:Theme.Material.Light.NoActionBar" />
+</resources>

+ 13 - 0
app/src/main/res/xml/backup_rules.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>

+ 19 - 0
app/src/main/res/xml/data_extraction_rules.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>

+ 6 - 0
app/src/main/res/xml/network_security_config.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+    <domain-config cleartextTrafficPermitted="true">
+        <domain includeSubdomains="true">192.168.0.31</domain>
+    </domain-config>
+</network-security-config>

+ 16 - 0
app/src/test/java/io/nexilis/alpha/ExampleUnitTest.kt

@@ -0,0 +1,16 @@
+package io.nexilis.alpha
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}

+ 10 - 0
build.gradle

@@ -0,0 +1,10 @@
+buildscript {
+
+}// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+    id 'com.android.application' version '8.2.2' apply false
+    id 'com.android.library' version '8.2.2' apply false
+    id 'org.jetbrains.kotlin.android' version '1.9.10' apply false
+    id 'com.google.dagger.hilt.android' version '2.48' apply false
+    id 'com.google.devtools.ksp' version '1.9.10-1.0.13' apply false
+}

+ 1 - 0
cpaas-lite/.gitignore

@@ -0,0 +1 @@
+/build

+ 62 - 0
cpaas-lite/build.gradle

@@ -0,0 +1,62 @@
+plugins {
+    id 'com.android.library'
+    id 'org.jetbrains.kotlin.android'
+    id 'com.google.dagger.hilt.android'
+    id 'kotlin-kapt'
+    id 'com.google.devtools.ksp'
+}
+
+android {
+    namespace 'io.nexilis.service'
+    compileSdk 34
+
+    defaultConfig {
+        minSdk 23
+        targetSdk 34
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.core:core-ktx:1.12.0'
+    implementation files('libs/annotation.jar')
+
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+
+    implementation "androidx.room:room-runtime:2.6.1"
+    annotationProcessor("androidx.room:room-compiler:2.6.1")
+    ksp "androidx.room:room-compiler:2.6.1"
+    implementation "androidx.room:room-ktx:2.6.1"
+
+    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
+    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0"
+
+    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"
+    kapt "com.google.dagger:hilt-android-compiler:2.48"
+}
+
+kapt {
+    correctErrorTypes = true
+}

+ 0 - 0
cpaas-lite/consumer-rules.pro


BIN
cpaas-lite/libs/annotation.jar


+ 21 - 0
cpaas-lite/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 22 - 0
cpaas-lite/src/androidTest/java/io/nexilis/service/ExampleInstrumentedTest.kt

@@ -0,0 +1,22 @@
+package io.nexilis.service
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("io.nexilis.service.test", appContext.packageName)
+    }
+}

+ 32 - 0
cpaas-lite/src/main/AndroidManifest.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+
+    <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.BOOT_COMPLETED" />
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+                <action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
+                <action android:name="android.net.wifi.STATE_CHANGE" />
+                <action android:name="android.location.PROVIDERS_CHANGED" />
+                <action android:name="android.intent.action.SCREEN_ON" />
+                <action android:name="android.intent.action.SCREEN_OFF" />
+                <action android:name="io.newuniverse.SDK.nuSDKReceiver.KEEP_ALIVE" />
+                <action android:name="android.intent.action.ACTION_SHUTDOWN" />
+            </intent-filter>
+        </receiver>
+        <service
+            android:name="io.newuniverse.SDK.nuSDKService"
+            android:enabled="true"
+            android:exported="false"
+            android:foregroundServiceType="microphone|camera|mediaProjection" />
+    </application>
+</manifest>

+ 102 - 0
cpaas-lite/src/main/java/io/nexilis/service/Api.kt

@@ -0,0 +1,102 @@
+package io.nexilis.service
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import io.newuniverse.SDK.nuSDKService
+import io.nexilis.service.core.Data
+import io.nexilis.service.core.optString
+import io.nexilis.service.core.put
+import java.security.SecureRandom
+import java.util.UUID
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+class Api {
+
+    companion object {
+        internal val lock = ReentrantLock()
+
+        internal var condition: Condition? = null
+
+        fun connect(account: String, activity: Activity) {
+            Log.d(tag, "connect:$account")
+            Thread {
+                try {
+                    activity.startService(Intent(activity, nuSDKService::class.java))
+                    while (nuSDKService.getInstance(pass) == null || !nuSDKService.getInstance(pass)
+                            .bCLMServiceReady()
+                    ) {
+                        Thread.sleep(100)
+                    }
+                    val preferences = activity.getSharedPreferences(
+                        namePreference,
+                        Context.MODE_PRIVATE
+                    )
+                    val random = SecureRandom().nextInt(9 + 1 - 1) + 1
+                    val user = preferences.optString(
+                        key = "pin", default = random.toString()
+                    )
+                    Log.d(tag, "initConnection:$pass,$account,$activity,$user")
+                    nuSDKService.getInstance(pass).initConnection(
+                        pass,
+                        account,
+                        activity,
+                        SdkCallback::class.java,
+                        SdkCallback(),
+                        "108.136.37.34",
+                        42823,
+                        user,
+                        "09:00",
+                        false,
+                        "108.136.37.34:42823"
+                    )
+                    val pin = preferences.optString(key = "pin", default = "")
+                    if (pin.isEmpty()) {
+                        condition = lock.newCondition()
+                        lock.withLock {
+                            Log.d(tag, "wait connection")
+                            condition?.let {
+                                val await = it.await(15, TimeUnit.SECONDS)
+                                Log.d(tag, "signup:start:$await")
+                                val response = Service.sendSync(
+                                    Data(
+                                        code = "SUA01",
+                                        status = System.nanoTime().toString(),
+                                        f_pin = user,
+                                        bodies = mutableMapOf("Api" to account, "A92" to "yayandw")
+                                    )
+                                )
+                                Log.d(tag, "signup:response:$response")
+                                response?.let { r ->
+                                    Log.d(tag, "signup:isOk:${r.isOk()}")
+                                    if (r.isOk()) {
+                                        val p = r.bodies["A00"] ?: ""
+                                        Log.d(tag, "signup:put:pin:$p")
+                                        preferences.put("pin", p)
+                                        Log.d(tag, "signup:changeUser:$p")
+                                        nuSDKService.getInstance(pass).changeUser(pass, p)
+                                        Log.d(tag, "signup:retrieve")
+                                        Service.sendAsync(
+                                            Data(
+                                                code = "A050",
+                                                status = System.nanoTime().toString(),
+                                                f_pin = user
+                                            )
+                                        )
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    Log.d(tag, "user pin: ${preferences.optString(key = "pin", default = "")}")
+                } catch (e: Exception) {
+                    Log.e(tag, e.message, e)
+                }
+            }.start()
+        }
+    }
+}

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

@@ -0,0 +1,101 @@
+package io.nexilis.service
+
+import android.util.Log
+import io.newuniverse.SDK.nuSDKCallBack
+import io.nexilis.service.core.Data
+import io.nexilis.service.core.Incoming
+import kotlin.concurrent.withLock
+
+class SdkCallback : nuSDKCallBack {
+
+    override fun connectionStateChanged(
+        p0: String?,
+        p1: String?,
+        p2: Boolean,
+        p3: Int,
+        p4: Int,
+        p5: Byte
+    ) {
+        Log.d(tag, "connectionStateChanged:$p0:$p1:$p2:$p3:$p4:$p5")
+        try {
+            Api.condition?.let {
+                Api.lock.withLock {
+                    if (p5 == "1".toByte()) it.signal()
+                }
+            }
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+    }
+
+    override fun gpsStateChanged(p0: Int) {
+
+    }
+
+    override fun sleepStateChanged(p0: Boolean) {
+
+    }
+
+    override fun callStateChanged(p0: Int, p1: String?): Int {
+        return 0
+    }
+
+    override fun bcastStateChanged(p0: Int, p1: String?): Int {
+        return 0
+    }
+
+    override fun sshareStateChanged(p0: Int, p1: String?): Int {
+        return 0
+    }
+
+    override fun incomingData(p0: String?, p1: Any?) {
+        Log.d(tag, "incomingData:$p0:$p1")
+        try {
+            p1?.let {
+                val data = Data()
+                when (it) {
+                    is String -> {
+                        data.parse(it)
+                    }
+                    is ByteArray -> {
+                        data.parse(it)
+                    }
+                    else -> {
+                        Log.d(tag, "unknown:$p0:$p1")
+                        return
+                    }
+                }
+                p0?.let { s ->
+                    data.packetId = s
+                }
+                Incoming.getInstance().process(data)
+            }
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+    }
+
+    override fun lateResponse(p0: String?, p1: String?) {
+
+    }
+
+    override fun asycnACKReceived(p0: String?) {
+
+    }
+
+    override fun locationUpdated(p0: Long, p1: String?) {
+
+    }
+
+    override fun imageData(p0: ByteArray?) {
+
+    }
+
+    override fun initSelfieSegmentation(p0: Int, p1: Int) {
+
+    }
+
+    override fun resetDB() {
+
+    }
+}

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

@@ -0,0 +1,141 @@
+package io.nexilis.service
+
+import android.content.Context
+import android.util.Log
+import io.newuniverse.SDK.nuSDKService
+import io.nexilis.service.core.Data
+import io.nexilis.service.core.decrypt
+import io.nexilis.service.core.optString
+import kotlinx.coroutines.*
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+private val passArray = charArrayOf(
+    '6',
+    'g',
+    'o',
+    'y',
+    'k',
+    't',
+    'u',
+    'j',
+    't',
+    'O',
+    'Q',
+    'J',
+    'Y',
+    '0',
+    't',
+    '9',
+    'z',
+    'l',
+    'u',
+    'Y',
+    'g',
+    'o',
+    '1',
+    '8',
+    '4',
+    'y',
+    'g',
+    'k',
+    '7',
+    'y',
+    'y',
+    'k',
+    'i',
+    'i',
+    'g'
+)
+
+private const val DEFAULT_TIMEOUT: Long = 30 * 1000
+
+
+internal val pass = String(passArray).decrypt()
+
+internal const val namePreference = "cpaas"
+
+internal const val tag = "SAPI"
+
+internal val context: Context by lazy { nuSDKService.getInstance(pass).applicationContext }
+
+internal val apiScope = CoroutineScope(SupervisorJob())
+
+class Service {
+
+    companion object {
+
+        fun sendSync(data: Data): Data? {
+            try {
+                Log.d(tag, "Service:sGetResponse:$data")
+                val response = nuSDKService.getInstance(pass)
+                    .sGetResponse(pass, data.toString(), DEFAULT_TIMEOUT, false)
+                    ?: return null
+                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")
+                var d: Any? = data.toString()
+                if (data.media.isNotEmpty()) {
+                    d = data.toByteArray()
+                }
+                return nuSDKService.getInstance(pass).sSend(pass, d, 1, DEFAULT_TIMEOUT)
+            } catch (e: Exception) {
+                Log.e(tag, e.message, e)
+            }
+            return null
+        }
+
+        val map = ConcurrentHashMap<String, suspend CoroutineScope.(Data) -> Unit>()
+
+        fun sendAsync(data: Data, callback: suspend CoroutineScope.(Data) -> Unit) {
+            map[data.status] = callback
+            sendAsync(data)
+        }
+
+        fun response(data: Data): String? {
+            try {
+                Log.d(tag, "Service:sSendResponse:$data")
+                return nuSDKService.getInstance(pass)
+                    .sSendResponse(pass, data.packetId, data.toString(), DEFAULT_TIMEOUT)
+            } catch (e: Exception) {
+                Log.e(tag, e.message, e)
+            }
+            return null
+        }
+
+        fun sendAck(id: String) {
+            try {
+                Log.d(tag, "Service:sendAck:$id")
+                val preferences = context.getSharedPreferences(
+                    namePreference,
+                    Context.MODE_PRIVATE
+                )
+                val pin = preferences.optString(key = "pin", default = "")
+                if (pin.isEmpty()) return
+                sendAsync(
+                    Data(
+                        code = "A02",
+                        status = id,
+                        f_pin = pin,
+                        bodies = mutableMapOf("A18" to id)
+                    )
+                )
+            } catch (e: Exception) {
+                Log.e(tag, e.message, e)
+            }
+        }
+
+    }
+
+}

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

@@ -0,0 +1,111 @@
+package io.nexilis.service.core
+
+import android.content.Context
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import io.nexilis.service.data.daos.*
+import io.nexilis.service.data.rooms.ApiRoomDatabase
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+object ApiModule {
+
+    @Provides
+    @Singleton
+    fun provideDatabase(@ApplicationContext context: Context): ApiRoomDatabase =
+        ApiRoomDatabase.getDatabase(context)
+
+    @Provides
+    fun provideBuddyDao(database: ApiRoomDatabase): BuddyDao {
+        return database.buddyDao()
+    }
+
+    @Provides
+    fun provideCallCenterHistoryDao(database: ApiRoomDatabase): CallCenterHistoryDao {
+        return database.callCenterHistoryDao()
+    }
+
+    @Provides
+    fun provideDiscussionForumDao(database: ApiRoomDatabase): DiscussionForumDao {
+        return database.discussionForumDao()
+    }
+
+    @Provides
+    fun provideFollowDao(database: ApiRoomDatabase): FollowDao {
+        return database.followDao()
+    }
+
+    @Provides
+    fun provideFormDao(database: ApiRoomDatabase): FormDao {
+        return database.formDao()
+    }
+
+    @Provides
+    fun provideFormItemDao(database: ApiRoomDatabase): FormItemDao {
+        return database.formItemDao()
+    }
+
+    @Provides
+    fun provideGroupDao(database: ApiRoomDatabase): GroupDao {
+        return database.groupDao()
+    }
+
+    @Provides
+    fun provideGroupMemberDao(database: ApiRoomDatabase): GroupMemberDao {
+        return database.groupMemberDao()
+    }
+
+    @Provides
+    fun provideInquiryDao(database: ApiRoomDatabase): InquiryDao {
+        return database.inquiryDao()
+    }
+
+    @Provides
+    fun provideLinkPreviewDao(database: ApiRoomDatabase): LinkPreviewDao {
+        return database.linkPreviewDao()
+    }
+
+    @Provides
+    fun provideMessageDao(database: ApiRoomDatabase): MessageDao {
+        return database.messageDao()
+    }
+
+    @Provides
+    fun provideMessageStatusDao(database: ApiRoomDatabase): MessageStatusDao {
+        return database.messageStatusDao()
+    }
+
+    @Provides
+    fun provideMessageSummaryDao(database: ApiRoomDatabase): MessageSummaryDao {
+        return database.messageSummaryDao()
+    }
+
+    @Provides
+    fun provideOutgoingDao(database: ApiRoomDatabase): OutgoingDao {
+        return database.outgoingDao()
+    }
+
+    @Provides
+    fun providePrefsDao(database: ApiRoomDatabase): PrefsDao {
+        return database.prefsDao()
+    }
+
+    @Provides
+    fun providePullDao(database: ApiRoomDatabase): PullDao {
+        return database.pullDao()
+    }
+
+    @Provides
+    fun provideServiceBankDao(database: ApiRoomDatabase): ServiceBankDao {
+        return database.serviceBankDao()
+    }
+
+    @Provides
+    fun provideWorkingAreaDao(database: ApiRoomDatabase): WorkingAreaDao {
+        return database.workingAreaDao()
+    }
+}

+ 179 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/Data.kt

@@ -0,0 +1,179 @@
+package io.nexilis.service.core
+
+import android.util.Log
+import java.util.concurrent.ConcurrentHashMap
+
+data class Data(
+    var type: String = "",
+    var version: String = "1.0.6",
+    var code: String = "",
+    var status: String = "",
+    var f_pin: String = "",
+    var l_pin: String = "",
+    var bodies: MutableMap<String, String> = mutableMapOf(),
+    var media: ByteArray = ByteArray(0)
+) {
+
+    private val tag: String = "Data"
+
+    private val charHeader = 0x01.toChar()
+    private val charEntry = 0x02.toChar()
+    private val charKeyVal = 0x03.toChar()
+    private val charArray = 0x04.toChar()
+
+    val stringHeader = charHeader.toString()
+    val stringEntry = charEntry.toString()
+    val stringKeyVal = charKeyVal.toString()
+    val stringArray = charArray.toString()
+
+    fun parse(string: String): Boolean {
+        var result = false
+        try {
+            string.split(stringHeader, ignoreCase = false, limit = 8).also {
+                if (it.size == 8) {
+                    type = it[0]
+                    version = it[1]
+                    code = it[2]
+                    status = it[3]
+                    f_pin = it[4]
+                    l_pin = it[5]
+                    bodies = parseBodies(it[6])
+                    media = it[7].toByteArray(Charsets.ISO_8859_1)
+                    result = true
+                }
+            }
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+        return result
+    }
+
+    fun parse(byteArray: ByteArray): Boolean {
+        var result = false
+        try {
+            val data = parseHeader(byteArray)
+            data.split(stringHeader, ignoreCase = false, limit = 7).also {
+                if (it.size == 7) {
+                    type = it[0]
+                    version = it[1]
+                    code = it[2]
+                    status = it[3]
+                    f_pin = it[4]
+                    l_pin = it[5]
+                    bodies = parseBodies(it[6])
+                }
+                media = byteArray.drop(data.length + 1).toByteArray()
+                result = true
+            }
+
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+        return result
+    }
+
+    private fun parseHeader(byteArray: ByteArray): String {
+        var string = ""
+        try {
+            var index = 0
+            for (element in byteArray) {
+                val c = element.toInt().toChar()
+                if (c == charHeader) {
+                    if (++index == 7) {
+                        break
+                    }
+                }
+                string += c
+            }
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+        return string
+    }
+
+    private fun parseBodies(string: String): ConcurrentHashMap<String, String> {
+        val concurrentHashMap: ConcurrentHashMap<String, String> = ConcurrentHashMap()
+        try {
+            string.split(stringEntry).also {
+                for (i in it) {
+                    val keyVal = i.split(stringKeyVal, ignoreCase = false, limit = 2)
+                    if (keyVal.size == 2) {
+                        concurrentHashMap[keyVal[0]] = keyVal[1]
+                    }
+                }
+            }
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+        return concurrentHashMap
+    }
+
+    private fun toBodies(): String {
+        var result = ""
+        for (m in bodies.entries) {
+            result += m.key + stringKeyVal + m.value + stringEntry
+        }
+        return result
+    }
+
+    override fun toString(): String {
+        return type + stringHeader + version + stringHeader + code + stringHeader + status + stringHeader + f_pin + stringHeader + l_pin + stringHeader + toBodies() + stringHeader + String(
+            media,
+            Charsets.ISO_8859_1
+        )
+    }
+
+    fun toByteArray(): ByteArray {
+        return (type + stringHeader + version + stringHeader + code + stringHeader + status + stringHeader + f_pin + stringHeader + l_pin + stringHeader + toBodies() + stringHeader).toByteArray() + media
+    }
+
+    fun isOk(): Boolean {
+        return (bodies["A97"] ?: "") == "00"
+    }
+
+    var packetId: String = ""
+
+    fun responseOk(): Data {
+        bodies.clear()
+        bodies["A97"] = "00"
+        media = ByteArray(0)
+        return this
+    }
+
+    fun responseNok(): Data {
+        bodies.clear()
+        bodies["A97"] = "10"
+        media = ByteArray(0)
+        return this
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as Data
+
+        if (type != other.type) return false
+        if (version != other.version) return false
+        if (code != other.code) return false
+        if (status != other.status) return false
+        if (f_pin != other.f_pin) return false
+        if (l_pin != other.l_pin) return false
+        if (bodies != other.bodies) return false
+        if (!media.contentEquals(other.media)) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = type.hashCode()
+        result = 31 * result + version.hashCode()
+        result = 31 * result + code.hashCode()
+        result = 31 * result + status.hashCode()
+        result = 31 * result + f_pin.hashCode()
+        result = 31 * result + l_pin.hashCode()
+        result = 31 * result + bodies.hashCode()
+        result = 31 * result + media.contentHashCode()
+        return result
+    }
+}

+ 332 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/Extension.kt

@@ -0,0 +1,332 @@
+package io.nexilis.service.core
+
+import android.content.Context
+import android.content.SharedPreferences
+import io.nexilis.service.namePreference
+import java.net.URLDecoder
+import java.net.URLEncoder
+import java.nio.charset.StandardCharsets
+import java.security.MessageDigest
+import java.security.SecureRandom
+
+fun String.decrypt(): String {
+    return Secret().decrypt(this)
+}
+
+fun String.encrypt(): String {
+    return Secret().encrypt(this)
+}
+
+fun SharedPreferences.optString(key: String, default: String): String {
+    return this.getString(key, default) ?: default
+}
+
+fun SharedPreferences.put(key: String, value: String) {
+    with(this.edit()) {
+        putString(key, value)
+        apply()
+    }
+}
+
+fun String.toMD5(): String {
+    val md = MessageDigest.getInstance("MD5")
+    val hashInBytes = md.digest(this.toByteArray(StandardCharsets.UTF_8))
+
+    val sb = StringBuilder()
+    for (b in hashInBytes) {
+        sb.append(String.format("%02x", b))
+    }
+    return sb.toString()
+}
+
+fun String.toNormalString(): String {
+    return URLDecoder.decode(this, "UTF-8").replace("<NL>", "\n").replace("<CR>", "\r")
+}
+
+fun String.toStupidString(): String {
+    return URLEncoder.encode(this, "UTF-8").replace("\n", "<NL>").replace("\n", "<CR>")
+}
+
+fun Context.getSharedPreferences(): SharedPreferences {
+    return getSharedPreferences(
+        namePreference,
+        Context.MODE_PRIVATE
+    )
+}
+
+class Secret {
+    private val iBB = 48 // 0
+
+    private val iBBT1 = 57 // 9
+
+    private val iBAT1 = 65 // A
+
+    private val iBBT2 = 90 // Z
+
+    private val iBAT2 = 97 // a
+
+    private val iBA = 122 // z
+
+
+    private val icBB = 33 // !
+
+    private val icBBT1 = 47 // /
+
+    private val icBAT1 = 58 // :
+
+    private val icBBT2 = 64 // @
+
+    private val icBAT2 = 91 // [
+
+    private val icBBT3 = 96 // @
+
+    private val icBAT3 = 123 // [
+
+    private val icBA = 126 // `
+
+    private val icIGNORE: HashSet<Int> = object : HashSet<Int>() {
+        init {
+            add(10) // \r
+            add(13) // \n
+            add(32) // <space>
+        }
+    }
+
+    /**
+     * Get random number with specific ranges
+     *
+     * @return a number
+     */
+    private fun getRandomNumber(): Int {
+        val gen = SecureRandom()
+        return gen.nextInt(9 + 1 - 1) + 1
+    }
+
+    private fun isSpecialChar(ch: Char): Boolean {
+        return ch.code >= icBB && ch.code <= icBBT1 || ch.code >= icBAT1 && ch.code <= icBBT2 || ch.code >= icBAT2 && ch.code <= icBBT3 || ch.code >= icBAT3 && ch.code <= icBA
+    }
+
+    /**
+     * Encrypts a string with random number index as token at first char
+     *
+     * @param str String to encrypt
+     * @return encrypted string
+     */
+    fun encrypt(string: String): String {
+        var str = string
+        val arr: CharArray = str.toCharArray()
+        val iRandom = getRandomNumber()
+        for (i in arr.indices) {
+            try {
+                if (isSpecialChar(arr[i])) {
+                    arr[i] = getNextcChar(arr[i], iRandom)
+                } else {
+                    arr[i] = getNextChar(arr[i], iRandom)
+                }
+            } catch (exception: Exception) {
+                exception.printStackTrace()
+                return "-1"
+            }
+        }
+        str = String(arr)
+        str = iRandom.toString() + getPalindrom(str)
+        return str
+    }
+
+    /**
+     * Decrypts a string
+     *
+     * @param str String to decrypt
+     * @return decrypted string
+     */
+    fun decrypt(str: String): String {
+        val arr: CharArray
+        var iRandom = 0
+        val sDecrypt: String
+        try {
+            iRandom = str.substring(0, 1).toInt()
+        } catch (ignored: NumberFormatException) {
+        }
+        sDecrypt = getPalindrom(str.substring(1))
+        arr = sDecrypt.toCharArray()
+        for (i in arr.indices) {
+            try {
+                if (isSpecialChar(arr[i])) {
+                    arr[i] = getBeforecChar(arr[i], iRandom)
+                } else {
+                    arr[i] = getBeforeChar(arr[i], iRandom)
+                }
+            } catch (exception: Exception) {
+                return "-1"
+            }
+        }
+        return String(arr)
+    }
+
+    /**
+     * Shift up a char with given number
+     *
+     * @param ch  Given char to shift
+     * @param inc Number to be shift
+     * @return shifted char
+     * @throws Exception
+     */
+    @Throws(Exception::class)
+    private fun getNextChar(ch: Char, inc: Int): Char {
+        var iAscii = ch.code
+        val iAsciiBefore = iAscii
+        if (icIGNORE.contains(iAscii)) {
+            return iAscii.toChar()
+        }
+        if (iAscii > iBA || iAscii < iBB) {
+            throw Exception("Unsupported Character")
+        } else {
+            iAscii = iAscii + inc
+            //jika diantara karakter ignore 1
+            if (iBBT1 < iAscii && iAsciiBefore <= iBBT1) {
+                iAscii = iBAT1 - 1 + (iAscii - iBBT1)
+            }
+            //jika diantara karakter ignore 2
+            if (iBBT2 < iAscii && iAsciiBefore <= iBBT2) {
+                iAscii = iBAT2 - 1 + (iAscii - iBBT2)
+            }
+            //jika lebih dari batas atas
+            if (iAscii > iBA) {
+                iAscii = iBB - 1 + (iAscii - iBA)
+            }
+        }
+        return iAscii.toChar()
+    }
+
+    /**
+     * Shift up a char with given number
+     *
+     * @param ch  Given char to shift
+     * @param inc Number to be shift
+     * @return shifted char
+     * @throws Exception
+     */
+    @Throws(Exception::class)
+    private fun getNextcChar(ch: Char, inc: Int): Char {
+        var iAscii = ch.code
+        val iAsciiBefore = iAscii
+        if (iAscii > icBA || iAscii < icBB) {
+            throw Exception("Unsupported Character")
+        } else {
+            iAscii = iAscii + inc
+            //jika diantara karakter ignore 1
+            if (icBBT1 < iAscii && iAsciiBefore <= icBBT1) {
+                iAscii = icBAT1 - 1 + (iAscii - icBBT1)
+            }
+            //jika diantara karakter ignore 2
+            if (icBBT2 < iAscii && iAsciiBefore <= icBBT2) {
+                iAscii = icBAT2 - 1 + (iAscii - icBBT2)
+            }
+            //jika diantara karakter ignore 2
+            if (icBBT3 < iAscii && iAsciiBefore <= icBBT3) {
+                iAscii = icBAT3 - 1 + (iAscii - icBBT3)
+            }
+            //jika lebih dari batas atas
+            if (iAscii > icBA) {
+                iAscii = icBB - 1 + (iAscii - icBA)
+            }
+        }
+        return iAscii.toChar()
+    }
+
+    /**
+     * Shift down a char with given number
+     *
+     * @param ch  Given char to shift
+     * @param inc Number to be shift
+     * @return shifted char
+     * @throws Exception
+     */
+    @Throws(Exception::class)
+    private fun getBeforeChar(ch: Char, inc: Int): Char {
+        var iAscii = ch.code
+        val iAsciiBefore = iAscii
+        if (icIGNORE.contains(iAscii)) {
+            return iAscii.toChar()
+        }
+        if (iAscii > iBA || iAscii < iBB) {
+            throw Exception()
+        } else {
+            if (!icIGNORE.contains(iAscii)) {
+                iAscii = iAscii - inc
+                //jika diantara karakter ignore 1
+                if (iBAT1 > iAscii && iAsciiBefore >= iBAT1) {
+                    iAscii = iBBT1 + 1 - (iBAT1 - iAscii)
+                }
+                //jika diantara karakter ignore 2
+                if (iBAT2 > iAscii && iAsciiBefore >= iBAT2) {
+                    iAscii = iBBT2 + 1 - (iBAT2 - iAscii)
+                }
+                if (iAscii < iBB) {
+                    iAscii = iBA + 1 + (iAscii - iBB)
+                }
+            }
+        }
+        return iAscii.toChar()
+    }
+
+    /**
+     * Shift down a char with given number
+     *
+     * @param ch  Given char to shift
+     * @param inc Number to be shift
+     * @return shifted char
+     * @throws Exception
+     */
+    @Throws(Exception::class)
+    private fun getBeforecChar(ch: Char, inc: Int): Char {
+        var iAscii = ch.code
+        val iAsciiBefore = iAscii
+        if (iAscii > icBA || iAscii < icBB) {
+            throw Exception()
+        } else {
+            iAscii = iAscii - inc
+            if (iAscii < icBB) {
+                iAscii = icBA + 1 + (iAscii - icBB)
+                //jika diantara karakter ignore 3
+                if (iAscii < icBAT3 && iAscii > icBBT3) {
+                    iAscii = icBBT3 + 1 - (icBAT3 - iAscii)
+                }
+            }
+
+            //jika diantara karakter ignore 3
+            if (icBAT3 > iAscii && iAsciiBefore >= icBAT3) {
+                iAscii = icBBT3 + 1 - (icBAT3 - iAscii)
+                //jika diantara karakter ignore 2
+//                if (icBAT2 > iAscii && iAsciiBefore >= icBAT2) {
+//                    iAscii = (icBBT2 + 1) - (icBAT2 - iAscii);
+//                }
+            }
+
+            //jika diantara karakter ignore 2
+            if (icBAT2 > iAscii && iAsciiBefore >= icBAT2) {
+                iAscii = icBBT2 + 1 - (icBAT2 - iAscii)
+            }
+            //jika diantara karakter ignore 1
+            if (icBAT1 > iAscii && iAsciiBefore >= icBAT1) {
+                iAscii = icBBT1 + 1 - (icBAT1 - iAscii)
+            }
+        }
+        return iAscii.toChar()
+    }
+
+    /**
+     * Reverses order of a string
+     *
+     * @param str
+     * @return reversed order of string
+     */
+    private fun getPalindrom(str: String): String {
+        val arr = str.toCharArray()
+        val arr2 = arr.clone()
+        for (i in arr.indices) {
+            arr2[i] = arr[arr.size - (i + 1)]
+        }
+        return String(arr2)
+    }
+}

+ 350 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/Incoming.kt

@@ -0,0 +1,350 @@
+package io.nexilis.service.core
+
+import android.util.Log
+import io.nexilis.service.Service
+import io.nexilis.service.apiScope
+import io.nexilis.service.context
+import io.nexilis.service.data.entities.Buddy
+import io.nexilis.service.data.entities.Message
+import io.nexilis.service.data.rooms.ApiRoomDatabase
+import io.nexilis.service.tag
+import kotlinx.coroutines.launch
+import org.json.JSONArray
+import java.util.zip.ZipInputStream
+
+class Incoming private constructor() {
+
+    companion object {
+
+        @Volatile
+        private var INSTANCE: Incoming? = null
+
+        fun getInstance(): Incoming {
+            return INSTANCE ?: synchronized(this) {
+                val instance = Incoming()
+                INSTANCE = instance
+                instance
+            }
+        }
+    }
+
+    fun process(data: Data) {
+        Log.d(tag, "process(${data.code}):$data")
+        apiScope.launch {
+            val callback = Service.map.remove(data.status)
+            if (callback != null) {
+                callback.invoke(this, data)
+                return@launch
+            }
+            when (data.code) {
+                "LF" -> {
+                    loginFile(data)
+                }
+                "A037", "A037A" -> {
+                    pushMySelf(data)
+                }
+                "A0052", "IBB", "A004" -> {
+                    pushBuddies(data)
+                }
+                "S0" -> {
+                    pushMessage(data)
+                }
+                "INQ" -> {
+                    inquiryMessage(data)
+                }
+                else -> {
+                    Log.d(tag, "unprocessed:$data")
+                }
+            }
+        }
+    }
+
+    private fun loginFile(data: Data) {
+        try {
+            if (data.media.isNotEmpty()) {
+                ZipInputStream(data.media.inputStream()).use { inputStream ->
+                    generateSequence { inputStream.nextEntry }.filterNot { it.isDirectory }
+                        .map { inputStream.bufferedReader().readText() }.toList()
+                }.forEach {
+                    it.lines().forEach { line ->
+                        Log.d(tag, "loginFile >>>> $line")
+                        val d = Data()
+                        if (d.parse(line)) {
+                            process(d)
+                        } else {
+                            Log.d(tag, "unknown data:$line")
+                        }
+                    }
+                }
+                Service.response(data.responseOk())
+                Service.sendAsync(
+                    Data(
+                        code = "IFF",
+                        status = System.nanoTime().toString(),
+                        f_pin = data.f_pin
+                    )
+                )
+            }
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+            Service.response(data.responseNok())
+        }
+    }
+
+    private suspend fun pushMySelf(data: Data) {
+        try {
+            ApiRoomDatabase.getDatabase(context).buddyDao().insert(
+                Buddy(
+                    data.bodies["A00"] ?: "",
+                    data.bodies["A23"] ?: "",
+                    data.bodies["A02"] ?: "",
+                    data.bodies["A03"] ?: "",
+                    data.bodies["A74"] ?: "",
+                    data.bodies["B7"] ?: "",
+                    data.bodies["A16"] ?: "",
+                    data.bodies["A10"] ?: "",
+                    data.bodies["A11"] ?: "",
+                    data.bodies["A12"] ?: "",
+                    data.bodies["A13"] ?: "",
+                    data.bodies["A14"] ?: "",
+                    data.bodies["A21"] ?: "",
+                    data.bodies["A22"] ?: "",
+                    "1",
+                    data.bodies["A59"] ?: "",
+                    data.bodies["A26"] ?: "",
+                    data.bodies["A27"] ?: "",
+                    data.bodies["B5"] ?: "",
+                    data.bodies["B6"] ?: "",
+                    data.bodies["A29"] ?: "",
+                    data.bodies["A48"] ?: "",
+                    data.bodies["A55"] ?: "",
+                    "0",
+                    "0",
+                    data.bodies["A54"] ?: "",
+                    data.bodies["tflr"] ?: "",
+                    "0",
+                    data.bodies["Bw"] ?: "",
+                    data.bodies["A129"] ?: "",
+                    "",
+                    "",
+                    "",
+                    "",
+                    data.bodies["A133"] ?: "",
+                    data.bodies["A137"] ?: "",
+                    "",
+                    "",
+                    data.bodies["A144"] ?: "",
+                    "",
+                    "",
+                    data.bodies["BE_INFO"] ?: "",
+                    data.bodies["ogi"] ?: "",
+                    data.bodies["ogn"] ?: "",
+                    data.bodies["ogt"] ?: "",
+                    data.bodies["A156"] ?: "",
+                    data.bodies["A157"] ?: "",
+                    data.bodies["A154"] ?: "",
+                    data.bodies["A155"] ?: "",
+                    data.bodies["FL4"] ?: "",
+                    data.bodies["FL5"] ?: "",
+                    data.bodies["FL6"] ?: "",
+                    data.bodies["A158"] ?: "",
+                    data.bodies["A164"] ?: "",
+                    data.bodies["A165"] ?: "",
+                    data.bodies["A166"] ?: "",
+                    data.bodies["A167"] ?: "",
+                    data.bodies["A168"] ?: "",
+                    data.bodies["A169"] ?: "",
+                    data.bodies["certf"] ?: "",
+                    data.bodies["oacc"] ?: "",
+                    data.bodies["B7T"] ?: "",
+                    data.bodies["RNAM"] ?: "",
+                    data.bodies["ISA"] ?: "",
+                    data.bodies["LSG"] ?: "",
+                    data.bodies["AID"] ?: "",
+                    data.bodies["ICP"] ?: "",
+                    data.bodies["WKA"] ?: "",
+                    data.bodies["ISL"] ?: ""
+                )
+            )
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+    }
+
+    private suspend fun pushBuddies(data: Data) {
+        try {
+            val d = data.bodies["A112"] ?: ""
+            val jsonArray = JSONArray(d)
+            for (i in 0 until jsonArray.length()) {
+                val jsonObject = jsonArray.getJSONObject(i)
+                ApiRoomDatabase.getDatabase(context).buddyDao().insert(
+                    Buddy(
+                        jsonObject.optString("A00") ?: "",
+                        jsonObject.optString("A23") ?: "",
+                        jsonObject.optString("A02") ?: "",
+                        jsonObject.optString("A03") ?: "",
+                        jsonObject.optString("A74") ?: "",
+                        jsonObject.optString("B7") ?: "",
+                        jsonObject.optString("A16") ?: "",
+                        jsonObject.optString("A10") ?: "",
+                        jsonObject.optString("A11") ?: "",
+                        jsonObject.optString("A12") ?: "",
+                        jsonObject.optString("A13") ?: "",
+                        jsonObject.optString("A14") ?: "",
+                        jsonObject.optString("A21") ?: "",
+                        jsonObject.optString("A22") ?: "",
+                        "0", // 0:friend, 1:self
+                        jsonObject.optString("A59") ?: "",
+                        jsonObject.optString("A26") ?: "",
+                        jsonObject.optString("A27") ?: "",
+                        jsonObject.optString("B5") ?: "",
+                        jsonObject.optString("B6") ?: "",
+                        jsonObject.optString("A29") ?: "",
+                        jsonObject.optString("A48") ?: "",
+                        jsonObject.optString("A55") ?: "",
+                        jsonObject.optString("A43") ?: "",
+                        "0",
+                        jsonObject.optString("A54") ?: "",
+                        jsonObject.optString("tflr") ?: "",
+                        "1",
+                        jsonObject.optString("Bw") ?: "",
+                        jsonObject.optString("A129") ?: "",
+                        "",
+                        "",
+                        "",
+                        "",
+                        jsonObject.optString("A133") ?: "",
+                        jsonObject.optString("A137") ?: "",
+                        jsonObject.optString("BT") ?: "",
+                        jsonObject.optString("Bb") ?: "",
+                        jsonObject.optString("A144") ?: "",
+                        "",
+                        "",
+                        jsonObject.optString("BE_INFO") ?: "",
+                        jsonObject.optString("ogi") ?: "",
+                        jsonObject.optString("ogn") ?: "",
+                        jsonObject.optString("ogt") ?: "",
+                        jsonObject.optString("A156") ?: "",
+                        jsonObject.optString("A157") ?: "",
+                        jsonObject.optString("A154") ?: "",
+                        jsonObject.optString("A155") ?: "",
+                        jsonObject.optString("FL4") ?: "",
+                        jsonObject.optString("FL5") ?: "",
+                        jsonObject.optString("FL6") ?: "",
+                        jsonObject.optString("A158") ?: "",
+                        jsonObject.optString("A164") ?: "",
+                        jsonObject.optString("A165") ?: "",
+                        jsonObject.optString("A166") ?: "",
+                        jsonObject.optString("A167") ?: "",
+                        jsonObject.optString("A168") ?: "",
+                        jsonObject.optString("A169") ?: "",
+                        jsonObject.optString("certf") ?: "",
+                        jsonObject.optString("oacc") ?: "",
+                        jsonObject.optString("B7T") ?: "",
+                        jsonObject.optString("RNAM") ?: "",
+                        jsonObject.optString("ISA") ?: "",
+                        jsonObject.optString("LSG") ?: "",
+                        jsonObject.optString("AID") ?: "",
+                        jsonObject.optString("ICP") ?: "",
+                        jsonObject.optString("WKA") ?: "",
+                        jsonObject.optString("ISL") ?: ""
+                    )
+                )
+            }
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+    }
+
+    private suspend fun pushMessage(data: Data) {
+        try {
+            val messageId = data.bodies["A18"] ?: ""
+            if (messageId.isEmpty()) return
+            ApiRoomDatabase.getDatabase(context).messageDao().get(messageId).value?.let {
+                if (it.isNotEmpty()) {
+                    Service.sendAck(messageId)
+                    return
+                }
+            }
+            val fPin = data.bodies["A00"] ?: ""
+            val lPin = data.bodies["A01"] ?: ""
+            val me = context.getSharedPreferences().getString("pin", "")
+            val opposite = if (me == fPin) lPin else fPin
+            ApiRoomDatabase.getDatabase(context).messageDao().insert(
+                Message(
+                    messageId,
+                    fPin,
+                    lPin,
+                    data.bodies["A06"] ?: "",
+                    data.bodies["A19"]?.toLong() ?: System.currentTimeMillis(),
+                    data.bodies["A15"] ?: "",
+                    data.bodies["A07"]?.toNormalString() ?: "",
+                    data.bodies["A63"] ?: "",
+                    data.bodies["A47"] ?: "",
+                    data.bodies["A57"] ?: "",
+                    data.bodies["A74"] ?: "",
+                    opposite,
+                    data.bodies["EX1"] ?: "",
+                    data.bodies["A68"] ?: "",
+                    data.bodies["B4"]?.toInt() ?: 0,
+                    data.bodies["A52"] ?: "",
+                    data.bodies["B8"] ?: "",
+                    data.bodies["B9"] ?: "",
+                    data.bodies["Bf"]?.toInt() ?: 0,
+                    data.bodies["BA"] ?: "",
+                    data.bodies["BN"] ?: "",
+                    0,
+                    data.bodies["A117"] ?: "1",
+                    data.bodies["A116"] ?: "",
+                    data.bodies["A118"] ?: "",
+                    data.bodies["A149"]?.toInt() ?: 0,
+                    0,
+                    data.bodies["BR"] ?: "",
+                    data.bodies["A121"] ?: "",
+                    data.bodies["STQ"]?.toInt() ?: 0,
+                    0,
+                    0,
+                    data.bodies["ACQ"]?.toInt() ?: 0,
+                    0,
+                    "",
+                    "",
+                    "",
+                    "",
+                    "",
+                    data.bodies["A36"] ?: "",
+                    data.bodies["tgf"] ?: "",
+                    data.bodies["tga"] ?: "",
+                    0,
+                    0,
+                    data.bodies["tgc"] ?: "",
+                    data.bodies["tgsa"] ?: "",
+                    data.bodies["A150"]?.toInt() ?: 0,
+                    data.bodies["mla"] ?: "",
+                    data.bodies["tpl"] ?: "",
+                    data.bodies["lcltm"]?.toLong() ?: System.currentTimeMillis(),
+                    data.bodies["ics"]?.toInt() ?: 0,
+                    data.bodies["icc"]?.toInt() ?: 0,
+                    data.bodies["ccid"] ?: ""
+                )
+            )
+            Service.sendAck(messageId)
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+    }
+
+    private fun inquiryMessage(data: Data) {
+        try {
+            val messageId = data.bodies["A18"] ?: ""
+            if (messageId.isEmpty()) return
+            ApiRoomDatabase.getDatabase(context).messageDao().get(messageId).value?.let {
+                Service.sendAsync(data.responseOk())
+                return
+            }
+            Service.sendAsync(data.responseNok())
+        } catch (e: Exception) {
+            Log.e(tag, e.message, e)
+        }
+    }
+
+}

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

@@ -0,0 +1,28 @@
+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 BuddyDao {
+
+    @Query("select * from Buddy where type = '0'")
+    fun getAll(): LiveData<List<Buddy>>
+
+    @Query("select * from Buddy where type = '1'")
+    fun getMe(): LiveData<Buddy>
+
+    @Query("select * from Buddy where f_pin = :pin")
+    fun getBuddy(pin: String): LiveData<Buddy>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: Buddy)
+
+    @Query("delete from Buddy")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,22 @@
+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 CallCenterHistoryDao {
+
+    @Query("select * from CallCenterHistory")
+    fun getAll(): LiveData<List<CallCenterHistory>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: CallCenterHistory)
+
+    @Query("delete from CallCenterHistory")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,22 @@
+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 DiscussionForumDao {
+
+    @Query("select * from DiscussionForum")
+    fun getAll(): LiveData<List<DiscussionForum>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: DiscussionForum)
+
+    @Query("delete from DiscussionForum")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,22 @@
+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 FollowDao {
+
+    @Query("select * from Follow")
+    fun getAll(): LiveData<List<Follow>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: Follow)
+
+    @Query("delete from Follow")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,22 @@
+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 FormDao {
+
+    @Query("select * from Form")
+    fun getAll(): LiveData<List<Form>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: Form)
+
+    @Query("delete from Form")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,22 @@
+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 FormItemDao {
+
+    @Query("select * from FormItem")
+    fun getAll(): LiveData<List<FormItem>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: FormItem)
+
+    @Query("delete from FormItem")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,25 @@
+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 GroupDao {
+
+    @Query("select * from `Group`")
+    fun getAll(): LiveData<List<Group>>
+
+    @Query("select * from `Group` where group_id = :id")
+    fun get(id: String): LiveData<List<Group>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: Group)
+
+    @Query("delete from `Group`")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,25 @@
+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 GroupMemberDao {
+
+    @Query("select * from GroupMember")
+    fun getAll(): LiveData<List<GroupMember>>
+
+    @Query("select * from GroupMember where group_id = :id")
+    fun get(id: String): LiveData<List<GroupMember>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: GroupMember)
+
+    @Query("delete from GroupMember")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,22 @@
+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 InquiryDao {
+
+    @Query("select * from Inquiry")
+    fun getAll(): LiveData<List<Inquiry>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: Inquiry)
+
+    @Query("delete from Inquiry")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,22 @@
+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 LinkPreviewDao {
+
+    @Query("select * from LinkPreview")
+    fun getAll(): LiveData<List<LinkPreview>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: LinkPreview)
+
+    @Query("delete from LinkPreview")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,28 @@
+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 MessageDao {
+
+    @Query("select * from Message")
+    fun getAll(): LiveData<List<Message>>
+
+    @Query("select * from Message where message_id = :id")
+    fun get(id: String): LiveData<List<Message>>
+
+    @Query("select * from Message where opposite_pin = :pin order by server_date desc")
+    fun getOpposite(pin: String): LiveData<List<Message>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: Message)
+
+    @Query("delete from Message")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,25 @@
+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 MessageStatusDao {
+
+    @Query("select * from MessageStatus")
+    fun getAll(): LiveData<List<MessageStatus>>
+
+    @Query("select * from MessageStatus where message_id = :id")
+    fun get(id: String): LiveData<List<MessageStatus>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: MessageStatus)
+
+    @Query("delete from MessageStatus")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,25 @@
+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 MessageSummaryDao {
+
+    @Query("select * from MessageSummary")
+    fun getAll(): LiveData<List<MessageSummary>>
+
+    @Query("select * from MessageSummary where l_pin = :pin")
+    fun get(pin: String): LiveData<List<MessageSummary>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: MessageSummary)
+
+    @Query("delete from MessageSummary")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,31 @@
+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)
+
+}

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

@@ -0,0 +1,25 @@
+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 PrefsDao {
+
+    @Query("select * from Prefs")
+    fun getAll(): LiveData<List<Prefs>>
+
+    @Query("select * from Prefs where `key` = :key")
+    fun get(key: String): LiveData<List<Prefs>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: Prefs)
+
+    @Query("delete from Prefs")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,25 @@
+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 PullDao {
+
+    @Query("select * from Pull")
+    fun getAll(): LiveData<List<Pull>>
+
+    @Query("select * from Pull where pull_key = :key")
+    fun get(key: String): LiveData<List<Pull>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: Pull)
+
+    @Query("delete from Pull")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,25 @@
+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 ServiceBankDao {
+
+    @Query("select * from ServiceBank")
+    fun getAll(): LiveData<List<ServiceBank>>
+
+    @Query("select * from ServiceBank where service_id = :id")
+    fun get(id: String): LiveData<List<ServiceBank>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: ServiceBank)
+
+    @Query("delete from ServiceBank")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,25 @@
+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 WorkingAreaDao {
+
+    @Query("select * from WorkingArea")
+    fun getAll(): LiveData<List<WorkingArea>>
+
+    @Query("select * from WorkingArea where area_id = :id")
+    fun get(id: String): LiveData<List<WorkingArea>>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: WorkingArea)
+
+    @Query("delete from WorkingArea")
+    suspend fun deleteAll()
+
+}

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

@@ -0,0 +1,82 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+import androidx.room.Index
+
+@Entity(
+    primaryKeys = ["f_pin"],
+    indices = [
+        Index(value = ["user_id", "ex_status", "first_name", "email", "msisdn"])
+    ]
+)
+data class Buddy(
+    val f_pin: String,
+    val upline_pin: String = "",
+    val first_name: String,
+    val last_name: String = "",
+    val image_id: String,
+    val user_id: String = f_pin,
+    val quote: String = "",
+    val connected: String = "0",
+    val last_update: String = System.currentTimeMillis().toString(),
+    val latitude: String = "0",
+    val longitude: String = "0",
+    val altitude: String = "0",
+    val cell: String = "",
+    val last_loc_update: String = "0",
+    val type: String = "",
+    val empty_2: String = "",
+    val timezone: String = "",
+    val privacy_flag: String = "",
+    val msisdn: String = "",
+    val email: String = "",
+    val created_date: String = System.currentTimeMillis().toString(),
+    val offline_mode: String = "0",
+    val network_type: String = "0",
+    val ex_block: String = "0",
+    val ex_follow: String = "0",
+    val ex_offmp: String = "0",
+    val ex_follower: String = "0",
+    val ex_status: String = "0",
+    val auto_quote: String = "",
+    val auto_quote_type: String = "",
+    val ex_broadcasting: String = "0",
+    val indicator_status: String = "0",
+    val muted: String = "0",
+    val pos_flag: String = "0",
+    val shop_code: String = "",
+    val shop_name: String = "",
+    val android_version: String = "0",
+    val device_id: String = "0",
+    val extension: String = "0",
+    val auto_quote_status: String = "0",
+    val connection_speed: String = "0",
+    val be_info: String = "",
+    val org_id: String = "",
+    val org_name: String = "",
+    val org_thumb: String = "",
+    val card_type: String = "",
+    val card_id: String = "",
+    val gender: String = "",
+    val birthdate: String = "",
+    val type_ads: String = "0",
+    val type_lp: String = "0",
+    val type_post: String = "0",
+    val address: String = "",
+    val bidang_industri: String = "",
+    val visi: String = "",
+    val misi: String = "",
+    val company_lat: String = "",
+    val company_lng: String = "",
+    val web: String = "",
+    val certificate_image: String = "",
+    val official_account: String = "0",
+    val user_type: String = "0",
+    val real_name: String = "",
+    val is_sub_account: String = "0",
+    val last_sign: String = "0",
+    val android_id: String = "",
+    val is_change_profile: String = "0",
+    val area: String = "0",
+    val is_second_layer: String = "0"
+) : MainEntity

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

@@ -0,0 +1,16 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["complaint_id"])
+data class CallCenterHistory(
+    val complaint_id: String,
+    val type: Int,
+    val title: String,
+    val time: String,
+    val f_pin: String,
+    val data: String,
+    val time_end: String,
+    val members: String,
+    val requester: String
+) : MainEntity

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

@@ -0,0 +1,26 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["chat_id"])
+data class DiscussionForum(
+    val chat_id: String,
+    val title: String,
+    val group_id: String,
+    val anonym: String,
+    val scope_id: String,
+    val thumb: String,
+    val category: String,
+    val activity: String,
+    val milis: String,
+    val sharing_flag: String,
+    val clients: String,
+    val owner: String,
+    val follow: Int = 0,
+    val raci_r: String,
+    val raci_a: String,
+    val raci_c: String,
+    val raci_i: String,
+    val act_thumb: String,
+    val client_thumb: String
+) : MainEntity

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

@@ -0,0 +1,8 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["f_pin"])
+data class Follow(
+    val f_pin: String
+) : MainEntity

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

@@ -0,0 +1,12 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["form_id"])
+data class Form(
+    val form_id: String,
+    val name: String,
+    val created_date: String,
+    val created_by: String,
+    val sq_no: Int
+) : MainEntity

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

@@ -0,0 +1,13 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["form_id", "key"])
+data class FormItem(
+    val form_id: String,
+    val key: String,
+    val label: String,
+    val value: String,
+    val type: String,
+    val sq_no: Int
+) : MainEntity

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

@@ -0,0 +1,27 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["group_id"])
+data class Group(
+    val group_id: String,
+    val f_name: String,
+    val scope_id: String,
+    val image_id: String,
+    val quote: String,
+    val last_update: String,
+    val created_by: String,
+    val created_date: String,
+    val ex_block: String = "0",
+    val folder_id: String,
+    val chat_modifier: Int = 1,
+    val group_type: Int = 0,
+    val parent: String,
+    val level: String,
+    val muted: Int = 0,
+    val is_open: Int = 0,
+    val official: Int = 0,
+    val level_edu: Int = -1,
+    val materi_edu: Int = -1,
+    val is_education: Int = 0
+) : MainEntity

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

@@ -0,0 +1,18 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["group_id", "f_pin"])
+data class GroupMember(
+    val group_id: String,
+    val f_pin: String,
+    val position: String = "0",
+    val user_id: String = "-",
+    val ac: String = "-",
+    val ac_desc: String = "-",
+    val first_name: String,
+    val last_name: String,
+    val msisdn: String,
+    val thumb_id: String,
+    val created_date: String = "0"
+) : MainEntity

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

@@ -0,0 +1,10 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["id"])
+data class Inquiry(
+    val id: String,
+    val status: Int = 0,
+    val message: String
+) : MainEntity

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

@@ -0,0 +1,17 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+import androidx.room.Index
+
+@Entity(
+    primaryKeys = ["id"],
+    indices = [
+        Index(value = ["link"], unique = true)
+    ]
+)
+data class LinkPreview(
+    val id: String,
+    val link: String,
+    val data_link: String,
+    val retry: Int = 0
+) : MainEntity

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

@@ -0,0 +1,5 @@
+package io.nexilis.service.data.entities
+
+interface MainEntity {
+
+}

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

@@ -0,0 +1,67 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+import androidx.room.Index
+
+@Entity(
+    primaryKeys = ["message_id"],
+    indices = [
+        Index(value = ["opposite_pin", "chat_id", "server_date", "account_type", "mail_account", "reff_id", "local_timestamp", "is_call_center"])
+    ]
+)
+data class Message(
+    val message_id: String,
+    val f_pin: String,
+    val l_pin: String,
+    val message_scope_id: String,
+    val server_date: Long,
+    val status: String,
+    val message_text: String,
+    val audio_id: String = "",
+    val video_id: String = "",
+    val image_id: String = "",
+    val thumb_id: String = "",
+    val opposite_pin: String,
+    val lock: String = "",
+    val format: String = "",
+    val broadcast_flag: Int = 0,
+    val blog_id: String = "",
+    val f_user_id: String = "",
+    val l_user_id: String = "",
+    val read_receipts: Int = 0,
+    val chat_id: String = "",
+    val file_id: String = "",
+    val delivery_receipts: Int = 0,
+    val account_type: String = "",
+    val contact: String = "",
+    val credential: String = "",
+    val attachment_flag: Int = 0,
+    val is_stared: Int = 0,
+    val f_display_name: String,
+    val reff_id: String = "",
+    val sent_qty: Int = 0,
+    val delivered_qty: Int = 0,
+    val read_qty: Int = 0,
+    val ack_qty: Int = 0,
+    val read_local_qty: Int = 0,
+    val delivered_pin: String = "",
+    val read_pin: String = "",
+    val ack_pin: String = "",
+    val read_local_pin: String = "",
+    val expired_qty: String = "",
+    val message_large_text: String,
+    val tag_forum: String = "",
+    val tag_activity: String = "",
+    val unk_numbers: Int = 0,
+    val conn_state: Int = 1,
+    val tag_client: String = "",
+    val tag_subactivity: String = "",
+    val messagenumber: Int = 0,
+    val mail_account: String = "",
+    val message_text_plain: String,
+    val local_timestamp: Long = 0,
+    val is_consult: Int = 0,
+    val is_call_center: Int = 0,
+    val call_center_id: String = "",
+    val is_work_mode: Int = 0
+) : MainEntity

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

@@ -0,0 +1,8 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["message_id"])
+data class MessageFavorite(
+    val message_id: String
+) : MainEntity

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

@@ -0,0 +1,24 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+import androidx.room.Index
+
+@Entity(
+    primaryKeys = ["message_id", "f_pin"],
+    indices = [
+        Index(value = ["message_id"])
+    ]
+)
+data class MessageStatus(
+    val message_id: String,
+    val status: Int = 0,
+    val f_pin: String,
+    val user_id: String,
+    val last_update: Long,
+    val time_delivered: Long,
+    val time_read: Long,
+    val time_ack: Long,
+    val longitude: String = "",
+    val latitude: String = "",
+    val location: String = ""
+) : MainEntity

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

@@ -0,0 +1,10 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["l_pin"])
+data class MessageSummary(
+    val l_pin: String,
+    val message_id: String,
+    val counter: Int = 0
+) : MainEntity

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

@@ -0,0 +1,16 @@
+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

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

@@ -0,0 +1,9 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["key"])
+data class Prefs(
+    val key: String,
+    val value: String,
+) : MainEntity

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

@@ -0,0 +1,12 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class Pull(
+    @PrimaryKey(autoGenerate = true) val id: Int,
+    val pull_type: String,
+    val pull_key: String = "0",
+    val time: String
+) : MainEntity

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

@@ -0,0 +1,12 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["service_id"])
+data class ServiceBank(
+    val service_id: String,
+    val service_name: String,
+    val description: String,
+    val parent: String,
+    val is_tablet: String
+) : MainEntity

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

@@ -0,0 +1,11 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["area_id"])
+data class WorkingArea(
+    val area_id: String,
+    val name: String,
+    val parent: String,
+    val level: String
+) : MainEntity

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

@@ -0,0 +1,116 @@
+package io.nexilis.service.data.repositories
+
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.LiveData
+import io.nexilis.service.Service
+import io.nexilis.service.context
+import io.nexilis.service.core.Data
+import io.nexilis.service.core.optString
+import io.nexilis.service.core.put
+import io.nexilis.service.core.toMD5
+import io.nexilis.service.data.daos.BuddyDao
+import io.nexilis.service.data.entities.Buddy
+import io.nexilis.service.data.entities.MainEntity
+import io.nexilis.service.namePreference
+import io.nexilis.service.tag
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+class BuddyRepository @Inject constructor(private val dao: BuddyDao) : Repository {
+
+    val all: LiveData<List<Buddy>> = dao.getAll()
+
+    val me: LiveData<Buddy> = dao.getMe()
+
+    fun getBuddy(pin: String): LiveData<Buddy> {
+        return dao.getBuddy(pin)
+    }
+
+    override suspend fun insert(entity: MainEntity) {
+        dao.insert(entity as Buddy)
+    }
+
+    suspend fun signUp(username: String, password: String, completion: (Boolean) -> Unit) {
+        val preferences = context.getSharedPreferences(
+            namePreference,
+            Context.MODE_PRIVATE
+        )
+        val pin = preferences.optString(key = "pin", default = "")
+        if (pin.isEmpty()) return
+        Service.sendAsync(
+            Data(
+                code = "A003",
+                status = System.nanoTime().toString(),
+                f_pin = pin,
+                bodies = mutableMapOf(
+                    "A02" to username,
+                    "A03" to "",
+                    "Bm" to password.toMD5(),
+                    "Bm0" to ""
+                )
+            )
+        ) { data ->
+            Log.d(tag, "insert >>>> $data")
+            if (data.isOk()) {
+                dao.insert(Buddy(f_pin = pin, first_name = username, image_id = "", type = "1"))
+                preferences.put("pin", pin)
+            }
+            withContext(Dispatchers.Main) {
+                completion(data.isOk())
+            }
+        }
+    }
+
+    suspend fun signIn(username: String, password: String, completion: (Boolean) -> Unit) {
+        val preferences = context.getSharedPreferences(
+            namePreference,
+            Context.MODE_PRIVATE
+        )
+        val pin = preferences.optString(key = "pin", default = "")
+        if (pin.isEmpty()) return
+        Service.sendSync(
+            Data(
+                code = "SSI01",
+                status = System.nanoTime().toString(),
+                f_pin = pin,
+                bodies = mutableMapOf(
+                    "A92" to username,
+                    "Bm" to password.toMD5()
+                )
+            )
+        )?.let { data ->
+            Log.d(tag, "insert >>>> $data")
+            if (data.isOk()) {
+                dao.insert(Buddy(f_pin = pin, first_name = username, image_id = "", type = "1"))
+                preferences.put("pin", pin)
+            }
+            withContext(Dispatchers.Main) {
+                completion(data.isOk())
+            }
+        }
+    }
+
+    suspend fun addFriend(pin: String) {
+        val preferences = context.getSharedPreferences(
+            namePreference,
+            Context.MODE_PRIVATE
+        )
+        val me = preferences.optString(key = "pin", default = "")
+        if (pin.isEmpty() || me.isEmpty()) return
+        Service.sendAsync(
+            Data(
+                code = "A100",
+                status = System.nanoTime().toString(),
+                f_pin = me,
+                bodies = mutableMapOf(
+                    "ffpin" to pin
+                )
+            )
+        ) { data ->
+            Log.d(tag, "addFriend >>>> $data")
+        }
+    }
+
+}

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

@@ -0,0 +1,17 @@
+package io.nexilis.service.data.repositories
+
+import androidx.lifecycle.LiveData
+import io.nexilis.service.data.daos.DiscussionForumDao
+import io.nexilis.service.data.entities.DiscussionForum
+import io.nexilis.service.data.entities.MainEntity
+import javax.inject.Inject
+
+class DiscussionForumRepository @Inject constructor(private val dao: DiscussionForumDao) :
+    Repository {
+
+    val all: LiveData<List<DiscussionForum>> = dao.getAll()
+
+    override suspend fun insert(entity: MainEntity) {
+        dao.insert(entity as DiscussionForum)
+    }
+}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików