diff --git a/apps/android/.gitignore b/apps/android/.gitignore index b2a8a63042..aa724b7707 100644 --- a/apps/android/.gitignore +++ b/apps/android/.gitignore @@ -8,17 +8,8 @@ /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store -build/ -release/ -debug/ +/build /captures .externalNativeBuild .cxx local.properties -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar diff --git a/apps/android/.idea/.gitignore b/apps/android/.idea/.gitignore deleted file mode 100644 index 26d33521af..0000000000 --- a/apps/android/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/apps/android/.idea/codeStyles/Project.xml b/apps/android/.idea/codeStyles/Project.xml deleted file mode 100644 index 4bec4ea8ae..0000000000 --- a/apps/android/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/apps/android/.idea/codeStyles/codeStyleConfig.xml b/apps/android/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a179b..0000000000 --- a/apps/android/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/apps/android/.idea/deploymentTargetDropDown.xml b/apps/android/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000000..b2a5949802 --- /dev/null +++ b/apps/android/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/android/.idea/inspectionProfiles/Project_Default.xml b/apps/android/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000..28422375b6 --- /dev/null +++ b/apps/android/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/apps/android/.idea/misc.xml b/apps/android/.idea/misc.xml index 345106f12f..1c2eb53046 100644 --- a/apps/android/.idea/misc.xml +++ b/apps/android/.idea/misc.xml @@ -3,9 +3,10 @@ diff --git a/apps/android/app/.gitignore b/apps/android/app/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/apps/android/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index dc2b3ed857..0f2c5d0297 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' - id 'kotlin-android' + id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' } android { @@ -17,6 +18,9 @@ android { ndk { abiFilters 'arm64-v8a' } + vectorDrawables { + useSupportLibrary true + } externalNativeBuild { cmake { cppFlags '' @@ -44,17 +48,29 @@ android { } } buildFeatures { - viewBinding true + compose true + } + composeOptions { + kotlinCompilerExtensionVersion compose_version + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } dependencies { - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.5.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - testImplementation 'junit:junit:4.+' + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' + implementation 'androidx.activity:activity-compose:1.3.1' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} \ No newline at end of file + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" +} diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml index 7113a1c6ac..b0fdea62da 100644 --- a/apps/android/app/src/main/AndroidManifest.xml +++ b/apps/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + android:exported="true" + android:label="@string/app_name" + android:theme="@style/Theme.SimpleX"> diff --git a/apps/android/app/src/main/cpp/simplex-api.c b/apps/android/app/src/main/cpp/simplex-api.c index 7aa1924386..f2dadcb131 100644 --- a/apps/android/app/src/main/cpp/simplex-api.c +++ b/apps/android/app/src/main/cpp/simplex-api.c @@ -8,7 +8,7 @@ void setLineBuffering(void); int pipe_std_to_socket(const char * name); JNIEXPORT jint JNICALL -Java_chat_simplex_app_MainActivityKt_pipeStdOutToSocket(JNIEnv *env, __unused jclass clazz, jstring socket_name) { +Java_chat_simplex_app_SimplexAppKt_pipeStdOutToSocket(JNIEnv *env, __unused jclass clazz, jstring socket_name) { const char *name = (*env)->GetStringUTFChars(env, socket_name, JNI_FALSE); int ret = pipe_std_to_socket(name); (*env)->ReleaseStringUTFChars(env, socket_name, name); @@ -16,7 +16,7 @@ Java_chat_simplex_app_MainActivityKt_pipeStdOutToSocket(JNIEnv *env, __unused jc } JNIEXPORT void JNICALL -Java_chat_simplex_app_MainActivityKt_initHS(__unused JNIEnv *env, __unused jclass clazz) { +Java_chat_simplex_app_SimplexAppKt_initHS(__unused JNIEnv *env, __unused jclass clazz) { hs_init(NULL, NULL); setLineBuffering(); } @@ -33,7 +33,7 @@ extern char *chat_send_cmd(controller ctl, const char *cmd); extern char *chat_recv_msg(controller ctl); JNIEXPORT jlong JNICALL -Java_chat_simplex_app_MainActivityKt_chatInit(JNIEnv *env, __unused jclass clazz, jstring datadir) { +Java_chat_simplex_app_SimplexAppKt_chatInit(JNIEnv *env, __unused jclass clazz, jstring datadir) { const char *_data = (*env)->GetStringUTFChars(env, datadir, JNI_FALSE); jlong res = (jlong)chat_init_store(_data); (*env)->ReleaseStringUTFChars(env, datadir, _data); @@ -41,12 +41,12 @@ Java_chat_simplex_app_MainActivityKt_chatInit(JNIEnv *env, __unused jclass clazz } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_MainActivityKt_chatGetUser(JNIEnv *env, __unused jclass clazz, jlong controller) { +Java_chat_simplex_app_SimplexAppKt_chatGetUser(JNIEnv *env, __unused jclass clazz, jlong controller) { return (*env)->NewStringUTF(env, chat_get_user((void*)controller)); } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_MainActivityKt_chatCreateUser(JNIEnv *env, __unused jclass clazz, jlong controller, jstring data) { +Java_chat_simplex_app_SimplexAppKt_chatCreateUser(JNIEnv *env, __unused jclass clazz, jlong controller, jstring data) { const char *_data = (*env)->GetStringUTFChars(env, data, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_create_user((void*)controller, _data)); (*env)->ReleaseStringUTFChars(env, data, _data); @@ -54,12 +54,12 @@ Java_chat_simplex_app_MainActivityKt_chatCreateUser(JNIEnv *env, __unused jclass } JNIEXPORT jlong JNICALL -Java_chat_simplex_app_MainActivityKt_chatStart(JNIEnv *env, jclass clazz, jlong controller) { +Java_chat_simplex_app_SimplexAppKt_chatStart(JNIEnv *env, jclass clazz, jlong controller) { return (jlong)chat_start((void*)controller); } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_MainActivityKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) { +Java_chat_simplex_app_SimplexAppKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) { const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg)); (*env)->ReleaseStringUTFChars(env, msg, _msg); @@ -67,6 +67,6 @@ Java_chat_simplex_app_MainActivityKt_chatSendCmd(JNIEnv *env, __unused jclass cl } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_MainActivityKt_chatRecvMsg(JNIEnv *env, __unused jclass clazz, jlong controller) { +Java_chat_simplex_app_SimplexAppKt_chatRecvMsg(JNIEnv *env, __unused jclass clazz, jlong controller) { return (*env)->NewStringUTF(env, chat_recv_msg((void*)controller)); } diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index 20897023c3..2a62c745fb 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -1,123 +1,37 @@ package chat.simplex.app -import android.net.LocalServerSocket +import android.app.Application import android.os.Bundle -import android.util.Log -import android.view.inputmethod.EditorInfo -import android.widget.ScrollView -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.AppCompatEditText -import java.io.BufferedReader -import java.io.InputStreamReader -import java.lang.ref.WeakReference -import java.util.* -import java.util.concurrent.Semaphore -import kotlin.concurrent.thread +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.runtime.Composable +import chat.simplex.app.ui.theme.SimpleXTheme +import androidx.lifecycle.AndroidViewModel +import chat.simplex.app.model.* +import chat.simplex.app.views.TerminalView +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* -// ghc's rts -external fun initHS() -// android-support -external fun pipeStdOutToSocket(socketName: String) : Int -// simplex-chat -typealias Store = Long -typealias Controller = Long -external fun chatInit(filesDir: String): Store -external fun chatGetUser(controller: Store) : String -external fun chatCreateUser(controller: Store, data: String) : String -external fun chatStart(controller: Store) : Controller -external fun chatSendCmd(controller: Controller, msg: String) : String -external fun chatRecvMsg(controller: Controller) : String - -class MainActivity : AppCompatActivity() { +class MainActivity: ComponentActivity() { + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { - weakActivity = WeakReference(this) - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - val store : Store = chatInit(this.applicationContext.filesDir.toString()) - // create user if needed - if(chatGetUser(store) == "{}") { - chatCreateUser(store, """ - {"displayName": "test", "fullName": "android test"} - """.trimIndent()) - } - Log.d("SIMPLEX (user)", chatGetUser(store)) - - val controller = chatStart(store) - - val cmdinput = this.findViewById(R.id.cmdInput) - - cmdinput.setOnEditorActionListener { _, actionId, _ -> - when (actionId) { - EditorInfo.IME_ACTION_SEND -> { - Log.d("SIMPLEX SEND", chatSendCmd(controller, cmdinput.text.toString())) - cmdinput.text?.clear() - true - } - else -> false + setContent { + SimpleXTheme { + MainPage(viewModel) } } - - thread(name="receiver") { - val chatlog = FifoQueue(500) - while(true) { - val msg = chatRecvMsg(controller) - Log.d("SIMPLEX RECV", msg) - chatlog.add(msg) - val currentText = chatlog.joinToString("\n") - weakActivity.get()?.runOnUiThread { - val log = weakActivity.get()?.findViewById(R.id.chatlog) - val scroll = weakActivity.get()?.findViewById(R.id.scroller) - log?.text = currentText - scroll?.scrollTo(0, scroll.getChildAt(0).height) - } - } - } - } - - companion object { - lateinit var weakActivity : WeakReference - init { - val socketName = "local.socket.address.listen.native.cmd2" - - val s = Semaphore(0) - thread(name="stdout/stderr pipe") { - Log.d("SIMPLEX", "starting server") - val server = LocalServerSocket(socketName) - Log.d("SIMPLEX", "started server") - s.release() - val receiver = server.accept() - Log.d("SIMPLEX", "started receiver") - val logbuffer = FifoQueue(500) - if (receiver != null) { - val inStream = receiver.inputStream - val inStreamReader = InputStreamReader(inStream) - val input = BufferedReader(inStreamReader) - - while(true) { - val line = input.readLine() ?: break - Log.d("SIMPLEX (stdout/stderr)", line) - logbuffer.add(line) - } - } - } - - System.loadLibrary("app-lib") - - s.acquire() - pipeStdOutToSocket(socketName) - - initHS() - } } } -class FifoQueue(private var capacity: Int) : LinkedList() { - override fun add(element: E): Boolean { - if(size > capacity) removeFirst() - return super.add(element) - } +class SimplexViewModel(application: Application) : AndroidViewModel(application) { + val chatModel = getApplication().chatModel +} + +@Composable +fun MainPage(vm: SimplexViewModel) { + TerminalView(vm.chatModel) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt new file mode 100644 index 0000000000..b08096a37e --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -0,0 +1,95 @@ +package chat.simplex.app + +import android.app.Application +import android.net.LocalServerSocket +import android.util.Log +import chat.simplex.app.model.ChatController +import chat.simplex.app.model.ChatModel +import java.io.BufferedReader +import java.io.InputStreamReader +import java.lang.ref.WeakReference +import java.util.* +import java.util.concurrent.Semaphore +import kotlin.concurrent.thread + +// ghc's rts +external fun initHS() +// android-support +external fun pipeStdOutToSocket(socketName: String) : Int + +// SimpleX API +typealias Controller = Long +typealias Store = Long +external fun chatInit(filesDir: String): Store +external fun chatGetUser(controller: Store) : String +external fun chatCreateUser(controller: Store, data: String) : String +external fun chatStart(controller: Store) : Controller +external fun chatSendCmd(controller: Controller, msg: String) : String +external fun chatRecvMsg(controller: Controller) : String + +class SimplexApp: Application() { + private lateinit var controller: ChatController + + override fun onCreate() { + super.onCreate() + val store: Store = chatInit(applicationContext.filesDir.toString()) + // create user if needed + if (chatGetUser(store) == "{}") { + chatCreateUser(store, """ + {"displayName": "test", "fullName": "android test"} + """.trimIndent()) + } + Log.d("SIMPLEX (user)", chatGetUser(store)) + controller = ChatController(chatStart(store)) + } + + val chatModel by lazy { + val m = ChatModel(controller) + controller.setModel(m) + controller.startReceiver() + m + } + + companion object { + lateinit var weakActivity: WeakReference + init { + val socketName = "local.socket.address.listen.native.cmd2" + + val s = Semaphore(0) + thread(name="stdout/stderr pipe") { + Log.d("SIMPLEX", "starting server") + val server = LocalServerSocket(socketName) + Log.d("SIMPLEX", "started server") + s.release() + val receiver = server.accept() + Log.d("SIMPLEX", "started receiver") + val logbuffer = FifoQueue(500) + if (receiver != null) { + val inStream = receiver.inputStream + val inStreamReader = InputStreamReader(inStream) + val input = BufferedReader(inStreamReader) + + while(true) { + val line = input.readLine() ?: break + Log.d("SIMPLEX (stdout/stderr)", line) + logbuffer.add(line) + } + } + } + + System.loadLibrary("app-lib") + + s.acquire() + pipeStdOutToSocket(socketName) + + initHS() + } + } +} + +class FifoQueue(private var capacity: Int) : LinkedList() { + override fun add(element: E): Boolean { + if(size > capacity) removeFirst() + return super.add(element) + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt new file mode 100644 index 0000000000..0ecab183ad --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -0,0 +1,105 @@ +package chat.simplex.app.model + +import androidx.compose.runtime.mutableStateListOf +import kotlinx.serialization.Serializable +import java.util.* + +class ChatModel(val controller: ChatController) { + val currentUser: User? = null + var terminalItems = mutableStateListOf() + + companion object { + val sampleData: ChatModel get() { + val m = ChatModel(ChatController.Mock()) + m.terminalItems = mutableStateListOf( + TerminalItem.Cmd(CC.ShowActiveUser()), + TerminalItem.Resp(CR.ActiveUser(User.sampleData)) + ) + return m + } + } +} + +enum class ChatType(val type: String) { + Direct("@"), + Group("#"), + ContactRequest("<@") +} + +@Serializable +class User ( + val userId: Int, + val userContactId: Int, + val localDisplayName: String, + val profile: Profile, + val activeUser: Boolean +) : NamedChat { + override val displayName: String get() = profile.displayName + override val fullName: String get() = profile.fullName + + companion object { + val sampleData = User( + userId = 1, + userContactId = 1, + localDisplayName = "alice", + profile = Profile.sampleData, + activeUser = true + ) + } +} + +typealias ChatId = String + +@Serializable +class Contact( + val contactId: Int, + val localDisplayName: String, + val profile: Profile, + val activeConn: Connection, + val viaGroup: Int? = null, +// no serializer for type Date? +// val createdAt: Date +): NamedChat { + val id: ChatId get() = "@$contactId" + val apiId: Int get() = contactId + val ready: Boolean get() = activeConn.connStatus == "ready" || activeConn.connStatus == "snd-ready" + override val displayName: String get() = profile.displayName + override val fullName: String get() = profile.fullName + + companion object { + val sampleData = Contact( + contactId = 1, + localDisplayName = "alice", + profile = Profile.sampleData, + activeConn = Connection.sampleData +// createdAt = Date() + ) + } +} + +@Serializable +class Connection(val connStatus: String) { + companion object { + val sampleData = Connection(connStatus = "ready") + } +} + +@Serializable +class Profile( + val displayName: String, + val fullName: String + ) { + companion object { + val sampleData = Profile( + displayName = "alice", + fullName = "Alice" + ) + } +} + +interface NamedChat { + abstract val displayName: String + abstract val fullName: String + val chatViewName: String + get() = displayName + (if (fullName == "" || fullName == displayName) "" else " / $fullName") +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SImpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SImpleXAPI.kt new file mode 100644 index 0000000000..c6bc16871a --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SImpleXAPI.kt @@ -0,0 +1,161 @@ +package chat.simplex.app.model + +import android.util.Log +import chat.simplex.app.chatRecvMsg +import chat.simplex.app.chatSendCmd +import kotlinx.serialization.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import java.lang.Exception +import java.util.* +import kotlin.concurrent.thread + +typealias Controller = Long + +open class ChatController(val ctrl: Controller) { + private lateinit var chatModel: ChatModel + + fun setModel(m: ChatModel) { + chatModel = m + } + + fun startReceiver() { + thread(name="receiver") { +// val chatlog = FifoQueue(500) + while(true) { + val json = chatRecvMsg(ctrl) + Log.d("SIMPLEX chatRecvMsg: ", json) + chatModel.terminalItems.add(TerminalItem.Resp(APIResponse.decodeStr(json))) + } + } + } + + fun sendCmd(cmd: String) { + val json = chatSendCmd(ctrl, cmd) + Log.d("SIMPLEX chatSendCmd: ", cmd) + Log.d("SIMPLEX chatSendCmd response: ", json) + chatModel.terminalItems.add(TerminalItem.Resp(APIResponse.decodeStr(json))) + } + + class Mock: ChatController(0) {} +} + +// ChatCommand +abstract class CC { + abstract val cmdString: String + abstract val cmdType: String + + class Console(val cmd: String): CC() { + override val cmdString get() = cmd + override val cmdType get() = "console command" + } + + class ShowActiveUser: CC() { + override val cmdString get() = "/u" + override val cmdType get() = "ShowActiveUser" + } + + class CreateActiveUser(val profile: Profile): CC() { + override val cmdString get() = "/u ${profile.displayName} ${profile.fullName}" + override val cmdType get() = "CreateActiveUser" + } + + class StartChat: CC() { + override val cmdString get() = "/_start" + override val cmdType get() = "StartChat" + } + + class ApiGetChats: CC() { + override val cmdString get() = "/_get chats" + override val cmdType get() = "ApiGetChats" + } + + companion object { + fun chatRef(type: ChatType, id: String) = "${type}${id}" + } +} + +val json = Json { + prettyPrint = true + ignoreUnknownKeys = true +} + +@Serializable +class APIResponse(val resp: CR) { + companion object { + fun decodeStr(str: String): CR { + try { + return json.decodeFromString(str).resp + } catch(e: Exception) { + try { + val data = json.parseToJsonElement(str) + return CR.Response(data.jsonObject["resp"]!!.jsonObject["type"]?.toString() ?: "invalid", json.encodeToString(data)) + } catch(e: Exception) { + return CR.Invalid(str) + } + } + } + } +} + +// ChatResponse +@Serializable +sealed class CR { + abstract val responseType: String + abstract val details: String + + @Serializable + @SerialName("activeUser") + class ActiveUser(val user: User): CR() { + override val responseType get() = "activeUser" + override val details get() = user.toString() + } + + @Serializable + @SerialName("contactSubscribed") + class ContactSubscribed(val contact: Contact): CR() { + override val responseType get() = "contactSubscribed" + override val details get() = contact.toString() + } + + @Serializable + class Response(val type: String, val json: String): CR() { + override val responseType get() = "* ${type}" + override val details get() = json + } + + @Serializable + class Invalid(val str: String): CR() { + override val responseType get() = "* invalid json" + override val details get() = str + } + + // {"resp": {"activeUser": {"user": {}}}} + // {"resp": {"anythingElse": }} -> Unknown(type = "anythingElse", json = "") + + // {"type": "activeUser", "user": } +} + +abstract class TerminalItem { + val date = Date() + abstract val label: String + abstract val details: String + + class Cmd(val cmd: CC): TerminalItem() { + override val label get() = "> ${cmd.cmdString.substring(0, 30)}" + override val details get() = cmd.cmdString + } + + class Resp(val resp: CR): TerminalItem() { + override val label get() = "< ${resp.responseType}" + override val details get() = resp.details + } + + companion object { + val sampleData = listOf( + TerminalItem.Cmd(CC.ShowActiveUser()), + TerminalItem.Resp(CR.ActiveUser(User.sampleData)) + ) + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Color.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Color.kt new file mode 100644 index 0000000000..40f91fc96e --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Color.kt @@ -0,0 +1,8 @@ +package chat.simplex.app.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple200 = Color(0xFFBB86FC) +val Purple500 = Color(0xFF6200EE) +val Purple700 = Color(0xFF3700B3) +val Teal200 = Color(0xFF03DAC5) \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Shape.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Shape.kt new file mode 100644 index 0000000000..d0a00450f6 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Shape.kt @@ -0,0 +1,11 @@ +package chat.simplex.app.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt new file mode 100644 index 0000000000..3d9d30c142 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt @@ -0,0 +1,44 @@ +package chat.simplex.app.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple200, + primaryVariant = Purple700, + secondary = Teal200 +) + +private val LightColorPalette = lightColors( + primary = Purple500, + primaryVariant = Purple700, + secondary = Teal200 + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + +@Composable +fun SimpleXTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Type.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Type.kt new file mode 100644 index 0000000000..bb89f6e849 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Type.kt @@ -0,0 +1,28 @@ +package chat.simplex.app.ui.theme + +import androidx.compose.material.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( + body1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ) + /* Other default text styles to override + button = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.W500, + fontSize = 14.sp + ), + caption = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 12.sp + ) + */ +) \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt new file mode 100644 index 0000000000..c85f9124e1 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt @@ -0,0 +1,38 @@ +package chat.simplex.app.views + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.TerminalItem +import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.views.chat.SendMsgView + + +@Composable +fun TerminalView(chatModel: ChatModel) { + Column { + TerminalLog(chatModel.terminalItems) + SendMsgView(chatModel.controller::sendCmd) + } +} + +@Composable +fun TerminalLog(terminalItems: List) { + LazyColumn { + items(terminalItems) { item -> + Text(item.label) + } + } +} + +@Preview +@Composable +fun PreviewSendMsgView() { + SimpleXTheme { + TerminalView(chatModel = ChatModel.sampleData) + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt new file mode 100644 index 0000000000..0573b30e7d --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt @@ -0,0 +1,40 @@ +package chat.simplex.app.views.chat + +import androidx.compose.foundation.layout.* +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import chat.simplex.app.ui.theme.SimpleXTheme + +@Composable +fun SendMsgView (sendMessage: (String) -> Unit) { + var cmd by remember { mutableStateOf("") } + Row { + TextField(value = cmd, onValueChange = { cmd = it }, modifier = Modifier.height(60.dp)) + Spacer(Modifier.height(10.dp)) + Button( + onClick = { + sendMessage(cmd) + cmd = "" + }, + modifier = Modifier.width(40.dp).height(60.dp), + enabled = cmd.isNotEmpty() + ) { + Text("Go") + } + } +} + +@Preview +@Composable +fun PreviewSendMsgView() { + SimpleXTheme { + SendMsgView( + sendMessage = { msg -> println(msg) } + ) + } +} diff --git a/apps/android/app/src/main/res/layout/activity_main.xml b/apps/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 87429b38a0..0000000000 --- a/apps/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - diff --git a/apps/android/app/src/main/res/values-night/themes.xml b/apps/android/app/src/main/res/values-night/themes.xml deleted file mode 100644 index 639e5393c7..0000000000 --- a/apps/android/app/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/apps/android/app/src/main/res/values/themes.xml b/apps/android/app/src/main/res/values/themes.xml index 781d40bcf4..ced725b2b2 100644 --- a/apps/android/app/src/main/res/values/themes.xml +++ b/apps/android/app/src/main/res/values/themes.xml @@ -1,16 +1,7 @@ - - - \ No newline at end of file diff --git a/apps/android/build.gradle b/apps/android/build.gradle index 9988a75fc6..6cb503be83 100644 --- a/apps/android/build.gradle +++ b/apps/android/build.gradle @@ -1,16 +1,25 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext { + compose_version = '1.1.0' + } repositories { google() mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:7.0.4" + classpath 'com.android.tools.build:gradle:7.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" + classpath "org.jetbrains.kotlin:kotlin-serialization:1.3.2" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } +}// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '7.1.1' apply false + id 'com.android.library' version '7.1.1' apply false + id 'org.jetbrains.kotlin.android' version '1.6.10' apply false + id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.10' } task clean(type: Delete) { diff --git a/apps/android/gradle.properties b/apps/android/gradle.properties index 98bed167dc..831abc7d9f 100644 --- a/apps/android/gradle.properties +++ b/apps/android/gradle.properties @@ -15,7 +15,11 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # Android operating system, and which are packaged with your app"s APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true \ No newline at end of file diff --git a/apps/android/gradle/wrapper/gradle-wrapper.jar b/apps/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..e708b1c023 Binary files /dev/null and b/apps/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/apps/android/gradle/wrapper/gradle-wrapper.properties b/apps/android/gradle/wrapper/gradle-wrapper.properties index 05bd558a5e..2da1e43ef5 100644 --- a/apps/android/gradle/wrapper/gradle-wrapper.properties +++ b/apps/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jan 21 23:13:54 GMT 2022 +#Mon Feb 14 14:23:51 GMT 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/apps/android/settings.gradle b/apps/android/settings.gradle index 71e4f1f472..64c6620acf 100644 --- a/apps/android/settings.gradle +++ b/apps/android/settings.gradle @@ -1,9 +1,15 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() - jcenter() // Warning: this repository is going to shut down soon } } rootProject.name = "SimpleX"