mobile: message actions (reply, share, copy) (#431)

* ios: add context menu to messages

* ios: UI for replies with quotes

* fix: scrolling crashing in chat

* ios: UI for message replies with quotes

* android: UI for message replies

* android: messages with quotes

* android: update imports

* android: refactor ChatItemView

* remove comments
This commit is contained in:
Evgeny Poberezkin 2022-03-17 09:42:59 +00:00 committed by GitHub
parent 148474e1ba
commit 744c451927
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1005 additions and 352 deletions

View File

@ -41,6 +41,7 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '1.8'
freeCompilerArgs += "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi" freeCompilerArgs += "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi"
freeCompilerArgs += "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi"
freeCompilerArgs += "-opt-in=androidx.compose.ui.text.ExperimentalTextApi" freeCompilerArgs += "-opt-in=androidx.compose.ui.text.ExperimentalTextApi"
freeCompilerArgs += "-opt-in=androidx.compose.material.ExperimentalMaterialApi" freeCompilerArgs += "-opt-in=androidx.compose.material.ExperimentalMaterialApi"
freeCompilerArgs += "-opt-in=com.google.accompanist.insets.ExperimentalAnimatedInsets" freeCompilerArgs += "-opt-in=com.google.accompanist.insets.ExperimentalAnimatedInsets"

View File

@ -112,9 +112,8 @@ fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) {
} }
//fun testJson() { //fun testJson() {
// val str = """ // val str: String = """
// {}
// """.trimIndent() // """.trimIndent()
// //
// println(json.decodeFromString<ChatItem>(str)) // println(json.decodeFromString<APIResponse>(str))
//} //}

View File

