diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index fa78a9a418..6adb2731ba 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -436,7 +436,8 @@ class AChatItem ( data class ChatItem ( val chatDir: CIDirection, val meta: CIMeta, - val content: CIContent + val content: CIContent, + val formattedText: List? = null ) { val id: Long get() = meta.itemId val timestampText: String get() = meta.timestampText @@ -567,6 +568,33 @@ sealed class MsgContent { } @Serializable -class RcvFileTransfer { +class FormattedText(val text: String, val format: Format? = null) +@Serializable +sealed class Format { + @Serializable @SerialName("bold") class Bold: Format() + @Serializable @SerialName("italic") class Italic: Format() + @Serializable @SerialName("underline") class Underline: Format() + @Serializable @SerialName("strikeThrough") class StrikeThrough: Format() + @Serializable @SerialName("snippet") class Snippet: Format() + @Serializable @SerialName("secret") class Secret: Format() + @Serializable @SerialName("colored") class Colored(val formatColor: FormatColor): Format() + @Serializable @SerialName("uri") class Uri: Format() + @Serializable @SerialName("email") class Email: Format() + @Serializable @SerialName("phone") class Phone: Format() } + +@Serializable +enum class FormatColor(val color: String) { + Red("red"), + Green("green"), + Blue("blue"), + Yellow("yellow"), + Cyan("cyan"), + Magenta("magenta"), + Black("black"), + White("white") +} + +@Serializable +class RcvFileTransfer diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt index c49602c49a..7097d40833 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt @@ -69,14 +69,13 @@ fun ChatPreviewView(chat: Chat, goToChat: () -> Unit) { Text( if (n < 1000) "$n" else "${n / 1000}k", color = MaterialTheme.colors.onPrimary, - style = MaterialTheme.typography.body2, fontSize = 14.sp, modifier = Modifier .background(MaterialTheme.colors.primary, shape = CircleShape) .align(Alignment.End) .badgeLayout() - .padding(horizontal = 4.dp) - .padding(vertical = 2.dp) + .padding(horizontal = 3.dp) + .padding(vertical = 1.dp) ) } } @@ -97,7 +96,7 @@ fun ChatPreviewViewExample() { 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." )), - chatStats = Chat.ChatStats() + chatStats = Chat.ChatStats(unreadCount = 3) ), goToChat = {} ) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index e0caf9d1a6..bff0c8a669 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -512,6 +512,7 @@ struct ChatItem: Identifiable, Decodable { var chatDir: CIDirection var meta: CIMeta var content: CIContent + var formattedText: [FormattedText]? var id: Int64 { get { meta.itemId } } @@ -657,3 +658,34 @@ extension MsgContent: Decodable { } } } + +struct FormattedText: Decodable { + var text: String + var format: Format? +} + +enum Format: Decodable { + case bold + case italic + case underline + case strikeThrough + case snippet + case secret + case colored(formatColor: FormatColor) + case uri + case email + case phone +} + +enum FormatColor: Decodable { + case red + case green + case blue + case yellow + case cyan + case magenta + case black + case white + + // TODO custom decoding, it won't parse as is +} diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 365311f057..e06ad337fe 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -13,6 +13,11 @@ 5C116CDD27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; }; 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; 5C1A4C1F27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; + 5C27CFFE27C61CAB00DD6182 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFF927C61CAB00DD6182 /* libgmp.a */; }; + 5C27CFFF27C61CAB00DD6182 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFFA27C61CAB00DD6182 /* libgmpxx.a */; }; + 5C27D00027C61CAB00DD6182 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFFB27C61CAB00DD6182 /* libffi.a */; }; + 5C27D00127C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFFC27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a */; }; + 5C27D00227C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFFD27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a */; }; 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; }; 5C2E260827A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; }; 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; }; @@ -25,11 +30,6 @@ 5C35CFC927B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; }; 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; }; 5C35CFCC27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; }; - 5C499F2D27BAF1E300ECB4C5 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C499F2827BAF1E300ECB4C5 /* libffi.a */; }; - 5C499F2E27BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C499F2927BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8.a */; }; - 5C499F2F27BAF1E300ECB4C5 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C499F2A27BAF1E300ECB4C5 /* libgmpxx.a */; }; - 5C499F3027BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C499F2B27BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8-ghc8.10.7.a */; }; - 5C499F3127BAF1E300ECB4C5 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C499F2C27BAF1E300ECB4C5 /* libgmp.a */; }; 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; 5C5346A927B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; 5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; }; @@ -118,6 +118,11 @@ 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = ""; }; 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = ""; }; 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = ""; }; + 5C27CFF927C61CAB00DD6182 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C27CFFA27C61CAB00DD6182 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C27CFFB27C61CAB00DD6182 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5C27CFFC27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a"; sourceTree = ""; }; + 5C27CFFD27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a"; sourceTree = ""; }; 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = ""; }; 5C2E260927A2C63500F70299 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = MyPlayground.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = ""; }; @@ -126,11 +131,6 @@ 5C35CFC727B2782E00FB6C6D /* BGManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGManager.swift; sourceTree = ""; }; 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NtfManager.swift; sourceTree = ""; }; 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = ""; }; - 5C499F2827BAF1E300ECB4C5 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C499F2927BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8.a"; sourceTree = ""; }; - 5C499F2A27BAF1E300ECB4C5 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 5C499F2B27BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8-ghc8.10.7.a"; sourceTree = ""; }; - 5C499F2C27BAF1E300ECB4C5 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = ""; }; 5C6AD81227A834E300348BD7 /* NewChatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton.swift; sourceTree = ""; }; 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMetaView.swift; sourceTree = ""; }; @@ -178,14 +178,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C499F3027BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8-ghc8.10.7.a in Frameworks */, - 5C499F2F27BAF1E300ECB4C5 /* libgmpxx.a in Frameworks */, - 5C499F3127BAF1E300ECB4C5 /* libgmp.a in Frameworks */, 5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */, - 5C499F2E27BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8.a in Frameworks */, + 5C27D00027C61CAB00DD6182 /* libffi.a in Frameworks */, + 5C27D00227C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a in Frameworks */, + 5C27CFFF27C61CAB00DD6182 /* libgmpxx.a in Frameworks */, 5C764E83279C748B000C6508 /* libz.tbd in Frameworks */, + 5C27CFFE27C61CAB00DD6182 /* libgmp.a in Frameworks */, 5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */, - 5C499F2D27BAF1E300ECB4C5 /* libffi.a in Frameworks */, + 5C27D00127C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -246,11 +246,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C499F2827BAF1E300ECB4C5 /* libffi.a */, - 5C499F2C27BAF1E300ECB4C5 /* libgmp.a */, - 5C499F2A27BAF1E300ECB4C5 /* libgmpxx.a */, - 5C499F2B27BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8-ghc8.10.7.a */, - 5C499F2927BAF1E300ECB4C5 /* libHSsimplex-chat-1.2.0-KiHnJvLnz2iELJvTN47xo8.a */, + 5C27CFFB27C61CAB00DD6182 /* libffi.a */, + 5C27CFF927C61CAB00DD6182 /* libgmp.a */, + 5C27CFFA27C61CAB00DD6182 /* libgmpxx.a */, + 5C27CFFC27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a */, + 5C27CFFD27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a */, ); path = Libraries; sourceTree = ""; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index fc163dc3dd..5ae406a0b8 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1295,7 +1295,7 @@ saveChatItem userId cd ci@NewChatItem {itemContent, itemTs, itemText, createdAt} tz <- liftIO getCurrentTimeZone ciId <- withStore $ \st -> createNewChatItem st userId cd ci let ciMeta = mkCIMeta ciId itemText ciStatusNew tz itemTs createdAt - pure $ ChatItem (toCIDirection cd) ciMeta itemContent $ parseMarkdownList itemText + pure $ ChatItem (toCIDirection cd) ciMeta itemContent $ parseMaybeMarkdownList itemText mkNewChatItem :: forall d. MsgDirectionI d => CIContent d -> MessageId -> UTCTime -> UTCTime -> NewChatItem d mkNewChatItem itemContent msgId itemTs createdAt = diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index 1384610fbd..544211f289 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -14,6 +14,7 @@ import Data.Either (fromRight) import Data.Functor (($>)) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M +import Data.Maybe (isNothing) import Data.String import Data.Text (Text) import qualified Data.Text as T @@ -31,8 +32,10 @@ data Format | StrikeThrough | Snippet | Secret - | Colored FormatColor + | Colored {formatColor :: FormatColor} | Uri + | Email + | Phone deriving (Eq, Show, Generic) colored :: Color -> Format @@ -127,6 +130,11 @@ colors = ("6", Magenta) ] +parseMaybeMarkdownList :: Text -> Maybe MarkdownList +parseMaybeMarkdownList s = + let m = markdownToList $ parseMarkdown s + in if all (isNothing . format) m then Nothing else Just m + parseMarkdownList :: Text -> MarkdownList parseMarkdownList = markdownToList . parseMarkdown diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 40c5fb4af4..ae93e7ffc7 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -78,7 +78,7 @@ data ChatItem (c :: ChatType) (d :: MsgDirection) = ChatItem { chatDir :: CIDirection c d, meta :: CIMeta d, content :: CIContent d, - formattedText :: [FormattedText] + formattedText :: Maybe [FormattedText] } deriving (Show, Generic) diff --git a/src/Simplex/Chat/Store.hs b/src/Simplex/Chat/Store.hs index 5c749dd5f1..1074537b1b 100644 --- a/src/Simplex/Chat/Store.hs +++ b/src/Simplex/Chat/Store.hs @@ -2709,7 +2709,7 @@ toDirectChatItem tz (itemId, itemTs, itemContent, itemText, itemStatus, createdA _ -> badItem where cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection c d -> CIStatus d -> CIContent d -> CChatItem c - cItem d cid ciStatus ciContent = CChatItem d (ChatItem cid (ciMeta ciStatus) ciContent $ parseMarkdownList itemText) + cItem d cid ciStatus ciContent = CChatItem d (ChatItem cid (ciMeta ciStatus) ciContent $ parseMaybeMarkdownList itemText) badItem = Left $ SEBadChatItem itemId ciMeta :: CIStatus d -> CIMeta d ciMeta status = mkCIMeta itemId itemText status tz itemTs createdAt @@ -2732,7 +2732,7 @@ toGroupChatItem tz userContactId ((itemId, itemTs, itemContent, itemText, itemSt _ -> badItem where cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection c d -> CIStatus d -> CIContent d -> CChatItem c - cItem d cid ciStatus ciContent = CChatItem d (ChatItem cid (ciMeta ciStatus) ciContent $ parseMarkdownList itemText) + cItem d cid ciStatus ciContent = CChatItem d (ChatItem cid (ciMeta ciStatus) ciContent $ parseMaybeMarkdownList itemText) badItem = Left $ SEBadChatItem itemId ciMeta :: CIStatus d -> CIMeta d ciMeta status = mkCIMeta itemId itemText status tz itemTs createdAt