2021-06-25 18:18:24 +01:00
|
|
|
{-# LANGUAGE ConstraintKinds #-}
|
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
|
|
|
{-# LANGUAGE LambdaCase #-}
|
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
|
2021-07-05 20:05:07 +01:00
|
|
|
module Simplex.Chat.View
|
2021-06-25 18:18:24 +01:00
|
|
|
( printToView,
|
|
|
|
|
showInvitation,
|
2021-07-04 18:42:24 +01:00
|
|
|
showChatError,
|
2021-06-25 18:18:24 +01:00
|
|
|
showContactDeleted,
|
|
|
|
|
showContactConnected,
|
|
|
|
|
showContactDisconnected,
|
|
|
|
|
showReceivedMessage,
|
|
|
|
|
showSentMessage,
|
2021-06-26 20:20:33 +01:00
|
|
|
safeDecodeUtf8,
|
2021-06-25 18:18:24 +01:00
|
|
|
)
|
|
|
|
|
where
|
|
|
|
|
|
|
|
|
|
import Control.Monad.IO.Unlift
|
|
|
|
|
import Control.Monad.Reader
|
|
|
|
|
import Data.ByteString.Char8 (ByteString)
|
2021-07-04 18:42:24 +01:00
|
|
|
import Data.Text (Text)
|
2021-06-25 18:18:24 +01:00
|
|
|
import qualified Data.Text as T
|
|
|
|
|
import Data.Time.Clock (DiffTime, UTCTime)
|
|
|
|
|
import Data.Time.Format (defaultTimeLocale, formatTime)
|
|
|
|
|
import Data.Time.LocalTime (TimeZone, ZonedTime, getCurrentTimeZone, getZonedTime, localDay, localTimeOfDay, timeOfDayToTime, utcToLocalTime, zonedTimeToLocalTime)
|
|
|
|
|
import Simplex.Chat.Controller
|
|
|
|
|
import Simplex.Chat.Markdown
|
|
|
|
|
import Simplex.Chat.Styled
|
2021-07-05 20:05:07 +01:00
|
|
|
import Simplex.Chat.Terminal (printToTerminal)
|
2021-07-04 18:42:24 +01:00
|
|
|
import Simplex.Chat.Types
|
2021-07-05 20:05:07 +01:00
|
|
|
import Simplex.Chat.Util (safeDecodeUtf8)
|
2021-06-25 18:18:24 +01:00
|
|
|
import Simplex.Messaging.Agent.Protocol
|
|
|
|
|
import System.Console.ANSI.Types
|
|
|
|
|
|
|
|
|
|
type ChatReader m = (MonadUnliftIO m, MonadReader ChatController m)
|
|
|
|
|
|
2021-07-05 19:54:44 +01:00
|
|
|
showInvitation :: ChatReader m => SMPQueueInfo -> m ()
|
|
|
|
|
showInvitation = printToView . invitation
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
showChatError :: ChatReader m => ChatError -> m ()
|
|
|
|
|
showChatError = printToView . chatError
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
showContactDeleted :: ChatReader m => ContactRef -> m ()
|
2021-06-25 18:18:24 +01:00
|
|
|
showContactDeleted = printToView . contactDeleted
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
showContactConnected :: ChatReader m => ContactRef -> m ()
|
2021-06-25 18:18:24 +01:00
|
|
|
showContactConnected = printToView . contactConnected
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
showContactDisconnected :: ChatReader m => ContactRef -> m ()
|
2021-06-25 18:18:24 +01:00
|
|
|
showContactDisconnected = printToView . contactDisconnected
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
showReceivedMessage :: ChatReader m => ContactRef -> UTCTime -> Text -> MsgIntegrity -> m ()
|
2021-06-25 18:18:24 +01:00
|
|
|
showReceivedMessage c utcTime msg mOk = printToView =<< liftIO (receivedMessage c utcTime msg mOk)
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
showSentMessage :: ChatReader m => ContactRef -> ByteString -> m ()
|
2021-06-25 18:18:24 +01:00
|
|
|
showSentMessage c msg = printToView =<< liftIO (sentMessage c msg)
|
|
|
|
|
|
2021-07-05 19:54:44 +01:00
|
|
|
invitation :: SMPQueueInfo -> [StyledString]
|
|
|
|
|
invitation qInfo =
|
|
|
|
|
[ "pass this invitation to your contact (via another channel): ",
|
2021-06-25 18:18:24 +01:00
|
|
|
"",
|
|
|
|
|
(bPlain . serializeSmpQueueInfo) qInfo,
|
|
|
|
|
"",
|
|
|
|
|
"and ask them to connect: /c <name_for_you> <invitation_above>"
|
|
|
|
|
]
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
contactDeleted :: ContactRef -> [StyledString]
|
2021-06-25 18:18:24 +01:00
|
|
|
contactDeleted c = [ttyContact c <> " is deleted"]
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
contactConnected :: ContactRef -> [StyledString]
|
2021-06-25 18:18:24 +01:00
|
|
|
contactConnected c = [ttyContact c <> " is connected"]
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
contactDisconnected :: ContactRef -> [StyledString]
|
2021-06-25 18:18:24 +01:00
|
|
|
contactDisconnected c = ["disconnected from " <> ttyContact c <> " - restart chat"]
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
receivedMessage :: ContactRef -> UTCTime -> Text -> MsgIntegrity -> IO [StyledString]
|
2021-06-25 18:18:24 +01:00
|
|
|
receivedMessage c utcTime msg mOk = do
|
|
|
|
|
t <- formatUTCTime <$> getCurrentTimeZone <*> getZonedTime
|
|
|
|
|
pure $ prependFirst (t <> " " <> ttyFromContact c) (msgPlain msg) ++ showIntegrity mOk
|
|
|
|
|
where
|
|
|
|
|
formatUTCTime :: TimeZone -> ZonedTime -> StyledString
|
|
|
|
|
formatUTCTime localTz currentTime =
|
|
|
|
|
let localTime = utcToLocalTime localTz utcTime
|
|
|
|
|
format =
|
|
|
|
|
if (localDay localTime < localDay (zonedTimeToLocalTime currentTime))
|
|
|
|
|
&& (timeOfDayToTime (localTimeOfDay localTime) > (6 * 60 * 60 :: DiffTime))
|
|
|
|
|
then "%m-%d" -- if message is from yesterday or before and 6 hours has passed since midnight
|
|
|
|
|
else "%H:%M"
|
|
|
|
|
in styleTime $ formatTime defaultTimeLocale format localTime
|
|
|
|
|
showIntegrity :: MsgIntegrity -> [StyledString]
|
|
|
|
|
showIntegrity MsgOk = []
|
|
|
|
|
showIntegrity (MsgError err) = msgError $ case err of
|
|
|
|
|
MsgSkipped fromId toId ->
|
|
|
|
|
"skipped message ID " <> show fromId
|
|
|
|
|
<> if fromId == toId then "" else ".." <> show toId
|
|
|
|
|
MsgBadId msgId -> "unexpected message ID " <> show msgId
|
|
|
|
|
MsgBadHash -> "incorrect message hash"
|
|
|
|
|
MsgDuplicate -> "duplicate message ID"
|
|
|
|
|
msgError :: String -> [StyledString]
|
|
|
|
|
msgError s = [styled (Colored Red) s]
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
sentMessage :: ContactRef -> ByteString -> IO [StyledString]
|
2021-06-25 18:18:24 +01:00
|
|
|
sentMessage c msg = do
|
|
|
|
|
time <- formatTime defaultTimeLocale "%H:%M" <$> getZonedTime
|
2021-07-04 18:42:24 +01:00
|
|
|
pure $ prependFirst (styleTime time <> " " <> ttyToContact c) (msgPlain $ safeDecodeUtf8 msg)
|
2021-06-25 18:18:24 +01:00
|
|
|
|
|
|
|
|
prependFirst :: StyledString -> [StyledString] -> [StyledString]
|
|
|
|
|
prependFirst s [] = [s]
|
|
|
|
|
prependFirst s (s' : ss) = (s <> s') : ss
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
msgPlain :: Text -> [StyledString]
|
|
|
|
|
msgPlain = map styleMarkdownText . T.lines
|
|
|
|
|
|
|
|
|
|
chatError :: ChatError -> [StyledString]
|
|
|
|
|
chatError = \case
|
|
|
|
|
ChatErrorContact e -> case e of
|
|
|
|
|
CENotFound c -> ["no contact " <> ttyContact c]
|
2021-07-05 19:54:44 +01:00
|
|
|
CEProfile s -> ["invalid profile: " <> plain s]
|
|
|
|
|
ChatErrorAgent err -> case err of
|
|
|
|
|
-- CONN e -> case e of
|
|
|
|
|
-- -- TODO replace with ChatErrorContact errors, these errors should never happen
|
|
|
|
|
-- NOT_FOUND -> ["no contact " <> ttyContact c]
|
|
|
|
|
-- DUPLICATE -> ["contact " <> ttyContact c <> " already exists"]
|
|
|
|
|
-- SIMPLEX -> ["contact " <> ttyContact c <> " did not accept invitation yet"]
|
2021-07-04 18:42:24 +01:00
|
|
|
e -> ["smp agent error: " <> plain (show e)]
|
2021-06-25 18:18:24 +01:00
|
|
|
e -> ["chat error: " <> plain (show e)]
|
|
|
|
|
|
|
|
|
|
printToView :: (MonadUnliftIO m, MonadReader ChatController m) => [StyledString] -> m ()
|
|
|
|
|
printToView s = asks chatTerminal >>= liftIO . (`printToTerminal` s)
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
ttyContact :: ContactRef -> StyledString
|
|
|
|
|
ttyContact = styled (Colored Green)
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
ttyToContact :: ContactRef -> StyledString
|
2021-07-07 22:46:38 +01:00
|
|
|
ttyToContact c = styled (Colored Cyan) $ "@" <> c <> " "
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
ttyFromContact :: ContactRef -> StyledString
|
|
|
|
|
ttyFromContact c = styled (Colored Yellow) $ c <> "> "
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
-- ttyGroup :: Group -> StyledString
|
|
|
|
|
-- ttyGroup (Group g) = styled (Colored Blue) $ "#" <> g
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
-- ttyFromGroup :: Group -> Contact -> StyledString
|
|
|
|
|
-- ttyFromGroup (Group g) (Contact a) = styled (Colored Yellow) $ "#" <> g <> " " <> a <> "> "
|
2021-06-25 18:18:24 +01:00
|
|
|
|
|
|
|
|
styleTime :: String -> StyledString
|
|
|
|
|
styleTime = Styled [SetColor Foreground Vivid Black]
|