@ -4,10 +4,8 @@ import android.content.Context
import android.util.Log import android.util.Log
import androidx.work.* import androidx.work.*
import chat.simplex.app.TAG import chat.simplex.app.TAG
import chat.simplex.app.chatRecvMsg
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import java.time.Duration import java.time.Duration
import java.util.concurrent.TimeUnit
class BGManager(appContext: Context, workerParams: WorkerParameters): //, ctrl: ChatCtrl): class BGManager(appContext: Context, workerParams: WorkerParameters): //, ctrl: ChatCtrl):
Worker(appContext, workerParams) { Worker(appContext, workerParams) {

View File

@ -466,24 +466,31 @@ data class ChatItem (
val chatDir: CIDirection, val chatDir: CIDirection,
val meta: CIMeta, val meta: CIMeta,
val content: CIContent, val content: CIContent,
val formattedText: List<FormattedText>? = null val formattedText: List<FormattedText>? = null,
val quotedItem: CIQuote? = null
) { ) {
val id: Long get() = meta.itemId val id: Long get() = meta.itemId
val timestampText: String get() = meta.timestampText val timestampText: String get() = meta.timestampText
val isRcvNew: Boolean get() = meta.itemStatus is CIStatus.RcvNew val isRcvNew: Boolean get() = meta.itemStatus is CIStatus.RcvNew
val memberDisplayName: String? get() =
if (chatDir is CIDirection.GroupRcv) chatDir.groupMember.memberProfile.displayName
else null
companion object { companion object {
fun getSampleData( fun getSampleData(
id: Long = 1, id: Long = 1,
dir: CIDirection = CIDirection.DirectSnd(), dir: CIDirection = CIDirection.DirectSnd(),
ts: Instant = Clock.System.now(), ts: Instant = Clock.System.now(),
text: String = "hello\nthere", text: String = "hello\nthere",
status: CIStatus = CIStatus.SndNew() status: CIStatus = CIStatus.SndNew(),
quotedItem: CIQuote? = null
) = ) =
ChatItem( ChatItem(
chatDir = dir, chatDir = dir,
meta = CIMeta.getSample(id, ts, text, status), meta = CIMeta.getSample(id, ts, text, status),
content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text)) content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text)),
quotedItem = quotedItem
) )
} }
} }
@ -566,9 +573,13 @@ sealed class CIStatus {
class RcvRead: CIStatus() class RcvRead: CIStatus()
} }
interface ItemContent {
val text: String
}
@Serializable @Serializable
sealed class CIContent { sealed class CIContent: ItemContent {
abstract val text: String abstract override val text: String
@Serializable @SerialName("sndMsgContent") @Serializable @SerialName("sndMsgContent")
class SndMsgContent(val msgContent: MsgContent): CIContent() { class SndMsgContent(val msgContent: MsgContent): CIContent() {
@ -591,6 +602,31 @@ sealed class CIContent {
} }
} }
@Serializable
class CIQuote (
val chatDir: CIDirection? = null,
val itemId: Long? = null,
val sharedMsgId: String? = null,
val sentAt: Instant,
val content: MsgContent,
val formattedText: List<FormattedText>? = null
): ItemContent {
override val text: String get() = content.text
fun sender(user: User): String? = when (chatDir) {
is CIDirection.DirectSnd -> "you"
is CIDirection.DirectRcv -> null
is CIDirection.GroupSnd -> user.displayName
is CIDirection.GroupRcv -> chatDir.groupMember.memberProfile.displayName
null -> null
}
companion object {
fun getSample(itemId: Long?, sentAt: Instant, text: String, chatDir: CIDirection?): CIQuote =
CIQuote(chatDir = chatDir, itemId = itemId, sentAt = sentAt, content = MsgContent.MCText(text))
}
}
@Serializable(with = MsgContentSerializer::class) @Serializable(with = MsgContentSerializer::class)
sealed class MsgContent { sealed class MsgContent {
abstract val text: String abstract val text: String

View File

@ -122,8 +122,10 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap
return null return null
} }
suspend fun apiSendMessage(type: ChatType, id: Long, mc: MsgContent): AChatItem? { suspend fun apiSendMessage(type: ChatType, id: Long, quotedItemId: Long? = null, mc: MsgContent): AChatItem? {
val r = sendCmd(CC.ApiSendMessage(type, id, mc)) val cmd = if (quotedItemId == null) CC.ApiSendMessage(type, id, mc)
else CC.ApiSendMessageQuote(type, id, quotedItemId, mc)
val r = sendCmd(cmd)
if (r is CR.NewChatItem ) return r.chatItem if (r is CR.NewChatItem ) return r.chatItem
Log.e(TAG, "apiSendMessage bad response: ${r.responseType} ${r.details}") Log.e(TAG, "apiSendMessage bad response: ${r.responseType} ${r.details}")
return null return null
@ -343,6 +345,7 @@ sealed class CC {
class ApiGetChats: CC() class ApiGetChats: CC()
class ApiGetChat(val type: ChatType, val id: Long): CC() class ApiGetChat(val type: ChatType, val id: Long): CC()
class ApiSendMessage(val type: ChatType, val id: Long, val mc: MsgContent): CC() class ApiSendMessage(val type: ChatType, val id: Long, val mc: MsgContent): CC()
class ApiSendMessageQuote(val type: ChatType, val id: Long, val itemId: Long, val mc: MsgContent): CC()
class GetUserSMPServers(): CC() class GetUserSMPServers(): CC()
class SetUserSMPServers(val smpServers: List<String>): CC() class SetUserSMPServers(val smpServers: List<String>): CC()
class AddContact: CC() class AddContact: CC()
@ -364,6 +367,7 @@ sealed class CC {
is ApiGetChats -> "/_get chats" is ApiGetChats -> "/_get chats"
is ApiGetChat -> "/_get chat ${chatRef(type, id)} count=100" is ApiGetChat -> "/_get chat ${chatRef(type, id)} count=100"
is ApiSendMessage -> "/_send ${chatRef(type, id)} ${mc.cmdString}" is ApiSendMessage -> "/_send ${chatRef(type, id)} ${mc.cmdString}"
is ApiSendMessageQuote -> "/_send_quote ${chatRef(type, id)} $itemId ${mc.cmdString}"
is GetUserSMPServers -> "/smp_servers" is GetUserSMPServers -> "/smp_servers"
is SetUserSMPServers -> "/smp_servers ${smpServersStr(smpServers)}" is SetUserSMPServers -> "/smp_servers ${smpServersStr(smpServers)}"
is AddContact -> "/connect" is AddContact -> "/connect"
@ -386,6 +390,7 @@ sealed class CC {
is ApiGetChats -> "apiGetChats" is ApiGetChats -> "apiGetChats"
is ApiGetChat -> "apiGetChat" is ApiGetChat -> "apiGetChat"
is ApiSendMessage -> "apiSendMessage" is ApiSendMessage -> "apiSendMessage"
is ApiSendMessageQuote -> "apiSendMessageQuote"
is GetUserSMPServers -> "getUserSMPServers" is GetUserSMPServers -> "getUserSMPServers"
is SetUserSMPServers -> "setUserSMPServers" is SetUserSMPServers -> "setUserSMPServers"
is AddContact -> "addContact" is AddContact -> "addContact"
@ -422,6 +427,7 @@ class APIResponse(val resp: CR, val corr: String? = null) {
json.decodeFromString(str) json.decodeFromString(str)
} catch(e: Exception) { } catch(e: Exception) {
try { try {
Log.d(TAG, e.localizedMessage)
val data = json.parseToJsonElement(str).jsonObject val data = json.parseToJsonElement(str).jsonObject
APIResponse( APIResponse(
resp = CR.Response(data["resp"]!!.jsonObject["type"]?.toString() ?: "invalid", json.encodeToString(data)), resp = CR.Response(data["resp"]!!.jsonObject["type"]?.toString() ?: "invalid", json.encodeToString(data)),

View File

@ -3,18 +3,19 @@ package chat.simplex.app.views.chat
import android.content.res.Configuration import android.content.res.Configuration
import android.util.Log import android.util.Log
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background import androidx.compose.foundation.*
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.* import androidx.compose.foundation.lazy.*
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.saveable.mapSaver
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@ -22,6 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.simplex.app.TAG import chat.simplex.app.TAG
import chat.simplex.app.model.* import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.item.ChatItemView import chat.simplex.app.views.chat.item.ChatItemView
import chat.simplex.app.views.helpers.* import chat.simplex.app.views.helpers.*
@ -35,9 +37,11 @@ import kotlinx.datetime.Clock
@Composable @Composable
fun ChatView(chatModel: ChatModel) { fun ChatView(chatModel: ChatModel) {
val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value } val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }
if (chat == null) { val user = chatModel.currentUser.value
if (chat == null || user == null) {
chatModel.chatId.value = null chatModel.chatId.value = null
} else { } else {
val quotedItem = remember { mutableStateOf<ChatItem?>(null) }
BackHandler { chatModel.chatId.value = null } BackHandler { chatModel.chatId.value = null }
// TODO a more advanced version would mark as read only if in view // TODO a more advanced version would mark as read only if in view
LaunchedEffect(chat.chatItems) { LaunchedEffect(chat.chatItems) {
@ -54,7 +58,7 @@ fun ChatView(chatModel: ChatModel) {
} }
} }
} }
ChatLayout(chat, chatModel.chatItems, ChatLayout(user, chat, chatModel.chatItems, quotedItem,
back = { chatModel.chatId.value = null }, back = { chatModel.chatId.value = null },
info = { ModalManager.shared.showCustomModal { close -> ChatInfoView(chatModel, close) } }, info = { ModalManager.shared.showCustomModal { close -> ChatInfoView(chatModel, close) } },
sendMessage = { msg -> sendMessage = { msg ->
@ -64,8 +68,10 @@ fun ChatView(chatModel: ChatModel) {
val newItem = chatModel.controller.apiSendMessage( val newItem = chatModel.controller.apiSendMessage(
type = cInfo.chatType, type = cInfo.chatType,
id = cInfo.apiId, id = cInfo.apiId,
quotedItemId = quotedItem.value?.meta?.itemId,
mc = MsgContent.MCText(msg) mc = MsgContent.MCText(msg)
) )
quotedItem.value = null
// hide "in progress" // hide "in progress"
if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem) if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem)
} }
@ -76,7 +82,10 @@ fun ChatView(chatModel: ChatModel) {
@Composable @Composable
fun ChatLayout( fun ChatLayout(
chat: Chat, chatItems: List<ChatItem>, user: User,
chat: Chat,
chatItems: List<ChatItem>,
quotedItem: MutableState<ChatItem?>,
back: () -> Unit, back: () -> Unit,
info: () -> Unit, info: () -> Unit,
sendMessage: (String) -> Unit sendMessage: (String) -> Unit
@ -88,11 +97,11 @@ fun ChatLayout(
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Scaffold( Scaffold(
topBar = { ChatInfoToolbar(chat, back, info) }, topBar = { ChatInfoToolbar(chat, back, info) },
bottomBar = { SendMsgView(sendMessage) }, bottomBar = { ComposeView(quotedItem, sendMessage) },
modifier = Modifier.navigationBarsWithImePadding() modifier = Modifier.navigationBarsWithImePadding()
) { contentPadding -> ) { contentPadding ->
Box(Modifier.padding(contentPadding)) { Box(Modifier.padding(contentPadding)) {
ChatItemsList(chatItems) ChatItemsList(user, chatItems, quotedItem)
} }
} }
} }
@ -152,7 +161,7 @@ val CIListStateSaver = run {
} }
@Composable @Composable
fun ChatItemsList(chatItems: List<ChatItem>) { fun ChatItemsList(user: User, chatItems: List<ChatItem>, quotedItem: MutableState<ChatItem?>) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
val keyboardState by getKeyboardState() val keyboardState by getKeyboardState()
val ciListState = rememberSaveable(stateSaver = CIListStateSaver) { val ciListState = rememberSaveable(stateSaver = CIListStateSaver) {
@ -160,9 +169,10 @@ fun ChatItemsList(chatItems: List<ChatItem>) {
} }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val cxt = LocalContext.current
LazyColumn(state = listState) { LazyColumn(state = listState) {
items(chatItems) { cItem -> items(chatItems) { cItem ->
ChatItemView(cItem, uriHandler) ChatItemView(user, cItem, quotedItem, cxt, uriHandler)
} }
val len = chatItems.count() val len = chatItems.count()
if (len > 1 && (keyboardState != ciListState.value.keyboardState || !ciListState.value.scrolled || len != ciListState.value.itemCount)) { if (len > 1 && (keyboardState != ciListState.value.keyboardState || !ciListState.value.scrolled || len != ciListState.value.itemCount)) {
@ -201,12 +211,14 @@ fun PreviewChatLayout() {
) )
) )
ChatLayout( ChatLayout(
user = User.sampleData,
chat = Chat( chat = Chat(
chatInfo = ChatInfo.Direct.sampleData, chatInfo = ChatInfo.Direct.sampleData,
chatItems = chatItems, chatItems = chatItems,
chatStats = Chat.ChatStats() chatStats = Chat.ChatStats()
), ),
chatItems = chatItems, chatItems = chatItems,
quotedItem = remember { mutableStateOf(null) },
back = {}, back = {},
info = {}, info = {},
sendMessage = {} sendMessage = {}

View File

@ -0,0 +1,14 @@
package chat.simplex.app.views.chat
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import chat.simplex.app.model.ChatItem
@Composable
fun ComposeView(quotedItem: MutableState<ChatItem?>, sendMessage: (String) -> Unit) {
Column {
QuotedItemView(quotedItem)
SendMsgView(sendMessage)
}
}

View File

@ -0,0 +1,77 @@
package chat.simplex.app.views.chat
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.model.CIDirection
import chat.simplex.app.model.ChatItem
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.item.*
import kotlinx.datetime.Clock
@Composable
fun QuotedItemView(quotedItem: MutableState<ChatItem?>) {
val qi = quotedItem.value
if (qi != null) {
val sent = qi.chatDir.sent
Row(
Modifier.padding(top = 8.dp)
.background(if (sent) SentColorLight else ReceivedColorLight),
verticalAlignment = Alignment.CenterVertically
) {
Box(
Modifier.padding(start = 16.dp)
.padding(vertical = 12.dp)
.fillMaxWidth()
.weight(1F)
) {
QuoteText(qi)
}
IconButton(onClick = { quotedItem.value = null }) {
Icon(
Icons.Outlined.Close,
"Remove quote",
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
}
}
}
}
@Composable
private fun QuoteText(qi: ChatItem) {
val member = qi.memberDisplayName
if (member == null) {
Text(qi.content.text, maxLines = 3)
} else {
val annotatedText = buildAnnotatedString {
withStyle(boldFont) { append(member) }
append(": ${qi.content.text}")
}
Text(annotatedText, maxLines = 3)
}
}
@Preview
@Composable
fun PreviewTextItemViewEmoji() {
SimpleXTheme {
QuotedItemView(
quotedItem = remember {
mutableStateOf(ChatItem.getSampleData(
1, CIDirection.DirectRcv(), Clock.System.now(), "hello"
))
}
)
}
}

View File

@ -21,14 +21,24 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.item.*
@Composable @Composable
fun SendMsgView(sendMessage: (String) -> Unit) { fun SendMsgView(sendMessage: (String) -> Unit) {
var cmd by remember { mutableStateOf("") } var msg by remember { mutableStateOf("") }
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
var textStyle by remember { mutableStateOf(smallFont) }
BasicTextField( BasicTextField(
value = cmd, value = msg,
onValueChange = { cmd = it }, onValueChange = {
textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground), msg = it
textStyle = if(isShortEmoji(it)) {
if (it.codePoints().count() < 4) largeEmojiFont else mediumEmojiFont
} else {
smallFont
}
},
textStyle = textStyle,
maxLines = 16, maxLines = 16,
keyboardOptions = KeyboardOptions.Default.copy( keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.Sentences, capitalization = KeyboardCapitalization.Sentences,
@ -54,7 +64,7 @@ fun SendMsgView(sendMessage: (String) -> Unit) {
) { ) {
innerTextField() innerTextField()
} }
val color = if (cmd.isNotEmpty()) MaterialTheme.colors.primary else Color.Gray val color = if (msg.isNotEmpty()) MaterialTheme.colors.primary else Color.Gray
Icon( Icon(
Icons.Outlined.ArrowUpward, Icons.Outlined.ArrowUpward,
"Send Message", "Send Message",
@ -65,9 +75,9 @@ fun SendMsgView(sendMessage: (String) -> Unit) {
.clip(CircleShape) .clip(CircleShape)
.background(color) .background(color)
.clickable { .clickable {
if (cmd.isNotEmpty()) { if (msg.isNotEmpty()) {
sendMessage(cmd) sendMessage(msg)
cmd = "" msg = ""
} }
} }
) )

View File

@ -1,33 +1,74 @@
package chat.simplex.app.views.chat.item package chat.simplex.app.views.chat.item
import android.content.Context
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.UriHandler import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.simplex.app.model.CIDirection import chat.simplex.app.model.*
import chat.simplex.app.model.ChatItem import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.copyText
import chat.simplex.app.views.helpers.shareText
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
@Composable @Composable
fun ChatItemView(chatItem: ChatItem, uriHandler: UriHandler? = null) { fun ChatItemView(user: User, cItem: ChatItem, quotedItem: MutableState<ChatItem?>, cxt: Context, uriHandler: UriHandler? = null) {
val sent = chatItem.chatDir.sent val sent = cItem.chatDir.sent
val alignment = if (sent) Alignment.CenterEnd else Alignment.CenterStart val alignment = if (sent) Alignment.CenterEnd else Alignment.CenterStart
var showMenu by remember { mutableStateOf(false) }
Box( Box(
modifier = Modifier modifier = Modifier
.padding(bottom = 4.dp) .padding(bottom = 4.dp)
.fillMaxWidth() .fillMaxWidth()
.padding( .padding(
start = if (sent) 60.dp else 16.dp, start = if (sent) 86.dp else 16.dp,
end = if (sent) 16.dp else 60.dp, end = if (sent) 16.dp else 86.dp,
), ),
contentAlignment = alignment, contentAlignment = alignment,
) { ) {
TextItemView(chatItem, uriHandler) Column(Modifier.combinedClickable(onLongClick = { showMenu = true }, onClick = {})) {
if (cItem.quotedItem == null && isShortEmoji(cItem.content.text)) {
EmojiItemView(cItem)
} else {
FramedItemView(user, cItem, uriHandler)
}
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
ItemAction("Reply", Icons.Outlined.Reply, onClick = {
quotedItem.value = cItem
showMenu = false
})
ItemAction("Share", Icons.Outlined.Share, onClick = {
shareText(cxt, cItem.content.text)
showMenu = false
})
ItemAction("Copy", Icons.Outlined.ContentCopy, onClick = {
copyText(cxt, cItem.content.text)
showMenu = false
})
}
}
}
}
@Composable
private fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit) {
DropdownMenuItem(onClick) {
Row {
Text(text, modifier = Modifier
.fillMaxWidth()
.weight(1F))
Icon(icon, text, tint = HighOrLowlight)
}
} }
} }
@ -36,9 +77,12 @@ fun ChatItemView(chatItem: ChatItem, uriHandler: UriHandler? = null) {
fun PreviewChatItemView() { fun PreviewChatItemView() {
SimpleXTheme { SimpleXTheme {
ChatItemView( ChatItemView(
chatItem = ChatItem.getSampleData( User.sampleData,
ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello" 1, CIDirection.DirectSnd(), Clock.System.now(), "hello"
) ),
quotedItem = remember { mutableStateOf(null) },
cxt = LocalContext.current
) )
} }
} }

View File

@ -0,0 +1,42 @@
package chat.simplex.app.views.chat.item
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.model.ChatItem
val largeEmojiFont: TextStyle = TextStyle(fontSize = 48.sp)
val mediumEmojiFont: TextStyle = TextStyle(fontSize = 36.sp)
@Composable
fun EmojiItemView(chatItem: ChatItem) {
Column(
Modifier.padding(vertical = 8.dp, horizontal = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
EmojiText(chatItem.content.text)
CIMetaView(chatItem)
}
}
@Composable
fun EmojiText(text: String) {
val s = text.trim()
Text(s, style = if (s.codePoints().count() < 4) largeEmojiFont else mediumEmojiFont)
}
private fun isSimpleEmoji(c: Int): Boolean = c > 0x238C
fun isEmoji(c: Int): Boolean = isSimpleEmoji(c) // || isCombinedIntoEmoji(c)
// TODO count perceived emojis, possibly using icu4j
fun isShortEmoji(str: String): Boolean {
val s = str.trim()
return s.codePoints().count() in 1..5 && s.codePoints().allMatch(::isEmoji)
}

View File

@ -0,0 +1,146 @@
package chat.simplex.app.views.chat.item
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.SimpleXTheme
import kotlinx.datetime.Clock
val SentColorLight = Color(0x1E45B8FF)
val ReceivedColorLight = Color(0x20B1B0B5)
val SentQuoteColorLight = Color(0x2545B8FF)
val ReceivedQuoteColorLight = Color(0x25B1B0B5)
@Composable
fun FramedItemView(user: User, ci: ChatItem, uriHandler: UriHandler? = null) {
val sent = ci.chatDir.sent
Surface(
shape = RoundedCornerShape(18.dp),
color = if (sent) SentColorLight else ReceivedColorLight
) {
Box(contentAlignment = Alignment.BottomEnd) {
Column(Modifier.width(IntrinsicSize.Max)) {
val qi = ci.quotedItem
if (qi != null) {
Box(
Modifier
.background(if (sent) SentQuoteColorLight else ReceivedQuoteColorLight)
.padding(vertical = 6.dp, horizontal = 12.dp)
.fillMaxWidth()
) {
MarkdownText(
qi, sender = qi.sender(user), senderBold = true, maxLines = 3,
style = TextStyle(fontSize = 15.sp, color = MaterialTheme.colors.onSurface)
)
}
}
Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) {
if (ci.formattedText == null && isShortEmoji(ci.content.text)) {
Column(
Modifier.padding(bottom = 2.dp).fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
EmojiText(ci.content.text)
Text("")
}
} else {
MarkdownText(
ci.content, ci.formattedText, ci.memberDisplayName,
metaText = ci.timestampText, uriHandler = uriHandler, senderBold = true
)
}
}
}
Box(Modifier.padding(bottom = 6.dp, end = 12.dp)) {
CIMetaView(ci)
}
}
}
}
@Preview
@Composable
fun PreviewTextItemViewSnd() {
SimpleXTheme {
FramedItemView(
User.sampleData,
ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello"
)
)
}
}
@Preview
@Composable
fun PreviewTextItemViewRcv() {
SimpleXTheme {
FramedItemView(
User.sampleData,
ChatItem.getSampleData(
1, CIDirection.DirectRcv(), Clock.System.now(), "hello"
)
)
}
}
@Preview
@Composable
fun PreviewTextItemViewLong() {
SimpleXTheme {
FramedItemView(
User.sampleData,
ChatItem.getSampleData(
1,
CIDirection.DirectSnd(),
Clock.System.now(),
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
)
)
}
}
@Preview
@Composable
fun PreviewTextItemViewQuote() {
SimpleXTheme {
FramedItemView(
User.sampleData,
ChatItem.getSampleData(
1, CIDirection.DirectSnd(),
Clock.System.now(),
"https://simplex.chat",
CIStatus.SndSent(),
quotedItem = CIQuote.getSample(1, Clock.System.now(), "hi", chatDir = CIDirection.DirectRcv())
)
)
}
}
@Preview
@Composable
fun PreviewTextItemViewEmoji() {
SimpleXTheme {
FramedItemView(
User.sampleData,
ChatItem.getSampleData(
1, CIDirection.DirectSnd(),
Clock.System.now(),
"👍",
CIStatus.SndSent(),
quotedItem = CIQuote.getSample(1, Clock.System.now(), "Lorem ipsum dolor sit amet", chatDir = CIDirection.DirectRcv())
)
)
}
}

View File

@ -1,48 +1,17 @@
package chat.simplex.app.views.chat.item package chat.simplex.app.views.chat.item
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.MaterialTheme
import androidx.compose.material.* import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.UriHandler import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.text.* import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.simplex.app.model.CIDirection import chat.simplex.app.model.*
import chat.simplex.app.model.ChatItem
import chat.simplex.app.ui.theme.SimpleXTheme
import kotlinx.datetime.Clock
// TODO move to theme
val SentColorLight = Color(0x1E45B8FF)
val ReceivedColorLight = Color(0x1EB1B0B5)
@Composable
fun TextItemView(chatItem: ChatItem, uriHandler: UriHandler? = null) {
val sent = chatItem.chatDir.sent
Surface(
shape = RoundedCornerShape(18.dp),
color = if (sent) SentColorLight else ReceivedColorLight
) {
Box(
modifier = Modifier.padding(vertical = 6.dp, horizontal = 12.dp)
) {
Box(contentAlignment = Alignment.BottomEnd) {
MarkdownText(chatItem, uriHandler = uriHandler, groupMemberBold = true)
CIMetaView(chatItem)
}
}
}
}
val reserveTimestampStyle = SpanStyle(color = Color.Transparent) val reserveTimestampStyle = SpanStyle(color = Color.Transparent)
val boldFont = SpanStyle(fontWeight = FontWeight.Medium) val boldFont = SpanStyle(fontWeight = FontWeight.Medium)
@ -56,33 +25,44 @@ fun appendGroupMember(b: AnnotatedString.Builder, chatItem: ChatItem, groupMembe
} }
} }
fun appendSender(b: AnnotatedString.Builder, sender: String?, senderBold: Boolean) {
if (sender != null) {
if (senderBold) b.withStyle(boldFont) { append(sender) }
else b.append(sender)
b.append(": ")
}
}
@Composable @Composable
fun MarkdownText ( fun MarkdownText (
chatItem: ChatItem, content: ItemContent,
formattedText: List<FormattedText>? = null,
sender: String? = null,
metaText: String? = null,
style: TextStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onSurface, lineHeight = 22.sp), style: TextStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onSurface, lineHeight = 22.sp),
maxLines: Int = Int.MAX_VALUE, maxLines: Int = Int.MAX_VALUE,
overflow: TextOverflow = TextOverflow.Clip, overflow: TextOverflow = TextOverflow.Clip,
uriHandler: UriHandler? = null, uriHandler: UriHandler? = null,
groupMemberBold: Boolean = false, senderBold: Boolean = false,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
if (chatItem.formattedText == null) { if (formattedText == null) {
val annotatedText = buildAnnotatedString { val annotatedText = buildAnnotatedString {
appendGroupMember(this, chatItem, groupMemberBold) appendSender(this, sender, senderBold)
append(chatItem.content.text) append(content.text)
withStyle(reserveTimestampStyle) { append(" ${chatItem.timestampText}") } if (metaText != null) withStyle(reserveTimestampStyle) { append(" $metaText") }
}
SelectionContainer {
Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow)
} }
Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow)
} else { } else {
var hasLinks = false
val annotatedText = buildAnnotatedString { val annotatedText = buildAnnotatedString {
appendGroupMember(this, chatItem, groupMemberBold) appendSender(this, sender, senderBold)
for (ft in chatItem.formattedText) { for (ft in formattedText) {
if (ft.format == null) append(ft.text) if (ft.format == null) append(ft.text)
else { else {
val link = ft.link val link = ft.link
if (link != null) { if (link != null) {
hasLinks = true
withAnnotation(tag = "URL", annotation = link) { withAnnotation(tag = "URL", annotation = link) {
withStyle(ft.format.style) { append(ft.text) } withStyle(ft.format.style) { append(ft.text) }
} }
@ -91,60 +71,17 @@ fun MarkdownText (
} }
} }
} }
withStyle(reserveTimestampStyle) { append(" ${chatItem.timestampText}") } if (metaText != null) withStyle(reserveTimestampStyle) { append(" $metaText") }
} }
if (uriHandler != null) { if (hasLinks && uriHandler != null) {
SelectionContainer { ClickableText(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow,
ClickableText(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow, onClick = { offset ->
onClick = { offset -> annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset)
annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) .firstOrNull()?.let { annotation -> uriHandler.openUri(annotation.item) }
.firstOrNull()?.let { annotation -> uriHandler.openUri(annotation.item) } }
} )
)
}
} else { } else {
SelectionContainer { Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow)
Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow)
}
} }
} }
} }
@Preview
@Composable
fun PreviewTextItemViewSnd() {
SimpleXTheme {
TextItemView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello"
)
)
}
}
@Preview
@Composable
fun PreviewTextItemViewRcv() {
SimpleXTheme {
TextItemView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectRcv(), Clock.System.now(), "hello"
)
)
}
}
@Preview
@Composable
fun PreviewTextItemViewLong() {
SimpleXTheme {
TextItemView(
chatItem = ChatItem.getSampleData(
1,
CIDirection.DirectSnd(),
Clock.System.now(),
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
)
)
}
}

View File

@ -39,9 +39,11 @@ fun ChatPreviewView(chat: Chat) {
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
if (chat.chatItems.count() > 0) { val ci = chat.chatItems.lastOrNull()
if (ci != null) {
MarkdownText( MarkdownText(
chat.chatItems.last(), ci.content, ci.formattedText, ci.memberDisplayName,
metaText = ci.timestampText,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )

View File

@ -1,7 +1,7 @@
package chat.simplex.app.views.helpers package chat.simplex.app.views.helpers
import android.content.Context import android.content.*
import android.content.Intent import androidx.core.content.ContextCompat
fun shareText(cxt: Context, text: String) { fun shareText(cxt: Context, text: String) {
val sendIntent: Intent = Intent().apply { val sendIntent: Intent = Intent().apply {
@ -12,3 +12,8 @@ fun shareText(cxt: Context, text: String) {
val shareIntent = Intent.createChooser(sendIntent, null) val shareIntent = Intent.createChooser(sendIntent, null)
cxt.startActivity(shareIntent) cxt.startActivity(shareIntent)
} }
fun copyText(cxt: Context, text: String) {
val clipboard = ContextCompat.getSystemService(cxt, ClipboardManager::class.java)
clipboard?.setPrimaryClip(ClipData.newPlainText("text", text))
}

View File

@ -21,7 +21,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.simplex.app.model.ChatModel import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.* import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.AlertManager import chat.simplex.app.views.helpers.AlertManager
import chat.simplex.app.views.helpers.withApi import chat.simplex.app.views.helpers.withApi

View File

@ -527,7 +527,8 @@ struct ChatItem: Identifiable, Decodable {
var meta: CIMeta var meta: CIMeta
var content: CIContent var content: CIContent
var formattedText: [FormattedText]? var formattedText: [FormattedText]?
var quotedItem: CIQuote?
var id: Int64 { get { meta.itemId } } var id: Int64 { get { meta.itemId } }
var timestampText: Text { get { meta.timestampText } } var timestampText: Text { get { meta.timestampText } }
@ -537,11 +538,22 @@ struct ChatItem: Identifiable, Decodable {
return false return false
} }
static func getSample (_ id: Int64, _ dir: CIDirection, _ ts: Date, _ text: String, _ status: CIStatus = .sndNew) -> ChatItem { var memberDisplayName: String? {
get {
if case let .groupRcv(groupMember) = chatDir {
return groupMember.memberProfile.displayName
} else {
return nil
}
}
}
static func getSample (_ id: Int64, _ dir: CIDirection, _ ts: Date, _ text: String, _ status: CIStatus = .sndNew, quotedItem: CIQuote? = nil) -> ChatItem {
ChatItem( ChatItem(
chatDir: dir, chatDir: dir,
meta: CIMeta.getSample(id, ts, text, status), meta: CIMeta.getSample(id, ts, text, status),
content: .sndMsgContent(msgContent: .text(text)) content: .sndMsgContent(msgContent: .text(text)),
quotedItem: quotedItem
) )
} }
} }
@ -603,7 +615,11 @@ enum CIStatus: Decodable {
case rcvRead case rcvRead
} }
enum CIContent: Decodable { protocol ItemContent {
var text: String { get }
}
enum CIContent: Decodable, ItemContent {
case sndMsgContent(msgContent: MsgContent) case sndMsgContent(msgContent: MsgContent)
case rcvMsgContent(msgContent: MsgContent) case rcvMsgContent(msgContent: MsgContent)
case sndFileInvitation(fileId: Int64, filePath: String) case sndFileInvitation(fileId: Int64, filePath: String)
@ -625,6 +641,33 @@ struct RcvFileTransfer: Decodable {
} }
struct CIQuote: Decodable, ItemContent {
var chatDir: CIDirection?
var itemId: Int64?
var sharedMsgId: String? = nil
var sentAt: Date
var content: MsgContent
var formattedText: [FormattedText]?
var text: String { get { content.text } }
var sender: String? {
get {
switch (chatDir) {
case .directSnd: return "you"
case .directRcv: return nil
case .groupSnd: return ChatModel.shared.currentUser?.displayName
case let .groupRcv(member): return member.memberProfile.displayName
case nil: return nil
}
}
}
static func getSample(_ itemId: Int64?, _ sentAt: Date, _ text: String, chatDir: CIDirection?) -> CIQuote {
CIQuote(chatDir: chatDir, itemId: itemId, sentAt: sentAt, content: .text(text))
}
}
enum MsgContent { enum MsgContent {
case text(String) case text(String)
// TODO include original JSON, possibly using https://github.com/zoul/generic-json-swift // TODO include original JSON, possibly using https://github.com/zoul/generic-json-swift

View File

@ -22,6 +22,7 @@ enum ChatCommand {
case apiGetChats case apiGetChats
case apiGetChat(type: ChatType, id: Int64) case apiGetChat(type: ChatType, id: Int64)
case apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) case apiSendMessage(type: ChatType, id: Int64, msg: MsgContent)
case apiSendMessageQuote(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent)
case getUserSMPServers case getUserSMPServers
case setUserSMPServers(smpServers: [String]) case setUserSMPServers(smpServers: [String])
case addContact case addContact
@ -45,6 +46,7 @@ enum ChatCommand {
case .apiGetChats: return "/_get chats" case .apiGetChats: return "/_get chats"
case let .apiGetChat(type, id): return "/_get chat \(ref(type, id)) count=100" case let .apiGetChat(type, id): return "/_get chat \(ref(type, id)) count=100"
case let .apiSendMessage(type, id, mc): return "/_send \(ref(type, id)) \(mc.cmdString)" case let .apiSendMessage(type, id, mc): return "/_send \(ref(type, id)) \(mc.cmdString)"
case let .apiSendMessageQuote(type, id, itemId, mc): return "/_send_quote \(ref(type, id)) \(itemId) \(mc.cmdString)"
case .getUserSMPServers: return "/smp_servers" case .getUserSMPServers: return "/smp_servers"
case let .setUserSMPServers(smpServers): return "/smp_servers \(smpServersStr(smpServers: smpServers))" case let .setUserSMPServers(smpServers): return "/smp_servers \(smpServersStr(smpServers: smpServers))"
case .addContact: return "/connect" case .addContact: return "/connect"
@ -71,6 +73,7 @@ enum ChatCommand {
case .apiGetChats: return "apiGetChats" case .apiGetChats: return "apiGetChats"
case .apiGetChat: return "apiGetChat" case .apiGetChat: return "apiGetChat"
case .apiSendMessage: return "apiSendMessage" case .apiSendMessage: return "apiSendMessage"
case .apiSendMessageQuote: return "apiSendMessageQuote"
case .getUserSMPServers: return "getUserSMPServers" case .getUserSMPServers: return "getUserSMPServers"
case .setUserSMPServers: return "setUserSMPServers" case .setUserSMPServers: return "setUserSMPServers"
case .addContact: return "addContact" case .addContact: return "addContact"
@ -362,9 +365,14 @@ func apiGetChat(type: ChatType, id: Int64) async throws -> Chat {
throw r throw r
} }
func apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) async throws -> ChatItem { func apiSendMessage(type: ChatType, id: Int64, quotedItemId: Int64?, msg: MsgContent) async throws -> ChatItem {
let chatModel = ChatModel.shared let chatModel = ChatModel.shared
let cmd = ChatCommand.apiSendMessage(type: type, id: id, msg: msg) let cmd: ChatCommand
if let itemId = quotedItemId {
cmd = .apiSendMessageQuote(type: type, id: id, itemId: itemId, msg: msg)
} else {
cmd = .apiSendMessage(type: type, id: id, msg: msg)
}
let r: ChatResponse let r: ChatResponse
if type == .direct { if type == .direct {
var cItem: ChatItem! var cItem: ChatItem!

View File

@ -12,25 +12,22 @@ struct EmojiItemView: View {
var chatItem: ChatItem var chatItem: ChatItem
var body: some View { var body: some View {
let sent = chatItem.chatDir.sent
let s = chatItem.content.text.trimmingCharacters(in: .whitespaces)
VStack(spacing: 1) { VStack(spacing: 1) {
Text(s) emojiText(chatItem.content.text)
.font(s.count < 4 ? largeEmojiFont : mediumEmojiFont)
.padding(.top, 8) .padding(.top, 8)
.padding(.horizontal, 6) .padding(.horizontal, 6)
.frame(maxWidth: .infinity, alignment: sent ? .trailing : .leading)
CIMetaView(chatItem: chatItem) CIMetaView(chatItem: chatItem)
.padding(.bottom, 8) .padding(.bottom, 8)
.padding(.horizontal, 12) .padding(.horizontal, 12)
.frame(maxWidth: .infinity, alignment: sent ? .trailing : .leading)
} }
.padding(.horizontal)
.frame(maxWidth: .infinity, alignment: sent ? .trailing : .leading)
} }
} }
func emojiText(_ text: String) -> Text {
let s = text.trimmingCharacters(in: .whitespaces)
return Text(s).font(s.count < 4 ? largeEmojiFont : mediumEmojiFont)
}
struct EmojiItemView_Previews: PreviewProvider { struct EmojiItemView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Group{ Group{

View File

@ -0,0 +1,112 @@
//
// FramedItemView.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 04/02/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
private let sentColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.12)
private let sentColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.17)
private let sentQuoteColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.11)
private let sentQuoteColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.09)
struct FramedItemView: View {
@Environment(\.colorScheme) var colorScheme
var chatItem: ChatItem
@State var msgWidth: CGFloat = 0
var body: some View {
let v = ZStack(alignment: .bottomTrailing) {
VStack(alignment: .leading, spacing: 0) {
if let qi = chatItem.quotedItem {
MsgContentView(
content: qi,
sender: qi.sender
)
.lineLimit(3)
.font(.subheadline)
.padding(.vertical, 6)
.padding(.horizontal, 12)
.frame(minWidth: msgWidth, alignment: .leading)
.background(
chatItem.chatDir.sent
? (colorScheme == .light ? sentQuoteColorLight : sentQuoteColorDark)
: Color(uiColor: .quaternarySystemFill)
)
.overlay(DetermineWidth())
}
if chatItem.formattedText == nil && isShortEmoji(chatItem.content.text) {
VStack {
emojiText(chatItem.content.text)
Text("")
}
.padding(.vertical, 6)
.padding(.horizontal, 12)
.overlay(DetermineWidth())
.frame(minWidth: msgWidth, alignment: .center)
.padding(.bottom, 2)
} else {
MsgContentView(
content: chatItem.content,
formattedText: chatItem.formattedText,
sender: chatItem.memberDisplayName,
metaText: chatItem.timestampText
)
.padding(.vertical, 6)
.padding(.horizontal, 12)
.overlay(DetermineWidth())
.frame(minWidth: 0, alignment: .leading)
.textSelection(.enabled)
}
}
.onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 }
CIMetaView(chatItem: chatItem)
.padding(.trailing, 12)
.padding(.bottom, 6)
}
.background(chatItemFrameColor(chatItem, colorScheme))
.cornerRadius(18)
switch chatItem.meta.itemStatus {
case .sndErrorAuth:
v.onTapGesture { msgDeliveryError("Most likely this contact has deleted the connection with you.") }
case let .sndError(agentError):
v.onTapGesture { msgDeliveryError("Unexpected error: \(String(describing: agentError))") }
default: v
}
}
private func msgDeliveryError(_ err: String) {
AlertManager.shared.showAlertMsg(
title: "Message delivery error",
message: err
)
}
}
func chatItemFrameColor(_ ci: ChatItem, _ colorScheme: ColorScheme) -> Color {
ci.chatDir.sent
? (colorScheme == .light ? sentColorLight : sentColorDark)
: Color(uiColor: .tertiarySystemGroupedBackground)
}
struct FramedItemView_Previews: PreviewProvider {
static var previews: some View {
Group{
FramedItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"))
FramedItemView(chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)))
FramedItemView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent, quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)))
FramedItemView(chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent, quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)))
FramedItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"))
FramedItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "))
FramedItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"))
FramedItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"))
}
.previewLayout(.fixed(width: 360, height: 200))
}
}

View File

@ -0,0 +1,97 @@
//
// MsgContentView.swift
// SimpleX
//
// Created by Evgeny on 13/03/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
private let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1)
private let linkColor = Color(uiColor: uiLinkColor)
struct MsgContentView: View {
var content: ItemContent
var formattedText: [FormattedText]? = nil
var sender: String? = nil
var metaText: Text? = nil
var body: some View {
let v = messageText(content, formattedText, sender)
if let mt = metaText {
return v + reserveSpaceForMeta(mt)
} else {
return v
}
}
private func reserveSpaceForMeta(_ meta: Text) -> Text {
(Text(" ") + meta)
.font(.caption)
.foregroundColor(.clear)
}
}
func messageText(_ content: ItemContent, _ formattedText: [FormattedText]?, _ sender: String?, preview: Bool = false) -> Text {
let s = content.text
var res: Text
if let ft = formattedText, ft.count > 0 {
res = formattText(ft[0], preview)
var i = 1
while i < ft.count {
res = res + formattText(ft[i], preview)
i = i + 1
}
} else {
res = Text(s)
}
if let s = sender {
let t = Text(s)
return (preview ? t : t.fontWeight(.medium)) + Text(": ") + res
} else {
return res
}
}
private func formattText(_ ft: FormattedText, _ preview: Bool) -> Text {
let t = ft.text
if let f = ft.format {
switch (f) {
case .bold: return Text(t).bold()
case .italic: return Text(t).italic()
case .strikeThrough: return Text(t).strikethrough()
case .snippet: return Text(t).font(.body.monospaced())
case .secret: return Text(t).foregroundColor(.clear).underline(color: .primary)
case let .colored(color): return Text(t).foregroundColor(color.uiColor)
case .uri: return linkText(t, t, preview, prefix: "")
case .email: return linkText(t, t, preview, prefix: "mailto:")
case .phone: return linkText(t, t.replacingOccurrences(of: " ", with: ""), preview, prefix: "tel:")
}
} else {
return Text(t)
}
}
private func linkText(_ s: String, _ link: String,
_ preview: Bool, prefix: String) -> Text {
preview
? Text(s).foregroundColor(linkColor).underline(color: linkColor)
: Text(AttributedString(s, attributes: AttributeContainer([
.link: NSURL(string: prefix + link) as Any,
.foregroundColor: uiLinkColor as Any
]))).underline()
}
struct MsgContentView_Previews: PreviewProvider {
static var previews: some View {
let chatItem = ChatItem.getSample(1, .directSnd, .now, "hello")
return MsgContentView(
content: chatItem.content,
formattedText: chatItem.formattedText,
sender: chatItem.memberDisplayName,
metaText: chatItem.timestampText
)
}
}

View File

@ -1,136 +0,0 @@
//
// TextItemView.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 04/02/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
private let sentColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.12)
private let sentColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.17)
private let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1)
private let linkColor = Color(uiColor: uiLinkColor)
struct TextItemView: View {
@Environment(\.colorScheme) var colorScheme
var chatItem: ChatItem
var width: CGFloat
private let codeFont = Font.custom("Courier", size: UIFont.preferredFont(forTextStyle: .body).pointSize)
var body: some View {
let sent = chatItem.chatDir.sent
let maxWidth = width * 0.78
return ZStack(alignment: .bottomTrailing) {
(messageText(chatItem) + reserveSpaceForMeta(chatItem.timestampText))
.padding(.vertical, 6)
.padding(.horizontal, 12)
.frame(minWidth: 0, alignment: .leading)
.textSelection(.enabled)
CIMetaView(chatItem: chatItem)
.padding(.trailing, 12)
.padding(.bottom, 6)
}
.background(
sent
? (colorScheme == .light ? sentColorLight : sentColorDark)
: Color(uiColor: .tertiarySystemGroupedBackground)
)
.cornerRadius(18)
.padding(.horizontal)
.frame(
maxWidth: maxWidth,
maxHeight: .infinity,
alignment: sent ? .trailing : .leading
)
.onTapGesture {
switch chatItem.meta.itemStatus {
case .sndErrorAuth: msgDeliveryError("Most likely this contact has deleted the connection with you.")
case let .sndError(agentError): msgDeliveryError("Unexpected error: \(String(describing: agentError))")
default: return
}
}
}
private func reserveSpaceForMeta(_ meta: Text) -> Text {
(Text(" ") + meta)
.font(.caption)
.foregroundColor(.clear)
}
private func msgDeliveryError(_ err: String) {
AlertManager.shared.showAlertMsg(
title: "Message delivery error",
message: err
)
}
}
func messageText(_ chatItem: ChatItem, preview: Bool = false) -> Text {
let s = chatItem.content.text
var res: Text
if let ft = chatItem.formattedText, ft.count > 0 {
res = formattedText(ft[0], preview)
var i = 1
while i < ft.count {
res = res + formattedText(ft[i], preview)
i = i + 1
}
} else {
res = Text(s)
}
if case let .groupRcv(groupMember) = chatItem.chatDir {
let m = Text(groupMember.memberProfile.displayName)
return (preview ? m : m.font(.headline)) + Text(": ") + res
} else {
return res
}
}
private func formattedText(_ ft: FormattedText, _ preview: Bool) -> Text {
let t = ft.text
if let f = ft.format {
switch (f) {
case .bold: return Text(t).bold()
case .italic: return Text(t).italic()
case .strikeThrough: return Text(t).strikethrough()
case .snippet: return Text(t).font(.body.monospaced())
case .secret: return Text(t).foregroundColor(.clear).underline(color: .primary)
case let .colored(color): return Text(t).foregroundColor(color.uiColor)
case .uri: return linkText(t, t, preview, prefix: "")
case .email: return linkText(t, t, preview, prefix: "mailto:")
case .phone: return linkText(t, t.replacingOccurrences(of: " ", with: ""), preview, prefix: "tel:")
}
} else {
return Text(t)
}
}
private func linkText(_ s: String, _ link: String,
_ preview: Bool, prefix: String) -> Text {
preview
? Text(s).foregroundColor(linkColor).underline(color: linkColor)
: Text(AttributedString(s, attributes: AttributeContainer([
.link: NSURL(string: prefix + link) as Any,
.foregroundColor: uiLinkColor as Any
]))).underline()
}
struct TextItemView_Previews: PreviewProvider {
static var previews: some View {
Group{
TextItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), width: 360)
TextItemView(chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello"), width: 360)
TextItemView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent), width: 360)
TextItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), width: 360)
TextItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), width: 360)
TextItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), width: 360)
TextItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), width: 360)
}
.previewLayout(.fixed(width: 360, height: 70))
}
}

