mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
core: split preferences to separate file
This commit is contained in:
parent
b033fdbeee
commit
9c49b038cd
19 changed files with 834 additions and 770 deletions
|
@ -125,6 +125,8 @@ library
|
||||||
Simplex.Chat.Terminal.Notification
|
Simplex.Chat.Terminal.Notification
|
||||||
Simplex.Chat.Terminal.Output
|
Simplex.Chat.Terminal.Output
|
||||||
Simplex.Chat.Types
|
Simplex.Chat.Types
|
||||||
|
Simplex.Chat.Types.Preferences
|
||||||
|
Simplex.Chat.Types.Util
|
||||||
Simplex.Chat.Util
|
Simplex.Chat.Util
|
||||||
Simplex.Chat.View
|
Simplex.Chat.View
|
||||||
other-modules:
|
other-modules:
|
||||||
|
|
|
@ -67,6 +67,8 @@ import Simplex.Chat.Store.Messages
|
||||||
import Simplex.Chat.Store.Profiles
|
import Simplex.Chat.Store.Profiles
|
||||||
import Simplex.Chat.Store.Shared
|
import Simplex.Chat.Store.Shared
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
|
import Simplex.Chat.Types.Util
|
||||||
import Simplex.FileTransfer.Client.Main (maxFileSize)
|
import Simplex.FileTransfer.Client.Main (maxFileSize)
|
||||||
import Simplex.FileTransfer.Client.Presets (defaultXFTPServers)
|
import Simplex.FileTransfer.Client.Presets (defaultXFTPServers)
|
||||||
import Simplex.FileTransfer.Description (ValidFileDescription, gb, kb, mb)
|
import Simplex.FileTransfer.Description (ValidFileDescription, gb, kb, mb)
|
||||||
|
|
|
@ -21,7 +21,8 @@ import Data.Time.Clock (UTCTime)
|
||||||
import Database.SQLite.Simple.FromField (FromField (..))
|
import Database.SQLite.Simple.FromField (FromField (..))
|
||||||
import Database.SQLite.Simple.ToField (ToField (..))
|
import Database.SQLite.Simple.ToField (ToField (..))
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Simplex.Chat.Types (Contact, ContactId, User, decodeJSON, encodeJSON)
|
import Simplex.Chat.Types (Contact, ContactId, User)
|
||||||
|
import Simplex.Chat.Types.Util (decodeJSON, encodeJSON)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON)
|
||||||
|
|
|
@ -47,6 +47,7 @@ import Simplex.Chat.Messages.CIContent
|
||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Store (AutoAccept, StoreError, UserContactLink, UserMsgReceiptSettings)
|
import Simplex.Chat.Store (AutoAccept, StoreError, UserContactLink, UserMsgReceiptSettings)
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent (AgentClient)
|
import Simplex.Messaging.Agent (AgentClient)
|
||||||
import Simplex.Messaging.Agent.Client (AgentLocks, ProtocolTestFailure)
|
import Simplex.Messaging.Agent.Client (AgentLocks, ProtocolTestFailure)
|
||||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig)
|
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig)
|
||||||
|
|
|
@ -28,6 +28,7 @@ import qualified Data.Text as T
|
||||||
import Data.Text.Encoding (encodeUtf8)
|
import Data.Text.Encoding (encodeUtf8)
|
||||||
import GHC.Generics
|
import GHC.Generics
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Util
|
||||||
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqScheme (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..))
|
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqScheme (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..))
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, sumTypeJSON)
|
||||||
|
|
|
@ -35,6 +35,7 @@ import Simplex.Chat.Markdown
|
||||||
import Simplex.Chat.Messages.CIContent
|
import Simplex.Chat.Messages.CIContent
|
||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptStatus (..))
|
import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptStatus (..))
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON)
|
||||||
|
|
|
@ -29,6 +29,8 @@ import Database.SQLite.Simple.ToField (ToField (..))
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
|
import Simplex.Chat.Types.Util
|
||||||
import Simplex.Messaging.Agent.Protocol (MsgErrorType (..), RatchetSyncState (..), SwitchPhase (..))
|
import Simplex.Messaging.Agent.Protocol (MsgErrorType (..), RatchetSyncState (..), SwitchPhase (..))
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON)
|
||||||
|
|
|
@ -41,6 +41,7 @@ import Database.SQLite.Simple.ToField (ToField (..))
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Simplex.Chat.Call
|
import Simplex.Chat.Call
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Util
|
||||||
import Simplex.Messaging.Encoding
|
import Simplex.Messaging.Encoding
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import Simplex.Chat.Store.Groups
|
||||||
import Simplex.Chat.Store.Shared
|
import Simplex.Chat.Store.Shared
|
||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (firstRow, firstRow')
|
import Simplex.Messaging.Agent.Store.SQLite (firstRow, firstRow')
|
||||||
|
|
||||||
getConnectionEntity :: DB.Connection -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity
|
getConnectionEntity :: DB.Connection -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity
|
||||||
|
|
|
@ -69,6 +69,7 @@ import qualified Database.SQLite.Simple as DB
|
||||||
import Database.SQLite.Simple.QQ (sql)
|
import Database.SQLite.Simple.QQ (sql)
|
||||||
import Simplex.Chat.Store.Shared
|
import Simplex.Chat.Store.Shared
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId)
|
import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId)
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,7 @@ import Simplex.Chat.Messages
|
||||||
import Simplex.Chat.Store.Direct
|
import Simplex.Chat.Store.Direct
|
||||||
import Simplex.Chat.Store.Shared
|
import Simplex.Chat.Store.Shared
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent.Protocol (ConnId, UserId)
|
import Simplex.Messaging.Agent.Protocol (ConnId, UserId)
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
|
|
|
@ -74,6 +74,7 @@ import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Store.Direct
|
import Simplex.Chat.Store.Direct
|
||||||
import Simplex.Chat.Store.Shared
|
import Simplex.Chat.Store.Shared
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId)
|
import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId)
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
|
|
|
@ -31,6 +31,7 @@ import GHC.Generics (Generic)
|
||||||
import Simplex.Chat.Messages
|
import Simplex.Chat.Messages
|
||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId)
|
import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId)
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
||||||
|
|
|
@ -13,9 +13,7 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE PatternSynonyms #-}
|
{-# LANGUAGE PatternSynonyms #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
{-# LANGUAGE StandaloneDeriving #-}
|
|
||||||
{-# LANGUAGE StrictData #-}
|
{-# LANGUAGE StrictData #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
|
||||||
{-# LANGUAGE TypeFamilyDependencies #-}
|
{-# LANGUAGE TypeFamilyDependencies #-}
|
||||||
{-# LANGUAGE UndecidableInstances #-}
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
||||||
|
@ -24,7 +22,6 @@
|
||||||
|
|
||||||
module Simplex.Chat.Types where
|
module Simplex.Chat.Types where
|
||||||
|
|
||||||
import Control.Applicative ((<|>))
|
|
||||||
import Crypto.Number.Serialize (os2ip)
|
import Crypto.Number.Serialize (os2ip)
|
||||||
import Data.Aeson (FromJSON (..), ToJSON (..))
|
import Data.Aeson (FromJSON (..), ToJSON (..))
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
|
@ -33,27 +30,22 @@ import qualified Data.Aeson.Types as JT
|
||||||
import qualified Data.Attoparsec.ByteString.Char8 as A
|
import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||||
import Data.ByteString.Char8 (ByteString, pack, unpack)
|
import Data.ByteString.Char8 (ByteString, pack, unpack)
|
||||||
import qualified Data.ByteString.Char8 as B
|
import qualified Data.ByteString.Char8 as B
|
||||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
|
||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
import Data.Maybe (fromMaybe, isJust)
|
import Data.Maybe (isJust)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Text.Encoding (encodeUtf8)
|
|
||||||
import Data.Time.Clock (UTCTime)
|
import Data.Time.Clock (UTCTime)
|
||||||
import Data.Typeable
|
import Database.SQLite.Simple.FromField (FromField (..))
|
||||||
import Database.SQLite.Simple (ResultError (..), SQLData (..))
|
|
||||||
import Database.SQLite.Simple.FromField (FieldParser, FromField (..), returnError)
|
|
||||||
import Database.SQLite.Simple.Internal (Field (..))
|
|
||||||
import Database.SQLite.Simple.Ok (Ok (Ok))
|
|
||||||
import Database.SQLite.Simple.ToField (ToField (..))
|
import Database.SQLite.Simple.ToField (ToField (..))
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import GHC.Records.Compat
|
import Simplex.Chat.Types.Preferences
|
||||||
|
import Simplex.Chat.Types.Util
|
||||||
import Simplex.FileTransfer.Description (FileDigest)
|
import Simplex.FileTransfer.Description (FileDigest)
|
||||||
import Simplex.Messaging.Agent.Protocol (ACommandTag (..), ACorrId, AParty (..), APartyCmdTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId)
|
import Simplex.Messaging.Agent.Protocol (ACommandTag (..), ACorrId, AParty (..), APartyCmdTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId)
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, sumTypeJSON, taggedObjectJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, sumTypeJSON, taggedObjectJSON)
|
||||||
import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI)
|
import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI)
|
||||||
import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>))
|
import Simplex.Messaging.Util ((<$?>))
|
||||||
|
|
||||||
class IsContact a where
|
class IsContact a where
|
||||||
contactId' :: a -> ContactId
|
contactId' :: a -> ContactId
|
||||||
|
@ -353,659 +345,14 @@ defaultChatSettings = ChatSettings
|
||||||
pattern DisableNtfs :: ChatSettings
|
pattern DisableNtfs :: ChatSettings
|
||||||
pattern DisableNtfs <- ChatSettings {enableNtfs = False}
|
pattern DisableNtfs <- ChatSettings {enableNtfs = False}
|
||||||
|
|
||||||
data ChatFeature
|
|
||||||
= CFTimedMessages
|
|
||||||
| CFFullDelete
|
|
||||||
| CFReactions
|
|
||||||
| CFVoice
|
|
||||||
| CFCalls
|
|
||||||
deriving (Show, Generic)
|
|
||||||
|
|
||||||
data SChatFeature (f :: ChatFeature) where
|
|
||||||
SCFTimedMessages :: SChatFeature 'CFTimedMessages
|
|
||||||
SCFFullDelete :: SChatFeature 'CFFullDelete
|
|
||||||
SCFReactions :: SChatFeature 'CFReactions
|
|
||||||
SCFVoice :: SChatFeature 'CFVoice
|
|
||||||
SCFCalls :: SChatFeature 'CFCalls
|
|
||||||
|
|
||||||
deriving instance Show (SChatFeature f)
|
|
||||||
|
|
||||||
data AChatFeature = forall f. FeatureI f => ACF (SChatFeature f)
|
|
||||||
|
|
||||||
deriving instance Show AChatFeature
|
|
||||||
|
|
||||||
chatFeatureNameText :: ChatFeature -> Text
|
|
||||||
chatFeatureNameText = \case
|
|
||||||
CFTimedMessages -> "Disappearing messages"
|
|
||||||
CFFullDelete -> "Full deletion"
|
|
||||||
CFReactions -> "Message reactions"
|
|
||||||
CFVoice -> "Voice messages"
|
|
||||||
CFCalls -> "Audio/video calls"
|
|
||||||
|
|
||||||
chatFeatureNameText' :: SChatFeature f -> Text
|
|
||||||
chatFeatureNameText' = chatFeatureNameText . chatFeature
|
|
||||||
|
|
||||||
featureAllowed :: SChatFeature f -> (PrefEnabled -> Bool) -> Contact -> Bool
|
featureAllowed :: SChatFeature f -> (PrefEnabled -> Bool) -> Contact -> Bool
|
||||||
featureAllowed feature forWhom Contact {mergedPreferences} =
|
featureAllowed feature forWhom Contact {mergedPreferences} =
|
||||||
let ContactUserPreference {enabled} = getContactUserPreference feature mergedPreferences
|
let ContactUserPreference {enabled} = getContactUserPreference feature mergedPreferences
|
||||||
in forWhom enabled
|
in forWhom enabled
|
||||||
|
|
||||||
instance ToJSON ChatFeature where
|
|
||||||
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CF"
|
|
||||||
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CF"
|
|
||||||
|
|
||||||
instance FromJSON ChatFeature where
|
|
||||||
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CF"
|
|
||||||
|
|
||||||
allChatFeatures :: [AChatFeature]
|
|
||||||
allChatFeatures =
|
|
||||||
[ ACF SCFTimedMessages,
|
|
||||||
ACF SCFFullDelete,
|
|
||||||
ACF SCFReactions,
|
|
||||||
ACF SCFVoice,
|
|
||||||
ACF SCFCalls
|
|
||||||
]
|
|
||||||
|
|
||||||
chatPrefSel :: SChatFeature f -> Preferences -> Maybe (FeaturePreference f)
|
|
||||||
chatPrefSel = \case
|
|
||||||
SCFTimedMessages -> timedMessages
|
|
||||||
SCFFullDelete -> fullDelete
|
|
||||||
SCFReactions -> reactions
|
|
||||||
SCFVoice -> voice
|
|
||||||
SCFCalls -> calls
|
|
||||||
|
|
||||||
chatFeature :: SChatFeature f -> ChatFeature
|
|
||||||
chatFeature = \case
|
|
||||||
SCFTimedMessages -> CFTimedMessages
|
|
||||||
SCFFullDelete -> CFFullDelete
|
|
||||||
SCFReactions -> CFReactions
|
|
||||||
SCFVoice -> CFVoice
|
|
||||||
SCFCalls -> CFCalls
|
|
||||||
|
|
||||||
class PreferenceI p where
|
|
||||||
getPreference :: SChatFeature f -> p -> FeaturePreference f
|
|
||||||
|
|
||||||
instance PreferenceI Preferences where
|
|
||||||
getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f prefs)
|
|
||||||
|
|
||||||
instance PreferenceI (Maybe Preferences) where
|
|
||||||
getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f =<< prefs)
|
|
||||||
|
|
||||||
instance PreferenceI FullPreferences where
|
|
||||||
getPreference = \case
|
|
||||||
SCFTimedMessages -> timedMessages
|
|
||||||
SCFFullDelete -> fullDelete
|
|
||||||
SCFReactions -> reactions
|
|
||||||
SCFVoice -> voice
|
|
||||||
SCFCalls -> calls
|
|
||||||
{-# INLINE getPreference #-}
|
|
||||||
|
|
||||||
setPreference :: forall f. FeatureI f => SChatFeature f -> Maybe FeatureAllowed -> Maybe Preferences -> Preferences
|
|
||||||
setPreference f allow_ prefs_ = setPreference_ f pref $ fromMaybe emptyChatPrefs prefs_
|
|
||||||
where
|
|
||||||
pref = setAllow <$> allow_
|
|
||||||
setAllow :: FeatureAllowed -> FeaturePreference f
|
|
||||||
setAllow = setField @"allow" (getPreference f prefs)
|
|
||||||
prefs = mergePreferences Nothing prefs_
|
|
||||||
|
|
||||||
setPreference' :: SChatFeature f -> Maybe (FeaturePreference f) -> Maybe Preferences -> Preferences
|
|
||||||
setPreference' f pref_ prefs_ = setPreference_ f pref_ $ fromMaybe emptyChatPrefs prefs_
|
|
||||||
|
|
||||||
setPreference_ :: SChatFeature f -> Maybe (FeaturePreference f) -> Preferences -> Preferences
|
|
||||||
setPreference_ f pref_ prefs =
|
|
||||||
case f of
|
|
||||||
SCFTimedMessages -> prefs {timedMessages = pref_}
|
|
||||||
SCFFullDelete -> prefs {fullDelete = pref_}
|
|
||||||
SCFReactions -> prefs {reactions = pref_}
|
|
||||||
SCFVoice -> prefs {voice = pref_}
|
|
||||||
SCFCalls -> prefs {calls = pref_}
|
|
||||||
|
|
||||||
-- collection of optional chat preferences for the user and the contact
|
|
||||||
data Preferences = Preferences
|
|
||||||
{ timedMessages :: Maybe TimedMessagesPreference,
|
|
||||||
fullDelete :: Maybe FullDeletePreference,
|
|
||||||
reactions :: Maybe ReactionsPreference,
|
|
||||||
voice :: Maybe VoicePreference,
|
|
||||||
calls :: Maybe CallsPreference
|
|
||||||
}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON Preferences where
|
|
||||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
|
||||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
|
||||||
|
|
||||||
instance ToField Preferences where
|
|
||||||
toField = toField . encodeJSON
|
|
||||||
|
|
||||||
instance FromField Preferences where
|
|
||||||
fromField = fromTextField_ decodeJSON
|
|
||||||
|
|
||||||
data GroupFeature
|
|
||||||
= GFTimedMessages
|
|
||||||
| GFDirectMessages
|
|
||||||
| GFFullDelete
|
|
||||||
| GFReactions
|
|
||||||
| GFVoice
|
|
||||||
| GFFiles
|
|
||||||
deriving (Show, Generic)
|
|
||||||
|
|
||||||
data SGroupFeature (f :: GroupFeature) where
|
|
||||||
SGFTimedMessages :: SGroupFeature 'GFTimedMessages
|
|
||||||
SGFDirectMessages :: SGroupFeature 'GFDirectMessages
|
|
||||||
SGFFullDelete :: SGroupFeature 'GFFullDelete
|
|
||||||
SGFReactions :: SGroupFeature 'GFReactions
|
|
||||||
SGFVoice :: SGroupFeature 'GFVoice
|
|
||||||
SGFFiles :: SGroupFeature 'GFFiles
|
|
||||||
|
|
||||||
deriving instance Show (SGroupFeature f)
|
|
||||||
|
|
||||||
data AGroupFeature = forall f. GroupFeatureI f => AGF (SGroupFeature f)
|
|
||||||
|
|
||||||
deriving instance Show AGroupFeature
|
|
||||||
|
|
||||||
groupFeatureNameText :: GroupFeature -> Text
|
|
||||||
groupFeatureNameText = \case
|
|
||||||
GFTimedMessages -> "Disappearing messages"
|
|
||||||
GFDirectMessages -> "Direct messages"
|
|
||||||
GFFullDelete -> "Full deletion"
|
|
||||||
GFReactions -> "Message reactions"
|
|
||||||
GFVoice -> "Voice messages"
|
|
||||||
GFFiles -> "Files and media"
|
|
||||||
|
|
||||||
groupFeatureNameText' :: SGroupFeature f -> Text
|
|
||||||
groupFeatureNameText' = groupFeatureNameText . toGroupFeature
|
|
||||||
|
|
||||||
groupFeatureAllowed :: GroupFeatureI f => SGroupFeature f -> GroupInfo -> Bool
|
groupFeatureAllowed :: GroupFeatureI f => SGroupFeature f -> GroupInfo -> Bool
|
||||||
groupFeatureAllowed feature gInfo = groupFeatureAllowed' feature $ fullGroupPreferences gInfo
|
groupFeatureAllowed feature gInfo = groupFeatureAllowed' feature $ fullGroupPreferences gInfo
|
||||||
|
|
||||||
groupFeatureAllowed' :: GroupFeatureI f => SGroupFeature f -> FullGroupPreferences -> Bool
|
|
||||||
groupFeatureAllowed' feature prefs =
|
|
||||||
getField @"enable" (getGroupPreference feature prefs) == FEOn
|
|
||||||
|
|
||||||
instance ToJSON GroupFeature where
|
|
||||||
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "GF"
|
|
||||||
toJSON = J.genericToJSON . enumJSON $ dropPrefix "GF"
|
|
||||||
|
|
||||||
instance FromJSON GroupFeature where
|
|
||||||
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "GF"
|
|
||||||
|
|
||||||
allGroupFeatures :: [AGroupFeature]
|
|
||||||
allGroupFeatures =
|
|
||||||
[ AGF SGFTimedMessages,
|
|
||||||
AGF SGFDirectMessages,
|
|
||||||
AGF SGFFullDelete,
|
|
||||||
AGF SGFReactions,
|
|
||||||
AGF SGFVoice,
|
|
||||||
AGF SGFFiles
|
|
||||||
]
|
|
||||||
|
|
||||||
groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f)
|
|
||||||
groupPrefSel = \case
|
|
||||||
SGFTimedMessages -> timedMessages
|
|
||||||
SGFDirectMessages -> directMessages
|
|
||||||
SGFFullDelete -> fullDelete
|
|
||||||
SGFReactions -> reactions
|
|
||||||
SGFVoice -> voice
|
|
||||||
SGFFiles -> files
|
|
||||||
|
|
||||||
toGroupFeature :: SGroupFeature f -> GroupFeature
|
|
||||||
toGroupFeature = \case
|
|
||||||
SGFTimedMessages -> GFTimedMessages
|
|
||||||
SGFDirectMessages -> GFDirectMessages
|
|
||||||
SGFFullDelete -> GFFullDelete
|
|
||||||
SGFReactions -> GFReactions
|
|
||||||
SGFVoice -> GFVoice
|
|
||||||
SGFFiles -> GFFiles
|
|
||||||
|
|
||||||
class GroupPreferenceI p where
|
|
||||||
getGroupPreference :: SGroupFeature f -> p -> GroupFeaturePreference f
|
|
||||||
|
|
||||||
instance GroupPreferenceI GroupPreferences where
|
|
||||||
getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt prefs)
|
|
||||||
|
|
||||||
instance GroupPreferenceI (Maybe GroupPreferences) where
|
|
||||||
getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs)
|
|
||||||
|
|
||||||
instance GroupPreferenceI FullGroupPreferences where
|
|
||||||
getGroupPreference = \case
|
|
||||||
SGFTimedMessages -> timedMessages
|
|
||||||
SGFDirectMessages -> directMessages
|
|
||||||
SGFFullDelete -> fullDelete
|
|
||||||
SGFReactions -> reactions
|
|
||||||
SGFVoice -> voice
|
|
||||||
SGFFiles -> files
|
|
||||||
{-# INLINE getGroupPreference #-}
|
|
||||||
|
|
||||||
-- collection of optional group preferences
|
|
||||||
data GroupPreferences = GroupPreferences
|
|
||||||
{ timedMessages :: Maybe TimedMessagesGroupPreference,
|
|
||||||
directMessages :: Maybe DirectMessagesGroupPreference,
|
|
||||||
fullDelete :: Maybe FullDeleteGroupPreference,
|
|
||||||
reactions :: Maybe ReactionsGroupPreference,
|
|
||||||
voice :: Maybe VoiceGroupPreference,
|
|
||||||
files :: Maybe FilesGroupPreference
|
|
||||||
}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON GroupPreferences where
|
|
||||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
|
||||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
|
||||||
|
|
||||||
instance ToField GroupPreferences where
|
|
||||||
toField = toField . encodeJSON
|
|
||||||
|
|
||||||
instance FromField GroupPreferences where
|
|
||||||
fromField = fromTextField_ decodeJSON
|
|
||||||
|
|
||||||
setGroupPreference :: forall f. GroupFeatureI f => SGroupFeature f -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences
|
|
||||||
setGroupPreference f enable prefs_ = setGroupPreference_ f pref prefs
|
|
||||||
where
|
|
||||||
prefs = mergeGroupPreferences prefs_
|
|
||||||
pref :: GroupFeaturePreference f
|
|
||||||
pref = setField @"enable" (getGroupPreference f prefs) enable
|
|
||||||
|
|
||||||
setGroupPreference' :: SGroupFeature f -> GroupFeaturePreference f -> Maybe GroupPreferences -> GroupPreferences
|
|
||||||
setGroupPreference' f pref prefs_ = setGroupPreference_ f pref prefs
|
|
||||||
where
|
|
||||||
prefs = mergeGroupPreferences prefs_
|
|
||||||
|
|
||||||
setGroupPreference_ :: SGroupFeature f -> GroupFeaturePreference f -> FullGroupPreferences -> GroupPreferences
|
|
||||||
setGroupPreference_ f pref prefs =
|
|
||||||
toGroupPreferences $ case f of
|
|
||||||
SGFTimedMessages -> prefs {timedMessages = pref}
|
|
||||||
SGFDirectMessages -> prefs {directMessages = pref}
|
|
||||||
SGFFullDelete -> prefs {fullDelete = pref}
|
|
||||||
SGFReactions -> prefs {reactions = pref}
|
|
||||||
SGFVoice -> prefs {voice = pref}
|
|
||||||
SGFFiles -> prefs {files = pref}
|
|
||||||
|
|
||||||
setGroupTimedMessagesPreference :: TimedMessagesGroupPreference -> Maybe GroupPreferences -> GroupPreferences
|
|
||||||
setGroupTimedMessagesPreference pref prefs_ =
|
|
||||||
toGroupPreferences $ prefs {timedMessages = pref}
|
|
||||||
where
|
|
||||||
prefs = mergeGroupPreferences prefs_
|
|
||||||
|
|
||||||
-- full collection of chat preferences defined in the app - it is used to ensure we include all preferences and to simplify processing
|
|
||||||
-- if some of the preferences are not defined in Preferences, defaults from defaultChatPrefs are used here.
|
|
||||||
data FullPreferences = FullPreferences
|
|
||||||
{ timedMessages :: TimedMessagesPreference,
|
|
||||||
fullDelete :: FullDeletePreference,
|
|
||||||
reactions :: ReactionsPreference,
|
|
||||||
voice :: VoicePreference,
|
|
||||||
calls :: CallsPreference
|
|
||||||
}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON FullPreferences where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
-- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing
|
|
||||||
-- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here.
|
|
||||||
data FullGroupPreferences = FullGroupPreferences
|
|
||||||
{ timedMessages :: TimedMessagesGroupPreference,
|
|
||||||
directMessages :: DirectMessagesGroupPreference,
|
|
||||||
fullDelete :: FullDeleteGroupPreference,
|
|
||||||
reactions :: ReactionsGroupPreference,
|
|
||||||
voice :: VoiceGroupPreference,
|
|
||||||
files :: FilesGroupPreference
|
|
||||||
}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON FullGroupPreferences where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
-- merged preferences of user for a given contact - they differentiate between specific preferences for the contact and global user preferences
|
|
||||||
data ContactUserPreferences = ContactUserPreferences
|
|
||||||
{ timedMessages :: ContactUserPreference TimedMessagesPreference,
|
|
||||||
fullDelete :: ContactUserPreference FullDeletePreference,
|
|
||||||
reactions :: ContactUserPreference ReactionsPreference,
|
|
||||||
voice :: ContactUserPreference VoicePreference,
|
|
||||||
calls :: ContactUserPreference CallsPreference
|
|
||||||
}
|
|
||||||
deriving (Eq, Show, Generic)
|
|
||||||
|
|
||||||
data ContactUserPreference p = ContactUserPreference
|
|
||||||
{ enabled :: PrefEnabled,
|
|
||||||
userPreference :: ContactUserPref p,
|
|
||||||
contactPreference :: p
|
|
||||||
}
|
|
||||||
deriving (Eq, Show, Generic)
|
|
||||||
|
|
||||||
data ContactUserPref p = CUPContact {preference :: p} | CUPUser {preference :: p}
|
|
||||||
deriving (Eq, Show, Generic)
|
|
||||||
|
|
||||||
instance ToJSON ContactUserPreferences where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
instance ToJSON p => ToJSON (ContactUserPreference p) where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
instance ToJSON p => ToJSON (ContactUserPref p) where
|
|
||||||
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CUP"
|
|
||||||
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CUP"
|
|
||||||
|
|
||||||
toChatPrefs :: FullPreferences -> Preferences
|
|
||||||
toChatPrefs FullPreferences {timedMessages, fullDelete, reactions, voice, calls} =
|
|
||||||
Preferences
|
|
||||||
{ timedMessages = Just timedMessages,
|
|
||||||
fullDelete = Just fullDelete,
|
|
||||||
reactions = Just reactions,
|
|
||||||
voice = Just voice,
|
|
||||||
calls = Just calls
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultChatPrefs :: FullPreferences
|
|
||||||
defaultChatPrefs =
|
|
||||||
FullPreferences
|
|
||||||
{ timedMessages = TimedMessagesPreference {allow = FAYes, ttl = Nothing},
|
|
||||||
fullDelete = FullDeletePreference {allow = FANo},
|
|
||||||
reactions = ReactionsPreference {allow = FAYes},
|
|
||||||
voice = VoicePreference {allow = FAYes},
|
|
||||||
calls = CallsPreference {allow = FAYes}
|
|
||||||
}
|
|
||||||
|
|
||||||
emptyChatPrefs :: Preferences
|
|
||||||
emptyChatPrefs = Preferences Nothing Nothing Nothing Nothing Nothing
|
|
||||||
|
|
||||||
defaultGroupPrefs :: FullGroupPreferences
|
|
||||||
defaultGroupPrefs =
|
|
||||||
FullGroupPreferences
|
|
||||||
{ timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400},
|
|
||||||
directMessages = DirectMessagesGroupPreference {enable = FEOff},
|
|
||||||
fullDelete = FullDeleteGroupPreference {enable = FEOff},
|
|
||||||
reactions = ReactionsGroupPreference {enable = FEOn},
|
|
||||||
voice = VoiceGroupPreference {enable = FEOn},
|
|
||||||
files = FilesGroupPreference {enable = FEOn}
|
|
||||||
}
|
|
||||||
|
|
||||||
emptyGroupPrefs :: GroupPreferences
|
|
||||||
emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing
|
|
||||||
|
|
||||||
data TimedMessagesPreference = TimedMessagesPreference
|
|
||||||
{ allow :: FeatureAllowed,
|
|
||||||
ttl :: Maybe Int
|
|
||||||
}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON TimedMessagesPreference where
|
|
||||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
|
||||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
|
||||||
|
|
||||||
data FullDeletePreference = FullDeletePreference {allow :: FeatureAllowed}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON FullDeletePreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
data ReactionsPreference = ReactionsPreference {allow :: FeatureAllowed}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON ReactionsPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
data VoicePreference = VoicePreference {allow :: FeatureAllowed}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON VoicePreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
data CallsPreference = CallsPreference {allow :: FeatureAllowed}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON CallsPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
class (Eq (FeaturePreference f), HasField "allow" (FeaturePreference f) FeatureAllowed) => FeatureI f where
|
|
||||||
type FeaturePreference (f :: ChatFeature) = p | p -> f
|
|
||||||
sFeature :: SChatFeature f
|
|
||||||
prefParam :: FeaturePreference f -> Maybe Int
|
|
||||||
|
|
||||||
instance HasField "allow" TimedMessagesPreference FeatureAllowed where
|
|
||||||
hasField p = (\allow -> p {allow}, allow (p :: TimedMessagesPreference))
|
|
||||||
|
|
||||||
instance HasField "allow" FullDeletePreference FeatureAllowed where
|
|
||||||
hasField p = (\allow -> p {allow}, allow (p :: FullDeletePreference))
|
|
||||||
|
|
||||||
instance HasField "allow" ReactionsPreference FeatureAllowed where
|
|
||||||
hasField p = (\allow -> p {allow}, allow (p :: ReactionsPreference))
|
|
||||||
|
|
||||||
instance HasField "allow" VoicePreference FeatureAllowed where
|
|
||||||
hasField p = (\allow -> p {allow}, allow (p :: VoicePreference))
|
|
||||||
|
|
||||||
instance HasField "allow" CallsPreference FeatureAllowed where
|
|
||||||
hasField p = (\allow -> p {allow}, allow (p :: CallsPreference))
|
|
||||||
|
|
||||||
instance FeatureI 'CFTimedMessages where
|
|
||||||
type FeaturePreference 'CFTimedMessages = TimedMessagesPreference
|
|
||||||
sFeature = SCFTimedMessages
|
|
||||||
prefParam TimedMessagesPreference {ttl} = ttl
|
|
||||||
|
|
||||||
instance FeatureI 'CFFullDelete where
|
|
||||||
type FeaturePreference 'CFFullDelete = FullDeletePreference
|
|
||||||
sFeature = SCFFullDelete
|
|
||||||
prefParam _ = Nothing
|
|
||||||
|
|
||||||
instance FeatureI 'CFReactions where
|
|
||||||
type FeaturePreference 'CFReactions = ReactionsPreference
|
|
||||||
sFeature = SCFReactions
|
|
||||||
prefParam _ = Nothing
|
|
||||||
|
|
||||||
instance FeatureI 'CFVoice where
|
|
||||||
type FeaturePreference 'CFVoice = VoicePreference
|
|
||||||
sFeature = SCFVoice
|
|
||||||
prefParam _ = Nothing
|
|
||||||
|
|
||||||
instance FeatureI 'CFCalls where
|
|
||||||
type FeaturePreference 'CFCalls = CallsPreference
|
|
||||||
sFeature = SCFCalls
|
|
||||||
prefParam _ = Nothing
|
|
||||||
|
|
||||||
data GroupPreference = GroupPreference
|
|
||||||
{enable :: GroupFeatureEnabled}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
data TimedMessagesGroupPreference = TimedMessagesGroupPreference
|
|
||||||
{ enable :: GroupFeatureEnabled,
|
|
||||||
ttl :: Maybe Int
|
|
||||||
}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
data DirectMessagesGroupPreference = DirectMessagesGroupPreference
|
|
||||||
{enable :: GroupFeatureEnabled}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
data FullDeleteGroupPreference = FullDeleteGroupPreference
|
|
||||||
{enable :: GroupFeatureEnabled}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
data ReactionsGroupPreference = ReactionsGroupPreference
|
|
||||||
{enable :: GroupFeatureEnabled}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
data VoiceGroupPreference = VoiceGroupPreference
|
|
||||||
{enable :: GroupFeatureEnabled}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
data FilesGroupPreference = FilesGroupPreference
|
|
||||||
{enable :: GroupFeatureEnabled}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON GroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
instance ToJSON TimedMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
instance ToJSON DirectMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
instance ToJSON ReactionsGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
instance ToJSON FullDeleteGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
instance ToJSON VoiceGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
instance ToJSON FilesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
class (Eq (GroupFeaturePreference f), HasField "enable" (GroupFeaturePreference f) GroupFeatureEnabled) => GroupFeatureI f where
|
|
||||||
type GroupFeaturePreference (f :: GroupFeature) = p | p -> f
|
|
||||||
sGroupFeature :: SGroupFeature f
|
|
||||||
groupPrefParam :: GroupFeaturePreference f -> Maybe Int
|
|
||||||
|
|
||||||
instance HasField "enable" GroupPreference GroupFeatureEnabled where
|
|
||||||
hasField p = (\enable -> p {enable}, enable (p :: GroupPreference))
|
|
||||||
|
|
||||||
instance HasField "enable" TimedMessagesGroupPreference GroupFeatureEnabled where
|
|
||||||
hasField p = (\enable -> p {enable}, enable (p :: TimedMessagesGroupPreference))
|
|
||||||
|
|
||||||
instance HasField "enable" DirectMessagesGroupPreference GroupFeatureEnabled where
|
|
||||||
hasField p = (\enable -> p {enable}, enable (p :: DirectMessagesGroupPreference))
|
|
||||||
|
|
||||||
instance HasField "enable" ReactionsGroupPreference GroupFeatureEnabled where
|
|
||||||
hasField p = (\enable -> p {enable}, enable (p :: ReactionsGroupPreference))
|
|
||||||
|
|
||||||
instance HasField "enable" FullDeleteGroupPreference GroupFeatureEnabled where
|
|
||||||
hasField p = (\enable -> p {enable}, enable (p :: FullDeleteGroupPreference))
|
|
||||||
|
|
||||||
instance HasField "enable" VoiceGroupPreference GroupFeatureEnabled where
|
|
||||||
hasField p = (\enable -> p {enable}, enable (p :: VoiceGroupPreference))
|
|
||||||
|
|
||||||
instance HasField "enable" FilesGroupPreference GroupFeatureEnabled where
|
|
||||||
hasField p = (\enable -> p {enable}, enable (p :: FilesGroupPreference))
|
|
||||||
|
|
||||||
instance GroupFeatureI 'GFTimedMessages where
|
|
||||||
type GroupFeaturePreference 'GFTimedMessages = TimedMessagesGroupPreference
|
|
||||||
sGroupFeature = SGFTimedMessages
|
|
||||||
groupPrefParam TimedMessagesGroupPreference {ttl} = ttl
|
|
||||||
|
|
||||||
instance GroupFeatureI 'GFDirectMessages where
|
|
||||||
type GroupFeaturePreference 'GFDirectMessages = DirectMessagesGroupPreference
|
|
||||||
sGroupFeature = SGFDirectMessages
|
|
||||||
groupPrefParam _ = Nothing
|
|
||||||
|
|
||||||
instance GroupFeatureI 'GFFullDelete where
|
|
||||||
type GroupFeaturePreference 'GFFullDelete = FullDeleteGroupPreference
|
|
||||||
sGroupFeature = SGFFullDelete
|
|
||||||
groupPrefParam _ = Nothing
|
|
||||||
|
|
||||||
instance GroupFeatureI 'GFReactions where
|
|
||||||
type GroupFeaturePreference 'GFReactions = ReactionsGroupPreference
|
|
||||||
sGroupFeature = SGFReactions
|
|
||||||
groupPrefParam _ = Nothing
|
|
||||||
|
|
||||||
instance GroupFeatureI 'GFVoice where
|
|
||||||
type GroupFeaturePreference 'GFVoice = VoiceGroupPreference
|
|
||||||
sGroupFeature = SGFVoice
|
|
||||||
groupPrefParam _ = Nothing
|
|
||||||
|
|
||||||
instance GroupFeatureI 'GFFiles where
|
|
||||||
type GroupFeaturePreference 'GFFiles = FilesGroupPreference
|
|
||||||
sGroupFeature = SGFFiles
|
|
||||||
groupPrefParam _ = Nothing
|
|
||||||
|
|
||||||
groupPrefStateText :: HasField "enable" p GroupFeatureEnabled => GroupFeature -> p -> Maybe Int -> Text
|
|
||||||
groupPrefStateText feature pref param =
|
|
||||||
let enabled = getField @"enable" pref
|
|
||||||
paramText = if enabled == FEOn then groupParamText_ feature param else ""
|
|
||||||
in groupFeatureNameText feature <> ": " <> safeDecodeUtf8 (strEncode enabled) <> paramText
|
|
||||||
|
|
||||||
groupParamText_ :: GroupFeature -> Maybe Int -> Text
|
|
||||||
groupParamText_ feature param = case feature of
|
|
||||||
GFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param
|
|
||||||
_ -> ""
|
|
||||||
|
|
||||||
groupPreferenceText :: forall f. GroupFeatureI f => GroupFeaturePreference f -> Text
|
|
||||||
groupPreferenceText pref =
|
|
||||||
let feature = toGroupFeature $ sGroupFeature @f
|
|
||||||
in groupPrefStateText feature pref $ groupPrefParam pref
|
|
||||||
|
|
||||||
timedTTLText :: Int -> Text
|
|
||||||
timedTTLText 0 = "0 sec"
|
|
||||||
timedTTLText ttl = do
|
|
||||||
let (m', s) = ttl `quotRem` 60
|
|
||||||
(h', m) = m' `quotRem` 60
|
|
||||||
(d', h) = h' `quotRem` 24
|
|
||||||
(mm, d) = d' `quotRem` 30
|
|
||||||
T.pack . unwords $
|
|
||||||
[mms mm | mm /= 0] <> [ds d | d /= 0] <> [hs h | h /= 0] <> [ms m | m /= 0] <> [ss s | s /= 0]
|
|
||||||
where
|
|
||||||
ss s = show s <> " sec"
|
|
||||||
ms m = show m <> " min"
|
|
||||||
hs 1 = "1 hour"
|
|
||||||
hs h = show h <> " hours"
|
|
||||||
ds 1 = "1 day"
|
|
||||||
ds 7 = "1 week"
|
|
||||||
ds 14 = "2 weeks"
|
|
||||||
ds d = show d <> " days"
|
|
||||||
mms 1 = "1 month"
|
|
||||||
mms mm = show mm <> " months"
|
|
||||||
|
|
||||||
toGroupPreference :: GroupFeatureI f => GroupFeaturePreference f -> GroupPreference
|
|
||||||
toGroupPreference p = GroupPreference {enable = getField @"enable" p}
|
|
||||||
|
|
||||||
data FeatureAllowed
|
|
||||||
= FAAlways -- allow unconditionally
|
|
||||||
| FAYes -- allow, if peer allows it
|
|
||||||
| FANo -- do not allow
|
|
||||||
deriving (Eq, Show, Generic)
|
|
||||||
|
|
||||||
instance FromField FeatureAllowed where fromField = fromBlobField_ strDecode
|
|
||||||
|
|
||||||
instance ToField FeatureAllowed where toField = toField . strEncode
|
|
||||||
|
|
||||||
instance StrEncoding FeatureAllowed where
|
|
||||||
strEncode = \case
|
|
||||||
FAAlways -> "always"
|
|
||||||
FAYes -> "yes"
|
|
||||||
FANo -> "no"
|
|
||||||
strDecode = \case
|
|
||||||
"always" -> Right FAAlways
|
|
||||||
"yes" -> Right FAYes
|
|
||||||
"no" -> Right FANo
|
|
||||||
r -> Left $ "bad FeatureAllowed " <> B.unpack r
|
|
||||||
strP = strDecode <$?> A.takeByteString
|
|
||||||
|
|
||||||
instance FromJSON FeatureAllowed where
|
|
||||||
parseJSON = strParseJSON "FeatureAllowed"
|
|
||||||
|
|
||||||
instance ToJSON FeatureAllowed where
|
|
||||||
toJSON = strToJSON
|
|
||||||
toEncoding = strToJEncoding
|
|
||||||
|
|
||||||
data GroupFeatureEnabled = FEOn | FEOff
|
|
||||||
deriving (Eq, Show, Generic)
|
|
||||||
|
|
||||||
instance FromField GroupFeatureEnabled where fromField = fromBlobField_ strDecode
|
|
||||||
|
|
||||||
instance ToField GroupFeatureEnabled where toField = toField . strEncode
|
|
||||||
|
|
||||||
instance StrEncoding GroupFeatureEnabled where
|
|
||||||
strEncode = \case
|
|
||||||
FEOn -> "on"
|
|
||||||
FEOff -> "off"
|
|
||||||
strDecode = \case
|
|
||||||
"on" -> Right FEOn
|
|
||||||
"off" -> Right FEOff
|
|
||||||
r -> Left $ "bad GroupFeatureEnabled " <> B.unpack r
|
|
||||||
strP = strDecode <$?> A.takeByteString
|
|
||||||
|
|
||||||
instance FromJSON GroupFeatureEnabled where
|
|
||||||
parseJSON = strParseJSON "GroupFeatureEnabled"
|
|
||||||
|
|
||||||
instance ToJSON GroupFeatureEnabled where
|
|
||||||
toJSON = strToJSON
|
|
||||||
toEncoding = strToJEncoding
|
|
||||||
|
|
||||||
groupFeatureState :: GroupFeatureI f => GroupFeaturePreference f -> (GroupFeatureEnabled, Maybe Int)
|
|
||||||
groupFeatureState p =
|
|
||||||
let enable = getField @"enable" p
|
|
||||||
param = if enable == FEOn then groupPrefParam p else Nothing
|
|
||||||
in (enable, param)
|
|
||||||
|
|
||||||
mergePreferences :: Maybe Preferences -> Maybe Preferences -> FullPreferences
|
|
||||||
mergePreferences contactPrefs userPreferences =
|
|
||||||
FullPreferences
|
|
||||||
{ timedMessages = pref SCFTimedMessages,
|
|
||||||
fullDelete = pref SCFFullDelete,
|
|
||||||
reactions = pref SCFReactions,
|
|
||||||
voice = pref SCFVoice,
|
|
||||||
calls = pref SCFCalls
|
|
||||||
}
|
|
||||||
where
|
|
||||||
pref :: SChatFeature f -> FeaturePreference f
|
|
||||||
pref f =
|
|
||||||
let sel = chatPrefSel f
|
|
||||||
in fromMaybe (getPreference f defaultChatPrefs) $ (contactPrefs >>= sel) <|> (userPreferences >>= sel)
|
|
||||||
|
|
||||||
mergeUserChatPrefs :: User -> Contact -> FullPreferences
|
mergeUserChatPrefs :: User -> Contact -> FullPreferences
|
||||||
mergeUserChatPrefs user ct = mergeUserChatPrefs' user (contactConnIncognito ct) (userPreferences ct)
|
mergeUserChatPrefs user ct = mergeUserChatPrefs' user (contactConnIncognito ct) (userPreferences ct)
|
||||||
|
|
||||||
|
@ -1014,94 +361,6 @@ mergeUserChatPrefs' user connectedIncognito userPreferences =
|
||||||
let userPrefs = if connectedIncognito then Nothing else preferences' user
|
let userPrefs = if connectedIncognito then Nothing else preferences' user
|
||||||
in mergePreferences (Just userPreferences) userPrefs
|
in mergePreferences (Just userPreferences) userPrefs
|
||||||
|
|
||||||
mergeGroupPreferences :: Maybe GroupPreferences -> FullGroupPreferences
|
|
||||||
mergeGroupPreferences groupPreferences =
|
|
||||||
FullGroupPreferences
|
|
||||||
{ timedMessages = pref SGFTimedMessages,
|
|
||||||
directMessages = pref SGFDirectMessages,
|
|
||||||
fullDelete = pref SGFFullDelete,
|
|
||||||
reactions = pref SGFReactions,
|
|
||||||
voice = pref SGFVoice,
|
|
||||||
files = pref SGFFiles
|
|
||||||
}
|
|
||||||
where
|
|
||||||
pref :: SGroupFeature f -> GroupFeaturePreference f
|
|
||||||
pref pt = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPreferences >>= groupPrefSel pt)
|
|
||||||
|
|
||||||
toGroupPreferences :: FullGroupPreferences -> GroupPreferences
|
|
||||||
toGroupPreferences groupPreferences =
|
|
||||||
GroupPreferences
|
|
||||||
{ timedMessages = pref SGFTimedMessages,
|
|
||||||
directMessages = pref SGFDirectMessages,
|
|
||||||
fullDelete = pref SGFFullDelete,
|
|
||||||
reactions = pref SGFReactions,
|
|
||||||
voice = pref SGFVoice,
|
|
||||||
files = pref SGFFiles
|
|
||||||
}
|
|
||||||
where
|
|
||||||
pref :: SGroupFeature f -> Maybe (GroupFeaturePreference f)
|
|
||||||
pref f = Just $ getGroupPreference f groupPreferences
|
|
||||||
|
|
||||||
data PrefEnabled = PrefEnabled {forUser :: Bool, forContact :: Bool}
|
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
|
||||||
|
|
||||||
instance ToJSON PrefEnabled where
|
|
||||||
toJSON = J.genericToJSON J.defaultOptions
|
|
||||||
toEncoding = J.genericToEncoding J.defaultOptions
|
|
||||||
|
|
||||||
prefEnabled :: FeatureI f => Bool -> FeaturePreference f -> FeaturePreference f -> PrefEnabled
|
|
||||||
prefEnabled asymmetric user contact = case (getField @"allow" user, getField @"allow" contact) of
|
|
||||||
(FAAlways, FANo) -> PrefEnabled {forUser = False, forContact = asymmetric}
|
|
||||||
(FANo, FAAlways) -> PrefEnabled {forUser = asymmetric, forContact = False}
|
|
||||||
(_, FANo) -> PrefEnabled False False
|
|
||||||
(FANo, _) -> PrefEnabled False False
|
|
||||||
_ -> PrefEnabled True True
|
|
||||||
|
|
||||||
prefStateText :: ChatFeature -> FeatureAllowed -> Maybe Int -> Text
|
|
||||||
prefStateText feature allowed param = case allowed of
|
|
||||||
FANo -> "cancelled " <> chatFeatureNameText feature
|
|
||||||
_ -> "offered " <> chatFeatureNameText feature <> paramText_ feature param
|
|
||||||
|
|
||||||
featureStateText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text
|
|
||||||
featureStateText feature enabled param =
|
|
||||||
chatFeatureNameText feature <> ": " <> prefEnabledToText feature enabled param <> case enabled of
|
|
||||||
PrefEnabled {forUser = True} -> paramText_ feature param
|
|
||||||
_ -> ""
|
|
||||||
|
|
||||||
paramText_ :: ChatFeature -> Maybe Int -> Text
|
|
||||||
paramText_ feature param = case feature of
|
|
||||||
CFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param
|
|
||||||
_ -> ""
|
|
||||||
|
|
||||||
prefEnabledToText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text
|
|
||||||
prefEnabledToText f enabled param = case enabled of
|
|
||||||
PrefEnabled True True -> enabledStr
|
|
||||||
PrefEnabled False False -> "off"
|
|
||||||
PrefEnabled {forUser = True, forContact = False} -> enabledStr <> " for you"
|
|
||||||
PrefEnabled {forUser = False, forContact = True} -> enabledStr <> " for contact"
|
|
||||||
where
|
|
||||||
enabledStr = case f of
|
|
||||||
CFTimedMessages -> if isJust param then "enabled" else "allowed"
|
|
||||||
_ -> "enabled"
|
|
||||||
|
|
||||||
preferenceText :: forall f. FeatureI f => FeaturePreference f -> Text
|
|
||||||
preferenceText p =
|
|
||||||
let feature = chatFeature $ sFeature @f
|
|
||||||
allowed = getField @"allow" p
|
|
||||||
paramText = if allowed == FAAlways || allowed == FAYes then paramText_ feature (prefParam p) else ""
|
|
||||||
in safeDecodeUtf8 (strEncode allowed) <> paramText
|
|
||||||
|
|
||||||
featureState :: FeatureI f => ContactUserPreference (FeaturePreference f) -> (PrefEnabled, Maybe Int)
|
|
||||||
featureState ContactUserPreference {enabled, userPreference} =
|
|
||||||
let param = if forUser enabled then prefParam $ preference userPreference else Nothing
|
|
||||||
in (enabled, param)
|
|
||||||
|
|
||||||
preferenceState :: FeatureI f => FeaturePreference f -> (FeatureAllowed, Maybe Int)
|
|
||||||
preferenceState pref =
|
|
||||||
let allow = getField @"allow" pref
|
|
||||||
param = if allow == FAAlways || allow == FAYes then prefParam pref else Nothing
|
|
||||||
in (allow, param)
|
|
||||||
|
|
||||||
updateMergedPreferences :: User -> Contact -> Contact
|
updateMergedPreferences :: User -> Contact -> Contact
|
||||||
updateMergedPreferences user ct =
|
updateMergedPreferences user ct =
|
||||||
let mergedPreferences = contactUserPreferences user (userPreferences ct) (preferences' ct) (contactConnIncognito ct)
|
let mergedPreferences = contactUserPreferences user (userPreferences ct) (preferences' ct) (contactConnIncognito ct)
|
||||||
|
@ -1135,14 +394,6 @@ contactUserPreferences user userPreferences contactPreferences connectedIncognit
|
||||||
ctUserPrefs = mergeUserChatPrefs' user connectedIncognito userPreferences
|
ctUserPrefs = mergeUserChatPrefs' user connectedIncognito userPreferences
|
||||||
ctPrefs = mergePreferences contactPreferences Nothing
|
ctPrefs = mergePreferences contactPreferences Nothing
|
||||||
|
|
||||||
getContactUserPreference :: SChatFeature f -> ContactUserPreferences -> ContactUserPreference (FeaturePreference f)
|
|
||||||
getContactUserPreference = \case
|
|
||||||
SCFTimedMessages -> timedMessages
|
|
||||||
SCFFullDelete -> fullDelete
|
|
||||||
SCFReactions -> reactions
|
|
||||||
SCFVoice -> voice
|
|
||||||
SCFCalls -> calls
|
|
||||||
|
|
||||||
data Profile = Profile
|
data Profile = Profile
|
||||||
{ displayName :: ContactName,
|
{ displayName :: ContactName,
|
||||||
fullName :: Text,
|
fullName :: Text,
|
||||||
|
@ -1433,14 +684,6 @@ instance ToJSON GroupMemberRole where
|
||||||
toJSON = strToJSON
|
toJSON = strToJSON
|
||||||
toEncoding = strToJEncoding
|
toEncoding = strToJEncoding
|
||||||
|
|
||||||
fromBlobField_ :: Typeable k => (ByteString -> Either String k) -> FieldParser k
|
|
||||||
fromBlobField_ p = \case
|
|
||||||
f@(Field (SQLBlob b) _) ->
|
|
||||||
case p b of
|
|
||||||
Right k -> Ok k
|
|
||||||
Left e -> returnError ConversionFailed f ("could not parse field: " ++ e)
|
|
||||||
f -> returnError ConversionFailed f "expecting SQLBlob column type"
|
|
||||||
|
|
||||||
newtype Probe = Probe {unProbe :: ByteString}
|
newtype Probe = Probe {unProbe :: ByteString}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
@ -2194,12 +1437,6 @@ data XGrpMemIntroCont = XGrpMemIntroCont
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
encodeJSON :: ToJSON a => a -> Text
|
|
||||||
encodeJSON = safeDecodeUtf8 . LB.toStrict . J.encode
|
|
||||||
|
|
||||||
decodeJSON :: FromJSON a => Text -> Maybe a
|
|
||||||
decodeJSON = J.decode . LB.fromStrict . encodeUtf8
|
|
||||||
|
|
||||||
data ServerCfg p = ServerCfg
|
data ServerCfg p = ServerCfg
|
||||||
{ server :: ProtoServerWithAuth p,
|
{ server :: ProtoServerWithAuth p,
|
||||||
preset :: Bool,
|
preset :: Bool,
|
||||||
|
|
778
src/Simplex/Chat/Types/Preferences.hs
Normal file
778
src/Simplex/Chat/Types/Preferences.hs
Normal file
|
@ -0,0 +1,778 @@
|
||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE StandaloneDeriving #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
{-# LANGUAGE TypeFamilyDependencies #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
||||||
|
|
||||||
|
{-# HLINT ignore "Use newtype instead of data" #-}
|
||||||
|
|
||||||
|
module Simplex.Chat.Types.Preferences where
|
||||||
|
|
||||||
|
import Control.Applicative ((<|>))
|
||||||
|
import Data.Aeson (FromJSON (..), ToJSON (..))
|
||||||
|
import qualified Data.Aeson as J
|
||||||
|
import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||||
|
import qualified Data.ByteString.Char8 as B
|
||||||
|
import Data.Maybe (fromMaybe, isJust)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Database.SQLite.Simple.FromField (FromField (..))
|
||||||
|
import Database.SQLite.Simple.ToField (ToField (..))
|
||||||
|
import GHC.Generics (Generic)
|
||||||
|
import GHC.Records.Compat
|
||||||
|
import Simplex.Chat.Types.Util
|
||||||
|
import Simplex.Messaging.Encoding.String
|
||||||
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, sumTypeJSON)
|
||||||
|
import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>))
|
||||||
|
|
||||||
|
data ChatFeature
|
||||||
|
= CFTimedMessages
|
||||||
|
| CFFullDelete
|
||||||
|
| CFReactions
|
||||||
|
| CFVoice
|
||||||
|
| CFCalls
|
||||||
|
deriving (Show, Generic)
|
||||||
|
|
||||||
|
data SChatFeature (f :: ChatFeature) where
|
||||||
|
SCFTimedMessages :: SChatFeature 'CFTimedMessages
|
||||||
|
SCFFullDelete :: SChatFeature 'CFFullDelete
|
||||||
|
SCFReactions :: SChatFeature 'CFReactions
|
||||||
|
SCFVoice :: SChatFeature 'CFVoice
|
||||||
|
SCFCalls :: SChatFeature 'CFCalls
|
||||||
|
|
||||||
|
deriving instance Show (SChatFeature f)
|
||||||
|
|
||||||
|
data AChatFeature = forall f. FeatureI f => ACF (SChatFeature f)
|
||||||
|
|
||||||
|
deriving instance Show AChatFeature
|
||||||
|
|
||||||
|
chatFeatureNameText :: ChatFeature -> Text
|
||||||
|
chatFeatureNameText = \case
|
||||||
|
CFTimedMessages -> "Disappearing messages"
|
||||||
|
CFFullDelete -> "Full deletion"
|
||||||
|
CFReactions -> "Message reactions"
|
||||||
|
CFVoice -> "Voice messages"
|
||||||
|
CFCalls -> "Audio/video calls"
|
||||||
|
|
||||||
|
chatFeatureNameText' :: SChatFeature f -> Text
|
||||||
|
chatFeatureNameText' = chatFeatureNameText . chatFeature
|
||||||
|
|
||||||
|
instance ToJSON ChatFeature where
|
||||||
|
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CF"
|
||||||
|
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CF"
|
||||||
|
|
||||||
|
instance FromJSON ChatFeature where
|
||||||
|
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CF"
|
||||||
|
|
||||||
|
allChatFeatures :: [AChatFeature]
|
||||||
|
allChatFeatures =
|
||||||
|
[ ACF SCFTimedMessages,
|
||||||
|
ACF SCFFullDelete,
|
||||||
|
ACF SCFReactions,
|
||||||
|
ACF SCFVoice,
|
||||||
|
ACF SCFCalls
|
||||||
|
]
|
||||||
|
|
||||||
|
chatPrefSel :: SChatFeature f -> Preferences -> Maybe (FeaturePreference f)
|
||||||
|
chatPrefSel = \case
|
||||||
|
SCFTimedMessages -> timedMessages
|
||||||
|
SCFFullDelete -> fullDelete
|
||||||
|
SCFReactions -> reactions
|
||||||
|
SCFVoice -> voice
|
||||||
|
SCFCalls -> calls
|
||||||
|
|
||||||
|
chatFeature :: SChatFeature f -> ChatFeature
|
||||||
|
chatFeature = \case
|
||||||
|
SCFTimedMessages -> CFTimedMessages
|
||||||
|
SCFFullDelete -> CFFullDelete
|
||||||
|
SCFReactions -> CFReactions
|
||||||
|
SCFVoice -> CFVoice
|
||||||
|
SCFCalls -> CFCalls
|
||||||
|
|
||||||
|
class PreferenceI p where
|
||||||
|
getPreference :: SChatFeature f -> p -> FeaturePreference f
|
||||||
|
|
||||||
|
instance PreferenceI Preferences where
|
||||||
|
getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f prefs)
|
||||||
|
|
||||||
|
instance PreferenceI (Maybe Preferences) where
|
||||||
|
getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f =<< prefs)
|
||||||
|
|
||||||
|
instance PreferenceI FullPreferences where
|
||||||
|
getPreference = \case
|
||||||
|
SCFTimedMessages -> timedMessages
|
||||||
|
SCFFullDelete -> fullDelete
|
||||||
|
SCFReactions -> reactions
|
||||||
|
SCFVoice -> voice
|
||||||
|
SCFCalls -> calls
|
||||||
|
{-# INLINE getPreference #-}
|
||||||
|
|
||||||
|
setPreference :: forall f. FeatureI f => SChatFeature f -> Maybe FeatureAllowed -> Maybe Preferences -> Preferences
|
||||||
|
setPreference f allow_ prefs_ = setPreference_ f pref $ fromMaybe emptyChatPrefs prefs_
|
||||||
|
where
|
||||||
|
pref = setAllow <$> allow_
|
||||||
|
setAllow :: FeatureAllowed -> FeaturePreference f
|
||||||
|
setAllow = setField @"allow" (getPreference f prefs)
|
||||||
|
prefs = mergePreferences Nothing prefs_
|
||||||
|
|
||||||
|
setPreference' :: SChatFeature f -> Maybe (FeaturePreference f) -> Maybe Preferences -> Preferences
|
||||||
|
setPreference' f pref_ prefs_ = setPreference_ f pref_ $ fromMaybe emptyChatPrefs prefs_
|
||||||
|
|
||||||
|
setPreference_ :: SChatFeature f -> Maybe (FeaturePreference f) -> Preferences -> Preferences
|
||||||
|
setPreference_ f pref_ prefs =
|
||||||
|
case f of
|
||||||
|
SCFTimedMessages -> prefs {timedMessages = pref_}
|
||||||
|
SCFFullDelete -> prefs {fullDelete = pref_}
|
||||||
|
SCFReactions -> prefs {reactions = pref_}
|
||||||
|
SCFVoice -> prefs {voice = pref_}
|
||||||
|
SCFCalls -> prefs {calls = pref_}
|
||||||
|
|
||||||
|
-- collection of optional chat preferences for the user and the contact
|
||||||
|
data Preferences = Preferences
|
||||||
|
{ timedMessages :: Maybe TimedMessagesPreference,
|
||||||
|
fullDelete :: Maybe FullDeletePreference,
|
||||||
|
reactions :: Maybe ReactionsPreference,
|
||||||
|
voice :: Maybe VoicePreference,
|
||||||
|
calls :: Maybe CallsPreference
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON Preferences where
|
||||||
|
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
|
||||||
|
instance ToField Preferences where
|
||||||
|
toField = toField . encodeJSON
|
||||||
|
|
||||||
|
instance FromField Preferences where
|
||||||
|
fromField = fromTextField_ decodeJSON
|
||||||
|
|
||||||
|
data GroupFeature
|
||||||
|
= GFTimedMessages
|
||||||
|
| GFDirectMessages
|
||||||
|
| GFFullDelete
|
||||||
|
| GFReactions
|
||||||
|
| GFVoice
|
||||||
|
| GFFiles
|
||||||
|
deriving (Show, Generic)
|
||||||
|
|
||||||
|
data SGroupFeature (f :: GroupFeature) where
|
||||||
|
SGFTimedMessages :: SGroupFeature 'GFTimedMessages
|
||||||
|
SGFDirectMessages :: SGroupFeature 'GFDirectMessages
|
||||||
|
SGFFullDelete :: SGroupFeature 'GFFullDelete
|
||||||
|
SGFReactions :: SGroupFeature 'GFReactions
|
||||||
|
SGFVoice :: SGroupFeature 'GFVoice
|
||||||
|
SGFFiles :: SGroupFeature 'GFFiles
|
||||||
|
|
||||||
|
deriving instance Show (SGroupFeature f)
|
||||||
|
|
||||||
|
data AGroupFeature = forall f. GroupFeatureI f => AGF (SGroupFeature f)
|
||||||
|
|
||||||
|
deriving instance Show AGroupFeature
|
||||||
|
|
||||||
|
groupFeatureNameText :: GroupFeature -> Text
|
||||||
|
groupFeatureNameText = \case
|
||||||
|
GFTimedMessages -> "Disappearing messages"
|
||||||
|
GFDirectMessages -> "Direct messages"
|
||||||
|
GFFullDelete -> "Full deletion"
|
||||||
|
GFReactions -> "Message reactions"
|
||||||
|
GFVoice -> "Voice messages"
|
||||||
|
GFFiles -> "Files and media"
|
||||||
|
|
||||||
|
groupFeatureNameText' :: SGroupFeature f -> Text
|
||||||
|
groupFeatureNameText' = groupFeatureNameText . toGroupFeature
|
||||||
|
|
||||||
|
groupFeatureAllowed' :: GroupFeatureI f => SGroupFeature f -> FullGroupPreferences -> Bool
|
||||||
|
groupFeatureAllowed' feature prefs =
|
||||||
|
getField @"enable" (getGroupPreference feature prefs) == FEOn
|
||||||
|
|
||||||
|
instance ToJSON GroupFeature where
|
||||||
|
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "GF"
|
||||||
|
toJSON = J.genericToJSON . enumJSON $ dropPrefix "GF"
|
||||||
|
|
||||||
|
instance FromJSON GroupFeature where
|
||||||
|
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "GF"
|
||||||
|
|
||||||
|
allGroupFeatures :: [AGroupFeature]
|
||||||
|
allGroupFeatures =
|
||||||
|
[ AGF SGFTimedMessages,
|
||||||
|
AGF SGFDirectMessages,
|
||||||
|
AGF SGFFullDelete,
|
||||||
|
AGF SGFReactions,
|
||||||
|
AGF SGFVoice,
|
||||||
|
AGF SGFFiles
|
||||||
|
]
|
||||||
|
|
||||||
|
groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f)
|
||||||
|
groupPrefSel = \case
|
||||||
|
SGFTimedMessages -> timedMessages
|
||||||
|
SGFDirectMessages -> directMessages
|
||||||
|
SGFFullDelete -> fullDelete
|
||||||
|
SGFReactions -> reactions
|
||||||
|
SGFVoice -> voice
|
||||||
|
SGFFiles -> files
|
||||||
|
|
||||||
|
toGroupFeature :: SGroupFeature f -> GroupFeature
|
||||||
|
toGroupFeature = \case
|
||||||
|
SGFTimedMessages -> GFTimedMessages
|
||||||
|
SGFDirectMessages -> GFDirectMessages
|
||||||
|
SGFFullDelete -> GFFullDelete
|
||||||
|
SGFReactions -> GFReactions
|
||||||
|
SGFVoice -> GFVoice
|
||||||
|
SGFFiles -> GFFiles
|
||||||
|
|
||||||
|
class GroupPreferenceI p where
|
||||||
|
getGroupPreference :: SGroupFeature f -> p -> GroupFeaturePreference f
|
||||||
|
|
||||||
|
instance GroupPreferenceI GroupPreferences where
|
||||||
|
getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt prefs)
|
||||||
|
|
||||||
|
instance GroupPreferenceI (Maybe GroupPreferences) where
|
||||||
|
getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs)
|
||||||
|
|
||||||
|
instance GroupPreferenceI FullGroupPreferences where
|
||||||
|
getGroupPreference = \case
|
||||||
|
SGFTimedMessages -> timedMessages
|
||||||
|
SGFDirectMessages -> directMessages
|
||||||
|
SGFFullDelete -> fullDelete
|
||||||
|
SGFReactions -> reactions
|
||||||
|
SGFVoice -> voice
|
||||||
|
SGFFiles -> files
|
||||||
|
{-# INLINE getGroupPreference #-}
|
||||||
|
|
||||||
|
-- collection of optional group preferences
|
||||||
|
data GroupPreferences = GroupPreferences
|
||||||
|
{ timedMessages :: Maybe TimedMessagesGroupPreference,
|
||||||
|
directMessages :: Maybe DirectMessagesGroupPreference,
|
||||||
|
fullDelete :: Maybe FullDeleteGroupPreference,
|
||||||
|
reactions :: Maybe ReactionsGroupPreference,
|
||||||
|
voice :: Maybe VoiceGroupPreference,
|
||||||
|
files :: Maybe FilesGroupPreference
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON GroupPreferences where
|
||||||
|
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
|
||||||
|
instance ToField GroupPreferences where
|
||||||
|
toField = toField . encodeJSON
|
||||||
|
|
||||||
|
instance FromField GroupPreferences where
|
||||||
|
fromField = fromTextField_ decodeJSON
|
||||||
|
|
||||||
|
setGroupPreference :: forall f. GroupFeatureI f => SGroupFeature f -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences
|
||||||
|
setGroupPreference f enable prefs_ = setGroupPreference_ f pref prefs
|
||||||
|
where
|
||||||
|
prefs = mergeGroupPreferences prefs_
|
||||||
|
pref :: GroupFeaturePreference f
|
||||||
|
pref = setField @"enable" (getGroupPreference f prefs) enable
|
||||||
|
|
||||||
|
setGroupPreference' :: SGroupFeature f -> GroupFeaturePreference f -> Maybe GroupPreferences -> GroupPreferences
|
||||||
|
setGroupPreference' f pref prefs_ = setGroupPreference_ f pref prefs
|
||||||
|
where
|
||||||
|
prefs = mergeGroupPreferences prefs_
|
||||||
|
|
||||||
|
setGroupPreference_ :: SGroupFeature f -> GroupFeaturePreference f -> FullGroupPreferences -> GroupPreferences
|
||||||
|
setGroupPreference_ f pref prefs =
|
||||||
|
toGroupPreferences $ case f of
|
||||||
|
SGFTimedMessages -> prefs {timedMessages = pref}
|
||||||
|
SGFDirectMessages -> prefs {directMessages = pref}
|
||||||
|
SGFFullDelete -> prefs {fullDelete = pref}
|
||||||
|
SGFReactions -> prefs {reactions = pref}
|
||||||
|
SGFVoice -> prefs {voice = pref}
|
||||||
|
SGFFiles -> prefs {files = pref}
|
||||||
|
|
||||||
|
setGroupTimedMessagesPreference :: TimedMessagesGroupPreference -> Maybe GroupPreferences -> GroupPreferences
|
||||||
|
setGroupTimedMessagesPreference pref prefs_ =
|
||||||
|
toGroupPreferences $ prefs {timedMessages = pref}
|
||||||
|
where
|
||||||
|
prefs = mergeGroupPreferences prefs_
|
||||||
|
|
||||||
|
-- full collection of chat preferences defined in the app - it is used to ensure we include all preferences and to simplify processing
|
||||||
|
-- if some of the preferences are not defined in Preferences, defaults from defaultChatPrefs are used here.
|
||||||
|
data FullPreferences = FullPreferences
|
||||||
|
{ timedMessages :: TimedMessagesPreference,
|
||||||
|
fullDelete :: FullDeletePreference,
|
||||||
|
reactions :: ReactionsPreference,
|
||||||
|
voice :: VoicePreference,
|
||||||
|
calls :: CallsPreference
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON FullPreferences where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
-- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing
|
||||||
|
-- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here.
|
||||||
|
data FullGroupPreferences = FullGroupPreferences
|
||||||
|
{ timedMessages :: TimedMessagesGroupPreference,
|
||||||
|
directMessages :: DirectMessagesGroupPreference,
|
||||||
|
fullDelete :: FullDeleteGroupPreference,
|
||||||
|
reactions :: ReactionsGroupPreference,
|
||||||
|
voice :: VoiceGroupPreference,
|
||||||
|
files :: FilesGroupPreference
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON FullGroupPreferences where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
-- merged preferences of user for a given contact - they differentiate between specific preferences for the contact and global user preferences
|
||||||
|
data ContactUserPreferences = ContactUserPreferences
|
||||||
|
{ timedMessages :: ContactUserPreference TimedMessagesPreference,
|
||||||
|
fullDelete :: ContactUserPreference FullDeletePreference,
|
||||||
|
reactions :: ContactUserPreference ReactionsPreference,
|
||||||
|
voice :: ContactUserPreference VoicePreference,
|
||||||
|
calls :: ContactUserPreference CallsPreference
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
data ContactUserPreference p = ContactUserPreference
|
||||||
|
{ enabled :: PrefEnabled,
|
||||||
|
userPreference :: ContactUserPref p,
|
||||||
|
contactPreference :: p
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
data ContactUserPref p = CUPContact {preference :: p} | CUPUser {preference :: p}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON ContactUserPreferences where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
instance ToJSON p => ToJSON (ContactUserPreference p) where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
instance ToJSON p => ToJSON (ContactUserPref p) where
|
||||||
|
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CUP"
|
||||||
|
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CUP"
|
||||||
|
|
||||||
|
toChatPrefs :: FullPreferences -> Preferences
|
||||||
|
toChatPrefs FullPreferences {timedMessages, fullDelete, reactions, voice, calls} =
|
||||||
|
Preferences
|
||||||
|
{ timedMessages = Just timedMessages,
|
||||||
|
fullDelete = Just fullDelete,
|
||||||
|
reactions = Just reactions,
|
||||||
|
voice = Just voice,
|
||||||
|
calls = Just calls
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultChatPrefs :: FullPreferences
|
||||||
|
defaultChatPrefs =
|
||||||
|
FullPreferences
|
||||||
|
{ timedMessages = TimedMessagesPreference {allow = FAYes, ttl = Nothing},
|
||||||
|
fullDelete = FullDeletePreference {allow = FANo},
|
||||||
|
reactions = ReactionsPreference {allow = FAYes},
|
||||||
|
voice = VoicePreference {allow = FAYes},
|
||||||
|
calls = CallsPreference {allow = FAYes}
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyChatPrefs :: Preferences
|
||||||
|
emptyChatPrefs = Preferences Nothing Nothing Nothing Nothing Nothing
|
||||||
|
|
||||||
|
defaultGroupPrefs :: FullGroupPreferences
|
||||||
|
defaultGroupPrefs =
|
||||||
|
FullGroupPreferences
|
||||||
|
{ timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400},
|
||||||
|
directMessages = DirectMessagesGroupPreference {enable = FEOff},
|
||||||
|
fullDelete = FullDeleteGroupPreference {enable = FEOff},
|
||||||
|
reactions = ReactionsGroupPreference {enable = FEOn},
|
||||||
|
voice = VoiceGroupPreference {enable = FEOn},
|
||||||
|
files = FilesGroupPreference {enable = FEOn}
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyGroupPrefs :: GroupPreferences
|
||||||
|
emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing
|
||||||
|
|
||||||
|
data TimedMessagesPreference = TimedMessagesPreference
|
||||||
|
{ allow :: FeatureAllowed,
|
||||||
|
ttl :: Maybe Int
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON TimedMessagesPreference where
|
||||||
|
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
|
||||||
|
data FullDeletePreference = FullDeletePreference {allow :: FeatureAllowed}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON FullDeletePreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
data ReactionsPreference = ReactionsPreference {allow :: FeatureAllowed}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON ReactionsPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
data VoicePreference = VoicePreference {allow :: FeatureAllowed}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON VoicePreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
data CallsPreference = CallsPreference {allow :: FeatureAllowed}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON CallsPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
class (Eq (FeaturePreference f), HasField "allow" (FeaturePreference f) FeatureAllowed) => FeatureI f where
|
||||||
|
type FeaturePreference (f :: ChatFeature) = p | p -> f
|
||||||
|
sFeature :: SChatFeature f
|
||||||
|
prefParam :: FeaturePreference f -> Maybe Int
|
||||||
|
|
||||||
|
instance HasField "allow" TimedMessagesPreference FeatureAllowed where
|
||||||
|
hasField p = (\allow -> p {allow}, allow (p :: TimedMessagesPreference))
|
||||||
|
|
||||||
|
instance HasField "allow" FullDeletePreference FeatureAllowed where
|
||||||
|
hasField p = (\allow -> p {allow}, allow (p :: FullDeletePreference))
|
||||||
|
|
||||||
|
instance HasField "allow" ReactionsPreference FeatureAllowed where
|
||||||
|
hasField p = (\allow -> p {allow}, allow (p :: ReactionsPreference))
|
||||||
|
|
||||||
|
instance HasField "allow" VoicePreference FeatureAllowed where
|
||||||
|
hasField p = (\allow -> p {allow}, allow (p :: VoicePreference))
|
||||||
|
|
||||||
|
instance HasField "allow" CallsPreference FeatureAllowed where
|
||||||
|
hasField p = (\allow -> p {allow}, allow (p :: CallsPreference))
|
||||||
|
|
||||||
|
instance FeatureI 'CFTimedMessages where
|
||||||
|
type FeaturePreference 'CFTimedMessages = TimedMessagesPreference
|
||||||
|
sFeature = SCFTimedMessages
|
||||||
|
prefParam TimedMessagesPreference {ttl} = ttl
|
||||||
|
|
||||||
|
instance FeatureI 'CFFullDelete where
|
||||||
|
type FeaturePreference 'CFFullDelete = FullDeletePreference
|
||||||
|
sFeature = SCFFullDelete
|
||||||
|
prefParam _ = Nothing
|
||||||
|
|
||||||
|
instance FeatureI 'CFReactions where
|
||||||
|
type FeaturePreference 'CFReactions = ReactionsPreference
|
||||||
|
sFeature = SCFReactions
|
||||||
|
prefParam _ = Nothing
|
||||||
|
|
||||||
|
instance FeatureI 'CFVoice where
|
||||||
|
type FeaturePreference 'CFVoice = VoicePreference
|
||||||
|
sFeature = SCFVoice
|
||||||
|
prefParam _ = Nothing
|
||||||
|
|
||||||
|
instance FeatureI 'CFCalls where
|
||||||
|
type FeaturePreference 'CFCalls = CallsPreference
|
||||||
|
sFeature = SCFCalls
|
||||||
|
prefParam _ = Nothing
|
||||||
|
|
||||||
|
data GroupPreference = GroupPreference
|
||||||
|
{enable :: GroupFeatureEnabled}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
data TimedMessagesGroupPreference = TimedMessagesGroupPreference
|
||||||
|
{ enable :: GroupFeatureEnabled,
|
||||||
|
ttl :: Maybe Int
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
data DirectMessagesGroupPreference = DirectMessagesGroupPreference
|
||||||
|
{enable :: GroupFeatureEnabled}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
data FullDeleteGroupPreference = FullDeleteGroupPreference
|
||||||
|
{enable :: GroupFeatureEnabled}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
data ReactionsGroupPreference = ReactionsGroupPreference
|
||||||
|
{enable :: GroupFeatureEnabled}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
data VoiceGroupPreference = VoiceGroupPreference
|
||||||
|
{enable :: GroupFeatureEnabled}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
data FilesGroupPreference = FilesGroupPreference
|
||||||
|
{enable :: GroupFeatureEnabled}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON GroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
instance ToJSON TimedMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
instance ToJSON DirectMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
instance ToJSON ReactionsGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
instance ToJSON FullDeleteGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
instance ToJSON VoiceGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
instance ToJSON FilesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
class (Eq (GroupFeaturePreference f), HasField "enable" (GroupFeaturePreference f) GroupFeatureEnabled) => GroupFeatureI f where
|
||||||
|
type GroupFeaturePreference (f :: GroupFeature) = p | p -> f
|
||||||
|
sGroupFeature :: SGroupFeature f
|
||||||
|
groupPrefParam :: GroupFeaturePreference f -> Maybe Int
|
||||||
|
|
||||||
|
instance HasField "enable" GroupPreference GroupFeatureEnabled where
|
||||||
|
hasField p = (\enable -> p {enable}, enable (p :: GroupPreference))
|
||||||
|
|
||||||
|
instance HasField "enable" TimedMessagesGroupPreference GroupFeatureEnabled where
|
||||||
|
hasField p = (\enable -> p {enable}, enable (p :: TimedMessagesGroupPreference))
|
||||||
|
|
||||||
|
instance HasField "enable" DirectMessagesGroupPreference GroupFeatureEnabled where
|
||||||
|
hasField p = (\enable -> p {enable}, enable (p :: DirectMessagesGroupPreference))
|
||||||
|
|
||||||
|
instance HasField "enable" ReactionsGroupPreference GroupFeatureEnabled where
|
||||||
|
hasField p = (\enable -> p {enable}, enable (p :: ReactionsGroupPreference))
|
||||||
|
|
||||||
|
instance HasField "enable" FullDeleteGroupPreference GroupFeatureEnabled where
|
||||||
|
hasField p = (\enable -> p {enable}, enable (p :: FullDeleteGroupPreference))
|
||||||
|
|
||||||
|
instance HasField "enable" VoiceGroupPreference GroupFeatureEnabled where
|
||||||
|
hasField p = (\enable -> p {enable}, enable (p :: VoiceGroupPreference))
|
||||||
|
|
||||||
|
instance HasField "enable" FilesGroupPreference GroupFeatureEnabled where
|
||||||
|
hasField p = (\enable -> p {enable}, enable (p :: FilesGroupPreference))
|
||||||
|
|
||||||
|
instance GroupFeatureI 'GFTimedMessages where
|
||||||
|
type GroupFeaturePreference 'GFTimedMessages = TimedMessagesGroupPreference
|
||||||
|
sGroupFeature = SGFTimedMessages
|
||||||
|
groupPrefParam TimedMessagesGroupPreference {ttl} = ttl
|
||||||
|
|
||||||
|
instance GroupFeatureI 'GFDirectMessages where
|
||||||
|
type GroupFeaturePreference 'GFDirectMessages = DirectMessagesGroupPreference
|
||||||
|
sGroupFeature = SGFDirectMessages
|
||||||
|
groupPrefParam _ = Nothing
|
||||||
|
|
||||||
|
instance GroupFeatureI 'GFFullDelete where
|
||||||
|
type GroupFeaturePreference 'GFFullDelete = FullDeleteGroupPreference
|
||||||
|
sGroupFeature = SGFFullDelete
|
||||||
|
groupPrefParam _ = Nothing
|
||||||
|
|
||||||
|
instance GroupFeatureI 'GFReactions where
|
||||||
|
type GroupFeaturePreference 'GFReactions = ReactionsGroupPreference
|
||||||
|
sGroupFeature = SGFReactions
|
||||||
|
groupPrefParam _ = Nothing
|
||||||
|
|
||||||
|
instance GroupFeatureI 'GFVoice where
|
||||||
|
type GroupFeaturePreference 'GFVoice = VoiceGroupPreference
|
||||||
|
sGroupFeature = SGFVoice
|
||||||
|
groupPrefParam _ = Nothing
|
||||||
|
|
||||||
|
instance GroupFeatureI 'GFFiles where
|
||||||
|
type GroupFeaturePreference 'GFFiles = FilesGroupPreference
|
||||||
|
sGroupFeature = SGFFiles
|
||||||
|
groupPrefParam _ = Nothing
|
||||||
|
|
||||||
|
groupPrefStateText :: HasField "enable" p GroupFeatureEnabled => GroupFeature -> p -> Maybe Int -> Text
|
||||||
|
groupPrefStateText feature pref param =
|
||||||
|
let enabled = getField @"enable" pref
|
||||||
|
paramText = if enabled == FEOn then groupParamText_ feature param else ""
|
||||||
|
in groupFeatureNameText feature <> ": " <> safeDecodeUtf8 (strEncode enabled) <> paramText
|
||||||
|
|
||||||
|
groupParamText_ :: GroupFeature -> Maybe Int -> Text
|
||||||
|
groupParamText_ feature param = case feature of
|
||||||
|
GFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param
|
||||||
|
_ -> ""
|
||||||
|
|
||||||
|
groupPreferenceText :: forall f. GroupFeatureI f => GroupFeaturePreference f -> Text
|
||||||
|
groupPreferenceText pref =
|
||||||
|
let feature = toGroupFeature $ sGroupFeature @f
|
||||||
|
in groupPrefStateText feature pref $ groupPrefParam pref
|
||||||
|
|
||||||
|
timedTTLText :: Int -> Text
|
||||||
|
timedTTLText 0 = "0 sec"
|
||||||
|
timedTTLText ttl = do
|
||||||
|
let (m', s) = ttl `quotRem` 60
|
||||||
|
(h', m) = m' `quotRem` 60
|
||||||
|
(d', h) = h' `quotRem` 24
|
||||||
|
(mm, d) = d' `quotRem` 30
|
||||||
|
T.pack . unwords $
|
||||||
|
[mms mm | mm /= 0] <> [ds d | d /= 0] <> [hs h | h /= 0] <> [ms m | m /= 0] <> [ss s | s /= 0]
|
||||||
|
where
|
||||||
|
ss s = show s <> " sec"
|
||||||
|
ms m = show m <> " min"
|
||||||
|
hs 1 = "1 hour"
|
||||||
|
hs h = show h <> " hours"
|
||||||
|
ds 1 = "1 day"
|
||||||
|
ds 7 = "1 week"
|
||||||
|
ds 14 = "2 weeks"
|
||||||
|
ds d = show d <> " days"
|
||||||
|
mms 1 = "1 month"
|
||||||
|
mms mm = show mm <> " months"
|
||||||
|
|
||||||
|
toGroupPreference :: GroupFeatureI f => GroupFeaturePreference f -> GroupPreference
|
||||||
|
toGroupPreference p = GroupPreference {enable = getField @"enable" p}
|
||||||
|
|
||||||
|
data FeatureAllowed
|
||||||
|
= FAAlways -- allow unconditionally
|
||||||
|
| FAYes -- allow, if peer allows it
|
||||||
|
| FANo -- do not allow
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance FromField FeatureAllowed where fromField = fromBlobField_ strDecode
|
||||||
|
|
||||||
|
instance ToField FeatureAllowed where toField = toField . strEncode
|
||||||
|
|
||||||
|
instance StrEncoding FeatureAllowed where
|
||||||
|
strEncode = \case
|
||||||
|
FAAlways -> "always"
|
||||||
|
FAYes -> "yes"
|
||||||
|
FANo -> "no"
|
||||||
|
strDecode = \case
|
||||||
|
"always" -> Right FAAlways
|
||||||
|
"yes" -> Right FAYes
|
||||||
|
"no" -> Right FANo
|
||||||
|
r -> Left $ "bad FeatureAllowed " <> B.unpack r
|
||||||
|
strP = strDecode <$?> A.takeByteString
|
||||||
|
|
||||||
|
instance FromJSON FeatureAllowed where
|
||||||
|
parseJSON = strParseJSON "FeatureAllowed"
|
||||||
|
|
||||||
|
instance ToJSON FeatureAllowed where
|
||||||
|
toJSON = strToJSON
|
||||||
|
toEncoding = strToJEncoding
|
||||||
|
|
||||||
|
data GroupFeatureEnabled = FEOn | FEOff
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance FromField GroupFeatureEnabled where fromField = fromBlobField_ strDecode
|
||||||
|
|
||||||
|
instance ToField GroupFeatureEnabled where toField = toField . strEncode
|
||||||
|
|
||||||
|
instance StrEncoding GroupFeatureEnabled where
|
||||||
|
strEncode = \case
|
||||||
|
FEOn -> "on"
|
||||||
|
FEOff -> "off"
|
||||||
|
strDecode = \case
|
||||||
|
"on" -> Right FEOn
|
||||||
|
"off" -> Right FEOff
|
||||||
|
r -> Left $ "bad GroupFeatureEnabled " <> B.unpack r
|
||||||
|
strP = strDecode <$?> A.takeByteString
|
||||||
|
|
||||||
|
instance FromJSON GroupFeatureEnabled where
|
||||||
|
parseJSON = strParseJSON "GroupFeatureEnabled"
|
||||||
|
|
||||||
|
instance ToJSON GroupFeatureEnabled where
|
||||||
|
toJSON = strToJSON
|
||||||
|
toEncoding = strToJEncoding
|
||||||
|
|
||||||
|
groupFeatureState :: GroupFeatureI f => GroupFeaturePreference f -> (GroupFeatureEnabled, Maybe Int)
|
||||||
|
groupFeatureState p =
|
||||||
|
let enable = getField @"enable" p
|
||||||
|
param = if enable == FEOn then groupPrefParam p else Nothing
|
||||||
|
in (enable, param)
|
||||||
|
|
||||||
|
mergePreferences :: Maybe Preferences -> Maybe Preferences -> FullPreferences
|
||||||
|
mergePreferences contactPrefs userPreferences =
|
||||||
|
FullPreferences
|
||||||
|
{ timedMessages = pref SCFTimedMessages,
|
||||||
|
fullDelete = pref SCFFullDelete,
|
||||||
|
reactions = pref SCFReactions,
|
||||||
|
voice = pref SCFVoice,
|
||||||
|
calls = pref SCFCalls
|
||||||
|
}
|
||||||
|
where
|
||||||
|
pref :: SChatFeature f -> FeaturePreference f
|
||||||
|
pref f =
|
||||||
|
let sel = chatPrefSel f
|
||||||
|
in fromMaybe (getPreference f defaultChatPrefs) $ (contactPrefs >>= sel) <|> (userPreferences >>= sel)
|
||||||
|
|
||||||
|
mergeGroupPreferences :: Maybe GroupPreferences -> FullGroupPreferences
|
||||||
|
mergeGroupPreferences groupPreferences =
|
||||||
|
FullGroupPreferences
|
||||||
|
{ timedMessages = pref SGFTimedMessages,
|
||||||
|
directMessages = pref SGFDirectMessages,
|
||||||
|
fullDelete = pref SGFFullDelete,
|
||||||
|
reactions = pref SGFReactions,
|
||||||
|
voice = pref SGFVoice,
|
||||||
|
files = pref SGFFiles
|
||||||
|
}
|
||||||
|
where
|
||||||
|
pref :: SGroupFeature f -> GroupFeaturePreference f
|
||||||
|
pref pt = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPreferences >>= groupPrefSel pt)
|
||||||
|
|
||||||
|
toGroupPreferences :: FullGroupPreferences -> GroupPreferences
|
||||||
|
toGroupPreferences groupPreferences =
|
||||||
|
GroupPreferences
|
||||||
|
{ timedMessages = pref SGFTimedMessages,
|
||||||
|
directMessages = pref SGFDirectMessages,
|
||||||
|
fullDelete = pref SGFFullDelete,
|
||||||
|
reactions = pref SGFReactions,
|
||||||
|
voice = pref SGFVoice,
|
||||||
|
files = pref SGFFiles
|
||||||
|
}
|
||||||
|
where
|
||||||
|
pref :: SGroupFeature f -> Maybe (GroupFeaturePreference f)
|
||||||
|
pref f = Just $ getGroupPreference f groupPreferences
|
||||||
|
|
||||||
|
data PrefEnabled = PrefEnabled {forUser :: Bool, forContact :: Bool}
|
||||||
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON PrefEnabled where
|
||||||
|
toJSON = J.genericToJSON J.defaultOptions
|
||||||
|
toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
|
prefEnabled :: FeatureI f => Bool -> FeaturePreference f -> FeaturePreference f -> PrefEnabled
|
||||||
|
prefEnabled asymmetric user contact = case (getField @"allow" user, getField @"allow" contact) of
|
||||||
|
(FAAlways, FANo) -> PrefEnabled {forUser = False, forContact = asymmetric}
|
||||||
|
(FANo, FAAlways) -> PrefEnabled {forUser = asymmetric, forContact = False}
|
||||||
|
(_, FANo) -> PrefEnabled False False
|
||||||
|
(FANo, _) -> PrefEnabled False False
|
||||||
|
_ -> PrefEnabled True True
|
||||||
|
|
||||||
|
prefStateText :: ChatFeature -> FeatureAllowed -> Maybe Int -> Text
|
||||||
|
prefStateText feature allowed param = case allowed of
|
||||||
|
FANo -> "cancelled " <> chatFeatureNameText feature
|
||||||
|
_ -> "offered " <> chatFeatureNameText feature <> paramText_ feature param
|
||||||
|
|
||||||
|
featureStateText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text
|
||||||
|
featureStateText feature enabled param =
|
||||||
|
chatFeatureNameText feature <> ": " <> prefEnabledToText feature enabled param <> case enabled of
|
||||||
|
PrefEnabled {forUser = True} -> paramText_ feature param
|
||||||
|
_ -> ""
|
||||||
|
|
||||||
|
paramText_ :: ChatFeature -> Maybe Int -> Text
|
||||||
|
paramText_ feature param = case feature of
|
||||||
|
CFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param
|
||||||
|
_ -> ""
|
||||||
|
|
||||||
|
prefEnabledToText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text
|
||||||
|
prefEnabledToText f enabled param = case enabled of
|
||||||
|
PrefEnabled True True -> enabledStr
|
||||||
|
PrefEnabled False False -> "off"
|
||||||
|
PrefEnabled {forUser = True, forContact = False} -> enabledStr <> " for you"
|
||||||
|
PrefEnabled {forUser = False, forContact = True} -> enabledStr <> " for contact"
|
||||||
|
where
|
||||||
|
enabledStr = case f of
|
||||||
|
CFTimedMessages -> if isJust param then "enabled" else "allowed"
|
||||||
|
_ -> "enabled"
|
||||||
|
|
||||||
|
preferenceText :: forall f. FeatureI f => FeaturePreference f -> Text
|
||||||
|
preferenceText p =
|
||||||
|
let feature = chatFeature $ sFeature @f
|
||||||
|
allowed = getField @"allow" p
|
||||||
|
paramText = if allowed == FAAlways || allowed == FAYes then paramText_ feature (prefParam p) else ""
|
||||||
|
in safeDecodeUtf8 (strEncode allowed) <> paramText
|
||||||
|
|
||||||
|
featureState :: FeatureI f => ContactUserPreference (FeaturePreference f) -> (PrefEnabled, Maybe Int)
|
||||||
|
featureState ContactUserPreference {enabled, userPreference} =
|
||||||
|
let param = if forUser enabled then prefParam $ preference userPreference else Nothing
|
||||||
|
in (enabled, param)
|
||||||
|
|
||||||
|
preferenceState :: FeatureI f => FeaturePreference f -> (FeatureAllowed, Maybe Int)
|
||||||
|
preferenceState pref =
|
||||||
|
let allow = getField @"allow" pref
|
||||||
|
param = if allow == FAAlways || allow == FAYes then prefParam pref else Nothing
|
||||||
|
in (allow, param)
|
||||||
|
|
||||||
|
getContactUserPreference :: SChatFeature f -> ContactUserPreferences -> ContactUserPreference (FeaturePreference f)
|
||||||
|
getContactUserPreference = \case
|
||||||
|
SCFTimedMessages -> timedMessages
|
||||||
|
SCFFullDelete -> fullDelete
|
||||||
|
SCFReactions -> reactions
|
||||||
|
SCFVoice -> voice
|
||||||
|
SCFCalls -> calls
|
30
src/Simplex/Chat/Types/Util.hs
Normal file
30
src/Simplex/Chat/Types/Util.hs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
|
||||||
|
module Simplex.Chat.Types.Util where
|
||||||
|
|
||||||
|
import Data.Aeson (ToJSON, FromJSON)
|
||||||
|
import qualified Data.Aeson as J
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Text.Encoding (encodeUtf8)
|
||||||
|
import Data.Typeable
|
||||||
|
import Database.SQLite.Simple (ResultError (..), SQLData (..))
|
||||||
|
import Database.SQLite.Simple.FromField (FieldParser, returnError)
|
||||||
|
import Database.SQLite.Simple.Internal (Field (..))
|
||||||
|
import Database.SQLite.Simple.Ok (Ok (Ok))
|
||||||
|
import Simplex.Messaging.Util (safeDecodeUtf8)
|
||||||
|
|
||||||
|
encodeJSON :: ToJSON a => a -> Text
|
||||||
|
encodeJSON = safeDecodeUtf8 . LB.toStrict . J.encode
|
||||||
|
|
||||||
|
decodeJSON :: FromJSON a => Text -> Maybe a
|
||||||
|
decodeJSON = J.decode . LB.fromStrict . encodeUtf8
|
||||||
|
|
||||||
|
fromBlobField_ :: Typeable k => (ByteString -> Either String k) -> FieldParser k
|
||||||
|
fromBlobField_ p = \case
|
||||||
|
f@(Field (SQLBlob b) _) ->
|
||||||
|
case p b of
|
||||||
|
Right k -> Ok k
|
||||||
|
Left e -> returnError ConversionFailed f ("could not parse field: " ++ e)
|
||||||
|
f -> returnError ConversionFailed f "expecting SQLBlob column type"
|
|
@ -42,6 +42,7 @@ import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Store (AutoAccept (..), StoreError (..), UserContactLink (..))
|
import Simplex.Chat.Store (AutoAccept (..), StoreError (..), UserContactLink (..))
|
||||||
import Simplex.Chat.Styled
|
import Simplex.Chat.Styled
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import qualified Simplex.FileTransfer.Protocol as XFTP
|
import qualified Simplex.FileTransfer.Protocol as XFTP
|
||||||
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..))
|
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..))
|
||||||
import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..))
|
import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..))
|
||||||
|
|
|
@ -20,6 +20,7 @@ import qualified Data.Text as T
|
||||||
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), InlineFilesConfig (..), defaultInlineFilesConfig)
|
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), InlineFilesConfig (..), defaultInlineFilesConfig)
|
||||||
import Simplex.Chat.Store.Profiles (getUserContactProfiles)
|
import Simplex.Chat.Store.Profiles (getUserContactProfiles)
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (withTransaction)
|
import Simplex.Messaging.Agent.Store.SQLite (withTransaction)
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import System.Directory (doesFileExist)
|
import System.Directory (doesFileExist)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Data.ByteString.Char8 (ByteString)
|
||||||
import Data.Time.Clock.System (SystemTime (..), systemToUTCTime)
|
import Data.Time.Clock.System (SystemTime (..), systemToUTCTime)
|
||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent.Protocol
|
import Simplex.Messaging.Agent.Protocol
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Crypto.Ratchet
|
import Simplex.Messaging.Crypto.Ratchet
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue