mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-29 04:39:53 +00:00
started android / compose app (#301)
* new compose project * classes for chat command and response * use val with get() for commands and responses * chat model * initial jetpack compose set up * wire it up with chat * first ability to send and receive messages * refactor model/controller interface * JSON samples * terminal view with items * playing around with json * JSON serialization works * parsing API responses in the terminal * add subclass for contactSubscribed reponse * remove android-poc * remove JSON example Co-authored-by: IanRDavies <ian_davies_@hotmail.co.uk>
This commit is contained in:
parent
322ab9d854
commit
ce02c514cf
29 changed files with 665 additions and 352 deletions
11
apps/android/.gitignore
vendored
11
apps/android/.gitignore
vendored
|
@ -8,17 +8,8 @@
|
||||||
/.idea/navEditor.xml
|
/.idea/navEditor.xml
|
||||||
/.idea/assetWizardSettings.xml
|
/.idea/assetWizardSettings.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
build/
|
/build
|
||||||
release/
|
|
||||||
debug/
|
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
*.jar
|
|
||||||
*.war
|
|
||||||
*.nar
|
|
||||||
*.ear
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
3
apps/android/.idea/.gitignore
generated
vendored
3
apps/android/.idea/.gitignore
generated
vendored
|
@ -1,3 +0,0 @@
|
||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
117
apps/android/.idea/codeStyles/Project.xml
generated
117
apps/android/.idea/codeStyles/Project.xml
generated
|
@ -1,117 +0,0 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<code_scheme name="Project" version="173">
|
|
||||||
<codeStyleSettings language="XML">
|
|
||||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
|
||||||
<indentOptions>
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
|
||||||
</indentOptions>
|
|
||||||
<arrangement>
|
|
||||||
<rules>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>xmlns:android</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>xmlns:.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:id</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:name</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>name</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>style</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
</rules>
|
|
||||||
</arrangement>
|
|
||||||
</codeStyleSettings>
|
|
||||||
</code_scheme>
|
|
||||||
</component>
|
|
|
@ -1,5 +0,0 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<state>
|
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
|
||||||
</state>
|
|
||||||
</component>
|
|
17
apps/android/.idea/deploymentTargetDropDown.xml
generated
Normal file
17
apps/android/.idea/deploymentTargetDropDown.xml
generated
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<runningDeviceTargetSelectedWithDropDown>
|
||||||
|
<Target>
|
||||||
|
<type value="RUNNING_DEVICE_TARGET" />
|
||||||
|
<deviceKey>
|
||||||
|
<Key>
|
||||||
|
<type value="VIRTUAL_DEVICE_PATH" />
|
||||||
|
<value value="$USER_HOME$/.android/avd/Pixel_4a_API_32.avd" />
|
||||||
|
</Key>
|
||||||
|
</deviceKey>
|
||||||
|
</Target>
|
||||||
|
</targetSelectedWithDropDown>
|
||||||
|
<timeTargetWasSelectedWithDropDown value="2022-02-15T15:32:14.669079Z" />
|
||||||
|
</component>
|
||||||
|
</project>
|
20
apps/android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
20
apps/android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
7
apps/android/.idea/misc.xml
generated
7
apps/android/.idea/misc.xml
generated
|
@ -3,9 +3,10 @@
|
||||||
<component name="DesignSurface">
|
<component name="DesignSurface">
|
||||||
<option name="filePathToZoomLevelMap">
|
<option name="filePathToZoomLevelMap">
|
||||||
<map>
|
<map>
|
||||||
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2328125" />
|
<entry key="../../../../../../../layout/compose-model-1644940819446.xml" value="0.4484797297297297" />
|
||||||
<entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.2328125" />
|
<entry key="../../../../../../../layout/compose-model-1644941851914.xml" value="0.28378378378378377" />
|
||||||
<entry key="app/src/main/res/layout/activity_main.xml" value="1.0" />
|
<entry key="../../../../../../../layout/compose-model-1644956742665.xml" value="1.0" />
|
||||||
|
<entry key="../../../../../../../layout/compose-model-1644963789622.xml" value="0.8420454545454545" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|
1
apps/android/app/.gitignore
vendored
Normal file
1
apps/android/app/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
|
@ -1,6 +1,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
id 'org.jetbrains.kotlin.plugin.serialization'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
@ -17,6 +18,9 @@ android {
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'arm64-v8a'
|
abiFilters 'arm64-v8a'
|
||||||
}
|
}
|
||||||
|
vectorDrawables {
|
||||||
|
useSupportLibrary true
|
||||||
|
}
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
cppFlags ''
|
cppFlags ''
|
||||||
|
@ -44,17 +48,29 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
compose true
|
||||||
|
}
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion compose_version
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
resources {
|
||||||
|
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.7.0'
|
implementation 'androidx.core:core-ktx:1.7.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
implementation "androidx.compose.ui:ui:$compose_version"
|
||||||
implementation 'com.google.android.material:material:1.5.0'
|
implementation "androidx.compose.material:material:$compose_version"
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
|
||||||
testImplementation 'junit:junit:4.+'
|
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.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
}
|
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
|
||||||
|
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="SimplexApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -15,7 +16,9 @@
|
||||||
android:theme="@style/Theme.SimpleX">
|
android:theme="@style/Theme.SimpleX">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true">
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.SimpleX">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ void setLineBuffering(void);
|
||||||
int pipe_std_to_socket(const char * name);
|
int pipe_std_to_socket(const char * name);
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
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);
|
const char *name = (*env)->GetStringUTFChars(env, socket_name, JNI_FALSE);
|
||||||
int ret = pipe_std_to_socket(name);
|
int ret = pipe_std_to_socket(name);
|
||||||
(*env)->ReleaseStringUTFChars(env, socket_name, name);
|
(*env)->ReleaseStringUTFChars(env, socket_name, name);
|
||||||
|
@ -16,7 +16,7 @@ Java_chat_simplex_app_MainActivityKt_pipeStdOutToSocket(JNIEnv *env, __unused jc
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
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);
|
hs_init(NULL, NULL);
|
||||||
setLineBuffering();
|
setLineBuffering();
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ extern char *chat_send_cmd(controller ctl, const char *cmd);
|
||||||
extern char *chat_recv_msg(controller ctl);
|
extern char *chat_recv_msg(controller ctl);
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
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);
|
const char *_data = (*env)->GetStringUTFChars(env, datadir, JNI_FALSE);
|
||||||
jlong res = (jlong)chat_init_store(_data);
|
jlong res = (jlong)chat_init_store(_data);
|
||||||
(*env)->ReleaseStringUTFChars(env, datadir, _data);
|
(*env)->ReleaseStringUTFChars(env, datadir, _data);
|
||||||
|
@ -41,12 +41,12 @@ Java_chat_simplex_app_MainActivityKt_chatInit(JNIEnv *env, __unused jclass clazz
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
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));
|
return (*env)->NewStringUTF(env, chat_get_user((void*)controller));
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
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);
|
const char *_data = (*env)->GetStringUTFChars(env, data, JNI_FALSE);
|
||||||
jstring res = (*env)->NewStringUTF(env, chat_create_user((void*)controller, _data));
|
jstring res = (*env)->NewStringUTF(env, chat_create_user((void*)controller, _data));
|
||||||
(*env)->ReleaseStringUTFChars(env, data, _data);
|
(*env)->ReleaseStringUTFChars(env, data, _data);
|
||||||
|
@ -54,12 +54,12 @@ Java_chat_simplex_app_MainActivityKt_chatCreateUser(JNIEnv *env, __unused jclass
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
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);
|
return (jlong)chat_start((void*)controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
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);
|
const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE);
|
||||||
jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg));
|
jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg));
|
||||||
(*env)->ReleaseStringUTFChars(env, msg, _msg);
|
(*env)->ReleaseStringUTFChars(env, msg, _msg);
|
||||||
|
@ -67,6 +67,6 @@ Java_chat_simplex_app_MainActivityKt_chatSendCmd(JNIEnv *env, __unused jclass cl
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
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));
|
return (*env)->NewStringUTF(env, chat_recv_msg((void*)controller));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,123 +1,37 @@
|
||||||
package chat.simplex.app
|
package chat.simplex.app
|
||||||
|
|
||||||
import android.net.LocalServerSocket
|
import android.app.Application
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import androidx.activity.ComponentActivity
|
||||||
import android.view.inputmethod.EditorInfo
|
import androidx.activity.compose.setContent
|
||||||
import android.widget.ScrollView
|
import androidx.activity.viewModels
|
||||||
import android.widget.TextView
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||||
import androidx.appcompat.widget.AppCompatEditText
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import java.io.BufferedReader
|
import chat.simplex.app.model.*
|
||||||
import java.io.InputStreamReader
|
import chat.simplex.app.views.TerminalView
|
||||||
import java.lang.ref.WeakReference
|
import kotlinx.serialization.*
|
||||||
import java.util.*
|
import kotlinx.serialization.json.*
|
||||||
import java.util.concurrent.Semaphore
|
import kotlinx.serialization.modules.*
|
||||||
import kotlin.concurrent.thread
|
|
||||||
|
|
||||||
// ghc's rts
|
|
||||||
external fun initHS()
|
|
||||||
// android-support
|
|
||||||
external fun pipeStdOutToSocket(socketName: String) : Int
|
|
||||||
|
|
||||||
// simplex-chat
|
class MainActivity: ComponentActivity() {
|
||||||
typealias Store = Long
|
private val viewModel by viewModels<SimplexViewModel>()
|
||||||
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() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
weakActivity = WeakReference(this)
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContent {
|
||||||
|
SimpleXTheme {
|
||||||
val store : Store = chatInit(this.applicationContext.filesDir.toString())
|
MainPage(viewModel)
|
||||||
// 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<AppCompatEditText>(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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread(name="receiver") {
|
|
||||||
val chatlog = FifoQueue<String>(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<TextView>(R.id.chatlog)
|
|
||||||
val scroll = weakActivity.get()?.findViewById<ScrollView>(R.id.scroller)
|
|
||||||
log?.text = currentText
|
|
||||||
scroll?.scrollTo(0, scroll.getChildAt(0).height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
lateinit var weakActivity : WeakReference<MainActivity>
|
|
||||||
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<String>(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<E>(private var capacity: Int) : LinkedList<E>() {
|
class SimplexViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
override fun add(element: E): Boolean {
|
val chatModel = getApplication<SimplexApp>().chatModel
|
||||||
if(size > capacity) removeFirst()
|
}
|
||||||
return super.add(element)
|
|
||||||
}
|
@Composable
|
||||||
|
fun MainPage(vm: SimplexViewModel) {
|
||||||
|
TerminalView(vm.chatModel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<MainActivity>
|
||||||
|
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<String>(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<E>(private var capacity: Int) : LinkedList<E>() {
|
||||||
|
override fun add(element: E): Boolean {
|
||||||
|
if(size > capacity) removeFirst()
|
||||||
|
return super.add(element)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<TerminalItem>()
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
|
@ -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<String>(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<APIResponse>(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": {<user>}}}}
|
||||||
|
// {"resp": {"anythingElse": <json> }} -> Unknown(type = "anythingElse", json = "<the whole thing including resp>")
|
||||||
|
|
||||||
|
// {"type": "activeUser", "user": <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>(
|
||||||
|
TerminalItem.Cmd(CC.ShowActiveUser()),
|
||||||
|
TerminalItem.Resp(CR.ActiveUser(User.sampleData))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
@ -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)
|
||||||
|
)
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
)
|
|
@ -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<TerminalItem>) {
|
||||||
|
LazyColumn {
|
||||||
|
items(terminalItems) { item ->
|
||||||
|
Text(item.label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewSendMsgView() {
|
||||||
|
SimpleXTheme {
|
||||||
|
TerminalView(chatModel = ChatModel.sampleData)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".MainActivity">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/cmdInput"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:autofillHints=""
|
|
||||||
android:ems="10"
|
|
||||||
android:imeOptions="actionSend"
|
|
||||||
android:inputType="textPersonName"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:ignore="SpeakableTextPresentCheck" />
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:id="@+id/scroller"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/cmdInput"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/chatlog"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,16 +0,0 @@
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Theme.SimpleX" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
|
||||||
<!-- Primary brand color. -->
|
|
||||||
<item name="colorPrimary">@color/purple_200</item>
|
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
|
||||||
<item name="colorOnPrimary">@color/black</item>
|
|
||||||
<!-- Secondary brand color. -->
|
|
||||||
<item name="colorSecondary">@color/teal_200</item>
|
|
||||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
|
||||||
<!-- Status bar color. -->
|
|
||||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
|
@ -1,16 +1,7 @@
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Base application theme. -->
|
<resources>
|
||||||
<style name="Theme.SimpleX" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
|
||||||
<!-- Primary brand color. -->
|
<style name="Theme.SimpleX" parent="android:Theme.Material.Light.NoActionBar">
|
||||||
<item name="colorPrimary">@color/purple_500</item>
|
<item name="android:statusBarColor">@color/purple_700</item>
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
|
||||||
<!-- Secondary brand color. -->
|
|
||||||
<item name="colorSecondary">@color/teal_200</item>
|
|
||||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
|
||||||
<!-- Status bar color. -->
|
|
||||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
|
@ -1,16 +1,25 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
ext {
|
||||||
|
compose_version = '1.1.0'
|
||||||
|
}
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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-gradle-plugin:1.6.10"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-serialization:1.3.2"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// 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) {
|
task clean(type: Delete) {
|
||||||
|
|
|
@ -15,7 +15,11 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
# Android operating system, and which are packaged with your app"s APK
|
# Android operating system, and which are packaged with your app"s APK
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
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 for this project: "official" or "obsolete":
|
||||||
kotlin.code.style=official
|
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
|
BIN
apps/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
apps/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
|
@ -1,6 +1,6 @@
|
||||||
#Fri Jan 21 23:13:54 GMT 2022
|
#Mon Feb 14 14:23:51 GMT 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
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
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter() // Warning: this repository is going to shut down soon
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootProject.name = "SimpleX"
|
rootProject.name = "SimpleX"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue