Add simple C binding for iOS and Android projects (#120)

* Dev: add simple C binding for iOS and Android projects

* System: add ci script
This commit is contained in:
Andriy Druk 2021-11-04 09:52:38 +02:00 committed by GitHub
parent 7eea1a6178
commit 6d2d7fbf50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 405 additions and 114 deletions

17
.github/workflows/chat-lib-tests.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Chat Lib Tests
on: [push]
jobs:
test:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- name: Test
run: cd packages/chat;
cmake . -Bbuild;
cd build;
cmake --build .;
ctest --verbose

6
.gitignore vendored
View File

@ -49,3 +49,9 @@ stack.yaml.lock
# chat database
*.db
*.db.bak
packages/android/.idea
packages/chat/.idea
packages/chat/build
packages/chat/Testing
packages/ios/SimpleX Chat.xcodeproj/project.xcworkspace/xcuserdata
packages/ios/SimpleX Chat.xcodeproj/xcuserdata

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="/usr/local/Cellar/gradle/7.2/libexec" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -41,17 +41,29 @@ android {
}
}
buildFeatures {
viewBinding true
compose true
}
composeOptions {
kotlinCompilerExtensionVersion "1.0.4"
kotlinCompilerVersion "1.5.31"
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation "androidx.compose.ui:ui:1.0.4"
implementation "androidx.compose.runtime:runtime-livedata:1.0.4"
implementation "androidx.compose.material:material:1.0.4"
implementation "androidx.compose.animation:animation:1.0.4"
implementation "androidx.compose.animation:animation-core:1.0.4"
implementation 'androidx.activity:activity-compose:1.4.0-rc01'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'androidx.compose.ui:ui-tooling-preview:1.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

View File

@ -11,6 +11,7 @@
android:theme="@style/Theme.SimpleXChat">
<activity
android:name=".MainActivity"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -9,6 +9,10 @@ cmake_minimum_required(VERSION 3.10.2)
project("app")
include_directories(include)
add_subdirectory( ../../../../../chat/src build/src )
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
@ -45,4 +49,6 @@ target_link_libraries( # Specifies the target library.
# Links the target library to the log library
# included in the NDK.
${log-lib})
${log-lib}
SimpleXChatLib)

View File

