mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-26 19:35:48 +00:00
simple welcome message (#152)
* simple welcome message * show welcome message only once * show onboarding progress
This commit is contained in:
committed by
GitHub
parent
7fa671f829
commit
20e7feb953
+14
-4
@@ -44,7 +44,7 @@ import Simplex.Chat.Store
|
||||
import Simplex.Chat.Styled (plain)
|
||||
import Simplex.Chat.Terminal
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Util (ifM, unlessM)
|
||||
import Simplex.Chat.Util (ifM, unlessM, whenM)
|
||||
import Simplex.Chat.View
|
||||
import Simplex.Messaging.Agent
|
||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), defaultAgentConfig)
|
||||
@@ -70,6 +70,7 @@ data ChatCommand
|
||||
| GroupsHelp
|
||||
| MyAddressHelp
|
||||
| MarkdownHelp
|
||||
| Welcome
|
||||
| AddContact
|
||||
| Connect AConnectionRequest
|
||||
| DeleteContact ContactName
|
||||
@@ -129,7 +130,9 @@ simplexChat cfg opts t =
|
||||
|
||||
newChatController :: WithTerminal t => ChatConfig -> ChatOpts -> t -> (Notification -> IO ()) -> IO ChatController
|
||||
newChatController config@ChatConfig {agentConfig = cfg, dbPoolSize, tbqSize} ChatOpts {dbFile, smpServers} t sendNotification = do
|
||||
chatStore <- createStore (dbFile <> ".chat.db") dbPoolSize
|
||||
let f = chatStoreFile dbFile
|
||||
firstTime <- not <$> doesFileExist f
|
||||
chatStore <- createStore f dbPoolSize
|
||||
currentUser <- newTVarIO =<< getCreateActiveUser chatStore
|
||||
chatTerminal <- newChatTerminal t
|
||||
smpAgent <- getSMPAgentClient cfg {dbFile = dbFile <> ".agent.db", smpServers}
|
||||
@@ -142,7 +145,10 @@ newChatController config@ChatConfig {agentConfig = cfg, dbPoolSize, tbqSize} Cha
|
||||
pure ChatController {..}
|
||||
|
||||
runSimplexChat :: ChatController -> IO ()
|
||||
runSimplexChat = runReaderT (race_ runTerminalInput runChatController)
|
||||
runSimplexChat = runReaderT $ do
|
||||
user <- readTVarIO =<< asks currentUser
|
||||
whenM (asks firstTime) . printToView . chatWelcome user $ Onboarding 0 0 0 0 0
|
||||
race_ runTerminalInput runChatController
|
||||
|
||||
runChatController :: (MonadUnliftIO m, MonadReader ChatController m) => m ()
|
||||
runChatController =
|
||||
@@ -187,6 +193,9 @@ processChatCommand user@User {userId, profile} = \case
|
||||
GroupsHelp -> printToView groupsHelpInfo
|
||||
MyAddressHelp -> printToView myAddressHelpInfo
|
||||
MarkdownHelp -> printToView markdownInfo
|
||||
Welcome -> do
|
||||
ob <- withStore (`getOnboarding` userId)
|
||||
printToView $ chatWelcome user ob
|
||||
AddContact -> do
|
||||
(connId, cReq) <- withAgent (`createConnection` SCMInvitation)
|
||||
withStore $ \st -> createDirectConnection st userId connId
|
||||
@@ -548,7 +557,7 @@ processAgentMessage user@User {userId, profile} agentConnId agentMessage = do
|
||||
-- TODO update contact profile
|
||||
pure ()
|
||||
XOk -> pure ()
|
||||
_ -> messageError "INFO for existing contact must have x.grp.mem.info, x.info or x.ok"
|
||||
_ -> messageError "INFO for existing contact must have x.grp.mem.info, x.info or x.ok"
|
||||
CON ->
|
||||
withStore (\st -> getViaGroupMember st user ct) >>= \case
|
||||
Nothing -> do
|
||||
@@ -1186,6 +1195,7 @@ chatCommandP =
|
||||
<|> ("/accept @" <|> "/accept " <|> "/ac @" <|> "/ac ") *> (AcceptContact <$> displayName)
|
||||
<|> ("/reject @" <|> "/reject " <|> "/rc @" <|> "/rc ") *> (RejectContact <$> displayName)
|
||||
<|> ("/markdown" <|> "/m") $> MarkdownHelp
|
||||
<|> ("/welcome" <|> "/w") $> Welcome
|
||||
<|> ("/profile " <|> "/p ") *> (UpdateProfile <$> userProfile)
|
||||
<|> ("/profile" <|> "/p") $> ShowProfile
|
||||
<|> ("/quit" <|> "/q" <|> "/exit") $> QuitChat
|
||||
|
||||
@@ -37,6 +37,7 @@ data ChatConfig = ChatConfig
|
||||
|
||||
data ChatController = ChatController
|
||||
{ currentUser :: TVar User,
|
||||
firstTime :: Bool,
|
||||
smpAgent :: AgentClient,
|
||||
chatTerminal :: ChatTerminal,
|
||||
chatStore :: SQLiteStore,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Simplex.Chat.Help
|
||||
( chatHelpInfo,
|
||||
( chatWelcome,
|
||||
chatHelpInfo,
|
||||
filesHelpInfo,
|
||||
groupsHelpInfo,
|
||||
myAddressHelpInfo,
|
||||
@@ -11,13 +13,24 @@ where
|
||||
|
||||
import Data.List (intersperse)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Simplex.Chat.Markdown
|
||||
import Simplex.Chat.Styled
|
||||
import Simplex.Chat.Types (Onboarding (..), Profile (..), User (..))
|
||||
import System.Console.ANSI.Types
|
||||
|
||||
highlight :: Text -> Markdown
|
||||
highlight = Markdown (Colored Cyan)
|
||||
|
||||
blue :: Text -> Markdown
|
||||
blue = Markdown (Colored Blue)
|
||||
|
||||
cyan :: Text -> Markdown
|
||||
cyan = Markdown (Colored Cyan)
|
||||
|
||||
yellow :: Text -> Markdown
|
||||
yellow = Markdown (Colored Yellow)
|
||||
|
||||
green :: Text -> Markdown
|
||||
green = Markdown (Colored Green)
|
||||
|
||||
@@ -27,6 +40,36 @@ indent = " "
|
||||
listHighlight :: [Text] -> Markdown
|
||||
listHighlight = mconcat . intersperse ", " . map highlight
|
||||
|
||||
chatWelcome :: User -> Onboarding -> [StyledString]
|
||||
chatWelcome user Onboarding {contactsCount, createdGroups, membersCount, filesSentCount, addressCount} =
|
||||
map
|
||||
styleMarkdown
|
||||
[ blue " __ __",
|
||||
cyan " ___ ___ __ __ ___ _ ___" <> blue "\\ \\ / /" <> yellow " ___ _ _ _ _____",
|
||||
cyan " / __|_ _| \\/ | _ \\ | | __ " <> blue "\\ V /" <> yellow " / __| || | /_\\_ _|",
|
||||
cyan " \\__ \\| || |\\/| | _/ |__| _|" <> blue " / . \\" <> yellow "| (__| __ |/ _ \\| |",
|
||||
cyan " |___/___|_| |_|_| |____|___" <> blue "/_/ \\_\\" <> yellow "\\___|_||_/_/ \\_\\_|",
|
||||
"",
|
||||
"Welcome " <> green userName <> "!",
|
||||
"Thank you for installing SimpleX Chat!",
|
||||
"",
|
||||
"To try out how it works:",
|
||||
"[" <> check (contactsCount >= 2) <> "] connect with 2 friends - " <> highlight "/help" <> " for instructions",
|
||||
"[" <> check (createdGroups >= 1 && membersCount >= 2) <> "] create a group with them - " <> highlight "/group #friends",
|
||||
"[" <> check (filesSentCount >= 1) <> "] send your photo, e.g. to the group - " <> highlight "/file #friends ./photo.jpg",
|
||||
"[" <> check (addressCount >= 1) <> "] create your chat " <> highlight "/address" <> " and share it with your friends",
|
||||
"",
|
||||
"To help us build SimpleX Chat:",
|
||||
"> star GitHub repo: https://github.com/simplex-chat/simplex-chat",
|
||||
"> join Reddit group: https://www.reddit.com/r/SimpleXChat/",
|
||||
"",
|
||||
"To show this message again - " <> highlight "/welcome"
|
||||
]
|
||||
where
|
||||
User {profile = Profile {displayName, fullName}} = user
|
||||
userName = if T.null fullName then displayName else fullName
|
||||
check c = if c then green "*" else " "
|
||||
|
||||
chatHelpInfo :: [StyledString]
|
||||
chatHelpInfo =
|
||||
map
|
||||
|
||||
@@ -17,6 +17,7 @@ module Simplex.Chat.Store
|
||||
( SQLiteStore,
|
||||
StoreError (..),
|
||||
createStore,
|
||||
chatStoreFile,
|
||||
createUser,
|
||||
getUsers,
|
||||
setActiveUser,
|
||||
@@ -88,6 +89,7 @@ module Simplex.Chat.Store
|
||||
deleteRcvFileChunks,
|
||||
getFileTransfer,
|
||||
getFileTransferProgress,
|
||||
getOnboarding,
|
||||
)
|
||||
where
|
||||
|
||||
@@ -136,6 +138,9 @@ migrations =
|
||||
createStore :: FilePath -> Int -> IO SQLiteStore
|
||||
createStore dbFilePath poolSize = createSQLiteStore dbFilePath poolSize migrations
|
||||
|
||||
chatStoreFile :: FilePath -> FilePath
|
||||
chatStoreFile = (<> ".chat.db")
|
||||
|
||||
checkConstraint :: StoreError -> IO (Either StoreError a) -> IO (Either StoreError a)
|
||||
checkConstraint err action = action `E.catch` (pure . Left . handleSQLError err)
|
||||
|
||||
@@ -1586,6 +1591,21 @@ getSndFileTransfers_ db userId fileId =
|
||||
Just recipientDisplayName -> Right SndFileTransfer {..}
|
||||
Nothing -> Left $ SESndFileInvalid fileId
|
||||
|
||||
getOnboarding :: MonadUnliftIO m => SQLiteStore -> UserId -> m Onboarding
|
||||
getOnboarding st userId =
|
||||
liftIO . withTransaction st $ \db -> do
|
||||
contactsCount <- intQuery db "SELECT COUNT(contact_id) FROM contacts WHERE user_id = ?"
|
||||
createdGroups <- headOrZero <$> DB.query db "SELECT COUNT(g.group_id) FROM groups g JOIN group_members m WHERE g.user_id = ? AND m.member_status = ?" (userId, GSMemCreator)
|
||||
membersCount <- headOrZero <$> DB.query db "SELECT COUNT(group_member_id) FROM group_members WHERE user_id = ? AND (member_status = ? OR member_status = ?)" (userId, GSMemConnected, GSMemComplete)
|
||||
filesSentCount <- intQuery db "SELECT COUNT(s.file_id) FROM snd_files s JOIN files f USING (file_id) WHERE f.user_id = ?"
|
||||
addressCount <- intQuery db "SELECT COUNT(user_contact_link_id) FROM user_contact_links WHERE user_id = ?"
|
||||
pure $ Onboarding {..}
|
||||
where
|
||||
intQuery :: DB.Connection -> DB.Query -> IO Int
|
||||
intQuery db q = headOrZero <$> DB.query db q (Only userId)
|
||||
headOrZero [] = 0
|
||||
headOrZero (n : _) = fromOnly n
|
||||
|
||||
-- | Saves unique local display name based on passed displayName, suffixed with _N if required.
|
||||
-- This function should be called inside transaction.
|
||||
withLocalDisplayName :: forall a. DB.Connection -> UserId -> Text -> (Text -> IO a) -> IO (Either StoreError a)
|
||||
|
||||
@@ -518,3 +518,11 @@ serializeIntroStatus = \case
|
||||
GMIntroReConnected -> "re-con"
|
||||
GMIntroToConnected -> "to-con"
|
||||
GMIntroConnected -> "con"
|
||||
|
||||
data Onboarding = Onboarding
|
||||
{ contactsCount :: Int,
|
||||
createdGroups :: Int,
|
||||
membersCount :: Int,
|
||||
filesSentCount :: Int,
|
||||
addressCount :: Int
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module Simplex.Chat.Util where
|
||||
|
||||
import Control.Monad (when)
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import Data.Text (Text)
|
||||
import Data.Text.Encoding (decodeUtf8With)
|
||||
@@ -12,5 +13,8 @@ safeDecodeUtf8 = decodeUtf8With onError
|
||||
ifM :: Monad m => m Bool -> m a -> m a -> m a
|
||||
ifM ba t f = ba >>= \b -> if b then t else f
|
||||
|
||||
whenM :: Monad m => m Bool -> m () -> m ()
|
||||
whenM ba a = ba >>= (`when` a)
|
||||
|
||||
unlessM :: Monad m => m Bool -> m () -> m ()
|
||||
unlessM b = ifM b $ pure ()
|
||||
|
||||
Reference in New Issue
Block a user