View File

@ -10,13 +10,12 @@ import SwiftUI
struct ChatItemView: View { struct ChatItemView: View {
var chatItem: ChatItem var chatItem: ChatItem
var width: CGFloat
var body: some View { var body: some View {
if (isShortEmoji(chatItem.content.text)) { if (chatItem.quotedItem == nil && isShortEmoji(chatItem.content.text)) {
EmojiItemView(chatItem: chatItem) EmojiItemView(chatItem: chatItem)
} else { } else {
TextItemView(chatItem: chatItem, width: width) FramedItemView(chatItem: chatItem)
} }
} }
} }
@ -24,11 +23,11 @@ struct ChatItemView: View {
struct ChatItemView_Previews: PreviewProvider { struct ChatItemView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Group{ Group{
ChatItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), width: 360) ChatItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"))
ChatItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), width: 360) ChatItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"))
ChatItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), width: 360) ChatItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"))
ChatItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), width: 360) ChatItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"))
ChatItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), width: 360) ChatItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"))
} }
.previewLayout(.fixed(width: 360, height: 70)) .previewLayout(.fixed(width: 360, height: 70))
} }

View File

@ -12,6 +12,7 @@ struct ChatView: View {
@EnvironmentObject var chatModel: ChatModel @EnvironmentObject var chatModel: ChatModel
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@ObservedObject var chat: Chat @ObservedObject var chat: Chat
@State var quotedItem: ChatItem? = nil
@State private var inProgress: Bool = false @State private var inProgress: Bool = false
@FocusState private var keyboardVisible: Bool @FocusState private var keyboardVisible: Bool
@State private var showChatInfo = false @State private var showChatInfo = false
@ -21,12 +22,27 @@ struct ChatView: View {
return VStack { return VStack {
GeometryReader { g in GeometryReader { g in
let maxWidth = g.size.width * 0.78
ScrollViewReader { proxy in ScrollViewReader { proxy in
ScrollView { ScrollView {
VStack(spacing: 5) { LazyVStack(spacing: 5) {
ForEach(chatModel.chatItems, id: \.id) { ForEach(chatModel.chatItems) { ci in
ChatItemView(chatItem: $0, width: g.size.width) let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading
.frame(minWidth: 0, maxWidth: .infinity, alignment: $0.chatDir.sent ? .trailing : .leading) ChatItemView(chatItem: ci)
.contextMenu {
Button {
withAnimation { quotedItem = ci }
} label: { Label("Reply", systemImage: "arrowshape.turn.up.left") }
Button {
showShareSheet(items: [ci.content.text])
} label: { Label("Share", systemImage: "square.and.arrow.up") }
Button {
UIPasteboard.general.string = ci.content.text
} label: { Label("Copy", systemImage: "doc.on.doc") }
}
.padding(.horizontal)
.frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment)
.frame(minWidth: 0, maxWidth: .infinity, alignment: alignment)
} }
.onAppear { .onAppear {
DispatchQueue.main.async { DispatchQueue.main.async {
@ -54,7 +70,8 @@ struct ChatView: View {
Spacer(minLength: 0) Spacer(minLength: 0)
SendMessageView( ComposeView(
quotedItem: $quotedItem,
sendMessage: sendMessage, sendMessage: sendMessage,
inProgress: inProgress, inProgress: inProgress,
keyboardVisible: $keyboardVisible keyboardVisible: $keyboardVisible
@ -115,8 +132,14 @@ struct ChatView: View {
func sendMessage(_ msg: String) { func sendMessage(_ msg: String) {
Task { Task {
do { do {
let chatItem = try await apiSendMessage(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, msg: .text(msg)) let chatItem = try await apiSendMessage(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
quotedItemId: quotedItem?.meta.itemId,
msg: .text(msg)
)
DispatchQueue.main.async { DispatchQueue.main.async {
quotedItem = nil
chatModel.addChatItem(chat.chatInfo, chatItem) chatModel.addChatItem(chat.chatInfo, chatItem)
} }
} catch { } catch {

View File

@ -0,0 +1,42 @@
//
// ComposeView.swift
// SimpleX
//
// Created by Evgeny on 13/03/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct ComposeView: View {
@Binding var quotedItem: ChatItem?
var sendMessage: (String) -> Void
var inProgress: Bool = false
@FocusState.Binding var keyboardVisible: Bool
var body: some View {
VStack(spacing: 0) {
QuotedItemView(quotedItem: $quotedItem)
.transition(.move(edge: .bottom))
SendMessageView(
sendMessage: sendMessage,
inProgress: inProgress,
keyboardVisible: $keyboardVisible
)
.background(.background)
}
}
}
struct ComposeView_Previews: PreviewProvider {
static var previews: some View {
@FocusState var keyboardVisible: Bool
@State var quotedItem: ChatItem? = ChatItem.getSample(1, .directSnd, .now, "hello")
return ComposeView(
quotedItem: $quotedItem,
sendMessage: { print ($0) },
keyboardVisible: $keyboardVisible
)
}
}

View File

@ -0,0 +1,49 @@
//
// QuotedItemView.swift
// SimpleX
//
// Created by Evgeny on 13/03/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct QuotedItemView: View {
@Environment(\.colorScheme) var colorScheme
@Binding var quotedItem: ChatItem?
var body: some View {
if let qi = quotedItem {
HStack {
quoteText(qi).lineLimit(3)
Spacer()
Button {
withAnimation { quotedItem = nil }
} label: {
Image(systemName: "multiply")
}
}
.padding(12)
.frame(maxWidth: .infinity)
.background(chatItemFrameColor(qi, colorScheme))
.padding(.top, 8)
} else {
EmptyView()
}
}
func quoteText(_ qi: ChatItem) -> some View {
if let s = qi.memberDisplayName {
return (Text(s).fontWeight(.medium) + Text(": \(qi.content.text)"))
} else {
return Text(qi.content.text)
}
}
}
struct QuotedItemView_Previews: PreviewProvider {
static var previews: some View {
@State var quotedItem: ChatItem? = ChatItem.getSample(1, .directSnd, .now, "hello")
return QuotedItemView(quotedItem: $quotedItem)
}
}

View File

@ -51,7 +51,7 @@ struct ChatPreviewView: View {
if let cItem = cItem { if let cItem = cItem {
ZStack(alignment: .topTrailing) { ZStack(alignment: .topTrailing) {
(itemStatusMark(cItem) + messageText(cItem, preview: true)) (itemStatusMark(cItem) + messageText(cItem.content, cItem.formattedText, cItem.memberDisplayName, preview: true))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .topLeading) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .topLeading)
.padding(.leading, 8) .padding(.leading, 8)
.padding(.trailing, 36) .padding(.trailing, 36)

View File

@ -0,0 +1,35 @@
//
// DetermineWidth.swift
// SimpleX
//
// Created by Evgeny on 14/03/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct DetermineWidth: View {
typealias Key = MaximumWidthPreferenceKey
var body: some View {
GeometryReader {
proxy in
Color.clear
.anchorPreference(key: Key.self, value: .bounds) {
anchor in proxy[anchor].size.width
}
}
}
}
struct MaximumWidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
struct DetermineWidth_Previews: PreviewProvider {
static var previews: some View {
DetermineWidth()
}
}

View File

@ -9,6 +9,16 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; }; 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; };
5C063D2827A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; }; 5C063D2827A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; };
5C0E5EF627E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF127E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj.a */; };
5C0E5EF727E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF127E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj.a */; };
5C0E5EF827E24676003DE3D0 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF227E24676003DE3D0 /* libffi.a */; };
5C0E5EF927E24676003DE3D0 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF227E24676003DE3D0 /* libffi.a */; };
5C0E5EFA27E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF327E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj-ghc8.10.7.a */; };
5C0E5EFB27E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF327E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj-ghc8.10.7.a */; };
5C0E5EFC27E24676003DE3D0 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF427E24676003DE3D0 /* libgmp.a */; };
5C0E5EFD27E24676003DE3D0 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF427E24676003DE3D0 /* libgmp.a */; };
5C0E5EFE27E24676003DE3D0 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF527E24676003DE3D0 /* libgmpxx.a */; };
5C0E5EFF27E24676003DE3D0 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E5EF527E24676003DE3D0 /* libgmpxx.a */; };
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; }; 5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
5C116CDD27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; }; 5C116CDD27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; };
@ -25,6 +35,10 @@
5C35CFC927B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; }; 5C35CFC927B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; };
5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; }; 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; };
5C35CFCC27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; }; 5C35CFCC27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; };
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; };
5C3A88CF27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; };
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; };
5C3A88D227DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; };
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
5C5346A927B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; 5C5346A927B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */; }; 5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */; };
@ -45,16 +59,6 @@
5C764E85279C748C000C6508 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C764E7C279C71DB000C6508 /* libz.tbd */; }; 5C764E85279C748C000C6508 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C764E7C279C71DB000C6508 /* libz.tbd */; };
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; }; 5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; };
5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; }; 5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; };
5C79C23E27DB673900C829D6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23927DB673800C829D6 /* libffi.a */; };
5C79C23F27DB673900C829D6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23927DB673800C829D6 /* libffi.a */; };
5C79C24027DB673900C829D6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23A27DB673800C829D6 /* libgmp.a */; };
5C79C24127DB673900C829D6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23A27DB673800C829D6 /* libgmp.a */; };
5C79C24227DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23B27DB673800C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2.a */; };
5C79C24327DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23B27DB673800C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2.a */; };
5C79C24427DB673900C829D6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23C27DB673900C829D6 /* libgmpxx.a */; };
5C79C24527DB673900C829D6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23C27DB673900C829D6 /* libgmpxx.a */; };
5C79C24627DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23D27DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2-ghc8.10.7.a */; };
5C79C24727DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C79C23D27DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2-ghc8.10.7.a */; };
5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 5C8F01CC27A6F0D8007D2C8D /* CodeScanner */; }; 5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 5C8F01CC27A6F0D8007D2C8D /* CodeScanner */; };
5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */; }; 5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */; };
5C971E1E27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */; }; 5C971E1E27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */; };
@ -98,10 +102,14 @@
5CCD403B27A5F9BE00368C90 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */; }; 5CCD403B27A5F9BE00368C90 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */; };
5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407127ADB1D0007B033A /* Emoji.swift */; }; 5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407127ADB1D0007B033A /* Emoji.swift */; };
5CE4407327ADB1D0007B033A /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407127ADB1D0007B033A /* Emoji.swift */; }; 5CE4407327ADB1D0007B033A /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407127ADB1D0007B033A /* Emoji.swift */; };
5CE4407627ADB66A007B033A /* TextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407527ADB66A007B033A /* TextItemView.swift */; };
5CE4407727ADB66A007B033A /* TextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407527ADB66A007B033A /* TextItemView.swift */; };
5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; }; 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; };
5CE4407A27ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; }; 5CE4407A27ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; };
5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCE227DE9246000BD591 /* ComposeView.swift */; };
5CEACCE427DE9246000BD591 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCE227DE9246000BD591 /* ComposeView.swift */; };
5CEACCE727DE97B6000BD591 /* QuotedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCE627DE97B6000BD591 /* QuotedItemView.swift */; };
5CEACCE827DE97B6000BD591 /* QuotedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCE627DE97B6000BD591 /* QuotedItemView.swift */; };
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
5CEACCEE27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640F50E227CF991C001E05C2 /* SMPServers.swift */; }; 640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640F50E227CF991C001E05C2 /* SMPServers.swift */; };
640F50E427CF991C001E05C2 /* SMPServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640F50E227CF991C001E05C2 /* SMPServers.swift */; }; 640F50E427CF991C001E05C2 /* SMPServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640F50E227CF991C001E05C2 /* SMPServers.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -125,15 +133,21 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = "<group>"; }; 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = "<group>"; };
5C0E5EF127E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj.a"; sourceTree = "<group>"; };
5C0E5EF227E24676003DE3D0 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C0E5EF327E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj-ghc8.10.7.a"; sourceTree = "<group>"; };
5C0E5EF427E24676003DE3D0 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C0E5EF527E24676003DE3D0 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = "<group>"; }; 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = "<group>"; };
5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = "<group>"; }; 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = "<group>"; };
5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = "<group>"; }; 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = "<group>"; };
5C2E260927A2C63500F70299 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = MyPlayground.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = "<group>"; }; 5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = "<group>"; };
5C2E260E27A30FDC00F70299 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; }; 5C2E260E27A30FDC00F70299 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
5C2E261127A30FEA00F70299 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = "<group>"; }; 5C2E261127A30FEA00F70299 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = "<group>"; };
5C35CFC727B2782E00FB6C6D /* BGManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGManager.swift; sourceTree = "<group>"; }; 5C35CFC727B2782E00FB6C6D /* BGManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGManager.swift; sourceTree = "<group>"; };
5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NtfManager.swift; sourceTree = "<group>"; }; 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NtfManager.swift; sourceTree = "<group>"; };
5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineWidth.swift; sourceTree = "<group>"; };
5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.swift; sourceTree = "<group>"; };
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = "<group>"; }; 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = "<group>"; };
5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = "<group>"; }; 5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = "<group>"; };
5C577F7C27C83AA10006112D /* MarkdownHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownHelp.swift; sourceTree = "<group>"; }; 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownHelp.swift; sourceTree = "<group>"; };
@ -147,11 +161,6 @@
5C764E7E279C7275000C6508 /* SimpleX (macOS)-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SimpleX (macOS)-Bridging-Header.h"; sourceTree = "<group>"; }; 5C764E7E279C7275000C6508 /* SimpleX (macOS)-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SimpleX (macOS)-Bridging-Header.h"; sourceTree = "<group>"; };
5C764E7F279C7276000C6508 /* dummy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = dummy.m; sourceTree = "<group>"; }; 5C764E7F279C7276000C6508 /* dummy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = dummy.m; sourceTree = "<group>"; };
5C764E88279CBCB3000C6508 /* ChatModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatModel.swift; sourceTree = "<group>"; }; 5C764E88279CBCB3000C6508 /* ChatModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatModel.swift; sourceTree = "<group>"; };
5C79C23927DB673800C829D6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C79C23A27DB673800C829D6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C79C23B27DB673800C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2.a"; sourceTree = "<group>"; };
5C79C23C27DB673900C829D6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C79C23D27DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2-ghc8.10.7.a"; sourceTree = "<group>"; };
5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoView.swift; sourceTree = "<group>"; }; 5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoView.swift; sourceTree = "<group>"; };
5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoImage.swift; sourceTree = "<group>"; }; 5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoImage.swift; sourceTree = "<group>"; };
5C9FD96A27A56D4D0075386C /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; }; 5C9FD96A27A56D4D0075386C /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
@ -179,8 +188,10 @@
5CCD403627A5F9A200368C90 /* ConnectContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectContactView.swift; sourceTree = "<group>"; }; 5CCD403627A5F9A200368C90 /* ConnectContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectContactView.swift; sourceTree = "<group>"; };
5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = "<group>"; }; 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = "<group>"; };
5CE4407127ADB1D0007B033A /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; }; 5CE4407127ADB1D0007B033A /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; };
5CE4407527ADB66A007B033A /* TextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextItemView.swift; sourceTree = "<group>"; };
5CE4407827ADB701007B033A /* EmojiItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItemView.swift; sourceTree = "<group>"; }; 5CE4407827ADB701007B033A /* EmojiItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItemView.swift; sourceTree = "<group>"; };
5CEACCE227DE9246000BD591 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = "<group>"; };
5CEACCE627DE97B6000BD591 /* QuotedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuotedItemView.swift; sourceTree = "<group>"; };
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
640F50E227CF991C001E05C2 /* SMPServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMPServers.swift; sourceTree = "<group>"; }; 640F50E227CF991C001E05C2 /* SMPServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMPServers.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -190,13 +201,13 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */, 5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */,
5C79C24027DB673900C829D6 /* libgmp.a in Frameworks */, 5C0E5EFC27E24676003DE3D0 /* libgmp.a in Frameworks */,
5C764E83279C748B000C6508 /* libz.tbd in Frameworks */, 5C764E83279C748B000C6508 /* libz.tbd in Frameworks */,
5C0E5EF627E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj.a in Frameworks */,
5C0E5EFE27E24676003DE3D0 /* libgmpxx.a in Frameworks */,
5C0E5EF827E24676003DE3D0 /* libffi.a in Frameworks */,
5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */, 5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */,
5C79C23E27DB673900C829D6 /* libffi.a in Frameworks */, 5C0E5EFA27E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj-ghc8.10.7.a in Frameworks */,
5C79C24227DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2.a in Frameworks */,
5C79C24627DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2-ghc8.10.7.a in Frameworks */,
5C79C24427DB673900C829D6 /* libgmpxx.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -204,13 +215,13 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5C79C24527DB673900C829D6 /* libgmpxx.a in Frameworks */,
5C79C24727DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2-ghc8.10.7.a in Frameworks */,
5C764E85279C748C000C6508 /* libz.tbd in Frameworks */, 5C764E85279C748C000C6508 /* libz.tbd in Frameworks */,
5C79C23F27DB673900C829D6 /* libffi.a in Frameworks */, 5C0E5EF727E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj.a in Frameworks */,
5C79C24127DB673900C829D6 /* libgmp.a in Frameworks */,
5C764E84279C748C000C6508 /* libiconv.tbd in Frameworks */, 5C764E84279C748C000C6508 /* libiconv.tbd in Frameworks */,
5C79C24327DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2.a in Frameworks */, 5C0E5EFB27E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj-ghc8.10.7.a in Frameworks */,
5C0E5EFD27E24676003DE3D0 /* libgmp.a in Frameworks */,
5C0E5EF927E24676003DE3D0 /* libffi.a in Frameworks */,
5C0E5EFF27E24676003DE3D0 /* libgmpxx.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -249,10 +260,10 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5CE4407427ADB657007B033A /* ChatItem */, 5CE4407427ADB657007B033A /* ChatItem */,
5CEACCE527DE977C000BD591 /* ComposeMessage */,
5C2E260E27A30FDC00F70299 /* ChatView.swift */, 5C2E260E27A30FDC00F70299 /* ChatView.swift */,
5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */, 5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */,
5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */, 5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */,
5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */,
5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */, 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */,
5CE4407127ADB1D0007B033A /* Emoji.swift */, 5CE4407127ADB1D0007B033A /* Emoji.swift */,
); );
@ -262,11 +273,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = { 5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5C79C23927DB673800C829D6 /* libffi.a */, 5C0E5EF227E24676003DE3D0 /* libffi.a */,
5C79C23A27DB673800C829D6 /* libgmp.a */, 5C0E5EF427E24676003DE3D0 /* libgmp.a */,
5C79C23C27DB673900C829D6 /* libgmpxx.a */, 5C0E5EF527E24676003DE3D0 /* libgmpxx.a */,
5C79C23D27DB673900C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2-ghc8.10.7.a */, 5C0E5EF327E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj-ghc8.10.7.a */,
5C79C23B27DB673800C829D6 /* libHSsimplex-chat-1.3.1-9p94HNy0jcDHEpXhSSIQi2.a */, 5C0E5EF127E24676003DE3D0 /* libHSsimplex-chat-1.3.2-6OWqTXVUCEWLoNzSi0aRKj.a */,
); );
path = Libraries; path = Libraries;
sourceTree = "<group>"; sourceTree = "<group>";
@ -298,6 +309,7 @@
5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */, 5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */,
5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */, 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */,
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */, 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */,
5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -327,7 +339,6 @@
5C764E7D279C7275000C6508 /* SimpleX (iOS)-Bridging-Header.h */, 5C764E7D279C7275000C6508 /* SimpleX (iOS)-Bridging-Header.h */,
5C764E7E279C7275000C6508 /* SimpleX (macOS)-Bridging-Header.h */, 5C764E7E279C7275000C6508 /* SimpleX (macOS)-Bridging-Header.h */,
5C764E7F279C7276000C6508 /* dummy.m */, 5C764E7F279C7276000C6508 /* dummy.m */,
5C2E260927A2C63500F70299 /* MyPlayground.playground */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@ -408,13 +419,24 @@
5CE4407427ADB657007B033A /* ChatItem */ = { 5CE4407427ADB657007B033A /* ChatItem */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5CE4407527ADB66A007B033A /* TextItemView.swift */,
5C7505A127B65FDB00BE3227 /* CIMetaView.swift */, 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */,
5CE4407827ADB701007B033A /* EmojiItemView.swift */, 5CE4407827ADB701007B033A /* EmojiItemView.swift */,
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */,
5C3A88D027DF57800060F1C2 /* FramedItemView.swift */,
); );
path = ChatItem; path = ChatItem;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
5CEACCE527DE977C000BD591 /* ComposeMessage */ = {
isa = PBXGroup;
children = (
5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */,
5CEACCE227DE9246000BD591 /* ComposeView.swift */,
5CEACCE627DE97B6000BD591 /* QuotedItemView.swift */,
);
path = ComposeMessage;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -584,14 +606,16 @@
files = ( files = (
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */, 5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */,
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */, 5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */,
5CE4407627ADB66A007B033A /* TextItemView.swift in Sources */, 5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */,
5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */, 5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */,
5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */, 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */,
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */, 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */,
5C764E80279C7276000C6508 /* dummy.m in Sources */, 5C764E80279C7276000C6508 /* dummy.m in Sources */,
5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */, 5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */,
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */,
5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */, 5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */,
640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */, 640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */,
5CEACCE727DE97B6000BD591 /* QuotedItemView.swift in Sources */,
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */, 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */,
5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */, 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */,
5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */, 5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */,
@ -602,6 +626,7 @@
5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */, 5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */,
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */, 5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */,
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */, 5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */,
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */,
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */, 5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */,
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */, 5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */,
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */, 5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */,
@ -613,6 +638,7 @@
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */, 5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
5CCD403727A5F9A200368C90 /* ConnectContactView.swift in Sources */, 5CCD403727A5F9A200368C90 /* ConnectContactView.swift in Sources */,
5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */, 5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */,
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */, 5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */,
5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */, 5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */,
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */, 5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */,
@ -629,14 +655,16 @@
files = ( files = (
5C6AD81427A834E300348BD7 /* NewChatButton.swift in Sources */, 5C6AD81427A834E300348BD7 /* NewChatButton.swift in Sources */,
5CB924D827A8563F00ACCCDD /* SettingsView.swift in Sources */, 5CB924D827A8563F00ACCCDD /* SettingsView.swift in Sources */,
5CE4407727ADB66A007B033A /* TextItemView.swift in Sources */, 5CEACCE427DE9246000BD591 /* ComposeView.swift in Sources */,
5CB924E227A867BA00ACCCDD /* UserProfile.swift in Sources */, 5CB924E227A867BA00ACCCDD /* UserProfile.swift in Sources */,
5CE4407A27ADB701007B033A /* EmojiItemView.swift in Sources */, 5CE4407A27ADB701007B033A /* EmojiItemView.swift in Sources */,
5C5346A927B59A6A004DF848 /* ChatHelp.swift in Sources */, 5C5346A927B59A6A004DF848 /* ChatHelp.swift in Sources */,
5C764E81279C7276000C6508 /* dummy.m in Sources */, 5C764E81279C7276000C6508 /* dummy.m in Sources */,
5C7505A927B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */, 5C7505A927B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */,
5C3A88D227DF57800060F1C2 /* FramedItemView.swift in Sources */,
5CB924E527A8683A00ACCCDD /* UserAddress.swift in Sources */, 5CB924E527A8683A00ACCCDD /* UserAddress.swift in Sources */,
640F50E427CF991C001E05C2 /* SMPServers.swift in Sources */, 640F50E427CF991C001E05C2 /* SMPServers.swift in Sources */,
5CEACCE827DE97B6000BD591 /* QuotedItemView.swift in Sources */,
5C063D2827A4564100AEC577 /* ChatPreviewView.swift in Sources */, 5C063D2827A4564100AEC577 /* ChatPreviewView.swift in Sources */,
5C35CFCC27B2E91D00FB6C6D /* NtfManager.swift in Sources */, 5C35CFCC27B2E91D00FB6C6D /* NtfManager.swift in Sources */,
5C2E261327A30FEA00F70299 /* TerminalView.swift in Sources */, 5C2E261327A30FEA00F70299 /* TerminalView.swift in Sources */,
@ -647,6 +675,7 @@
5CB9250E27A9432000ACCCDD /* ChatListNavLink.swift in Sources */, 5CB9250E27A9432000ACCCDD /* ChatListNavLink.swift in Sources */,
5CA059EE279559F40002BEB4 /* ContentView.swift in Sources */, 5CA059EE279559F40002BEB4 /* ContentView.swift in Sources */,
5CCD403527A5F6DF00368C90 /* AddContactView.swift in Sources */, 5CCD403527A5F6DF00368C90 /* AddContactView.swift in Sources */,
5C3A88CF27DF50170060F1C2 /* DetermineWidth.swift in Sources */,
5C7505A627B679EE00BE3227 /* NavLinkPlain.swift in Sources */, 5C7505A627B679EE00BE3227 /* NavLinkPlain.swift in Sources */,
5C7505A327B65FDB00BE3227 /* CIMetaView.swift in Sources */, 5C7505A327B65FDB00BE3227 /* CIMetaView.swift in Sources */,
5C35CFC927B2782E00FB6C6D /* BGManager.swift in Sources */, 5C35CFC927B2782E00FB6C6D /* BGManager.swift in Sources */,
@ -658,6 +687,7 @@
5CA059EC279559F40002BEB4 /* SimpleXApp.swift in Sources */, 5CA059EC279559F40002BEB4 /* SimpleXApp.swift in Sources */,
5CCD403827A5F9A200368C90 /* ConnectContactView.swift in Sources */, 5CCD403827A5F9A200368C90 /* ConnectContactView.swift in Sources */,
5CCD403B27A5F9BE00368C90 /* CreateGroupView.swift in Sources */, 5CCD403B27A5F9BE00368C90 /* CreateGroupView.swift in Sources */,
5CEACCEE27DEA495000BD591 /* MsgContentView.swift in Sources */,
5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */, 5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */,
5C971E1E27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */, 5C971E1E27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */,
5CC1C99627A6CF7F000D9FF6 /* ShareSheet.swift in Sources */, 5CC1C99627A6CF7F000D9FF6 /* ShareSheet.swift in Sources */,
@ -839,6 +869,10 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Libraries",
);
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios";
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim";
MARKETING_VERSION = 1.2; MARKETING_VERSION = 1.2;
@ -879,6 +913,10 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Libraries",
);
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios";
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim";
MARKETING_VERSION = 1.2; MARKETING_VERSION = 1.2;

View File

@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "CodeScanner",
"repositoryURL": "https://github.com/twostraws/CodeScanner",
"state": {
"branch": null,
"revision": "c27a66149b7483fe42e2ec6aad61d5c3fffe522d",
"version": "2.1.1"
}
}
]
},
"version": 1
}

View File

@ -2,20 +2,20 @@ packages: .
source-repository-package source-repository-package
type: git type: git
location: git://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: 5c6ec96d6477371d8e617bcc71e6ecbcdd5c78cc tag: 5c6ec96d6477371d8e617bcc71e6ecbcdd5c78cc
source-repository-package source-repository-package
type: git type: git
location: git://github.com/simplex-chat/aeson.git location: https://github.com/simplex-chat/aeson.git
tag: 3eb66f9a68f103b5f1489382aad89f5712a64db7 tag: 3eb66f9a68f103b5f1489382aad89f5712a64db7
source-repository-package source-repository-package
type: git type: git
location: git://github.com/simplex-chat/haskell-terminal.git location: https://github.com/simplex-chat/haskell-terminal.git
tag: f708b00009b54890172068f168bf98508ffcd495 tag: f708b00009b54890172068f168bf98508ffcd495
source-repository-package source-repository-package
type: git type: git
location: git://github.com/zw3rk/android-support.git location: https://github.com/zw3rk/android-support.git
tag: 3c3a5ab0b8b137a072c98d3d0937cbdc96918ddb tag: 3c3a5ab0b8b137a072c98d3d0937cbdc96918ddb