@ -1,10 +1,15 @@
#include <jni.h>
#include <string>
#include "../../../../../chat/include/protocol.h"
extern "C" JNIEXPORT jstring JNICALL
Java_chat_simplex_app_MainActivity_stringFromJNI(
Java_chat_simplex_app_Protocol_executeCommand(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
jclass clazz,
jstring command) {
const char *utf8command = env->GetStringUTFChars(command, JNI_FALSE);
jstring result = env->NewStringUTF(executeCommand(utf8command));
env->ReleaseStringUTFChars(command, utf8command);
return result;
}

View File

@ -1,34 +1,91 @@
package chat.simplex.app
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import chat.simplex.app.databinding.ActivityMainBinding
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Send
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setContent {
var history by remember { mutableStateOf(mutableListOf("Session started:")) }
var inputValue by remember { mutableStateOf("") }
var inProgress by remember { mutableStateOf(false) }
// Example of a call to a native method
binding.sampleText.text = stringFromJNI()
fun sendMessage() {
CoroutineScope(Dispatchers.IO).launch {
inProgress = true
val str = Protocol.executeCommand(inputValue)
inputValue = ""
val newList = mutableListOf<String>()
newList.addAll(history)
newList.add(str)
Thread.sleep(1000); // emulate work
history = newList
inProgress = false
}
}
/**
* A native method that is implemented by the 'app' native library,
* which is packaged with this application.
*/
external fun stringFromJNI(): String
companion object {
// Used to load the 'app' library on application startup.
init {
System.loadLibrary("app")
MaterialTheme(colors = lightColors()) {
Surface(color = Color.White) {
Column {
LazyColumn(Modifier.weight(1.0f).fillMaxWidth()) {
itemsIndexed(history) { index: Int, message: String ->
if (index == 0) {
Spacer(modifier = Modifier.height(8.dp))
}
MessageView(message = message)
}
}
Row {
TextField(
modifier = Modifier.weight(1f),
value = inputValue,
onValueChange = { inputValue = it },
keyboardOptions = KeyboardOptions(imeAction = androidx.compose.ui.text.input.ImeAction.Send),
keyboardActions = KeyboardActions { sendMessage() },
singleLine = true
)
if (inProgress) {
Box(Modifier.height(56.dp).width(56.dp)) {
CircularProgressIndicator(modifier = Modifier.align(Center))
}
}
else {
Button(
modifier = Modifier.height(56.dp),
onClick = { sendMessage() },
enabled = inputValue.isNotBlank(),
) {
Icon(
imageVector = Icons.Default.Send,
contentDescription = "Send"
)
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,35 @@
package chat.simplex.app
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun MessageView(message: String) {
Box(Modifier.padding(4.dp)) {
Surface(color = Color.LightGray, modifier = Modifier.clip(shape = RoundedCornerShape(8.dp))) {
Box(Modifier.padding(8.dp)) {
Text(text = message)
}
}
}
}
@Preview(showBackground = true,
widthDp = 200,
heightDp = 50,
backgroundColor = android.graphics.Color.BLACK.toLong())
@Composable
fun DefaultMessageView() {
// A surface container using the 'background' color from the theme
Surface(color = Color.White, modifier = Modifier.padding(16.dp)) {
MessageView(message = "Hello world!")
}
}

View File

@ -0,0 +1,18 @@
package chat.simplex.app
class Protocol {
companion object {
// Used to load the 'app' library on application startup.
init {
System.loadLibrary("app")
}
/**
* A native method that is implemented by the 'app' native library,
* which is packaged with this application.
*/
@JvmStatic
external fun executeCommand(command: String): String
}
}

View File

@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.2"
classpath "com.android.tools.build:gradle:7.0.3"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
// NOTE: Do not place your application dependencies here; they belong

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.11) # set min version of cmake
project(SimpleXChat)
enable_testing()
include_directories(include)
add_subdirectory( src build/src )
add_subdirectory( test build/test )

View File

@ -0,0 +1,17 @@
// Public API: Protocol.h
#ifndef protocol_h
#define protocol_h
#ifdef __cplusplus
extern "C" { // only need to export C interface if
// used by C++ source code
#endif
const char* executeCommand(const char* command);
#ifdef __cplusplus
}
#endif
#endif /* protocol_h */

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.10) # set min version of cmake
project(SimpleXChatLib) # create chat lib
set(CMAKE_CXX_STANDARD 17)
# set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-header-filter=../include;-checks=*;")
include_directories ("${PROJECT_SOURCE_DIR}/../include")
file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/*.cpp)
add_library(SimpleXChatLib STATIC ${SRC_FILES})

View File

@ -1,13 +0,0 @@
#include "protocol.h"
extern void commandSend(Message m) {
}
Message getMessage() {
Message m;
m.message = "123";
m.date = 1000;
return m;
}

View File

@ -0,0 +1,16 @@
#include "protocol.h"
#include <string>
using ::std::string;
const char* executeCommand(const char *command) {
if (command == nullptr) {
auto *output = new string("> \nFailed");
return output->c_str();
}
string cmd(command);
string result = "Successful";
auto *output = new string("> " + cmd + "\n" + result);
return output->c_str();
}

View File

@ -1,9 +0,0 @@
struct Message {
char* message;
long date;
}
void commandSend(Message m);
Message getMessage();

View File

@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.11)
project(SimpleXChatTest)
include(FetchContent)
FetchContent_Declare(
googletest
# Specify the commit you depend on and update it regularly.
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
FetchContent_MakeAvailable(googletest)
# Now simply link against gtest or gtest_main as needed. Eg
file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/*.cpp)
add_executable(SimpleXChatTest ${SRC_FILES})
target_link_libraries(SimpleXChatTest gtest_main SimpleXChatLib)
add_test(NAME SimpleXChatTest COMMAND SimpleXChatTest)

View File

@ -0,0 +1,18 @@
#include "protocol.h"
#include "gtest/gtest.h"
namespace {
TEST(ProtocolTest, SuccessfulCommand) {
EXPECT_STREQ(executeCommand("Test1"), "> Test1\nSuccessful");
EXPECT_STREQ(executeCommand("Test2"), "> Test2\nSuccessful");
EXPECT_STREQ(executeCommand("Test3"), "> Test3\nSuccessful");
}
TEST(ProtocolTest, NullCommand) {
EXPECT_STREQ(executeCommand(nullptr), "> \nFailed");
}
} // namespace

View File

@ -14,8 +14,9 @@
5CBF96BA272D9CAA0021E76D /* SimpleX_ChatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBF96B9272D9CAA0021E76D /* SimpleX_ChatTests.swift */; };
5CBF96C4272D9CAA0021E76D /* SimpleX_ChatUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBF96C3272D9CAA0021E76D /* SimpleX_ChatUITests.swift */; };
5CBF96C6272D9CAA0021E76D /* SimpleX_ChatUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBF96C5272D9CAA0021E76D /* SimpleX_ChatUITestsLaunchTests.swift */; };
5CBF96D3272DA0520021E76D /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBF96D2272DA0520021E76D /* Messages.swift */; };
5CBF96D9272DA0CD0021E76D /* protocol.c in Sources */ = {isa = PBXBuildFile; fileRef = 5CBF96D8272DA0CD0021E76D /* protocol.c */; };
843E324A272EB3B800348BAB /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843E3249272EB3B800348BAB /* MessageView.swift */; };
843E324C272F04F100348BAB /* protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 843E324B272F04F100348BAB /* protocol.h */; };
843E324E272F04FA00348BAB /* protocol.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 843E324D272F04FA00348BAB /* protocol.cpp */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -46,9 +47,10 @@
5CBF96BF272D9CAA0021E76D /* SimpleX ChatUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SimpleX ChatUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
5CBF96C3272D9CAA0021E76D /* SimpleX_ChatUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleX_ChatUITests.swift; sourceTree = "<group>"; };
5CBF96C5272D9CAA0021E76D /* SimpleX_ChatUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleX_ChatUITestsLaunchTests.swift; sourceTree = "<group>"; };
5CBF96D2272DA0520021E76D /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = "<group>"; };
5CBF96D7272DA0CD0021E76D /* protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = protocol.h; sourceTree = "<group>"; };
5CBF96D8272DA0CD0021E76D /* protocol.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = protocol.c; sourceTree = "<group>"; };
843E3243272EADA200348BAB /* SimpleX Chat-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SimpleX Chat-Bridging-Header.h"; sourceTree = "<group>"; };
843E3249272EB3B800348BAB /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = "<group>"; };
843E324B272F04F100348BAB /* protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = protocol.h; path = ../../../chat/include/protocol.h; sourceTree = "<group>"; };
843E324D272F04FA00348BAB /* protocol.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = protocol.cpp; path = ../../../chat/src/protocol.cpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -83,6 +85,7 @@
5CBF96B8272D9CAA0021E76D /* SimpleX ChatTests */,
5CBF96C2272D9CAA0021E76D /* SimpleX ChatUITests */,
5CBF96A6272D9CA60021E76D /* Products */,
843E3205272EA78D00348BAB /* Frameworks */,
);
sourceTree = "<group>";
};
@ -101,10 +104,11 @@
children = (
5CBF96A8272D9CA60021E76D /* SimpleX_ChatApp.swift */,
5CBF96AA272D9CA60021E76D /* ContentView.swift */,
843E3249272EB3B800348BAB /* MessageView.swift */,
5CBF96AC272D9CA90021E76D /* Assets.xcassets */,
5CBF96AE272D9CA90021E76D /* Preview Content */,
5CBF96D2272DA0520021E76D /* Messages.swift */,
5CBF96D4272DA0CD0021E76D /* chat */,
843E3246272EAEA900348BAB /* ChatLib */,
843E3243272EADA200348BAB /* SimpleX Chat-Bridging-Header.h */,
);
path = "SimpleX Chat";
sourceTree = "<group>";
@ -134,39 +138,41 @@
path = "SimpleX ChatUITests";
sourceTree = "<group>";
};
5CBF96D4272DA0CD0021E76D /* chat */ = {
843E3205272EA78D00348BAB /* Frameworks */ = {
isa = PBXGroup;
children = (
5CBF96D5272DA0CD0021E76D /* tests */,
5CBF96D6272DA0CD0021E76D /* src */,
);
name = chat;
path = ../../chat;
name = Frameworks;
sourceTree = "<group>";
};
5CBF96D5272DA0CD0021E76D /* tests */ = {
843E3246272EAEA900348BAB /* ChatLib */ = {
isa = PBXGroup;
children = (
843E324B272F04F100348BAB /* protocol.h */,
843E324D272F04FA00348BAB /* protocol.cpp */,
);
path = tests;
sourceTree = "<group>";
};
5CBF96D6272DA0CD0021E76D /* src */ = {
isa = PBXGroup;
children = (
5CBF96D7272DA0CD0021E76D /* protocol.h */,
5CBF96D8272DA0CD0021E76D /* protocol.c */,
);
path = src;
path = ChatLib;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
843E31EA272DA43500348BAB /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
843E324C272F04F100348BAB /* protocol.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
5CBF96A4272D9CA60021E76D /* SimpleX Chat */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5CBF96C9272D9CAA0021E76D /* Build configuration list for PBXNativeTarget "SimpleX Chat" */;
buildPhases = (
843E31EA272DA43500348BAB /* Headers */,
5CBF96A1272D9CA60021E76D /* Sources */,
5CBF96A2272D9CA60021E76D /* Frameworks */,
5CBF96A3272D9CA60021E76D /* Resources */,
@ -223,11 +229,12 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1310;
LastSwiftUpdateCheck = 1300;
LastUpgradeCheck = 1310;
TargetAttributes = {
5CBF96A4272D9CA60021E76D = {
CreatedOnToolsVersion = 13.1;
LastSwiftMigration = 1300;
};
5CBF96B4272D9CAA0021E76D = {
CreatedOnToolsVersion = 13.1;
@ -290,10 +297,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
843E324A272EB3B800348BAB /* MessageView.swift in Sources */,
5CBF96AB272D9CA60021E76D /* ContentView.swift in Sources */,
5CBF96D9272DA0CD0021E76D /* protocol.c in Sources */,
5CBF96D3272DA0520021E76D /* Messages.swift in Sources */,
5CBF96A9272D9CA60021E76D /* SimpleX_ChatApp.swift in Sources */,
843E324E272F04FA00348BAB /* protocol.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -451,6 +458,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"SimpleX Chat/Preview Content\"";
@ -470,6 +478,8 @@
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.SimpleX-Chat";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "SimpleX Chat/SimpleX Chat-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@ -480,6 +490,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"SimpleX Chat/Preview Content\"";
@ -499,6 +510,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.SimpleX-Chat";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "SimpleX Chat/SimpleX Chat-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};

View File

@ -8,9 +8,56 @@
import SwiftUI
struct ContentView: View {
@State var history: [String] = ["Start session:"]
@State var text: String = ""
@State var inProgress: Bool = false
func sendMessage() {
DispatchQueue.global().async {
let string: String = self.$text.wrappedValue
inProgress = true
text = ""
if let result = executeCommand(string),
let stringResult = String(utf8String: result) {
sleep(1) // emulate work
history += [stringResult]
}
inProgress = false
}
}
var body: some View {
Text("Hello, world!")
.padding()
VStack {
ScrollView {
LazyVStack {
ForEach(history, id: \.self) { msg in
MessageView(message: msg, isCurrentUser: false)
}
}
.padding(10)
}
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading)
HStack {
TextField("Message...", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(minHeight: CGFloat(30))
if (inProgress) {
ProgressView()
.frame(width: 40,
height: 20,
alignment: .center)
}
else {
Button(action: sendMessage) {
Text("Send")
}.disabled(text.isEmpty)
}
}.frame(minHeight: CGFloat(30)).padding()
}
}
}
@ -19,3 +66,4 @@ struct ContentView_Previews: PreviewProvider {
ContentView()
}
}

View File

@ -0,0 +1,32 @@
//
// ContentView.swift
// SimpleX Chat
//
// Created by Evgeny on 30/10/2021.
//
import SwiftUI
struct MessageView: View {
var message: String
var isCurrentUser: Bool
var body: some View {
Text(message)
.padding(10)
.foregroundColor(isCurrentUser ? Color.white : Color.black)
.background(isCurrentUser ? Color.blue : Color(UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1.0)))
.cornerRadius(10)
.frame(minWidth: 100,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .leading)
}
}
struct MessageView_Previews: PreviewProvider {
static var previews: some View {
MessageView(message: "> Send message: \"Hello world!\"\nSuccessful", isCurrentUser: false)
}
}

View File

@ -0,0 +1,5 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "protocol.h"