mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-29 04:39:53 +00:00
Merge branch 'stable'
This commit is contained in:
commit
49bf3cc673
8 changed files with 99 additions and 27 deletions
|
@ -987,6 +987,7 @@ final class ChatModel: ObservableObject {
|
||||||
if let i = getChatIndex(id) {
|
if let i = getChatIndex(id) {
|
||||||
let removed = chats.remove(at: i)
|
let removed = chats.remove(at: i)
|
||||||
ChatTagsModel.shared.removePresetChatTags(removed.chatInfo, removed.chatStats)
|
ChatTagsModel.shared.removePresetChatTags(removed.chatInfo, removed.chatStats)
|
||||||
|
removeWallpaperFilesFromChat(removed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1025,6 +1026,23 @@ final class ChatModel: ObservableObject {
|
||||||
_ = upsertGroupMember(groupInfo, updatedMember)
|
_ = upsertGroupMember(groupInfo, updatedMember)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeWallpaperFilesFromChat(_ chat: Chat) {
|
||||||
|
if case let .direct(contact) = chat.chatInfo {
|
||||||
|
removeWallpaperFilesFromTheme(contact.uiThemes)
|
||||||
|
} else if case let .group(groupInfo) = chat.chatInfo {
|
||||||
|
removeWallpaperFilesFromTheme(groupInfo.uiThemes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeWallpaperFilesFromAllChats(_ user: User) {
|
||||||
|
// Currently, only removing everything from currently active user is supported. Inactive users are TODO
|
||||||
|
if user.userId == currentUser?.userId {
|
||||||
|
chats.forEach {
|
||||||
|
removeWallpaperFilesFromChat($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ShowingInvitation {
|
struct ShowingInvitation {
|
||||||
|
|
|
@ -298,6 +298,7 @@ struct UserProfilesView: View {
|
||||||
private func removeUser(_ user: User, _ delSMPQueues: Bool, viewPwd: String?) async {
|
private func removeUser(_ user: User, _ delSMPQueues: Bool, viewPwd: String?) async {
|
||||||
do {
|
do {
|
||||||
if user.activeUser {
|
if user.activeUser {
|
||||||
|
ChatModel.shared.removeWallpaperFilesFromAllChats(user)
|
||||||
if let newActive = m.users.first(where: { u in !u.user.activeUser && !u.user.hidden }) {
|
if let newActive = m.users.first(where: { u in !u.user.activeUser && !u.user.hidden }) {
|
||||||
try await changeActiveUserAsync_(newActive.user.userId, viewPwd: nil)
|
try await changeActiveUserAsync_(newActive.user.userId, viewPwd: nil)
|
||||||
try await deleteUser()
|
try await deleteUser()
|
||||||
|
@ -323,6 +324,7 @@ struct UserProfilesView: View {
|
||||||
|
|
||||||
func deleteUser() async throws {
|
func deleteUser() async throws {
|
||||||
try await apiDeleteUser(user.userId, delSMPQueues, viewPwd: viewPwd)
|
try await apiDeleteUser(user.userId, delSMPQueues, viewPwd: viewPwd)
|
||||||
|
removeWallpaperFilesFromTheme(user.uiThemes)
|
||||||
await MainActor.run { withAnimation { m.removeUser(user) } }
|
await MainActor.run { withAnimation { m.removeUser(user) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,17 +267,26 @@ public func saveWallpaperFile(image: UIImage) -> String? {
|
||||||
|
|
||||||
public func removeWallpaperFile(fileName: String? = nil) {
|
public func removeWallpaperFile(fileName: String? = nil) {
|
||||||
do {
|
do {
|
||||||
try FileManager.default.contentsOfDirectory(atPath: getWallpaperDirectory().path).forEach {
|
try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: getWallpaperDirectory().path), includingPropertiesForKeys: nil, options: []).forEach { url in
|
||||||
if URL(fileURLWithPath: $0).lastPathComponent == fileName { try FileManager.default.removeItem(atPath: $0) }
|
if url.lastPathComponent == fileName {
|
||||||
|
try FileManager.default.removeItem(at: url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("FileUtils.removeWallpaperFile error: \(error.localizedDescription)")
|
logger.error("FileUtils.removeWallpaperFile error: \(error)")
|
||||||
}
|
}
|
||||||
if let fileName {
|
if let fileName {
|
||||||
WallpaperType.cachedImages.removeValue(forKey: fileName)
|
WallpaperType.cachedImages.removeValue(forKey: fileName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func removeWallpaperFilesFromTheme(_ theme: ThemeModeOverrides?) {
|
||||||
|
if let theme {
|
||||||
|
removeWallpaperFile(fileName: theme.light?.wallpaper?.imageFile)
|
||||||
|
removeWallpaperFile(fileName: theme.dark?.wallpaper?.imageFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func generateNewFileName(_ prefix: String, _ ext: String, fullPath: Bool = false) -> String {
|
public func generateNewFileName(_ prefix: String, _ ext: String, fullPath: Bool = false) -> String {
|
||||||
uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)", fullPath: fullPath)
|
uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)", fullPath: fullPath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -726,17 +726,11 @@ object ChatModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeChat(rhId: Long?, id: String) {
|
fun removeChat(rhId: Long?, id: String) {
|
||||||
var removed: Chat? = null
|
val i = getChatIndex(rhId, id)
|
||||||
chats.removeAll {
|
if (i != -1) {
|
||||||
val found = it.id == id && it.remoteHostId == rhId
|
val chat = chats.removeAt(i)
|
||||||
if (found) {
|
removePresetChatTags(chat.chatInfo, chat.chatStats)
|
||||||
removed = it
|
removeWallpaperFilesFromChat(chat)
|
||||||
}
|
|
||||||
found
|
|
||||||
}
|
|
||||||
|
|
||||||
removed?.let {
|
|
||||||
removePresetChatTags(it.chatInfo, it.chatStats)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import androidx.compose.ui.unit.*
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.ui.theme.ThemeModeOverrides
|
||||||
import chat.simplex.common.ui.theme.ThemeOverrides
|
import chat.simplex.common.ui.theme.ThemeOverrides
|
||||||
import chat.simplex.common.views.chatlist.connectIfOpenedViaUri
|
import chat.simplex.common.views.chatlist.connectIfOpenedViaUri
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
@ -316,6 +317,30 @@ fun removeWallpaperFile(fileName: String? = null) {
|
||||||
WallpaperType.cachedImages.remove(fileName)
|
WallpaperType.cachedImages.remove(fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeWallpaperFilesFromTheme(theme: ThemeModeOverrides?) {
|
||||||
|
if (theme != null) {
|
||||||
|
removeWallpaperFile(theme.light?.wallpaper?.imageFile)
|
||||||
|
removeWallpaperFile(theme.dark?.wallpaper?.imageFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeWallpaperFilesFromChat(chat: Chat) {
|
||||||
|
if (chat.chatInfo is ChatInfo.Direct) {
|
||||||
|
removeWallpaperFilesFromTheme(chat.chatInfo.contact.uiThemes)
|
||||||
|
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||||
|
removeWallpaperFilesFromTheme(chat.chatInfo.groupInfo.uiThemes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeWallpaperFilesFromAllChats(user: User) {
|
||||||
|
// Currently, only removing everything from currently active user is supported. Inactive users are TODO
|
||||||
|
if (user.userId == chatModel.currentUser.value?.userId) {
|
||||||
|
chatModel.chats.value.forEach {
|
||||||
|
removeWallpaperFilesFromChat(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> createTmpFileAndDelete(dir: File = tmpDir, onCreated: (File) -> T): T {
|
fun <T> createTmpFileAndDelete(dir: File = tmpDir, onCreated: (File) -> T): T {
|
||||||
val tmpFile = File(dir, UUID.randomUUID().toString())
|
val tmpFile = File(dir, UUID.randomUUID().toString())
|
||||||
tmpFile.parentFile.mkdirs()
|
tmpFile.parentFile.mkdirs()
|
||||||
|
|
|
@ -347,6 +347,7 @@ private suspend fun doRemoveUser(m: ChatModel, user: User, users: List<User>, de
|
||||||
try {
|
try {
|
||||||
when {
|
when {
|
||||||
user.activeUser -> {
|
user.activeUser -> {
|
||||||
|
removeWallpaperFilesFromAllChats(user)
|
||||||
val newActive = users.firstOrNull { u -> !u.activeUser && !u.hidden }
|
val newActive = users.firstOrNull { u -> !u.activeUser && !u.hidden }
|
||||||
if (newActive != null) {
|
if (newActive != null) {
|
||||||
m.controller.changeActiveUser_(user.remoteHostId, newActive.userId, null)
|
m.controller.changeActiveUser_(user.remoteHostId, newActive.userId, null)
|
||||||
|
@ -366,6 +367,7 @@ private suspend fun doRemoveUser(m: ChatModel, user: User, users: List<User>, de
|
||||||
m.controller.apiDeleteUser(user, delSMPQueues, viewPwd)
|
m.controller.apiDeleteUser(user, delSMPQueues, viewPwd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
removeWallpaperFilesFromTheme(user.uiThemes)
|
||||||
m.removeUser(user)
|
m.removeUser(user)
|
||||||
ntfManager.cancelNotificationsForUser(user.userId)
|
ntfManager.cancelNotificationsForUser(user.userId)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
@ -106,7 +106,7 @@ import Simplex.Messaging.Version
|
||||||
import Simplex.RemoteControl.Invitation (RCInvitation (..), RCSignedInvitation (..))
|
import Simplex.RemoteControl.Invitation (RCInvitation (..), RCSignedInvitation (..))
|
||||||
import Simplex.RemoteControl.Types (RCCtrlAddress (..))
|
import Simplex.RemoteControl.Types (RCCtrlAddress (..))
|
||||||
import System.Exit (ExitCode, exitSuccess)
|
import System.Exit (ExitCode, exitSuccess)
|
||||||
import System.FilePath (takeFileName, (</>))
|
import System.FilePath (takeExtension, takeFileName, (</>))
|
||||||
import System.IO (Handle, IOMode (..))
|
import System.IO (Handle, IOMode (..))
|
||||||
import System.Random (randomRIO)
|
import System.Random (randomRIO)
|
||||||
import UnliftIO.Async
|
import UnliftIO.Async
|
||||||
|
@ -146,6 +146,15 @@ imageExtensions = [".jpg", ".jpeg", ".png", ".gif"]
|
||||||
fixedImagePreview :: ImageData
|
fixedImagePreview :: ImageData
|
||||||
fixedImagePreview = ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg=="
|
fixedImagePreview = ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg=="
|
||||||
|
|
||||||
|
imageFilePrefix :: String
|
||||||
|
imageFilePrefix = "IMG_"
|
||||||
|
|
||||||
|
voiceFilePrefix :: String
|
||||||
|
voiceFilePrefix = "voice_"
|
||||||
|
|
||||||
|
videoFilePrefix :: String
|
||||||
|
videoFilePrefix = "video_"
|
||||||
|
|
||||||
-- enableSndFiles has no effect when mainApp is True
|
-- enableSndFiles has no effect when mainApp is True
|
||||||
startChatController :: Bool -> Bool -> CM' (Async ())
|
startChatController :: Bool -> Bool -> CM' (Async ())
|
||||||
startChatController mainApp enableSndFiles = do
|
startChatController mainApp enableSndFiles = do
|
||||||
|
@ -897,7 +906,8 @@ processChatCommand' vr = \case
|
||||||
ifM
|
ifM
|
||||||
(doesFileExist fsFromPath)
|
(doesFileExist fsFromPath)
|
||||||
( do
|
( do
|
||||||
fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName
|
newFileName <- liftIO $ maybe (pure fileName) (generateNewFileName fileName) $ mediaFilePrefix mc
|
||||||
|
fsNewPath <- liftIO $ filesFolder `uniqueCombine` newFileName
|
||||||
liftIO $ B.writeFile fsNewPath "" -- create empty file
|
liftIO $ B.writeFile fsNewPath "" -- create empty file
|
||||||
encrypt <- chatReadVar encryptLocalFiles
|
encrypt <- chatReadVar encryptLocalFiles
|
||||||
cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing
|
cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing
|
||||||
|
@ -934,6 +944,17 @@ processChatCommand' vr = \case
|
||||||
when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF"
|
when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF"
|
||||||
liftIO . CF.hPut w $ LB.fromStrict ch
|
liftIO . CF.hPut w $ LB.fromStrict ch
|
||||||
when (size' > 0) $ copyChunks r w size'
|
when (size' > 0) $ copyChunks r w size'
|
||||||
|
mediaFilePrefix :: MsgContent -> Maybe FilePath
|
||||||
|
mediaFilePrefix = \case
|
||||||
|
MCImage {} -> Just imageFilePrefix
|
||||||
|
MCVoice {} -> Just voiceFilePrefix
|
||||||
|
MCVideo {} -> Just videoFilePrefix
|
||||||
|
_ -> Nothing
|
||||||
|
generateNewFileName fileName prefix = do
|
||||||
|
currentDate <- liftIO getCurrentTime
|
||||||
|
let formattedDate = formatTime defaultTimeLocale "%Y%m%d_%H%M%S" currentDate
|
||||||
|
let ext = takeExtension fileName
|
||||||
|
pure $ prefix <> formattedDate <> ext
|
||||||
APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user
|
APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user
|
||||||
UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId
|
UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId
|
||||||
APIChatRead chatRef@(ChatRef cType chatId) -> withUser $ \_ -> case cType of
|
APIChatRead chatRef@(ChatRef cType chatId) -> withUser $ \_ -> case cType of
|
||||||
|
|
|
@ -785,8 +785,9 @@ testMultiForwardFiles =
|
||||||
bob <## " message without file"
|
bob <## " message without file"
|
||||||
|
|
||||||
bob <# "@cath <- @alice"
|
bob <# "@cath <- @alice"
|
||||||
bob <## " test_1.jpg"
|
|
||||||
bob <# "/f @cath test_1.jpg"
|
jpgFileName <- T.unpack . T.strip . T.pack <$> getTermLine bob
|
||||||
|
bob <# ("/f @cath " <> jpgFileName)
|
||||||
bob <## "use /fc 5 to cancel sending"
|
bob <## "use /fc 5 to cancel sending"
|
||||||
|
|
||||||
bob <# "@cath <- @alice"
|
bob <# "@cath <- @alice"
|
||||||
|
@ -808,8 +809,8 @@ testMultiForwardFiles =
|
||||||
cath <## " message without file"
|
cath <## " message without file"
|
||||||
|
|
||||||
cath <# "bob> -> forwarded"
|
cath <# "bob> -> forwarded"
|
||||||
cath <## " test_1.jpg"
|
cath <## (" " <> jpgFileName)
|
||||||
cath <# "bob> sends file test_1.jpg (136.5 KiB / 139737 bytes)"
|
cath <# ("bob> sends file " <> jpgFileName <> " (136.5 KiB / 139737 bytes)")
|
||||||
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||||
|
|
||||||
cath <# "bob> -> forwarded"
|
cath <# "bob> -> forwarded"
|
||||||
|
@ -824,15 +825,15 @@ testMultiForwardFiles =
|
||||||
cath <## ""
|
cath <## ""
|
||||||
|
|
||||||
-- file transfer
|
-- file transfer
|
||||||
bob <## "completed uploading file 5 (test_1.jpg) for cath"
|
bob <## ("completed uploading file 5 (" <> jpgFileName <> ") for cath")
|
||||||
bob <## "completed uploading file 6 (test_1.pdf) for cath"
|
bob <## "completed uploading file 6 (test_1.pdf) for cath"
|
||||||
|
|
||||||
cath ##> "/fr 1"
|
cath ##> "/fr 1"
|
||||||
cath
|
cath
|
||||||
<### [ "saving file 1 from bob to test_1.jpg",
|
<### [ ConsoleString $ "saving file 1 from bob to " <> jpgFileName,
|
||||||
"started receiving file 1 (test_1.jpg) from bob"
|
ConsoleString $ "started receiving file 1 (" <> jpgFileName <> ") from bob"
|
||||||
]
|
]
|
||||||
cath <## "completed receiving file 1 (test_1.jpg) from bob"
|
cath <## ("completed receiving file 1 (" <> jpgFileName <> ") from bob")
|
||||||
|
|
||||||
cath ##> "/fr 2"
|
cath ##> "/fr 2"
|
||||||
cath
|
cath
|
||||||
|
@ -841,9 +842,9 @@ testMultiForwardFiles =
|
||||||
]
|
]
|
||||||
cath <## "completed receiving file 2 (test_1.pdf) from bob"
|
cath <## "completed receiving file 2 (test_1.pdf) from bob"
|
||||||
|
|
||||||
src1B <- B.readFile "./tests/tmp/bob_app_files/test_1.jpg"
|
src1B <- B.readFile ("./tests/tmp/bob_app_files/" <> jpgFileName)
|
||||||
src1B `shouldBe` dest1
|
src1B `shouldBe` dest1
|
||||||
dest1C <- B.readFile "./tests/tmp/cath_app_files/test_1.jpg"
|
dest1C <- B.readFile ("./tests/tmp/cath_app_files/" <> jpgFileName)
|
||||||
dest1C `shouldBe` src1B
|
dest1C `shouldBe` src1B
|
||||||
|
|
||||||
src2B <- B.readFile "./tests/tmp/bob_app_files/test_1.pdf"
|
src2B <- B.readFile "./tests/tmp/bob_app_files/test_1.pdf"
|
||||||
|
@ -886,5 +887,5 @@ testMultiForwardFiles =
|
||||||
checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do
|
checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do
|
||||||
bob ##> "/clear alice"
|
bob ##> "/clear alice"
|
||||||
bob <## "alice: all messages are removed locally ONLY"
|
bob <## "alice: all messages are removed locally ONLY"
|
||||||
fwdFileExists <- doesFileExist "./tests/tmp/bob_app_files/test_1.jpg"
|
fwdFileExists <- doesFileExist ("./tests/tmp/bob_app_files/" <> jpgFileName)
|
||||||
fwdFileExists `shouldBe` True
|
fwdFileExists `shouldBe` True
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue