mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1d4f52185 | |||
| 373070f388 | |||
| a03ccd0ba6 | |||
| a7982cccf9 | |||
| dccd6631af | |||
| b0516b6440 | |||
| bf91666f07 | |||
| ab47a5a27e | |||
| 23581f59ae | |||
| 291df6e9d0 | |||
| dae0b63c22 | |||
| 0964982782 | |||
| 9eb6034e1a |
@@ -34,8 +34,7 @@ struct GroupPreferencesView: View {
|
|||||||
featureSection(.reactions, $preferences.reactions.enable)
|
featureSection(.reactions, $preferences.reactions.enable)
|
||||||
featureSection(.voice, $preferences.voice.enable, $preferences.voice.role)
|
featureSection(.voice, $preferences.voice.enable, $preferences.voice.role)
|
||||||
featureSection(.files, $preferences.files.enable, $preferences.files.role)
|
featureSection(.files, $preferences.files.enable, $preferences.files.role)
|
||||||
// TODO enable simplexLinks preference in 5.8
|
featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role)
|
||||||
// featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role)
|
|
||||||
featureSection(.history, $preferences.history.enable)
|
featureSection(.history, $preferences.history.enable)
|
||||||
|
|
||||||
if groupInfo.canEdit {
|
if groupInfo.canEdit {
|
||||||
@@ -102,8 +101,6 @@ struct GroupPreferencesView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 36)
|
.frame(height: 36)
|
||||||
// remove in v5.8
|
|
||||||
.disabled(true)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
settingsRow(icon, color: color) {
|
settingsRow(icon, color: color) {
|
||||||
|
|||||||
@@ -76,11 +76,6 @@
|
|||||||
5C9CC7AD28C55D7800BEF955 /* DatabaseEncryptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */; };
|
5C9CC7AD28C55D7800BEF955 /* DatabaseEncryptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */; };
|
||||||
5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D13A2282187BB00AB8B43 /* WebRTC.swift */; };
|
5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D13A2282187BB00AB8B43 /* WebRTC.swift */; };
|
||||||
5C9D811A2AA8727A001D49FD /* CryptoFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */; };
|
5C9D811A2AA8727A001D49FD /* CryptoFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */; };
|
||||||
5C9F3DD62BFBCDD90003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD12BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a */; };
|
|
||||||
5C9F3DD72BFBCDD90003B86B /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD22BFBCDD80003B86B /* libffi.a */; };
|
|
||||||
5C9F3DD82BFBCDD90003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD32BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a */; };
|
|
||||||
5C9F3DD92BFBCDD90003B86B /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD42BFBCDD90003B86B /* libgmpxx.a */; };
|
|
||||||
5C9F3DDA2BFBCDD90003B86B /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD52BFBCDD90003B86B /* libgmp.a */; };
|
|
||||||
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */; };
|
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */; };
|
||||||
5CA059DC279559F40002BEB4 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DB279559F40002BEB4 /* Tests_iOS.swift */; };
|
5CA059DC279559F40002BEB4 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DB279559F40002BEB4 /* Tests_iOS.swift */; };
|
||||||
5CA059DE279559F40002BEB4 /* Tests_iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */; };
|
5CA059DE279559F40002BEB4 /* Tests_iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */; };
|
||||||
@@ -144,6 +139,11 @@
|
|||||||
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
|
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
|
||||||
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; };
|
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; };
|
||||||
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; };
|
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; };
|
||||||
|
5CEE87942C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE878F2C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a */; };
|
||||||
|
5CEE87952C024F4F00583B8A /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87902C024F4F00583B8A /* libgmp.a */; };
|
||||||
|
5CEE87962C024F4F00583B8A /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87912C024F4F00583B8A /* libgmpxx.a */; };
|
||||||
|
5CEE87972C024F4F00583B8A /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87922C024F4F00583B8A /* libffi.a */; };
|
||||||
|
5CEE87982C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87932C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a */; };
|
||||||
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
|
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
|
||||||
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
|
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
|
||||||
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
|
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
|
||||||
@@ -354,11 +354,6 @@
|
|||||||
5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseEncryptionView.swift; sourceTree = "<group>"; };
|
5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseEncryptionView.swift; sourceTree = "<group>"; };
|
||||||
5C9D13A2282187BB00AB8B43 /* WebRTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTC.swift; sourceTree = "<group>"; };
|
5C9D13A2282187BB00AB8B43 /* WebRTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTC.swift; sourceTree = "<group>"; };
|
||||||
5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoFile.swift; sourceTree = "<group>"; };
|
5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoFile.swift; sourceTree = "<group>"; };
|
||||||
5C9F3DD12BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a"; sourceTree = "<group>"; };
|
|
||||||
5C9F3DD22BFBCDD80003B86B /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
|
||||||
5C9F3DD32BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a"; sourceTree = "<group>"; };
|
|
||||||
5C9F3DD42BFBCDD90003B86B /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
|
||||||
5C9F3DD52BFBCDD90003B86B /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
|
||||||
5C9FD96A27A56D4D0075386C /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
|
5C9FD96A27A56D4D0075386C /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
|
||||||
5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageView.swift; sourceTree = "<group>"; };
|
5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageView.swift; sourceTree = "<group>"; };
|
||||||
5CA059C3279559F40002BEB4 /* SimpleXApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXApp.swift; sourceTree = "<group>"; };
|
5CA059C3279559F40002BEB4 /* SimpleXApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXApp.swift; sourceTree = "<group>"; };
|
||||||
@@ -440,6 +435,11 @@
|
|||||||
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
|
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
|
||||||
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = "<group>"; };
|
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = "<group>"; };
|
||||||
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = "<group>"; };
|
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = "<group>"; };
|
||||||
|
5CEE878F2C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||||
|
5CEE87902C024F4F00583B8A /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||||
|
5CEE87912C024F4F00583B8A /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||||
|
5CEE87922C024F4F00583B8A /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||||
|
5CEE87932C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a"; sourceTree = "<group>"; };
|
||||||
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; };
|
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; };
|
||||||
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; };
|
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; };
|
||||||
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
|
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
|
||||||
@@ -529,13 +529,13 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
5C9F3DD92BFBCDD90003B86B /* libgmpxx.a in Frameworks */,
|
5CEE87942C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a in Frameworks */,
|
||||||
5C9F3DDA2BFBCDD90003B86B /* libgmp.a in Frameworks */,
|
5CEE87972C024F4F00583B8A /* libffi.a in Frameworks */,
|
||||||
5C9F3DD82BFBCDD90003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a in Frameworks */,
|
5CEE87962C024F4F00583B8A /* libgmpxx.a in Frameworks */,
|
||||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||||
5C9F3DD62BFBCDD90003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a in Frameworks */,
|
5CEE87952C024F4F00583B8A /* libgmp.a in Frameworks */,
|
||||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||||
5C9F3DD72BFBCDD90003B86B /* libffi.a in Frameworks */,
|
5CEE87982C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -601,11 +601,11 @@
|
|||||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5C9F3DD22BFBCDD80003B86B /* libffi.a */,
|
5CEE87922C024F4F00583B8A /* libffi.a */,
|
||||||
5C9F3DD52BFBCDD90003B86B /* libgmp.a */,
|
5CEE87902C024F4F00583B8A /* libgmp.a */,
|
||||||
5C9F3DD42BFBCDD90003B86B /* libgmpxx.a */,
|
5CEE87912C024F4F00583B8A /* libgmpxx.a */,
|
||||||
5C9F3DD12BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a */,
|
5CEE878F2C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a */,
|
||||||
5C9F3DD32BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a */,
|
5CEE87932C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a */,
|
||||||
);
|
);
|
||||||
path = Libraries;
|
path = Libraries;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1552,7 +1552,7 @@
|
|||||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 218;
|
CURRENT_PROJECT_VERSION = 220;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -1601,7 +1601,7 @@
|
|||||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 218;
|
CURRENT_PROJECT_VERSION = 220;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -1687,7 +1687,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 218;
|
CURRENT_PROJECT_VERSION = 220;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
GCC_OPTIMIZATION_LEVEL = s;
|
GCC_OPTIMIZATION_LEVEL = s;
|
||||||
@@ -1724,7 +1724,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 218;
|
CURRENT_PROJECT_VERSION = 220;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_CODE_COVERAGE = NO;
|
ENABLE_CODE_COVERAGE = NO;
|
||||||
@@ -1761,7 +1761,7 @@
|
|||||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 218;
|
CURRENT_PROJECT_VERSION = 220;
|
||||||
DEFINES_MODULE = YES;
|
DEFINES_MODULE = YES;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
@@ -1812,7 +1812,7 @@
|
|||||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 218;
|
CURRENT_PROJECT_VERSION = 220;
|
||||||
DEFINES_MODULE = YES;
|
DEFINES_MODULE = YES;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import chat.simplex.app.SimplexService.Companion.showPassphraseNotification
|
|||||||
import chat.simplex.common.model.ChatController
|
import chat.simplex.common.model.ChatController
|
||||||
import chat.simplex.common.views.helpers.DBMigrationResult
|
import chat.simplex.common.views.helpers.DBMigrationResult
|
||||||
import chat.simplex.common.platform.chatModel
|
import chat.simplex.common.platform.chatModel
|
||||||
import chat.simplex.common.platform.initChatControllerAndRunMigrations
|
import chat.simplex.common.platform.initChatControllerOnStart
|
||||||
import chat.simplex.common.views.helpers.DatabaseUtils
|
import chat.simplex.common.views.helpers.DatabaseUtils
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -60,7 +60,7 @@ class MessagesFetcherWork(
|
|||||||
try {
|
try {
|
||||||
// In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here
|
// In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here
|
||||||
if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) {
|
if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) {
|
||||||
initChatControllerAndRunMigrations()
|
initChatControllerOnStart()
|
||||||
}
|
}
|
||||||
withTimeout(durationSeconds * 1000L) {
|
withTimeout(durationSeconds * 1000L) {
|
||||||
val chatController = ChatController
|
val chatController = ChatController
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.content.Context
|
|||||||
import chat.simplex.common.platform.Log
|
import chat.simplex.common.platform.Log
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.media.AudioManager
|
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -66,6 +65,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
context = this
|
context = this
|
||||||
initHaskell()
|
initHaskell()
|
||||||
initMultiplatform()
|
initMultiplatform()
|
||||||
|
runMigrations()
|
||||||
tmpDir.deleteRecursively()
|
tmpDir.deleteRecursively()
|
||||||
tmpDir.mkdir()
|
tmpDir.mkdir()
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
// It's important, otherwise, user may be locked in undefined state
|
// It's important, otherwise, user may be locked in undefined state
|
||||||
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||||
} else if (DatabaseUtils.ksAppPassword.get() == null || DatabaseUtils.ksSelfDestructPassword.get() == null) {
|
} else if (DatabaseUtils.ksAppPassword.get() == null || DatabaseUtils.ksSelfDestructPassword.get() == null) {
|
||||||
initChatControllerAndRunMigrations()
|
initChatControllerOnStart()
|
||||||
}
|
}
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this@SimplexApp)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(this@SimplexApp)
|
||||||
}
|
}
|
||||||
@@ -254,7 +254,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
override fun androidSetNightModeIfSupported() {
|
override fun androidSetNightModeIfSupported() {
|
||||||
if (Build.VERSION.SDK_INT < 31) return
|
if (Build.VERSION.SDK_INT < 31) return
|
||||||
|
|
||||||
val light = if (CurrentColors.value.name == DefaultTheme.SYSTEM.name) {
|
val light = if (CurrentColors.value.name == DefaultTheme.SYSTEM_THEME_NAME) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
CurrentColors.value.colors.isLight
|
CurrentColors.value.colors.isLight
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class SimplexService: Service() {
|
|||||||
isServiceStarted = true
|
isServiceStarted = true
|
||||||
// In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here
|
// In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here
|
||||||
if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) {
|
if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) {
|
||||||
initChatControllerAndRunMigrations()
|
initChatControllerOnStart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ kotlin {
|
|||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlin("test"))
|
implementation(kotlin("test"))
|
||||||
|
implementation(kotlin("test-junit"))
|
||||||
|
implementation(kotlin("test-common"))
|
||||||
|
implementation(kotlin("test-annotations-common"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val androidMain by getting {
|
val androidMain by getting {
|
||||||
|
|||||||
+2
@@ -15,8 +15,10 @@ actual val dataDir: File = androidAppContext.dataDir
|
|||||||
actual val tmpDir: File = androidAppContext.getDir("temp", Application.MODE_PRIVATE)
|
actual val tmpDir: File = androidAppContext.getDir("temp", Application.MODE_PRIVATE)
|
||||||
actual val filesDir: File = File(dataDir.absolutePath + File.separator + "files")
|
actual val filesDir: File = File(dataDir.absolutePath + File.separator + "files")
|
||||||
actual val appFilesDir: File = File(filesDir.absolutePath + File.separator + "app_files")
|
actual val appFilesDir: File = File(filesDir.absolutePath + File.separator + "app_files")
|
||||||
|
actual val wallpapersDir: File = File(filesDir.absolutePath + File.separator + "assets" + File.separator + "wallpapers").also { it.mkdirs() }
|
||||||
actual val coreTmpDir: File = File(filesDir.absolutePath + File.separator + "temp_files")
|
actual val coreTmpDir: File = File(filesDir.absolutePath + File.separator + "temp_files")
|
||||||
actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "files"
|
actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "files"
|
||||||
|
actual val preferencesDir = File(dataDir.absolutePath + File.separator + "shared_prefs")
|
||||||
|
|
||||||
actual val chatDatabaseFileName: String = "files_chat.db"
|
actual val chatDatabaseFileName: String = "files_chat.db"
|
||||||
actual val agentDatabaseFileName: String = "files_agent.db"
|
actual val agentDatabaseFileName: String = "files_agent.db"
|
||||||
|
|||||||
+7
@@ -7,6 +7,8 @@ import android.content.SharedPreferences
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.text.BidiFormatter
|
import android.text.BidiFormatter
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
@@ -14,9 +16,11 @@ import androidx.compose.ui.text.font.FontStyle
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import chat.simplex.common.model.AppPreferences
|
import chat.simplex.common.model.AppPreferences
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
import com.russhwolf.settings.SharedPreferencesSettings
|
import com.russhwolf.settings.SharedPreferencesSettings
|
||||||
|
import dev.icerock.moko.resources.ImageResource
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import dev.icerock.moko.resources.desc.desc
|
import dev.icerock.moko.resources.desc.desc
|
||||||
|
|
||||||
@@ -51,3 +55,6 @@ actual fun windowWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp
|
|||||||
actual fun desktopExpandWindowToWidth(width: Dp) {}
|
actual fun desktopExpandWindowToWidth(width: Dp) {}
|
||||||
|
|
||||||
actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text)
|
actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text)
|
||||||
|
|
||||||
|
actual fun ImageResource.toComposeImageBitmap(): ImageBitmap? =
|
||||||
|
getDrawable(androidAppContext)?.toBitmap()?.asImageBitmap()
|
||||||
|
|||||||
+8
-23
@@ -19,14 +19,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.*
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
|
||||||
import chat.simplex.common.R
|
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
@@ -34,7 +30,6 @@ import chat.simplex.common.model.ChatModel
|
|||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.helpers.APPLICATION_ID
|
import chat.simplex.common.helpers.APPLICATION_ID
|
||||||
import chat.simplex.common.helpers.saveAppLocale
|
import chat.simplex.common.helpers.saveAppLocale
|
||||||
import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor
|
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.ImageResource
|
import dev.icerock.moko.resources.ImageResource
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
@@ -46,9 +41,8 @@ enum class AppIcon(val image: ImageResource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) {
|
actual fun AppearanceView(m: ChatModel) {
|
||||||
val appIcon = remember { mutableStateOf(findEnabledIcon()) }
|
val appIcon = remember { mutableStateOf(findEnabledIcon()) }
|
||||||
|
|
||||||
fun setAppIcon(newIcon: AppIcon) {
|
fun setAppIcon(newIcon: AppIcon) {
|
||||||
if (appIcon.value == newIcon) return
|
if (appIcon.value == newIcon) return
|
||||||
val newComponent = ComponentName(APPLICATION_ID, "chat.simplex.app.MainActivity_${newIcon.name.lowercase()}")
|
val newComponent = ComponentName(APPLICATION_ID, "chat.simplex.app.MainActivity_${newIcon.name.lowercase()}")
|
||||||
@@ -65,18 +59,11 @@ actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatMod
|
|||||||
|
|
||||||
appIcon.value = newIcon
|
appIcon.value = newIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
AppearanceScope.AppearanceLayout(
|
AppearanceScope.AppearanceLayout(
|
||||||
appIcon,
|
appIcon,
|
||||||
m.controller.appPrefs.appLanguage,
|
m.controller.appPrefs.appLanguage,
|
||||||
m.controller.appPrefs.systemDarkTheme,
|
m.controller.appPrefs.systemDarkTheme,
|
||||||
changeIcon = ::setAppIcon,
|
changeIcon = ::setAppIcon,
|
||||||
showSettingsModal = showSettingsModal,
|
|
||||||
editColor = { name, initialColor ->
|
|
||||||
ModalManager.start.showModalCloseable { close ->
|
|
||||||
ColorEditor(name, initialColor, close)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +73,6 @@ fun AppearanceScope.AppearanceLayout(
|
|||||||
languagePref: SharedPreference<String?>,
|
languagePref: SharedPreference<String?>,
|
||||||
systemDarkTheme: SharedPreference<String?>,
|
systemDarkTheme: SharedPreference<String?>,
|
||||||
changeIcon: (AppIcon) -> Unit,
|
changeIcon: (AppIcon) -> Unit,
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
|
||||||
editColor: (ThemeColor, Color) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
@@ -120,6 +105,13 @@ fun AppearanceScope.AppearanceLayout(
|
|||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SectionDividerSpaced(maxTopPadding = true)
|
||||||
|
ThemesSection(systemDarkTheme)
|
||||||
|
|
||||||
|
SectionDividerSpaced(maxTopPadding = true)
|
||||||
|
ProfileImageSection()
|
||||||
|
|
||||||
SectionDividerSpaced()
|
SectionDividerSpaced()
|
||||||
|
|
||||||
SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) {
|
SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) {
|
||||||
@@ -145,11 +137,6 @@ fun AppearanceScope.AppearanceLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionDividerSpaced(maxTopPadding = true)
|
|
||||||
ProfileImageSection()
|
|
||||||
|
|
||||||
SectionDividerSpaced(maxTopPadding = true)
|
|
||||||
ThemesSection(systemDarkTheme, showSettingsModal, editColor)
|
|
||||||
SectionBottomSpacer()
|
SectionBottomSpacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,8 +156,6 @@ fun PreviewAppearanceSettings() {
|
|||||||
languagePref = SharedPreference({ null }, {}),
|
languagePref = SharedPreference({ null }, {}),
|
||||||
systemDarkTheme = SharedPreference({ null }, {}),
|
systemDarkTheme = SharedPreference({ null }, {}),
|
||||||
changeIcon = {},
|
changeIcon = {},
|
||||||
showSettingsModal = { {} },
|
|
||||||
editColor = { _, _ -> },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-7
@@ -4,7 +4,7 @@ import androidx.compose.material.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.font.*
|
import androidx.compose.ui.text.font.*
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
@@ -399,6 +399,18 @@ object ChatModel {
|
|||||||
currentUser.value = updated
|
currentUser.value = updated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateCurrentUserUiThemes(rhId: Long?, uiThemes: ThemeModeOverrides?) {
|
||||||
|
val current = currentUser.value ?: return
|
||||||
|
val updated = current.copy(
|
||||||
|
uiThemes = uiThemes
|
||||||
|
)
|
||||||
|
val i = users.indexOfFirst { it.user.userId == current.userId && it.user.remoteHostId == rhId }
|
||||||
|
if (i != -1) {
|
||||||
|
users[i] = users[i].copy(user = updated)
|
||||||
|
}
|
||||||
|
currentUser.value = updated
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem {
|
suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem {
|
||||||
val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct)
|
val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@@ -682,7 +694,8 @@ data class User(
|
|||||||
override val showNtfs: Boolean,
|
override val showNtfs: Boolean,
|
||||||
val sendRcptsContacts: Boolean,
|
val sendRcptsContacts: Boolean,
|
||||||
val sendRcptsSmallGroups: Boolean,
|
val sendRcptsSmallGroups: Boolean,
|
||||||
val viewPwdHash: UserPwdHash?
|
val viewPwdHash: UserPwdHash?,
|
||||||
|
val uiThemes: ThemeModeOverrides? = null,
|
||||||
): NamedChat, UserLike {
|
): NamedChat, UserLike {
|
||||||
override val displayName: String get() = profile.displayName
|
override val displayName: String get() = profile.displayName
|
||||||
override val fullName: String get() = profile.fullName
|
override val fullName: String get() = profile.fullName
|
||||||
@@ -709,6 +722,7 @@ data class User(
|
|||||||
sendRcptsContacts = true,
|
sendRcptsContacts = true,
|
||||||
sendRcptsSmallGroups = false,
|
sendRcptsSmallGroups = false,
|
||||||
viewPwdHash = null,
|
viewPwdHash = null,
|
||||||
|
uiThemes = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1041,7 +1055,8 @@ data class Contact(
|
|||||||
override val updatedAt: Instant,
|
override val updatedAt: Instant,
|
||||||
val chatTs: Instant?,
|
val chatTs: Instant?,
|
||||||
val contactGroupMemberId: Long? = null,
|
val contactGroupMemberId: Long? = null,
|
||||||
val contactGrpInvSent: Boolean
|
val contactGrpInvSent: Boolean,
|
||||||
|
val uiThemes: ThemeModeOverrides? = null,
|
||||||
): SomeChat, NamedChat {
|
): SomeChat, NamedChat {
|
||||||
override val chatType get() = ChatType.Direct
|
override val chatType get() = ChatType.Direct
|
||||||
override val id get() = "@$contactId"
|
override val id get() = "@$contactId"
|
||||||
@@ -1113,7 +1128,8 @@ data class Contact(
|
|||||||
createdAt = Clock.System.now(),
|
createdAt = Clock.System.now(),
|
||||||
updatedAt = Clock.System.now(),
|
updatedAt = Clock.System.now(),
|
||||||
chatTs = Clock.System.now(),
|
chatTs = Clock.System.now(),
|
||||||
contactGrpInvSent = false
|
contactGrpInvSent = false,
|
||||||
|
uiThemes = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1253,7 +1269,8 @@ data class GroupInfo (
|
|||||||
val chatSettings: ChatSettings,
|
val chatSettings: ChatSettings,
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant,
|
||||||
override val updatedAt: Instant,
|
override val updatedAt: Instant,
|
||||||
val chatTs: Instant?
|
val chatTs: Instant?,
|
||||||
|
val uiThemes: ThemeModeOverrides? = null,
|
||||||
): SomeChat, NamedChat {
|
): SomeChat, NamedChat {
|
||||||
override val chatType get() = ChatType.Group
|
override val chatType get() = ChatType.Group
|
||||||
override val id get() = "#$groupId"
|
override val id get() = "#$groupId"
|
||||||
@@ -1295,7 +1312,8 @@ data class GroupInfo (
|
|||||||
chatSettings = ChatSettings(enableNtfs = MsgFilter.All, sendRcpts = null, favorite = false),
|
chatSettings = ChatSettings(enableNtfs = MsgFilter.All, sendRcpts = null, favorite = false),
|
||||||
createdAt = Clock.System.now(),
|
createdAt = Clock.System.now(),
|
||||||
updatedAt = Clock.System.now(),
|
updatedAt = Clock.System.now(),
|
||||||
chatTs = Clock.System.now()
|
chatTs = Clock.System.now(),
|
||||||
|
uiThemes = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1915,12 +1933,13 @@ data class ChatItem (
|
|||||||
itemDeleted: CIDeleted? = null,
|
itemDeleted: CIDeleted? = null,
|
||||||
itemEdited: Boolean = false,
|
itemEdited: Boolean = false,
|
||||||
itemTimed: CITimed? = null,
|
itemTimed: CITimed? = null,
|
||||||
|
itemLive: Boolean = false,
|
||||||
deletable: Boolean = true,
|
deletable: Boolean = true,
|
||||||
editable: Boolean = true
|
editable: Boolean = true
|
||||||
) =
|
) =
|
||||||
ChatItem(
|
ChatItem(
|
||||||
chatDir = dir,
|
chatDir = dir,
|
||||||
meta = CIMeta.getSample(id, ts, text, status, sentViaProxy, itemForwarded, itemDeleted, itemEdited, itemTimed, deletable, editable),
|
meta = CIMeta.getSample(id, ts, text, status, sentViaProxy, itemForwarded, itemDeleted, itemEdited, itemTimed, itemLive, deletable, editable),
|
||||||
content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text)),
|
content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text)),
|
||||||
quotedItem = quotedItem,
|
quotedItem = quotedItem,
|
||||||
reactions = listOf(),
|
reactions = listOf(),
|
||||||
|
|||||||
+92
-34
@@ -26,8 +26,7 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.builtins.MapSerializer
|
import kotlinx.serialization.builtins.*
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@@ -168,13 +167,20 @@ class AppPreferences {
|
|||||||
val selfDestruct = mkBoolPreference(SHARED_PREFS_SELF_DESTRUCT, false)
|
val selfDestruct = mkBoolPreference(SHARED_PREFS_SELF_DESTRUCT, false)
|
||||||
val selfDestructDisplayName = mkStrPreference(SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME, null)
|
val selfDestructDisplayName = mkStrPreference(SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME, null)
|
||||||
|
|
||||||
val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name)
|
val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM_THEME_NAME)
|
||||||
val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.name)
|
val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.themeName)
|
||||||
val themeOverrides = mkMapPreference(SHARED_PREFS_THEMES, mapOf(), encode = {
|
val currentThemeIds = mkMapPreference(SHARED_PREFS_CURRENT_THEME_IDs, mapOf(), encode = {
|
||||||
|
json.encodeToString(MapSerializer(String.serializer(), String.serializer()), it)
|
||||||
|
}, decode = {
|
||||||
|
json.decodeFromString(MapSerializer(String.serializer(), String.serializer()), it)
|
||||||
|
})
|
||||||
|
// Deprecated. Remove key from preferences in 2025
|
||||||
|
val themeOverridesOld = mkMapPreference(SHARED_PREFS_THEMES_OLD, mapOf(), encode = {
|
||||||
json.encodeToString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it)
|
json.encodeToString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it)
|
||||||
}, decode = {
|
}, decode = {
|
||||||
json.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it)
|
jsonCoerceInputValues.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it)
|
||||||
}, settingsThemes)
|
}, settingsThemes)
|
||||||
|
val themeOverrides = mkThemeOverridesPreference()
|
||||||
val profileImageCornerRadius = mkFloatPreference(SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS, 22.5f)
|
val profileImageCornerRadius = mkFloatPreference(SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS, 22.5f)
|
||||||
|
|
||||||
val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null)
|
val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null)
|
||||||
@@ -269,6 +275,12 @@ class AppPreferences {
|
|||||||
set = fun(value) = prefs.putString(prefName, encode(value))
|
set = fun(value) = prefs.putString(prefName, encode(value))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun mkThemeOverridesPreference(): SharedPreference<List<ThemeOverrides>> =
|
||||||
|
SharedPreference(
|
||||||
|
get = fun() = themeOverridesStore ?: (readThemeOverrides()).also { themeOverridesStore = it },
|
||||||
|
set = fun(value) { if (writeThemeOverrides(value)) { themeOverridesStore = value } }
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS"
|
const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS"
|
||||||
internal const val SHARED_PREFS_THEMES_ID = "chat.simplex.app.THEMES"
|
internal const val SHARED_PREFS_THEMES_ID = "chat.simplex.app.THEMES"
|
||||||
@@ -345,8 +357,10 @@ class AppPreferences {
|
|||||||
private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName"
|
private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName"
|
||||||
private const val SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED = "PQExperimentalEnabled" // no longer used
|
private const val SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED = "PQExperimentalEnabled" // no longer used
|
||||||
private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme"
|
private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme"
|
||||||
|
private const val SHARED_PREFS_CURRENT_THEME_IDs = "CurrentThemeIds"
|
||||||
private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme"
|
private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme"
|
||||||
private const val SHARED_PREFS_THEMES = "Themes"
|
private const val SHARED_PREFS_THEMES_OLD = "Themes"
|
||||||
|
private const val SHARED_PREFS_THEME_OVERRIDES = "ThemeOverrides"
|
||||||
private const val SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS = "ProfileImageCornerRadius"
|
private const val SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS = "ProfileImageCornerRadius"
|
||||||
private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion"
|
private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion"
|
||||||
private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode"
|
private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode"
|
||||||
@@ -361,6 +375,8 @@ class AppPreferences {
|
|||||||
|
|
||||||
private const val SHARED_PREFS_IOS_CALL_KIT_ENABLED = "iOSCallKitEnabled"
|
private const val SHARED_PREFS_IOS_CALL_KIT_ENABLED = "iOSCallKitEnabled"
|
||||||
private const val SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS = "iOSCallKitCallsInRecents"
|
private const val SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS = "iOSCallKitCallsInRecents"
|
||||||
|
|
||||||
|
private var themeOverridesStore: List<ThemeOverrides>? = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,8 +461,13 @@ object ChatController {
|
|||||||
Log.d(TAG, "startChatWithTemporaryDatabase")
|
Log.d(TAG, "startChatWithTemporaryDatabase")
|
||||||
val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl)
|
val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl)
|
||||||
apiSetNetworkConfig(netCfg, ctrl)
|
apiSetNetworkConfig(netCfg, ctrl)
|
||||||
apiSetTempFolder(getMigrationTempFilesDirectory().absolutePath, ctrl)
|
apiSetAppFilePaths(
|
||||||
apiSetFilesFolder(getMigrationTempFilesDirectory().absolutePath, ctrl)
|
getMigrationTempFilesDirectory().absolutePath,
|
||||||
|
getMigrationTempFilesDirectory().absolutePath,
|
||||||
|
wallpapersDir.parentFile.absolutePath,
|
||||||
|
remoteHostsDir.absolutePath,
|
||||||
|
ctrl
|
||||||
|
)
|
||||||
apiStartChat(ctrl)
|
apiStartChat(ctrl)
|
||||||
return migrationActiveUser
|
return migrationActiveUser
|
||||||
}
|
}
|
||||||
@@ -665,22 +686,10 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiSetTempFolder(tempFolder: String, ctrl: ChatCtrl? = null) {
|
suspend fun apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String, remoteHostsFolder: String, ctrl: ChatCtrl? = null) {
|
||||||
val r = sendCmd(null, CC.SetTempFolder(tempFolder), ctrl)
|
val r = sendCmd(null, CC.ApiSetAppFilePaths(filesFolder, tempFolder, assetsFolder, remoteHostsFolder), ctrl)
|
||||||
if (r is CR.CmdOk) return
|
if (r is CR.CmdOk) return
|
||||||
throw Exception("failed to set temp folder: ${r.responseType} ${r.details}")
|
throw Exception("failed to set app file paths: ${r.responseType} ${r.details}")
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun apiSetFilesFolder(filesFolder: String, ctrl: ChatCtrl? = null) {
|
|
||||||
val r = sendCmd(null, CC.SetFilesFolder(filesFolder), ctrl)
|
|
||||||
if (r is CR.CmdOk) return
|
|
||||||
throw Exception("failed to set files folder: ${r.responseType} ${r.details}")
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun apiSetRemoteHostsFolder(remoteHostsFolder: String) {
|
|
||||||
val r = sendCmd(null, CC.SetRemoteHostsFolder(remoteHostsFolder))
|
|
||||||
if (r is CR.CmdOk) return
|
|
||||||
throw Exception("failed to set remote hosts folder: ${r.responseType} ${r.details}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(null, CC.ApiSetEncryptLocalFiles(enable))
|
suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(null, CC.ApiSetEncryptLocalFiles(enable))
|
||||||
@@ -1166,6 +1175,20 @@ object ChatController {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun apiSetUserUIThemes(rh: Long?, userId: Long, themes: ThemeModeOverrides?): Boolean {
|
||||||
|
val r = sendCmd(rh, CC.ApiSetUserUIThemes(userId, themes))
|
||||||
|
if (r is CR.CmdOk) return true
|
||||||
|
Log.e(TAG, "apiSetUserUIThemes bad response: ${r.responseType} ${r.details}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun apiSetChatUIThemes(rh: Long?, chatId: ChatId, themes: ThemeModeOverrides?): Boolean {
|
||||||
|
val r = sendCmd(rh, CC.ApiSetChatUIThemes(chatId, themes))
|
||||||
|
if (r is CR.CmdOk) return true
|
||||||
|
Log.e(TAG, "apiSetChatUIThemes bad response: ${r.responseType} ${r.details}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun apiCreateUserAddress(rh: Long?): String? {
|
suspend fun apiCreateUserAddress(rh: Long?): String? {
|
||||||
val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null }
|
val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null }
|
||||||
val r = sendCmd(rh, CC.ApiCreateMyAddress(userId))
|
val r = sendCmd(rh, CC.ApiCreateMyAddress(userId))
|
||||||
@@ -2437,9 +2460,8 @@ sealed class CC {
|
|||||||
class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean, val viewPwd: String?): CC()
|
class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean, val viewPwd: String?): CC()
|
||||||
class StartChat(val mainApp: Boolean): CC()
|
class StartChat(val mainApp: Boolean): CC()
|
||||||
class ApiStopChat: CC()
|
class ApiStopChat: CC()
|
||||||
class SetTempFolder(val tempFolder: String): CC()
|
@Serializable
|
||||||
class SetFilesFolder(val filesFolder: String): CC()
|
class ApiSetAppFilePaths(val appFilesFolder: String, val appTempFolder: String, val appAssetsFolder: String, val appRemoteHostsFolder: String): CC()
|
||||||
class SetRemoteHostsFolder(val remoteHostsFolder: String): CC()
|
|
||||||
class ApiSetEncryptLocalFiles(val enable: Boolean): CC()
|
class ApiSetEncryptLocalFiles(val enable: Boolean): CC()
|
||||||
class ApiExportArchive(val config: ArchiveConfig): CC()
|
class ApiExportArchive(val config: ArchiveConfig): CC()
|
||||||
class ApiImportArchive(val config: ArchiveConfig): CC()
|
class ApiImportArchive(val config: ArchiveConfig): CC()
|
||||||
@@ -2507,6 +2529,8 @@ sealed class CC {
|
|||||||
class ApiSetContactPrefs(val contactId: Long, val prefs: ChatPreferences): CC()
|
class ApiSetContactPrefs(val contactId: Long, val prefs: ChatPreferences): CC()
|
||||||
class ApiSetContactAlias(val contactId: Long, val localAlias: String): CC()
|
class ApiSetContactAlias(val contactId: Long, val localAlias: String): CC()
|
||||||
class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC()
|
class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC()
|
||||||
|
class ApiSetUserUIThemes(val userId: Long, val themes: ThemeModeOverrides?): CC()
|
||||||
|
class ApiSetChatUIThemes(val chatId: String, val themes: ThemeModeOverrides?): CC()
|
||||||
class ApiCreateMyAddress(val userId: Long): CC()
|
class ApiCreateMyAddress(val userId: Long): CC()
|
||||||
class ApiDeleteMyAddress(val userId: Long): CC()
|
class ApiDeleteMyAddress(val userId: Long): CC()
|
||||||
class ApiShowMyAddress(val userId: Long): CC()
|
class ApiShowMyAddress(val userId: Long): CC()
|
||||||
@@ -2574,9 +2598,7 @@ sealed class CC {
|
|||||||
is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}${maybePwd(viewPwd)}"
|
is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}${maybePwd(viewPwd)}"
|
||||||
is StartChat -> "/_start main=${onOff(mainApp)}"
|
is StartChat -> "/_start main=${onOff(mainApp)}"
|
||||||
is ApiStopChat -> "/_stop"
|
is ApiStopChat -> "/_stop"
|
||||||
is SetTempFolder -> "/_temp_folder $tempFolder"
|
is ApiSetAppFilePaths -> "/set file paths ${json.encodeToString(this)}"
|
||||||
is SetFilesFolder -> "/_files_folder $filesFolder"
|
|
||||||
is SetRemoteHostsFolder -> "/remote_hosts_folder $remoteHostsFolder"
|
|
||||||
is ApiSetEncryptLocalFiles -> "/_files_encrypt ${onOff(enable)}"
|
is ApiSetEncryptLocalFiles -> "/_files_encrypt ${onOff(enable)}"
|
||||||
is ApiExportArchive -> "/_db export ${json.encodeToString(config)}"
|
is ApiExportArchive -> "/_db export ${json.encodeToString(config)}"
|
||||||
is ApiImportArchive -> "/_db import ${json.encodeToString(config)}"
|
is ApiImportArchive -> "/_db import ${json.encodeToString(config)}"
|
||||||
@@ -2656,6 +2678,8 @@ sealed class CC {
|
|||||||
is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}"
|
is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}"
|
||||||
is ApiSetContactAlias -> "/_set alias @$contactId ${localAlias.trim()}"
|
is ApiSetContactAlias -> "/_set alias @$contactId ${localAlias.trim()}"
|
||||||
is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}"
|
is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}"
|
||||||
|
is ApiSetUserUIThemes -> "/_set theme user $userId ${if (themes != null) json.encodeToString(themes) else ""}"
|
||||||
|
is ApiSetChatUIThemes -> "/_set theme $chatId ${if (themes != null) json.encodeToString(themes) else ""}"
|
||||||
is ApiCreateMyAddress -> "/_address $userId"
|
is ApiCreateMyAddress -> "/_address $userId"
|
||||||
is ApiDeleteMyAddress -> "/_delete_address $userId"
|
is ApiDeleteMyAddress -> "/_delete_address $userId"
|
||||||
is ApiShowMyAddress -> "/_show_address $userId"
|
is ApiShowMyAddress -> "/_show_address $userId"
|
||||||
@@ -2720,9 +2744,7 @@ sealed class CC {
|
|||||||
is ApiDeleteUser -> "apiDeleteUser"
|
is ApiDeleteUser -> "apiDeleteUser"
|
||||||
is StartChat -> "startChat"
|
is StartChat -> "startChat"
|
||||||
is ApiStopChat -> "apiStopChat"
|
is ApiStopChat -> "apiStopChat"
|
||||||
is SetTempFolder -> "setTempFolder"
|
is ApiSetAppFilePaths -> "apiSetAppFilePaths"
|
||||||
is SetFilesFolder -> "setFilesFolder"
|
|
||||||
is SetRemoteHostsFolder -> "setRemoteHostsFolder"
|
|
||||||
is ApiSetEncryptLocalFiles -> "apiSetEncryptLocalFiles"
|
is ApiSetEncryptLocalFiles -> "apiSetEncryptLocalFiles"
|
||||||
is ApiExportArchive -> "apiExportArchive"
|
is ApiExportArchive -> "apiExportArchive"
|
||||||
is ApiImportArchive -> "apiImportArchive"
|
is ApiImportArchive -> "apiImportArchive"
|
||||||
@@ -2790,6 +2812,8 @@ sealed class CC {
|
|||||||
is ApiSetContactPrefs -> "apiSetContactPrefs"
|
is ApiSetContactPrefs -> "apiSetContactPrefs"
|
||||||
is ApiSetContactAlias -> "apiSetContactAlias"
|
is ApiSetContactAlias -> "apiSetContactAlias"
|
||||||
is ApiSetConnectionAlias -> "apiSetConnectionAlias"
|
is ApiSetConnectionAlias -> "apiSetConnectionAlias"
|
||||||
|
is ApiSetUserUIThemes -> "apiSetUserUIThemes"
|
||||||
|
is ApiSetChatUIThemes -> "apiSetChatUIThemes"
|
||||||
is ApiCreateMyAddress -> "apiCreateMyAddress"
|
is ApiCreateMyAddress -> "apiCreateMyAddress"
|
||||||
is ApiDeleteMyAddress -> "apiDeleteMyAddress"
|
is ApiDeleteMyAddress -> "apiDeleteMyAddress"
|
||||||
is ApiShowMyAddress -> "apiShowMyAddress"
|
is ApiShowMyAddress -> "apiShowMyAddress"
|
||||||
@@ -4061,6 +4085,15 @@ val json = Json {
|
|||||||
explicitNulls = false
|
explicitNulls = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Can decode unknown enum to default value specified for this field
|
||||||
|
val jsonCoerceInputValues = Json {
|
||||||
|
prettyPrint = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = false
|
||||||
|
coerceInputValues = true
|
||||||
|
}
|
||||||
|
|
||||||
val jsonShort = Json {
|
val jsonShort = Json {
|
||||||
prettyPrint = false
|
prettyPrint = false
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
@@ -5521,6 +5554,11 @@ data class AppSettings(
|
|||||||
var androidCallOnLockScreen: AppSettingsLockScreenCalls? = null,
|
var androidCallOnLockScreen: AppSettingsLockScreenCalls? = null,
|
||||||
var iosCallKitEnabled: Boolean? = null,
|
var iosCallKitEnabled: Boolean? = null,
|
||||||
var iosCallKitCallsInRecents: Boolean? = null,
|
var iosCallKitCallsInRecents: Boolean? = null,
|
||||||
|
var uiProfileImageCornerRadius: Float? = null,
|
||||||
|
var uiColorScheme: String? = null,
|
||||||
|
var uiDarkColorScheme: String? = null,
|
||||||
|
var uiCurrentThemeIds: Map<String, String>? = null,
|
||||||
|
var uiThemes: List<ThemeOverrides>? = null,
|
||||||
) {
|
) {
|
||||||
fun prepareForExport(): AppSettings {
|
fun prepareForExport(): AppSettings {
|
||||||
val empty = AppSettings()
|
val empty = AppSettings()
|
||||||
@@ -5545,6 +5583,11 @@ data class AppSettings(
|
|||||||
if (androidCallOnLockScreen != def.androidCallOnLockScreen) { empty.androidCallOnLockScreen = androidCallOnLockScreen }
|
if (androidCallOnLockScreen != def.androidCallOnLockScreen) { empty.androidCallOnLockScreen = androidCallOnLockScreen }
|
||||||
if (iosCallKitEnabled != def.iosCallKitEnabled) { empty.iosCallKitEnabled = iosCallKitEnabled }
|
if (iosCallKitEnabled != def.iosCallKitEnabled) { empty.iosCallKitEnabled = iosCallKitEnabled }
|
||||||
if (iosCallKitCallsInRecents != def.iosCallKitCallsInRecents) { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents }
|
if (iosCallKitCallsInRecents != def.iosCallKitCallsInRecents) { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents }
|
||||||
|
if (uiProfileImageCornerRadius != def.uiProfileImageCornerRadius) { empty.uiProfileImageCornerRadius = uiProfileImageCornerRadius }
|
||||||
|
if (uiColorScheme != def.uiColorScheme) { empty.uiColorScheme = uiColorScheme }
|
||||||
|
if (uiDarkColorScheme != def.uiDarkColorScheme) { empty.uiDarkColorScheme = uiDarkColorScheme }
|
||||||
|
if (uiCurrentThemeIds != def.uiCurrentThemeIds) { empty.uiCurrentThemeIds = uiCurrentThemeIds }
|
||||||
|
if (uiThemes != def.uiThemes) { empty.uiThemes = uiThemes }
|
||||||
return empty
|
return empty
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5577,6 +5620,11 @@ data class AppSettings(
|
|||||||
androidCallOnLockScreen?.let { def.callOnLockScreen.set(it.toCallOnLockScreen()) }
|
androidCallOnLockScreen?.let { def.callOnLockScreen.set(it.toCallOnLockScreen()) }
|
||||||
iosCallKitEnabled?.let { def.iosCallKitEnabled.set(it) }
|
iosCallKitEnabled?.let { def.iosCallKitEnabled.set(it) }
|
||||||
iosCallKitCallsInRecents?.let { def.iosCallKitCallsInRecents.set(it) }
|
iosCallKitCallsInRecents?.let { def.iosCallKitCallsInRecents.set(it) }
|
||||||
|
uiProfileImageCornerRadius?.let { def.profileImageCornerRadius.set(it) }
|
||||||
|
uiColorScheme?.let { def.currentTheme.set(it) }
|
||||||
|
uiDarkColorScheme?.let { def.systemDarkTheme.set(it) }
|
||||||
|
uiCurrentThemeIds?.let { def.currentThemeIds.set(it) }
|
||||||
|
uiThemes?.let { def.themeOverrides.set(it.skipDuplicates()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -5601,7 +5649,12 @@ data class AppSettings(
|
|||||||
confirmDBUpgrades = false,
|
confirmDBUpgrades = false,
|
||||||
androidCallOnLockScreen = AppSettingsLockScreenCalls.SHOW,
|
androidCallOnLockScreen = AppSettingsLockScreenCalls.SHOW,
|
||||||
iosCallKitEnabled = true,
|
iosCallKitEnabled = true,
|
||||||
iosCallKitCallsInRecents = false
|
iosCallKitCallsInRecents = false,
|
||||||
|
uiProfileImageCornerRadius = 22.5f,
|
||||||
|
uiColorScheme = DefaultTheme.SYSTEM_THEME_NAME,
|
||||||
|
uiDarkColorScheme = DefaultTheme.SIMPLEX.themeName,
|
||||||
|
uiCurrentThemeIds = null,
|
||||||
|
uiThemes = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
val current: AppSettings
|
val current: AppSettings
|
||||||
@@ -5628,6 +5681,11 @@ data class AppSettings(
|
|||||||
androidCallOnLockScreen = AppSettingsLockScreenCalls.from(def.callOnLockScreen.get()),
|
androidCallOnLockScreen = AppSettingsLockScreenCalls.from(def.callOnLockScreen.get()),
|
||||||
iosCallKitEnabled = def.iosCallKitEnabled.get(),
|
iosCallKitEnabled = def.iosCallKitEnabled.get(),
|
||||||
iosCallKitCallsInRecents = def.iosCallKitCallsInRecents.get(),
|
iosCallKitCallsInRecents = def.iosCallKitCallsInRecents.get(),
|
||||||
|
uiProfileImageCornerRadius = def.profileImageCornerRadius.get(),
|
||||||
|
uiColorScheme = def.currentTheme.get() ?: DefaultTheme.SYSTEM_THEME_NAME,
|
||||||
|
uiDarkColorScheme = def.systemDarkTheme.get() ?: DefaultTheme.SIMPLEX.themeName,
|
||||||
|
uiCurrentThemeIds = def.currentThemeIds.get(),
|
||||||
|
uiThemes = def.themeOverrides.get(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,6 @@ package com.sd.lib.compose.wheel_picker
|
|||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import chat.simplex.common.ui.theme.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -12,6 +11,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import chat.simplex.common.ui.theme.isInDarkTheme
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default implementation of focus view in vertical.
|
* The default implementation of focus view in vertical.
|
||||||
@@ -76,7 +76,7 @@ fun FWheelPickerFocusHorizontal(
|
|||||||
*/
|
*/
|
||||||
private val DefaultDividerColor: Color
|
private val DefaultDividerColor: Color
|
||||||
@Composable
|
@Composable
|
||||||
get() = (if (isSystemInDarkTheme()) {
|
get() = (if (isInDarkTheme()) {
|
||||||
Color.White
|
Color.White
|
||||||
} else {
|
} else {
|
||||||
Color.Black
|
Color.Black
|
||||||
|
|||||||
+6
@@ -42,6 +42,12 @@ fun runMigrations() {
|
|||||||
ChatController.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name)
|
ChatController.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name)
|
||||||
}
|
}
|
||||||
lastMigration.set(117)
|
lastMigration.set(117)
|
||||||
|
} else if (lastMigration.get() < 203) {
|
||||||
|
// Moving to a different key for storing themes as a List
|
||||||
|
val oldOverrides = ChatController.appPrefs.themeOverridesOld.get().values.toList()
|
||||||
|
ChatController.appPrefs.themeOverrides.set(oldOverrides)
|
||||||
|
ChatController.appPrefs.currentThemeIds.set(oldOverrides.associate { it.base.themeName to it.themeId })
|
||||||
|
lastMigration.set(203)
|
||||||
} else {
|
} else {
|
||||||
lastMigration.set(BuildConfigCommon.ANDROID_VERSION_CODE)
|
lastMigration.set(BuildConfigCommon.ANDROID_VERSION_CODE)
|
||||||
break
|
break
|
||||||
|
|||||||
+8
-7
@@ -43,14 +43,13 @@ val appPreferences: AppPreferences
|
|||||||
|
|
||||||
val chatController: ChatController = ChatController
|
val chatController: ChatController = ChatController
|
||||||
|
|
||||||
fun initChatControllerAndRunMigrations() {
|
fun initChatControllerOnStart() {
|
||||||
withLongRunningApi {
|
withLongRunningApi {
|
||||||
if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) {
|
if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) {
|
||||||
initChatController(startChat = ::showStartChatAfterRestartAlert)
|
initChatController(startChat = ::showStartChatAfterRestartAlert)
|
||||||
} else {
|
} else {
|
||||||
initChatController()
|
initChatController()
|
||||||
}
|
}
|
||||||
runMigrations()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,11 +87,13 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
platform.androidRestartNetworkObserver()
|
platform.androidRestartNetworkObserver()
|
||||||
controller.apiSetTempFolder(coreTmpDir.absolutePath)
|
controller.apiSetAppFilePaths(
|
||||||
controller.apiSetFilesFolder(appFilesDir.absolutePath)
|
appFilesDir.absolutePath,
|
||||||
if (appPlatform.isDesktop) {
|
coreTmpDir.absolutePath,
|
||||||
controller.apiSetRemoteHostsFolder(remoteHostsDir.absolutePath)
|
wallpapersDir.parentFile.absolutePath,
|
||||||
}
|
remoteHostsDir.absolutePath,
|
||||||
|
ctrl
|
||||||
|
)
|
||||||
controller.apiSetEncryptLocalFiles(controller.appPrefs.privacyEncryptLocalFiles.get())
|
controller.apiSetEncryptLocalFiles(controller.appPrefs.privacyEncryptLocalFiles.get())
|
||||||
// If we migrated successfully means previous re-encryption process on database level finished successfully too
|
// If we migrated successfully means previous re-encryption process on database level finished successfully too
|
||||||
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
|
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
|
||||||
|
|||||||
+56
-2
@@ -1,10 +1,12 @@
|
|||||||
package chat.simplex.common.platform
|
package chat.simplex.common.platform
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import chat.simplex.common.model.CIFile
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.model.CryptoFile
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.generalGetString
|
import chat.simplex.common.views.helpers.generalGetString
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
import com.charleskorn.kaml.*
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
@@ -14,8 +16,10 @@ expect val dataDir: File
|
|||||||
expect val tmpDir: File
|
expect val tmpDir: File
|
||||||
expect val filesDir: File
|
expect val filesDir: File
|
||||||
expect val appFilesDir: File
|
expect val appFilesDir: File
|
||||||
|
expect val wallpapersDir: File
|
||||||
expect val coreTmpDir: File
|
expect val coreTmpDir: File
|
||||||
expect val dbAbsolutePrefixPath: String
|
expect val dbAbsolutePrefixPath: String
|
||||||
|
expect val preferencesDir: File
|
||||||
|
|
||||||
expect val chatDatabaseFileName: String
|
expect val chatDatabaseFileName: String
|
||||||
expect val agentDatabaseFileName: String
|
expect val agentDatabaseFileName: String
|
||||||
@@ -78,6 +82,20 @@ fun getAppFilePath(fileName: String): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getWallpaperFilePath(fileName: String): String {
|
||||||
|
val rh = chatModel.currentRemoteHost.value
|
||||||
|
val s = File.separator
|
||||||
|
val path = if (rh == null) {
|
||||||
|
wallpapersDir.absolutePath + s + fileName
|
||||||
|
} else {
|
||||||
|
remoteHostsDir.absolutePath + s + rh.storePath + s + "simplex_v1_assets" + s + "wallpapers" + s + fileName
|
||||||
|
}
|
||||||
|
File(path).parentFile.mkdirs()
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPreferenceFilePath(fileName: String = "themes.yaml"): String = preferencesDir.absolutePath + File.separator + fileName
|
||||||
|
|
||||||
fun getLoadedFilePath(file: CIFile?): String? {
|
fun getLoadedFilePath(file: CIFile?): String? {
|
||||||
val f = file?.fileSource?.filePath
|
val f = file?.fileSource?.filePath
|
||||||
return if (f != null && file.loaded) {
|
return if (f != null && file.loaded) {
|
||||||
@@ -98,6 +116,42 @@ fun getLoadedFileSource(file: CIFile?): CryptoFile? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun readThemeOverrides(): List<ThemeOverrides> {
|
||||||
|
return try {
|
||||||
|
val file = File(getPreferenceFilePath("themes.yaml"))
|
||||||
|
if (!file.exists()) return emptyList()
|
||||||
|
|
||||||
|
file.inputStream().use {
|
||||||
|
val map = yaml.parseToYamlNode(it).yamlMap
|
||||||
|
val list = map.get<YamlList>("themes")
|
||||||
|
val res = ArrayList<ThemeOverrides>()
|
||||||
|
list?.items?.forEach {
|
||||||
|
try {
|
||||||
|
res.add(yaml.decodeFromYamlNode(ThemeOverrides.serializer(), it))
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "Error while reading specific theme: ${e.stackTraceToString()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.skipDuplicates()
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "Error while reading themes file: ${e.stackTraceToString()}")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeThemeOverrides(overrides: List<ThemeOverrides>): Boolean =
|
||||||
|
try {
|
||||||
|
File(getPreferenceFilePath("themes.yaml")).outputStream().use {
|
||||||
|
val string = yaml.encodeToString(ThemesFile(themes = overrides))
|
||||||
|
it.bufferedWriter().use { it.write(string) }
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "Error while writing themes file: ${e.stackTraceToString()}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
private fun fileReady(file: CIFile, filePath: String) =
|
private fun fileReady(file: CIFile, filePath: String) =
|
||||||
File(filePath).exists() &&
|
File(filePath).exists() &&
|
||||||
CIFile.cachedRemoteFileRequests[file.fileSource] != false
|
CIFile.cachedRemoteFileRequests[file.fileSource] != false
|
||||||
|
|||||||
+4
@@ -1,11 +1,13 @@
|
|||||||
package chat.simplex.common.platform
|
package chat.simplex.common.platform
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
|
import dev.icerock.moko.resources.ImageResource
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -31,3 +33,5 @@ expect fun windowWidth(): Dp
|
|||||||
expect fun desktopExpandWindowToWidth(width: Dp)
|
expect fun desktopExpandWindowToWidth(width: Dp)
|
||||||
|
|
||||||
expect fun isRtl(text: CharSequence): Boolean
|
expect fun isRtl(text: CharSequence): Boolean
|
||||||
|
|
||||||
|
expect fun ImageResource.toComposeImageBitmap(): ImageBitmap?
|
||||||
|
|||||||
+1
-12
@@ -31,17 +31,6 @@ val WarningOrange = Color(255, 127, 0, 255)
|
|||||||
val WarningYellow = Color(255, 192, 0, 255)
|
val WarningYellow = Color(255, 192, 0, 255)
|
||||||
val FileLight = Color(183, 190, 199, 255)
|
val FileLight = Color(183, 190, 199, 255)
|
||||||
val FileDark = Color(101, 101, 106, 255)
|
val FileDark = Color(101, 101, 106, 255)
|
||||||
val SentMessageColor = Color(0x1E45B8FF)
|
|
||||||
|
|
||||||
val MenuTextColor: Color @Composable get () = if (isInDarkTheme()) LocalContentColor.current.copy(alpha = 0.8f) else Color.Black
|
val MenuTextColor: Color @Composable get () = if (isInDarkTheme()) LocalContentColor.current.copy(alpha = 0.8f) else Color.Black
|
||||||
val NoteFolderIconColor: Color @Composable get() = with(CurrentColors.collectAsState().value.appColors.sentMessage) {
|
val NoteFolderIconColor: Color @Composable get() = MaterialTheme.appColors.primaryVariant2
|
||||||
// Default color looks too light and better to have it here a little bit brighter
|
|
||||||
if (alpha == SentMessageColor.alpha) {
|
|
||||||
copy(min(SentMessageColor.alpha + 0.1f, 1f))
|
|
||||||
} else {
|
|
||||||
// Color is non-standard and theme maker can choose color without alpha at all since the theme bound to dark/light variant,
|
|
||||||
// and it shouldn't be universal
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
+537
-76
@@ -6,53 +6,136 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.model.ChatController
|
import chat.simplex.common.model.ChatController
|
||||||
import chat.simplex.common.platform.isInNightMode
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.ui.theme.ThemeManager.colorFromReadableHex
|
||||||
|
import chat.simplex.common.ui.theme.ThemeManager.toReadableHex
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
enum class DefaultTheme {
|
enum class DefaultTheme {
|
||||||
SYSTEM, LIGHT, DARK, SIMPLEX;
|
LIGHT, DARK, SIMPLEX, BLACK;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SYSTEM_THEME_NAME: String = "SYSTEM"
|
||||||
|
}
|
||||||
|
|
||||||
|
val themeName: String
|
||||||
|
get() = name
|
||||||
|
|
||||||
|
val mode: DefaultThemeMode get() = if (this == LIGHT) DefaultThemeMode.LIGHT else DefaultThemeMode.DARK
|
||||||
|
|
||||||
// Call it only with base theme, not SYSTEM
|
// Call it only with base theme, not SYSTEM
|
||||||
fun hasChangedAnyColor(colors: Colors, appColors: AppColors): Boolean {
|
fun hasChangedAnyColor(overrides: ThemeOverrides?): Boolean {
|
||||||
val palette = when (this) {
|
if (overrides == null) return false
|
||||||
SYSTEM -> return false
|
return overrides.colors != ThemeColors() ||
|
||||||
LIGHT -> LightColorPalette
|
overrides.wallpaper != null && (overrides.wallpaper.background != null || overrides.wallpaper.tint != null)
|
||||||
DARK -> DarkColorPalette
|
|
||||||
SIMPLEX -> SimplexColorPalette
|
|
||||||
}
|
|
||||||
val appPalette = when (this) {
|
|
||||||
SYSTEM -> return false
|
|
||||||
LIGHT -> LightColorPaletteApp
|
|
||||||
DARK -> DarkColorPaletteApp
|
|
||||||
SIMPLEX -> SimplexColorPaletteApp
|
|
||||||
}
|
|
||||||
return colors.primary != palette.primary ||
|
|
||||||
colors.primaryVariant != palette.primaryVariant ||
|
|
||||||
colors.secondary != palette.secondary ||
|
|
||||||
colors.secondaryVariant != palette.secondaryVariant ||
|
|
||||||
colors.background != palette.background ||
|
|
||||||
colors.surface != palette.surface ||
|
|
||||||
appColors != appPalette
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AppColors(
|
@Serializable
|
||||||
val title: Color,
|
enum class DefaultThemeMode {
|
||||||
val sentMessage: Color,
|
@SerialName("light") LIGHT,
|
||||||
val receivedMessage: Color
|
@SerialName("dark") DARK
|
||||||
)
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class AppColors(
|
||||||
|
title: Color,
|
||||||
|
primaryVariant2: Color,
|
||||||
|
sentMessage: Color,
|
||||||
|
sentQuote: Color,
|
||||||
|
receivedMessage: Color,
|
||||||
|
receivedQuote: Color,
|
||||||
|
) {
|
||||||
|
var title by mutableStateOf(title, structuralEqualityPolicy())
|
||||||
|
internal set
|
||||||
|
var primaryVariant2 by mutableStateOf(primaryVariant2, structuralEqualityPolicy())
|
||||||
|
internal set
|
||||||
|
var sentMessage by mutableStateOf(sentMessage, structuralEqualityPolicy())
|
||||||
|
internal set
|
||||||
|
var sentQuote by mutableStateOf(sentQuote, structuralEqualityPolicy())
|
||||||
|
internal set
|
||||||
|
var receivedMessage by mutableStateOf(receivedMessage, structuralEqualityPolicy())
|
||||||
|
internal set
|
||||||
|
var receivedQuote by mutableStateOf(receivedQuote, structuralEqualityPolicy())
|
||||||
|
internal set
|
||||||
|
|
||||||
|
fun copy(
|
||||||
|
title: Color = this.title,
|
||||||
|
primaryVariant2: Color = this.primaryVariant2,
|
||||||
|
sentMessage: Color = this.sentMessage,
|
||||||
|
sentQuote: Color = this.sentQuote,
|
||||||
|
receivedMessage: Color = this.receivedMessage,
|
||||||
|
receivedQuote: Color = this.receivedQuote,
|
||||||
|
): AppColors = AppColors(
|
||||||
|
title,
|
||||||
|
primaryVariant2,
|
||||||
|
sentMessage,
|
||||||
|
sentQuote,
|
||||||
|
receivedMessage,
|
||||||
|
receivedQuote,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return buildString {
|
||||||
|
append("AppColors(")
|
||||||
|
append("title=$title, ")
|
||||||
|
append("primaryVariant2=$primaryVariant2, ")
|
||||||
|
append("sentMessage=$sentMessage, ")
|
||||||
|
append("sentQuote=$sentQuote, ")
|
||||||
|
append("receivedMessage=$receivedMessage, ")
|
||||||
|
append("receivedQuote=$receivedQuote")
|
||||||
|
append(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class AppWallpaper(
|
||||||
|
background: Color? = null,
|
||||||
|
tint: Color? = null,
|
||||||
|
type: WallpaperType = WallpaperType.Empty,
|
||||||
|
) {
|
||||||
|
var background by mutableStateOf(background, structuralEqualityPolicy())
|
||||||
|
internal set
|
||||||
|
var tint by mutableStateOf(tint, structuralEqualityPolicy())
|
||||||
|
internal set
|
||||||
|
var type by mutableStateOf(type, structuralEqualityPolicy())
|
||||||
|
internal set
|
||||||
|
|
||||||
|
fun copy(
|
||||||
|
background: Color? = this.background,
|
||||||
|
tint: Color? = this.tint,
|
||||||
|
type: WallpaperType = this.type,
|
||||||
|
): AppWallpaper = AppWallpaper(
|
||||||
|
background,
|
||||||
|
tint,
|
||||||
|
type,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return buildString {
|
||||||
|
append("AppWallpaper(")
|
||||||
|
append("background=$background, ")
|
||||||
|
append("tint=$tint, ")
|
||||||
|
append("type=$type")
|
||||||
|
append(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum class ThemeColor {
|
enum class ThemeColor {
|
||||||
PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, RECEIVED_MESSAGE;
|
PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, SENT_QUOTE, RECEIVED_MESSAGE, RECEIVED_QUOTE, PRIMARY_VARIANT2, WALLPAPER_BACKGROUND, WALLPAPER_TINT;
|
||||||
|
|
||||||
fun fromColors(colors: Colors, appColors: AppColors): Color {
|
fun fromColors(colors: Colors, appColors: AppColors, appWallpaper: AppWallpaper): Color? {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
PRIMARY -> colors.primary
|
PRIMARY -> colors.primary
|
||||||
PRIMARY_VARIANT -> colors.primaryVariant
|
PRIMARY_VARIANT -> colors.primaryVariant
|
||||||
@@ -61,8 +144,13 @@ enum class ThemeColor {
|
|||||||
BACKGROUND -> colors.background
|
BACKGROUND -> colors.background
|
||||||
SURFACE -> colors.surface
|
SURFACE -> colors.surface
|
||||||
TITLE -> appColors.title
|
TITLE -> appColors.title
|
||||||
|
PRIMARY_VARIANT2 -> appColors.primaryVariant2
|
||||||
SENT_MESSAGE -> appColors.sentMessage
|
SENT_MESSAGE -> appColors.sentMessage
|
||||||
|
SENT_QUOTE -> appColors.sentQuote
|
||||||
RECEIVED_MESSAGE -> appColors.receivedMessage
|
RECEIVED_MESSAGE -> appColors.receivedMessage
|
||||||
|
RECEIVED_QUOTE -> appColors.receivedQuote
|
||||||
|
WALLPAPER_BACKGROUND -> appWallpaper.background
|
||||||
|
WALLPAPER_TINT -> appWallpaper.tint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +163,13 @@ enum class ThemeColor {
|
|||||||
BACKGROUND -> generalGetString(MR.strings.color_background)
|
BACKGROUND -> generalGetString(MR.strings.color_background)
|
||||||
SURFACE -> generalGetString(MR.strings.color_surface)
|
SURFACE -> generalGetString(MR.strings.color_surface)
|
||||||
TITLE -> generalGetString(MR.strings.color_title)
|
TITLE -> generalGetString(MR.strings.color_title)
|
||||||
|
PRIMARY_VARIANT2 -> generalGetString(MR.strings.color_primary_variant2)
|
||||||
SENT_MESSAGE -> generalGetString(MR.strings.color_sent_message)
|
SENT_MESSAGE -> generalGetString(MR.strings.color_sent_message)
|
||||||
|
SENT_QUOTE -> generalGetString(MR.strings.color_sent_quote)
|
||||||
RECEIVED_MESSAGE -> generalGetString(MR.strings.color_received_message)
|
RECEIVED_MESSAGE -> generalGetString(MR.strings.color_received_message)
|
||||||
|
RECEIVED_QUOTE -> generalGetString(MR.strings.color_received_quote)
|
||||||
|
WALLPAPER_BACKGROUND -> generalGetString(MR.strings.color_wallpaper_background)
|
||||||
|
WALLPAPER_TINT -> generalGetString(MR.strings.color_wallpaper_tint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,45 +185,232 @@ data class ThemeColors(
|
|||||||
@SerialName("menus")
|
@SerialName("menus")
|
||||||
val surface: String? = null,
|
val surface: String? = null,
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
|
@SerialName("accentVariant2")
|
||||||
|
val primaryVariant2: String? = null,
|
||||||
val sentMessage: String? = null,
|
val sentMessage: String? = null,
|
||||||
|
@SerialName("sentReply")
|
||||||
|
val sentQuote: String? = null,
|
||||||
val receivedMessage: String? = null,
|
val receivedMessage: String? = null,
|
||||||
|
@SerialName("receivedReply")
|
||||||
|
val receivedQuote: String? = null,
|
||||||
) {
|
) {
|
||||||
fun toColors(base: DefaultTheme): Colors {
|
companion object {
|
||||||
|
fun from(colors: Colors, appColors: AppColors): ThemeColors =
|
||||||
|
ThemeColors(
|
||||||
|
primary = colors.primary.toReadableHex(),
|
||||||
|
primaryVariant = colors.primaryVariant.toReadableHex(),
|
||||||
|
secondary = colors.secondary.toReadableHex(),
|
||||||
|
secondaryVariant = colors.secondaryVariant.toReadableHex(),
|
||||||
|
background = colors.background.toReadableHex(),
|
||||||
|
surface = colors.surface.toReadableHex(),
|
||||||
|
title = appColors.title.toReadableHex(),
|
||||||
|
primaryVariant2 = appColors.primaryVariant2.toReadableHex(),
|
||||||
|
sentMessage = appColors.sentMessage.toReadableHex(),
|
||||||
|
sentQuote = appColors.sentQuote.toReadableHex(),
|
||||||
|
receivedMessage = appColors.receivedMessage.toReadableHex(),
|
||||||
|
receivedQuote = appColors.receivedQuote.toReadableHex(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ThemeWallpaper (
|
||||||
|
val preset: String? = null,
|
||||||
|
val scale: Float? = null,
|
||||||
|
val scaleType: WallpaperScaleType? = null,
|
||||||
|
val background: String? = null,
|
||||||
|
val tint: String? = null,
|
||||||
|
val image: String? = null,
|
||||||
|
val imageFile: String? = null,
|
||||||
|
) {
|
||||||
|
fun toAppWallpaper(): AppWallpaper {
|
||||||
|
return AppWallpaper(
|
||||||
|
background = background?.colorFromReadableHex(),
|
||||||
|
tint = tint?.colorFromReadableHex(),
|
||||||
|
type = WallpaperType.from(this) ?: WallpaperType.Empty
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withFilledWallpaperBase64(): ThemeWallpaper {
|
||||||
|
val aw = toAppWallpaper()
|
||||||
|
val type = aw.type
|
||||||
|
return ThemeWallpaper(
|
||||||
|
image = if (type is WallpaperType.Image && type.image != null) resizeImageToStrSize(type.image!!, 5_000_000) else null,
|
||||||
|
imageFile = null,
|
||||||
|
preset = if (type is WallpaperType.Preset) type.filename else null,
|
||||||
|
scale = if (type is WallpaperType.Preset) type.scale else if (type is WallpaperType.Image) type.scale else 1f,
|
||||||
|
scaleType = if (type is WallpaperType.Image) type.scaleType else null,
|
||||||
|
background = aw.background?.toReadableHex(),
|
||||||
|
tint = aw.tint?.toReadableHex(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withFilledWallpaperPath(): ThemeWallpaper {
|
||||||
|
val aw = toAppWallpaper()
|
||||||
|
val type = aw.type
|
||||||
|
return ThemeWallpaper(
|
||||||
|
image = null,
|
||||||
|
imageFile = if (type is WallpaperType.Image) type.filename else null,
|
||||||
|
preset = if (type is WallpaperType.Preset) type.filename else null,
|
||||||
|
scale = if (scale == null) null else if (type is WallpaperType.Preset) type.scale else if (type is WallpaperType.Image) scale else null,
|
||||||
|
scaleType = if (scaleType == null) null else if (type is WallpaperType.Image) type.scaleType else null,
|
||||||
|
background = aw.background?.toReadableHex(),
|
||||||
|
tint = aw.tint?.toReadableHex(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun importFromString(): ThemeWallpaper =
|
||||||
|
if (preset == null && image != null) {
|
||||||
|
// Need to save image from string and to save its path
|
||||||
|
try {
|
||||||
|
val parsed = base64ToBitmap(image)
|
||||||
|
val filename = saveWallpaperFile(parsed)
|
||||||
|
copy(image = null, imageFile = filename)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error while parsing/copying the image: ${e.stackTraceToString()}")
|
||||||
|
ThemeWallpaper()
|
||||||
|
}
|
||||||
|
} else this
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun from(type: WallpaperType, background: String?, tint: String?): ThemeWallpaper {
|
||||||
|
return ThemeWallpaper(
|
||||||
|
image = null,
|
||||||
|
imageFile = if (type is WallpaperType.Image) type.filename else null,
|
||||||
|
preset = if (type is WallpaperType.Preset) type.filename else null,
|
||||||
|
scale = if (type is WallpaperType.Preset) type.scale else if (type is WallpaperType.Image) type.scale else null,
|
||||||
|
scaleType = if (type is WallpaperType.Image) type.scaleType else null,
|
||||||
|
background = background,
|
||||||
|
tint = tint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ThemesFile(
|
||||||
|
val themes: List<ThemeOverrides> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ThemeOverrides (
|
||||||
|
val themeId: String = UUID.randomUUID().toString(),
|
||||||
|
val base: DefaultTheme,
|
||||||
|
val colors: ThemeColors = ThemeColors(),
|
||||||
|
val wallpaper: ThemeWallpaper? = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun isSame(type: WallpaperType?, themeName: String): Boolean =
|
||||||
|
(
|
||||||
|
(wallpaper?.preset != null && type is WallpaperType.Preset && wallpaper.preset == type.filename) ||
|
||||||
|
(wallpaper?.imageFile != null && type is WallpaperType.Image) ||
|
||||||
|
(wallpaper?.preset == null && wallpaper?.imageFile == null && (type == WallpaperType.Empty || type == null))
|
||||||
|
) && base.themeName == themeName
|
||||||
|
|
||||||
|
fun withUpdatedColor(name: ThemeColor, color: String?): ThemeOverrides {
|
||||||
|
return copy(
|
||||||
|
colors = when (name) {
|
||||||
|
ThemeColor.PRIMARY -> colors.copy(primary = color)
|
||||||
|
ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color)
|
||||||
|
ThemeColor.SECONDARY -> colors.copy(secondary = color)
|
||||||
|
ThemeColor.SECONDARY_VARIANT -> colors.copy(secondaryVariant = color)
|
||||||
|
ThemeColor.BACKGROUND -> colors.copy(background = color)
|
||||||
|
ThemeColor.SURFACE -> colors.copy(surface = color)
|
||||||
|
ThemeColor.TITLE -> colors.copy(title = color)
|
||||||
|
ThemeColor.PRIMARY_VARIANT2 -> colors.copy(primaryVariant2 = color)
|
||||||
|
ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color)
|
||||||
|
ThemeColor.SENT_QUOTE -> colors.copy(sentQuote = color)
|
||||||
|
ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color)
|
||||||
|
ThemeColor.RECEIVED_QUOTE -> colors.copy(receivedQuote = color)
|
||||||
|
ThemeColor.WALLPAPER_BACKGROUND -> colors.copy()
|
||||||
|
ThemeColor.WALLPAPER_TINT -> colors.copy()
|
||||||
|
}, wallpaper = when (name) {
|
||||||
|
ThemeColor.WALLPAPER_BACKGROUND -> wallpaper?.copy(background = color)
|
||||||
|
ThemeColor.WALLPAPER_TINT -> wallpaper?.copy(tint = color)
|
||||||
|
else -> wallpaper?.copy()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toColors(base: DefaultTheme, perChatTheme: ThemeColors?, perUserTheme: ThemeColors?, presetWallpaperTheme: ThemeColors?): Colors {
|
||||||
val baseColors = when (base) {
|
val baseColors = when (base) {
|
||||||
DefaultTheme.LIGHT -> LightColorPalette
|
DefaultTheme.LIGHT -> LightColorPalette
|
||||||
DefaultTheme.DARK -> DarkColorPalette
|
DefaultTheme.DARK -> DarkColorPalette
|
||||||
DefaultTheme.SIMPLEX -> SimplexColorPalette
|
DefaultTheme.SIMPLEX -> SimplexColorPalette
|
||||||
// shouldn't be here
|
DefaultTheme.BLACK -> BlackColorPalette
|
||||||
DefaultTheme.SYSTEM -> LightColorPalette
|
|
||||||
}
|
}
|
||||||
return baseColors.copy(
|
return baseColors.copy(
|
||||||
primary = primary?.colorFromReadableHex() ?: baseColors.primary,
|
primary = perChatTheme?.primary?.colorFromReadableHex() ?: perUserTheme?.primary?.colorFromReadableHex() ?: colors.primary?.colorFromReadableHex() ?: presetWallpaperTheme?.primary?.colorFromReadableHex() ?: baseColors.primary,
|
||||||
primaryVariant = primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant,
|
primaryVariant = perChatTheme?.primaryVariant?.colorFromReadableHex() ?: perUserTheme?.primaryVariant?.colorFromReadableHex() ?: colors.primaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant,
|
||||||
secondary = secondary?.colorFromReadableHex() ?: baseColors.secondary,
|
secondary = perChatTheme?.secondary?.colorFromReadableHex() ?: perUserTheme?.secondary?.colorFromReadableHex() ?: colors.secondary?.colorFromReadableHex() ?: presetWallpaperTheme?.secondary?.colorFromReadableHex() ?: baseColors.secondary,
|
||||||
secondaryVariant = secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant,
|
secondaryVariant = perChatTheme?.secondaryVariant?.colorFromReadableHex() ?: perUserTheme?.secondaryVariant?.colorFromReadableHex() ?: colors.secondaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant,
|
||||||
background = background?.colorFromReadableHex() ?: baseColors.background,
|
background = perChatTheme?.background?.colorFromReadableHex() ?: perUserTheme?.background?.colorFromReadableHex() ?: colors.background?.colorFromReadableHex() ?: presetWallpaperTheme?.background?.colorFromReadableHex() ?: baseColors.background,
|
||||||
surface = surface?.colorFromReadableHex() ?: baseColors.surface,
|
surface = perChatTheme?.surface?.colorFromReadableHex() ?: perUserTheme?.surface?.colorFromReadableHex() ?: colors.surface?.colorFromReadableHex() ?: presetWallpaperTheme?.surface?.colorFromReadableHex() ?: baseColors.surface,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toAppColors(base: DefaultTheme): AppColors {
|
fun toAppColors(base: DefaultTheme, perChatTheme: ThemeColors?, perChatWallpaperType: WallpaperType?, perUserTheme: ThemeColors?, perUserWallpaperType: WallpaperType?, presetWallpaperTheme: ThemeColors?): AppColors {
|
||||||
val baseColors = when (base) {
|
val baseColors = when (base) {
|
||||||
DefaultTheme.LIGHT -> LightColorPaletteApp
|
DefaultTheme.LIGHT -> LightColorPaletteApp
|
||||||
DefaultTheme.DARK -> DarkColorPaletteApp
|
DefaultTheme.DARK -> DarkColorPaletteApp
|
||||||
DefaultTheme.SIMPLEX -> SimplexColorPaletteApp
|
DefaultTheme.SIMPLEX -> SimplexColorPaletteApp
|
||||||
// shouldn't be here
|
DefaultTheme.BLACK -> BlackColorPaletteApp
|
||||||
DefaultTheme.SYSTEM -> LightColorPaletteApp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sentMessageFallback = colors.sentMessage?.colorFromReadableHex() ?: presetWallpaperTheme?.sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage
|
||||||
|
val sentQuoteFallback = colors.sentQuote?.colorFromReadableHex() ?: presetWallpaperTheme?.sentQuote?.colorFromReadableHex() ?: baseColors.sentQuote
|
||||||
|
val receivedMessageFallback = colors.receivedMessage?.colorFromReadableHex() ?: presetWallpaperTheme?.receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage
|
||||||
|
val receivedQuoteFallback = colors.receivedQuote?.colorFromReadableHex() ?: presetWallpaperTheme?.receivedQuote?.colorFromReadableHex() ?: baseColors.receivedQuote
|
||||||
return baseColors.copy(
|
return baseColors.copy(
|
||||||
title = title?.colorFromReadableHex() ?: baseColors.title,
|
title = perChatTheme?.title?.colorFromReadableHex() ?: perUserTheme?.title?.colorFromReadableHex() ?: colors.title?.colorFromReadableHex() ?: presetWallpaperTheme?.title?.colorFromReadableHex() ?: baseColors.title,
|
||||||
sentMessage = sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage,
|
primaryVariant2 = perChatTheme?.primaryVariant2?.colorFromReadableHex() ?: perUserTheme?.primaryVariant2?.colorFromReadableHex() ?: colors.primaryVariant2?.colorFromReadableHex() ?: presetWallpaperTheme?.primaryVariant2?.colorFromReadableHex() ?: baseColors.primaryVariant2,
|
||||||
receivedMessage = receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage,
|
sentMessage = if (perChatTheme?.sentMessage != null) perChatTheme.sentMessage.colorFromReadableHex()
|
||||||
|
else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.sentMessage?.colorFromReadableHex() ?: sentMessageFallback
|
||||||
|
else sentMessageFallback,
|
||||||
|
sentQuote = if (perChatTheme?.sentQuote != null) perChatTheme.sentQuote.colorFromReadableHex()
|
||||||
|
else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.sentQuote?.colorFromReadableHex() ?: sentQuoteFallback
|
||||||
|
else sentQuoteFallback,
|
||||||
|
receivedMessage = if (perChatTheme?.receivedMessage != null) perChatTheme.receivedMessage.colorFromReadableHex()
|
||||||
|
else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.receivedMessage?.colorFromReadableHex() ?: receivedMessageFallback
|
||||||
|
else receivedMessageFallback,
|
||||||
|
receivedQuote = if (perChatTheme?.receivedQuote != null) perChatTheme.receivedQuote.colorFromReadableHex()
|
||||||
|
else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.receivedQuote?.colorFromReadableHex() ?: receivedQuoteFallback
|
||||||
|
else receivedQuoteFallback,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun withFilledColors(base: DefaultTheme): ThemeColors {
|
fun toAppWallpaper(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverride?, materialBackgroundColor: Color): AppWallpaper {
|
||||||
val c = toColors(base)
|
val mainType = when {
|
||||||
val ac = toAppColors(base)
|
themeOverridesForType != null -> themeOverridesForType
|
||||||
|
// type can be null if override is empty `"wallpaper": "{}"`, in this case no wallpaper is needed, empty.
|
||||||
|
// It's not null to override upper level wallpaper
|
||||||
|
perChatTheme?.wallpaper != null -> perChatTheme.wallpaper.toAppWallpaper().type
|
||||||
|
perUserTheme?.wallpaper != null -> perUserTheme.wallpaper.toAppWallpaper().type
|
||||||
|
else -> wallpaper?.toAppWallpaper()?.type ?: return AppWallpaper()
|
||||||
|
}
|
||||||
|
val first: ThemeWallpaper? = if (mainType.sameType(perChatTheme?.wallpaper?.toAppWallpaper()?.type)) perChatTheme?.wallpaper else null
|
||||||
|
val second: ThemeWallpaper? = if (mainType.sameType(perUserTheme?.wallpaper?.toAppWallpaper()?.type)) perUserTheme?.wallpaper else null
|
||||||
|
val third: ThemeWallpaper? = if (mainType.sameType(this.wallpaper?.toAppWallpaper()?.type)) this.wallpaper else null
|
||||||
|
|
||||||
|
return AppWallpaper(type = when (mainType) {
|
||||||
|
is WallpaperType.Preset -> mainType.copy(
|
||||||
|
scale = mainType.scale ?: first?.scale ?: second?.scale ?: third?.scale
|
||||||
|
)
|
||||||
|
is WallpaperType.Image -> mainType.copy(
|
||||||
|
scale = if (themeOverridesForType == null) mainType.scale ?: first?.scale ?: second?.scale ?: third?.scale else second?.scale ?: third?.scale ?: mainType.scale,
|
||||||
|
scaleType = if (themeOverridesForType == null) mainType.scaleType ?: first?.scaleType ?: second?.scaleType ?: third?.scaleType else second?.scaleType ?: third?.scaleType ?: mainType.scaleType,
|
||||||
|
filename = if (themeOverridesForType == null) mainType.filename else first?.imageFile ?: second?.imageFile ?: third?.imageFile ?: mainType.filename,
|
||||||
|
)
|
||||||
|
is WallpaperType.Empty -> mainType
|
||||||
|
},
|
||||||
|
background = (first?.background ?: second?.background ?: third?.background)?.colorFromReadableHex() ?: mainType.defaultBackgroundColor(base, materialBackgroundColor),
|
||||||
|
tint = (first?.tint ?: second?.tint ?: third?.tint)?.colorFromReadableHex() ?: mainType.defaultTintColor(base)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withFilledColors(base: DefaultTheme, perChatTheme: ThemeColors?, perChatWallpaperType: WallpaperType?, perUserTheme: ThemeColors?, perUserWallpaperType: WallpaperType?, presetWallpaperTheme: ThemeColors?): ThemeColors {
|
||||||
|
val c = toColors(base, perChatTheme, perUserTheme, presetWallpaperTheme)
|
||||||
|
val ac = toAppColors(base, perChatTheme, perChatWallpaperType, perUserTheme, perUserWallpaperType, presetWallpaperTheme)
|
||||||
return ThemeColors(
|
return ThemeColors(
|
||||||
primary = c.primary.toReadableHex(),
|
primary = c.primary.toReadableHex(),
|
||||||
primaryVariant = c.primaryVariant.toReadableHex(),
|
primaryVariant = c.primaryVariant.toReadableHex(),
|
||||||
@@ -139,23 +419,71 @@ data class ThemeColors(
|
|||||||
background = c.background.toReadableHex(),
|
background = c.background.toReadableHex(),
|
||||||
surface = c.surface.toReadableHex(),
|
surface = c.surface.toReadableHex(),
|
||||||
title = ac.title.toReadableHex(),
|
title = ac.title.toReadableHex(),
|
||||||
|
primaryVariant2 = ac.primaryVariant2.toReadableHex(),
|
||||||
sentMessage = ac.sentMessage.toReadableHex(),
|
sentMessage = ac.sentMessage.toReadableHex(),
|
||||||
receivedMessage = ac.receivedMessage.toReadableHex()
|
sentQuote = ac.sentQuote.toReadableHex(),
|
||||||
|
receivedMessage = ac.receivedMessage.toReadableHex(),
|
||||||
|
receivedQuote = ac.receivedQuote.toReadableHex(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.colorFromReadableHex(): Color =
|
fun List<ThemeOverrides>.getTheme(themeId: String?): ThemeOverrides? =
|
||||||
Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong())
|
firstOrNull { it.themeId == themeId }
|
||||||
|
|
||||||
private fun Color.toReadableHex(): String = "#" + Integer.toHexString(toArgb())
|
fun List<ThemeOverrides>.getTheme(themeId: String?, type: WallpaperType?, base: DefaultTheme): ThemeOverrides? =
|
||||||
|
firstOrNull { it.themeId == themeId || it.isSame(type, base.themeName)}
|
||||||
|
|
||||||
|
fun List<ThemeOverrides>.replace(theme: ThemeOverrides): List<ThemeOverrides> {
|
||||||
|
val index = indexOfFirst { it.themeId == theme.themeId ||
|
||||||
|
// prevent situation when two themes has the same type but different theme id (maybe something was changed in prefs by hand)
|
||||||
|
it.isSame(WallpaperType.from(theme.wallpaper), theme.base.themeName)
|
||||||
|
}
|
||||||
|
return if (index != -1) {
|
||||||
|
val a = ArrayList(this)
|
||||||
|
a[index] = theme
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
this + theme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun List<ThemeOverrides>.sameTheme(type: WallpaperType?, themeName: String): ThemeOverrides? = firstOrNull { it.isSame(type, themeName) }
|
||||||
|
|
||||||
|
/** See [ThemesTest.testSkipDuplicates] */
|
||||||
|
fun List<ThemeOverrides>.skipDuplicates(): List<ThemeOverrides> {
|
||||||
|
val res = ArrayList<ThemeOverrides>()
|
||||||
|
forEach { theme ->
|
||||||
|
val themeType = WallpaperType.from(theme.wallpaper)
|
||||||
|
if (res.none { it.themeId == theme.themeId || it.isSame(themeType, theme.base.themeName) }) {
|
||||||
|
res.add(theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ThemeOverrides (
|
data class ThemeModeOverrides (
|
||||||
val base: DefaultTheme,
|
val light: ThemeModeOverride? = null,
|
||||||
val colors: ThemeColors
|
val dark: ThemeModeOverride? = null
|
||||||
) {
|
) {
|
||||||
fun withUpdatedColor(name: ThemeColor, color: String): ThemeOverrides {
|
fun preferredMode(darkTheme: Boolean): ThemeModeOverride? = when (darkTheme) {
|
||||||
|
false -> light
|
||||||
|
else -> dark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ThemeModeOverride (
|
||||||
|
val mode: DefaultThemeMode = CurrentColors.value.base.mode,
|
||||||
|
val colors: ThemeColors = ThemeColors(),
|
||||||
|
val wallpaper: ThemeWallpaper? = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
val type = WallpaperType.from(wallpaper)
|
||||||
|
|
||||||
|
fun withUpdatedColor(name: ThemeColor, color: String?): ThemeModeOverride {
|
||||||
return copy(colors = when (name) {
|
return copy(colors = when (name) {
|
||||||
ThemeColor.PRIMARY -> colors.copy(primary = color)
|
ThemeColor.PRIMARY -> colors.copy(primary = color)
|
||||||
ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color)
|
ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color)
|
||||||
@@ -164,9 +492,27 @@ data class ThemeOverrides (
|
|||||||
ThemeColor.BACKGROUND -> colors.copy(background = color)
|
ThemeColor.BACKGROUND -> colors.copy(background = color)
|
||||||
ThemeColor.SURFACE -> colors.copy(surface = color)
|
ThemeColor.SURFACE -> colors.copy(surface = color)
|
||||||
ThemeColor.TITLE -> colors.copy(title = color)
|
ThemeColor.TITLE -> colors.copy(title = color)
|
||||||
|
ThemeColor.PRIMARY_VARIANT2 -> colors.copy(primaryVariant2 = color)
|
||||||
ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color)
|
ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color)
|
||||||
|
ThemeColor.SENT_QUOTE -> colors.copy(sentQuote = color)
|
||||||
ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color)
|
ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color)
|
||||||
})
|
ThemeColor.RECEIVED_QUOTE -> colors.copy(receivedQuote = color)
|
||||||
|
ThemeColor.WALLPAPER_BACKGROUND -> colors.copy()
|
||||||
|
ThemeColor.WALLPAPER_TINT -> colors.copy()
|
||||||
|
}, wallpaper = when (name) {
|
||||||
|
ThemeColor.WALLPAPER_BACKGROUND -> wallpaper?.copy(background = color)
|
||||||
|
ThemeColor.WALLPAPER_TINT -> wallpaper?.copy(tint = color)
|
||||||
|
else -> wallpaper?.copy()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
fun withFilledAppDefaults(mode: DefaultThemeMode, base: DefaultTheme): ThemeModeOverride =
|
||||||
|
ThemeModeOverride(
|
||||||
|
mode = mode,
|
||||||
|
colors = ThemeOverrides(base = base).withFilledColors(base, null, null, null, null, null),
|
||||||
|
wallpaper = ThemeWallpaper(preset = PresetWallpaper.SCHOOL.filename)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +550,6 @@ val DarkColorPalette = darkColors(
|
|||||||
// background = Color.Black,
|
// background = Color.Black,
|
||||||
surface = Color(0xFF222222),
|
surface = Color(0xFF222222),
|
||||||
// background = Color(0xFF121212),
|
// background = Color(0xFF121212),
|
||||||
// surface = Color(0xFF121212),
|
|
||||||
error = Color.Red,
|
error = Color.Red,
|
||||||
onBackground = Color(0xFFFFFBFA),
|
onBackground = Color(0xFFFFFBFA),
|
||||||
onSurface = Color(0xFFFFFBFA),
|
onSurface = Color(0xFFFFFBFA),
|
||||||
@@ -212,8 +557,11 @@ val DarkColorPalette = darkColors(
|
|||||||
)
|
)
|
||||||
val DarkColorPaletteApp = AppColors(
|
val DarkColorPaletteApp = AppColors(
|
||||||
title = SimplexBlue,
|
title = SimplexBlue,
|
||||||
sentMessage = SentMessageColor,
|
primaryVariant2 = Color(0xFF18262E),
|
||||||
receivedMessage = Color(0x20B1B0B5)
|
sentMessage = Color(0xFF18262E),
|
||||||
|
sentQuote = Color(0xFF1D3847),
|
||||||
|
receivedMessage = Color(0xff262627),
|
||||||
|
receivedQuote = Color(0xff373739),
|
||||||
)
|
)
|
||||||
|
|
||||||
val LightColorPalette = lightColors(
|
val LightColorPalette = lightColors(
|
||||||
@@ -231,8 +579,11 @@ val LightColorPalette = lightColors(
|
|||||||
)
|
)
|
||||||
val LightColorPaletteApp = AppColors(
|
val LightColorPaletteApp = AppColors(
|
||||||
title = SimplexBlue,
|
title = SimplexBlue,
|
||||||
sentMessage = SentMessageColor,
|
primaryVariant2 = Color(0xFFE9F7FF),
|
||||||
receivedMessage = Color(0x20B1B0B5)
|
sentMessage = Color(0xFFE9F7FF),
|
||||||
|
sentQuote = Color(0xFFD6F0FF),
|
||||||
|
receivedMessage = Color(0xfff5f5f6),
|
||||||
|
receivedQuote = Color(0xffececee),
|
||||||
)
|
)
|
||||||
|
|
||||||
val SimplexColorPalette = darkColors(
|
val SimplexColorPalette = darkColors(
|
||||||
@@ -251,11 +602,39 @@ val SimplexColorPalette = darkColors(
|
|||||||
)
|
)
|
||||||
val SimplexColorPaletteApp = AppColors(
|
val SimplexColorPaletteApp = AppColors(
|
||||||
title = Color(0xFF267BE5),
|
title = Color(0xFF267BE5),
|
||||||
sentMessage = SentMessageColor,
|
primaryVariant2 = Color(0xFF172941),
|
||||||
receivedMessage = Color(0x20B1B0B5)
|
sentMessage = Color(0xFF172941),
|
||||||
|
sentQuote = Color(0xFF1C3A57),
|
||||||
|
receivedMessage = Color(0xff25283a),
|
||||||
|
receivedQuote = Color(0xff36394a),
|
||||||
)
|
)
|
||||||
|
|
||||||
val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = MutableStateFlow(ThemeManager.currentColors(isInNightMode()))
|
val BlackColorPalette = darkColors(
|
||||||
|
primary = Color(0xff0077e0), // If this value changes also need to update #0088ff in string resource files
|
||||||
|
primaryVariant = Color(0xff0077e0),
|
||||||
|
secondary = HighOrLowlight,
|
||||||
|
secondaryVariant = DarkGray,
|
||||||
|
background = Color(0xff070707),
|
||||||
|
surface = Color(0xff161617),
|
||||||
|
// background = Color(0xFF121212),
|
||||||
|
// surface = Color(0xFF121212),
|
||||||
|
error = Color.Red,
|
||||||
|
onBackground = Color(0xFFFFFBFA),
|
||||||
|
onSurface = Color(0xFFFFFBFA),
|
||||||
|
// onError: Color = Color.Black,
|
||||||
|
)
|
||||||
|
val BlackColorPaletteApp = AppColors(
|
||||||
|
title = Color(0xff0077e0),
|
||||||
|
primaryVariant2 = Color(0xff243747),
|
||||||
|
sentMessage = Color(0xFF18262E),
|
||||||
|
sentQuote = Color(0xFF1D3847),
|
||||||
|
receivedMessage = Color(0xff1b1b1b),
|
||||||
|
receivedQuote = Color(0xff29292b),
|
||||||
|
)
|
||||||
|
|
||||||
|
var systemInDarkThemeCurrently: Boolean = isInNightMode()
|
||||||
|
|
||||||
|
val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = MutableStateFlow(ThemeManager.currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get()))
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight
|
fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight
|
||||||
@@ -263,31 +642,113 @@ fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLi
|
|||||||
@Composable
|
@Composable
|
||||||
expect fun isSystemInDarkTheme(): Boolean
|
expect fun isSystemInDarkTheme(): Boolean
|
||||||
|
|
||||||
|
internal val LocalAppColors = staticCompositionLocalOf { LightColorPaletteApp }
|
||||||
|
internal val LocalAppWallpaper = staticCompositionLocalOf { AppWallpaper() }
|
||||||
|
|
||||||
|
val MaterialTheme.appColors: AppColors
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalAppColors.current
|
||||||
|
|
||||||
|
fun AppColors.updateColorsFrom(other: AppColors) {
|
||||||
|
title = other.title
|
||||||
|
primaryVariant2 = other.primaryVariant2
|
||||||
|
sentMessage = other.sentMessage
|
||||||
|
sentQuote = other.sentQuote
|
||||||
|
receivedMessage = other.receivedMessage
|
||||||
|
receivedQuote = other.receivedQuote
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AppWallpaper.updateWallpaperFrom(other: AppWallpaper) {
|
||||||
|
background = other.background
|
||||||
|
tint = other.tint
|
||||||
|
type = other.type
|
||||||
|
}
|
||||||
|
|
||||||
|
val MaterialTheme.wallpaper: AppWallpaper
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalAppWallpaper.current
|
||||||
|
|
||||||
fun reactOnDarkThemeChanges(isDark: Boolean) {
|
fun reactOnDarkThemeChanges(isDark: Boolean) {
|
||||||
if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.isLight == isDark) {
|
systemInDarkThemeCurrently = isDark
|
||||||
|
if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.value.colors.isLight == isDark) {
|
||||||
// Change active colors from light to dark and back based on system theme
|
// Change active colors from light to dark and back based on system theme
|
||||||
ThemeManager.applyTheme(DefaultTheme.SYSTEM.name, isDark)
|
ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
|
fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
|
||||||
LaunchedEffect(darkTheme) {
|
// TODO: Fix preview working with dark/light theme
|
||||||
// For preview
|
|
||||||
if (darkTheme != null)
|
// LaunchedEffect(darkTheme) {
|
||||||
CurrentColors.value = ThemeManager.currentColors(darkTheme)
|
// // For preview
|
||||||
}
|
// if (darkTheme != null)
|
||||||
val systemDark = isSystemInDarkTheme()
|
// CurrentColors.value = ThemeManager.currentColors(darkTheme, null, null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get())
|
||||||
LaunchedEffect(systemDark) {
|
// }
|
||||||
reactOnDarkThemeChanges(systemDark)
|
val systemDark = rememberUpdatedState(isSystemInDarkTheme())
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
// snapshotFlow vs LaunchedEffect reduce number of recomposes
|
||||||
|
snapshotFlow { systemDark.value }
|
||||||
|
.collect {
|
||||||
|
reactOnDarkThemeChanges(systemDark.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val theme by CurrentColors.collectAsState()
|
val theme by CurrentColors.collectAsState()
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
// snapshotFlow vs LaunchedEffect reduce number of recomposes when user is changed or it's themes
|
||||||
|
snapshotFlow { chatModel.currentUser.value?.uiThemes }
|
||||||
|
.collect {
|
||||||
|
ThemeManager.applyTheme(appPrefs.currentTheme.get()!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colors = theme.colors,
|
colors = theme.colors,
|
||||||
typography = Typography,
|
typography = Typography,
|
||||||
shapes = Shapes,
|
shapes = Shapes,
|
||||||
content = {
|
content = {
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colors.onBackground, content = content)
|
val rememberedAppColors = remember {
|
||||||
|
// Explicitly creating a new object here so we don't mutate the initial [appColors]
|
||||||
|
// provided, and overwrite the values set in it.
|
||||||
|
theme.appColors.copy()
|
||||||
|
}.apply { updateColorsFrom(theme.appColors) }
|
||||||
|
val rememberedWallpaper = remember {
|
||||||
|
// Explicitly creating a new object here so we don't mutate the initial [wallpaper]
|
||||||
|
// provided, and overwrite the values set in it.
|
||||||
|
theme.wallpaper.copy()
|
||||||
|
}.apply { updateWallpaperFrom(theme.wallpaper) }
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalContentColor provides MaterialTheme.colors.onBackground,
|
||||||
|
LocalAppColors provides rememberedAppColors,
|
||||||
|
LocalAppWallpaper provides rememberedWallpaper,
|
||||||
|
content = content)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SimpleXThemeOverride(theme: ThemeManager.ActiveTheme, content: @Composable () -> Unit) {
|
||||||
|
MaterialTheme(
|
||||||
|
colors = theme.colors,
|
||||||
|
typography = Typography,
|
||||||
|
shapes = Shapes,
|
||||||
|
content = {
|
||||||
|
val rememberedAppColors = remember {
|
||||||
|
// Explicitly creating a new object here so we don't mutate the initial [appColors]
|
||||||
|
// provided, and overwrite the values set in it.
|
||||||
|
theme.appColors.copy()
|
||||||
|
}.apply { updateColorsFrom(theme.appColors) }
|
||||||
|
val rememberedWallpaper = remember {
|
||||||
|
// Explicitly creating a new object here so we don't mutate the initial [wallpaper]
|
||||||
|
// provided, and overwrite the values set in it.
|
||||||
|
theme.wallpaper.copy()
|
||||||
|
}.apply { updateWallpaperFrom(theme.wallpaper) }
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalContentColor provides MaterialTheme.colors.onBackground,
|
||||||
|
LocalAppColors provides rememberedAppColors,
|
||||||
|
LocalAppWallpaper provides rememberedWallpaper,
|
||||||
|
content = content)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+184
-109
@@ -1,14 +1,14 @@
|
|||||||
package chat.simplex.common.ui.theme
|
package chat.simplex.common.ui.theme
|
||||||
|
|
||||||
import androidx.compose.material.Colors
|
import androidx.compose.material.Colors
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.model.AppPreferences
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.model.ChatController
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.platform.platform
|
import java.io.File
|
||||||
import chat.simplex.common.views.helpers.generalGetString
|
|
||||||
|
|
||||||
// https://github.com/rsms/inter
|
// https://github.com/rsms/inter
|
||||||
// I place it here because IDEA shows an error (but still works anyway) when this declaration inside Type.kt
|
// I place it here because IDEA shows an error (but still works anyway) when this declaration inside Type.kt
|
||||||
@@ -18,140 +18,215 @@ expect val EmojiFont: FontFamily
|
|||||||
object ThemeManager {
|
object ThemeManager {
|
||||||
private val appPrefs: AppPreferences = ChatController.appPrefs
|
private val appPrefs: AppPreferences = ChatController.appPrefs
|
||||||
|
|
||||||
data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors)
|
data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors, val wallpaper: AppWallpaper = AppWallpaper())
|
||||||
|
|
||||||
private fun systemDarkThemeColors(): Pair<Colors, DefaultTheme> = when (appPrefs.systemDarkTheme.get()) {
|
private fun systemDarkThemeColors(): Pair<Colors, DefaultTheme> = when (appPrefs.systemDarkTheme.get()) {
|
||||||
DefaultTheme.DARK.name -> DarkColorPalette to DefaultTheme.DARK
|
DefaultTheme.DARK.themeName -> DarkColorPalette to DefaultTheme.DARK
|
||||||
DefaultTheme.SIMPLEX.name -> SimplexColorPalette to DefaultTheme.SIMPLEX
|
DefaultTheme.SIMPLEX.themeName -> SimplexColorPalette to DefaultTheme.SIMPLEX
|
||||||
|
DefaultTheme.BLACK.themeName -> BlackColorPalette to DefaultTheme.BLACK
|
||||||
else -> SimplexColorPalette to DefaultTheme.SIMPLEX
|
else -> SimplexColorPalette to DefaultTheme.SIMPLEX
|
||||||
}
|
}
|
||||||
|
|
||||||
fun currentColors(darkForSystemTheme: Boolean): ActiveTheme {
|
private fun nonSystemThemeName(): String {
|
||||||
val themeName = appPrefs.currentTheme.get()!!
|
val themeName = appPrefs.currentTheme.get()!!
|
||||||
val themeOverrides = appPrefs.themeOverrides.get()
|
return if (themeName != DefaultTheme.SYSTEM_THEME_NAME) {
|
||||||
|
|
||||||
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
|
|
||||||
themeName
|
themeName
|
||||||
} else {
|
} else {
|
||||||
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name
|
if (systemInDarkThemeCurrently) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.themeName
|
||||||
}
|
}
|
||||||
val theme = themeOverrides[nonSystemThemeName]
|
}
|
||||||
|
|
||||||
|
fun defaultActiveTheme(appSettingsTheme: List<ThemeOverrides>): ThemeOverrides? {
|
||||||
|
val nonSystemThemeName = nonSystemThemeName()
|
||||||
|
val defaultThemeId = appPrefs.currentThemeIds.get()[nonSystemThemeName]
|
||||||
|
return appSettingsTheme.getTheme(defaultThemeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaultActiveTheme(perUserTheme: ThemeModeOverrides?, appSettingsTheme: List<ThemeOverrides>): ThemeModeOverride {
|
||||||
|
val perUserTheme = if (!CurrentColors.value.colors.isLight) perUserTheme?.dark else perUserTheme?.light
|
||||||
|
if (perUserTheme != null) {
|
||||||
|
return perUserTheme
|
||||||
|
}
|
||||||
|
val defaultTheme = defaultActiveTheme(appSettingsTheme)
|
||||||
|
return ThemeModeOverride(colors = defaultTheme?.colors ?: ThemeColors(), wallpaper = defaultTheme?.wallpaper)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun currentColors(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?, appSettingsTheme: List<ThemeOverrides>): ActiveTheme {
|
||||||
|
val themeName = appPrefs.currentTheme.get()!!
|
||||||
|
val nonSystemThemeName = nonSystemThemeName()
|
||||||
|
val defaultTheme = defaultActiveTheme(appSettingsTheme)
|
||||||
|
|
||||||
val baseTheme = when (nonSystemThemeName) {
|
val baseTheme = when (nonSystemThemeName) {
|
||||||
DefaultTheme.LIGHT.name -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
|
DefaultTheme.LIGHT.themeName -> ActiveTheme(DefaultTheme.LIGHT.themeName, DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.LIGHT)))
|
||||||
DefaultTheme.DARK.name -> Triple(DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp)
|
DefaultTheme.DARK.themeName -> ActiveTheme(DefaultTheme.DARK.themeName, DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.DARK)))
|
||||||
DefaultTheme.SIMPLEX.name -> Triple(DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp)
|
DefaultTheme.SIMPLEX.themeName -> ActiveTheme(DefaultTheme.SIMPLEX.themeName, DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.SIMPLEX)))
|
||||||
else -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
|
DefaultTheme.BLACK.themeName -> ActiveTheme(DefaultTheme.BLACK.themeName, DefaultTheme.BLACK, BlackColorPalette, BlackColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.BLACK)))
|
||||||
|
else -> ActiveTheme(DefaultTheme.LIGHT.themeName, DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.LIGHT)))
|
||||||
}
|
}
|
||||||
if (theme == null) {
|
|
||||||
return ActiveTheme(themeName, baseTheme.first, baseTheme.second, baseTheme.third)
|
val perUserTheme = if (baseTheme.colors.isLight) perUserTheme?.light else perUserTheme?.dark
|
||||||
|
val theme = (appSettingsTheme.sameTheme(themeOverridesForType ?: perChatTheme?.type ?: perUserTheme?.type ?: defaultTheme?.wallpaper?.toAppWallpaper()?.type, nonSystemThemeName) ?: defaultTheme)
|
||||||
|
|
||||||
|
if (theme == null && perUserTheme == null && perChatTheme == null && themeOverridesForType == null) {
|
||||||
|
return ActiveTheme(themeName, baseTheme.base, baseTheme.colors, baseTheme.appColors, baseTheme.wallpaper)
|
||||||
}
|
}
|
||||||
return ActiveTheme(themeName, baseTheme.first, theme.colors.toColors(theme.base), theme.colors.toAppColors(theme.base))
|
val presetWallpaperTheme = when {
|
||||||
|
perChatTheme?.wallpaper != null -> if (perChatTheme.wallpaper.preset != null) PresetWallpaper.from(perChatTheme.wallpaper.preset)?.colors?.get(baseTheme.base) else null
|
||||||
|
perUserTheme?.wallpaper != null -> if (perUserTheme.wallpaper.preset != null) PresetWallpaper.from(perUserTheme.wallpaper.preset)?.colors?.get(baseTheme.base) else null
|
||||||
|
else -> if (theme?.wallpaper?.preset != null) PresetWallpaper.from(theme.wallpaper.preset)?.colors?.get(baseTheme.base) else null
|
||||||
|
}
|
||||||
|
val themeOrEmpty = theme ?: ThemeOverrides(base = baseTheme.base)
|
||||||
|
val colors = themeOrEmpty.toColors(themeOrEmpty.base, perChatTheme?.colors, perUserTheme?.colors, presetWallpaperTheme)
|
||||||
|
return ActiveTheme(
|
||||||
|
themeName,
|
||||||
|
baseTheme.base,
|
||||||
|
colors,
|
||||||
|
themeOrEmpty.toAppColors(themeOrEmpty.base, perChatTheme?.colors, perChatTheme?.type, perUserTheme?.colors, perUserTheme?.type, presetWallpaperTheme),
|
||||||
|
themeOrEmpty.toAppWallpaper(themeOverridesForType, perChatTheme, perUserTheme, colors.background)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun currentThemeOverridesForExport(darkForSystemTheme: Boolean): ThemeOverrides {
|
fun currentThemeOverridesForExport(perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?): ThemeOverrides {
|
||||||
val themeName = appPrefs.currentTheme.get()!!
|
val current = currentColors(null, perChatTheme, perUserTheme, appPrefs.themeOverrides.get())
|
||||||
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
|
val wType = current.wallpaper.type
|
||||||
themeName
|
val wBackground = current.wallpaper.background
|
||||||
} else {
|
val wTint = current.wallpaper.tint
|
||||||
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name
|
return ThemeOverrides(
|
||||||
}
|
themeId = "",
|
||||||
val overrides = appPrefs.themeOverrides.get().toMutableMap()
|
base = current.base,
|
||||||
val nonFilledTheme = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors())
|
colors = ThemeColors.from(current.colors, current.appColors),
|
||||||
return nonFilledTheme.copy(colors = nonFilledTheme.colors.withFilledColors(CurrentColors.value.base))
|
wallpaper = if (wType !is WallpaperType.Empty) ThemeWallpaper.from(wType, wBackground?.toReadableHex(), wTint?.toReadableHex()).withFilledWallpaperBase64() else null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// colors, default theme enum, localized name of theme
|
fun applyTheme(theme: String) {
|
||||||
fun allThemes(darkForSystemTheme: Boolean): List<Triple<Colors, DefaultTheme, String>> {
|
|
||||||
val allThemes = ArrayList<Triple<Colors, DefaultTheme, String>>()
|
|
||||||
allThemes.add(
|
|
||||||
Triple(
|
|
||||||
if (darkForSystemTheme) systemDarkThemeColors().first else LightColorPalette,
|
|
||||||
DefaultTheme.SYSTEM,
|
|
||||||
generalGetString(MR.strings.theme_system)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
allThemes.add(
|
|
||||||
Triple(
|
|
||||||
LightColorPalette,
|
|
||||||
DefaultTheme.LIGHT,
|
|
||||||
generalGetString(MR.strings.theme_light)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
allThemes.add(
|
|
||||||
Triple(
|
|
||||||
DarkColorPalette,
|
|
||||||
DefaultTheme.DARK,
|
|
||||||
generalGetString(MR.strings.theme_dark)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
allThemes.add(
|
|
||||||
Triple(
|
|
||||||
SimplexColorPalette,
|
|
||||||
DefaultTheme.SIMPLEX,
|
|
||||||
generalGetString(MR.strings.theme_simplex)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return allThemes
|
|
||||||
}
|
|
||||||
|
|
||||||
fun applyTheme(theme: String, darkForSystemTheme: Boolean) {
|
|
||||||
appPrefs.currentTheme.set(theme)
|
appPrefs.currentTheme.set(theme)
|
||||||
CurrentColors.value = currentColors(darkForSystemTheme)
|
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
platform.androidSetNightModeIfSupported()
|
platform.androidSetNightModeIfSupported()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeDarkTheme(theme: String, darkForSystemTheme: Boolean) {
|
fun changeDarkTheme(theme: String) {
|
||||||
appPrefs.systemDarkTheme.set(theme)
|
appPrefs.systemDarkTheme.set(theme)
|
||||||
CurrentColors.value = currentColors(darkForSystemTheme)
|
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveAndApplyThemeColor(name: ThemeColor, color: Color? = null, darkForSystemTheme: Boolean) {
|
fun saveAndApplyThemeColor(baseTheme: DefaultTheme, name: ThemeColor, color: Color? = null, pref: SharedPreference<List<ThemeOverrides>> = appPrefs.themeOverrides) {
|
||||||
val themeName = appPrefs.currentTheme.get()!!
|
val nonSystemThemeName = baseTheme.themeName
|
||||||
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
|
val overrides = pref.get()
|
||||||
themeName
|
val themeId = appPrefs.currentThemeIds.get()[nonSystemThemeName]
|
||||||
} else {
|
val prevValue = overrides.getTheme(themeId) ?: ThemeOverrides(base = baseTheme)
|
||||||
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name
|
pref.set(overrides.replace(prevValue.withUpdatedColor(name, color?.toReadableHex())))
|
||||||
|
val themeIds = appPrefs.currentThemeIds.get().toMutableMap()
|
||||||
|
themeIds[nonSystemThemeName] = prevValue.themeId
|
||||||
|
appPrefs.currentThemeIds.set(themeIds)
|
||||||
|
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyThemeColor(name: ThemeColor, color: Color? = null, pref: MutableState<ThemeModeOverride>) {
|
||||||
|
pref.value = pref.value.withUpdatedColor(name, color?.toReadableHex())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveAndApplyWallpaper(baseTheme: DefaultTheme, type: WallpaperType?, pref: SharedPreference<List<ThemeOverrides>> = appPrefs.themeOverrides) {
|
||||||
|
val nonSystemThemeName = baseTheme.themeName
|
||||||
|
val overrides = pref.get()
|
||||||
|
val theme = overrides.sameTheme(type, baseTheme.themeName)
|
||||||
|
val prevValue = theme ?: ThemeOverrides(base = baseTheme)
|
||||||
|
pref.set(overrides.replace(prevValue.copy(wallpaper = if (type != null && type !is WallpaperType.Empty) ThemeWallpaper.from(type, prevValue.wallpaper?.background, prevValue.wallpaper?.tint) else null)))
|
||||||
|
val themeIds = appPrefs.currentThemeIds.get().toMutableMap()
|
||||||
|
themeIds[nonSystemThemeName] = prevValue.themeId
|
||||||
|
appPrefs.currentThemeIds.set(themeIds)
|
||||||
|
CurrentColors.value = currentColors( null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyFromSameThemeOverrides(type: WallpaperType?, lowerLevelOverride: ThemeModeOverride?, pref: MutableState<ThemeModeOverride>): Boolean {
|
||||||
|
val overrides = appPrefs.themeOverrides.get()
|
||||||
|
val sameWallpaper = if (lowerLevelOverride?.type?.sameType(type) == true) lowerLevelOverride.wallpaper else overrides.sameTheme(type, CurrentColors.value.base.themeName)?.wallpaper
|
||||||
|
if (sameWallpaper == null) {
|
||||||
|
if (type != null) {
|
||||||
|
pref.value = ThemeModeOverride(wallpaper = ThemeWallpaper.from(type, null, null).copy(scale = null, scaleType = null))
|
||||||
|
} else {
|
||||||
|
// Make an empty wallpaper to override any top level ones
|
||||||
|
pref.value = ThemeModeOverride(wallpaper = ThemeWallpaper())
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
var colorToSet = color
|
var type = sameWallpaper.toAppWallpaper().type
|
||||||
if (colorToSet == null) {
|
if (type is WallpaperType.Image && sameWallpaper.imageFile == type.filename) {
|
||||||
// Setting default color from a base theme
|
// same image file. Needs to be copied first in order to be able to remove the file once it's not needed anymore without affecting main theme override
|
||||||
colorToSet = when(nonSystemThemeName) {
|
val filename = saveWallpaperFile(File(getWallpaperFilePath(type.filename)).toURI())
|
||||||
DefaultTheme.LIGHT.name -> name.fromColors(LightColorPalette, LightColorPaletteApp)
|
if (filename != null) {
|
||||||
DefaultTheme.DARK.name -> name.fromColors(DarkColorPalette, DarkColorPaletteApp)
|
type = WallpaperType.Image(filename, type.scale, type.scaleType)
|
||||||
DefaultTheme.SIMPLEX.name -> name.fromColors(SimplexColorPalette, SimplexColorPaletteApp)
|
} else {
|
||||||
// Will not be here
|
Log.e(TAG, "Error while copying wallpaper from global overrides to chat overrides")
|
||||||
else -> return
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val overrides = appPrefs.themeOverrides.get().toMutableMap()
|
val prevValue = pref.value
|
||||||
val prevValue = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors())
|
pref.value = prevValue.copy(
|
||||||
overrides[nonSystemThemeName] = prevValue.withUpdatedColor(name, colorToSet.toReadableHex())
|
colors = ThemeColors(),
|
||||||
appPrefs.themeOverrides.set(overrides)
|
wallpaper = ThemeWallpaper.from(type, null, null).copy(scale = null, scaleType = null)
|
||||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
|
)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveAndApplyThemeOverrides(theme: ThemeOverrides, darkForSystemTheme: Boolean) {
|
fun applyWallpaper(type: WallpaperType?, pref: MutableState<ThemeModeOverride>) {
|
||||||
val overrides = appPrefs.themeOverrides.get().toMutableMap()
|
val prevValue = pref.value
|
||||||
val prevValue = overrides[theme.base.name] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors())
|
pref.value = prevValue.copy(
|
||||||
overrides[theme.base.name] = prevValue.copy(colors = theme.colors)
|
wallpaper = if (type != null)
|
||||||
appPrefs.themeOverrides.set(overrides)
|
ThemeWallpaper.from(type, prevValue.wallpaper?.background, prevValue.wallpaper?.tint)
|
||||||
appPrefs.currentTheme.set(theme.base.name)
|
else null
|
||||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetAllThemeColors(darkForSystemTheme: Boolean) {
|
fun saveAndApplyThemeOverrides(theme: ThemeOverrides, pref: SharedPreference<List<ThemeOverrides>> = appPrefs.themeOverrides) {
|
||||||
val themeName = appPrefs.currentTheme.get()!!
|
val wallpaper = theme.wallpaper?.importFromString()
|
||||||
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
|
val nonSystemThemeName = theme.base.themeName
|
||||||
themeName
|
val overrides = pref.get()
|
||||||
} else {
|
val prevValue = overrides.getTheme(null, wallpaper?.toAppWallpaper()?.type, theme.base) ?: ThemeOverrides(base = theme.base)
|
||||||
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name
|
if (prevValue.wallpaper?.imageFile != null) {
|
||||||
|
File(getWallpaperFilePath(prevValue.wallpaper.imageFile)).delete()
|
||||||
|
}
|
||||||
|
pref.set(overrides.replace(prevValue.copy(base = theme.base, colors = theme.colors, wallpaper = wallpaper)))
|
||||||
|
appPrefs.currentTheme.set(nonSystemThemeName)
|
||||||
|
val currentThemeIds = appPrefs.currentThemeIds.get().toMutableMap()
|
||||||
|
currentThemeIds[nonSystemThemeName] = prevValue.themeId
|
||||||
|
appPrefs.currentThemeIds.set(currentThemeIds)
|
||||||
|
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetAllThemeColors(pref: SharedPreference<List<ThemeOverrides>> = appPrefs.themeOverrides) {
|
||||||
|
val nonSystemThemeName = nonSystemThemeName()
|
||||||
|
val themeId = appPrefs.currentThemeIds.get()[nonSystemThemeName] ?: return
|
||||||
|
val overrides = pref.get()
|
||||||
|
val prevValue = overrides.getTheme(themeId) ?: return
|
||||||
|
pref.set(overrides.replace(prevValue.copy(colors = ThemeColors(), wallpaper = prevValue.wallpaper?.copy(background = null, tint = null))))
|
||||||
|
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetAllThemeColors(pref: MutableState<ThemeModeOverride>) {
|
||||||
|
val prevValue = pref.value
|
||||||
|
pref.value = prevValue.copy(colors = ThemeColors(), wallpaper = prevValue.wallpaper?.copy(background = null, tint = null))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeTheme(themeId: String?) {
|
||||||
|
val themes = ArrayList(appPrefs.themeOverrides.get())
|
||||||
|
themes.removeAll { it.themeId == themeId }
|
||||||
|
appPrefs.themeOverrides.set(themes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.colorFromReadableHex(): Color =
|
||||||
|
Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong())
|
||||||
|
|
||||||
|
fun Color.toReadableHex(): String {
|
||||||
|
val s = Integer.toHexString(toArgb())
|
||||||
|
return when {
|
||||||
|
this == Color.Transparent -> "#00ffffff"
|
||||||
|
s.length == 1 -> "#ff$s$s$s$s$s$s"
|
||||||
|
s.length == 2 -> "#ff$s$s$s"
|
||||||
|
s.length == 3 -> "#ff$s$s"
|
||||||
|
s.length == 6 && this.alpha == 0f -> "#00$s"
|
||||||
|
s.length == 6 -> "#ff$s"
|
||||||
|
else -> "#$s"
|
||||||
}
|
}
|
||||||
val overrides = appPrefs.themeOverrides.get().toMutableMap()
|
|
||||||
val prevValue = overrides[nonSystemThemeName] ?: return
|
|
||||||
overrides[nonSystemThemeName] = prevValue.copy(colors = ThemeColors())
|
|
||||||
appPrefs.themeOverrides.set(overrides)
|
|
||||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Color.toReadableHex(): String = "#" + Integer.toHexString(toArgb())
|
|
||||||
|
|||||||
+65
@@ -29,6 +29,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
import chat.simplex.common.model.ChatModel.controller
|
import chat.simplex.common.model.ChatModel.controller
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
@@ -337,6 +338,16 @@ fun ChatInfoLayout(
|
|||||||
if (cStats != null && cStats.ratchetSyncAllowed) {
|
if (cStats != null && cStats.ratchetSyncAllowed) {
|
||||||
SynchronizeConnectionButton(syncContactConnection)
|
SynchronizeConnectionButton(syncContactConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WallpaperButton {
|
||||||
|
ModalManager.end.showModal {
|
||||||
|
val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } }
|
||||||
|
val c = chat.value
|
||||||
|
if (c != null) {
|
||||||
|
ChatWallpaperEditorModal(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// } else if (developerTools) {
|
// } else if (developerTools) {
|
||||||
// SynchronizeConnectionButtonForce(syncContactConnectionForce)
|
// SynchronizeConnectionButtonForce(syncContactConnectionForce)
|
||||||
// }
|
// }
|
||||||
@@ -642,6 +653,15 @@ private fun SendReceiptsOption(currentUser: User, state: State<SendReceipts>, on
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WallpaperButton(onClick: () -> Unit) {
|
||||||
|
SettingsActionItem(
|
||||||
|
painterResource(MR.images.ic_image),
|
||||||
|
stringResource(MR.strings.settings_section_title_chat_theme),
|
||||||
|
click = onClick
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ClearChatButton(onClick: () -> Unit) {
|
fun ClearChatButton(onClick: () -> Unit) {
|
||||||
SettingsActionItem(
|
SettingsActionItem(
|
||||||
@@ -675,6 +695,51 @@ fun ShareAddressButton(onClick: () -> Unit) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ModalData.ChatWallpaperEditorModal(chat: Chat) {
|
||||||
|
val themes = remember(CurrentColors.collectAsState().value.base) {
|
||||||
|
(chat.chatInfo as? ChatInfo.Direct)?.contact?.uiThemes
|
||||||
|
?: (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.uiThemes
|
||||||
|
?: ThemeModeOverrides()
|
||||||
|
}
|
||||||
|
val globalThemeUsed = remember { stateGetOrPut("globalThemeUsed") { false } }
|
||||||
|
val initialTheme = remember(CurrentColors.collectAsState().value.base) {
|
||||||
|
val preferred = themes.preferredMode(!CurrentColors.value.colors.isLight)
|
||||||
|
globalThemeUsed.value = preferred == null
|
||||||
|
preferred ?: ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
}
|
||||||
|
ChatWallpaperEditor(
|
||||||
|
initialTheme,
|
||||||
|
applyToMode = if (themes.light == themes.dark) null else initialTheme.mode,
|
||||||
|
globalThemeUsed = globalThemeUsed,
|
||||||
|
save = { applyToMode, newTheme ->
|
||||||
|
save(applyToMode, newTheme, chatModel.getChat(chat.id) ?: chat)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun save(applyToMode: DefaultThemeMode?, newTheme: ThemeModeOverride?, chat: Chat) {
|
||||||
|
val unchangedThemes: ThemeModeOverrides = ((chat.chatInfo as? ChatInfo.Direct)?.contact?.uiThemes ?: (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.uiThemes) ?: ThemeModeOverrides()
|
||||||
|
val wallpaperFiles = listOf(unchangedThemes.light?.wallpaper?.imageFile, unchangedThemes.dark?.wallpaper?.imageFile)
|
||||||
|
var changedThemes: ThemeModeOverrides? = unchangedThemes
|
||||||
|
val changed = newTheme?.copy(wallpaper = newTheme.wallpaper?.withFilledWallpaperPath())
|
||||||
|
changedThemes = when (applyToMode) {
|
||||||
|
null -> changedThemes?.copy(light = changed?.copy(mode = DefaultThemeMode.LIGHT), dark = changed?.copy(mode = DefaultThemeMode.DARK))
|
||||||
|
DefaultThemeMode.LIGHT -> changedThemes?.copy(light = changed?.copy(mode = applyToMode))
|
||||||
|
DefaultThemeMode.DARK -> changedThemes?.copy(dark = changed?.copy(mode = applyToMode))
|
||||||
|
}
|
||||||
|
changedThemes = if (changedThemes?.light != null || changedThemes?.dark != null) changedThemes else null
|
||||||
|
val wallpaperFilesToDelete = wallpaperFiles - changedThemes?.light?.wallpaper?.imageFile - changedThemes?.dark?.wallpaper?.imageFile
|
||||||
|
wallpaperFilesToDelete.forEach(::removeWallpaperFile)
|
||||||
|
|
||||||
|
if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) {
|
||||||
|
if (chat.chatInfo is ChatInfo.Direct) {
|
||||||
|
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes)))
|
||||||
|
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||||
|
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi {
|
private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi {
|
||||||
val chatRh = chat.remoteHostId
|
val chatRh = chat.remoteHostId
|
||||||
chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let {
|
chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let {
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ sealed class CIInfoTab {
|
|||||||
@Composable
|
@Composable
|
||||||
fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools: Boolean) {
|
fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools: Boolean) {
|
||||||
val sent = ci.chatDir.sent
|
val sent = ci.chatDir.sent
|
||||||
val appColors = CurrentColors.collectAsState().value.appColors
|
val appColors = MaterialTheme.appColors
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val selection = remember { mutableStateOf<CIInfoTab>(CIInfoTab.History) }
|
val selection = remember { mutableStateOf<CIInfoTab>(CIInfoTab.History) }
|
||||||
|
|
||||||
|
|||||||
+362
-349
@@ -5,25 +5,24 @@ import androidx.compose.foundation.*
|
|||||||
import androidx.compose.foundation.gestures.*
|
import androidx.compose.foundation.gestures.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.*
|
import androidx.compose.foundation.lazy.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.mapSaver
|
import androidx.compose.runtime.saveable.mapSaver
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.*
|
import androidx.compose.ui.*
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.platform.*
|
import androidx.compose.ui.platform.*
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.intl.Locale
|
import androidx.compose.ui.text.intl.Locale
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
|
||||||
import androidx.compose.ui.text.*
|
import androidx.compose.ui.text.*
|
||||||
import androidx.compose.ui.unit.*
|
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.ChatModel.controller
|
import chat.simplex.common.model.ChatModel.controller
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.call.*
|
import chat.simplex.common.views.call.*
|
||||||
@@ -118,369 +117,373 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
val clipboard = LocalClipboardManager.current
|
val clipboard = LocalClipboardManager.current
|
||||||
when (chat.chatInfo) {
|
when (chat.chatInfo) {
|
||||||
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
|
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
|
||||||
ChatLayout(
|
val perChatTheme = remember(chat.chatInfo, CurrentColors.value.base) { if (chat.chatInfo is ChatInfo.Direct) chat.chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chat.chatInfo is ChatInfo.Group) chat.chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null }
|
||||||
chat,
|
val overrides = if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) else null
|
||||||
unreadCount,
|
SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) {
|
||||||
composeState,
|
ChatLayout(
|
||||||
composeView = {
|
chat,
|
||||||
Column(
|
unreadCount,
|
||||||
Modifier.fillMaxWidth(),
|
composeState,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
composeView = {
|
||||||
) {
|
Column(
|
||||||
if (
|
Modifier.fillMaxWidth(),
|
||||||
chat.chatInfo is ChatInfo.Direct
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
&& !chat.chatInfo.contact.ready
|
|
||||||
&& chat.chatInfo.contact.active
|
|
||||||
&& !chat.chatInfo.contact.nextSendGrpInv
|
|
||||||
) {
|
) {
|
||||||
Text(
|
if (
|
||||||
generalGetString(MR.strings.contact_connection_pending),
|
chat.chatInfo is ChatInfo.Direct
|
||||||
Modifier.padding(top = 4.dp),
|
&& !chat.chatInfo.contact.ready
|
||||||
fontSize = 14.sp,
|
&& chat.chatInfo.contact.active
|
||||||
color = MaterialTheme.colors.secondary
|
&& !chat.chatInfo.contact.nextSendGrpInv
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
generalGetString(MR.strings.contact_connection_pending),
|
||||||
|
Modifier.padding(top = 4.dp),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colors.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ComposeView(
|
||||||
|
chatModel, chat, composeState, attachmentOption,
|
||||||
|
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ComposeView(
|
},
|
||||||
chatModel, chat, composeState, attachmentOption,
|
attachmentOption,
|
||||||
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
|
attachmentBottomSheetState,
|
||||||
)
|
searchText,
|
||||||
}
|
useLinkPreviews = useLinkPreviews,
|
||||||
},
|
linkMode = chatModel.simplexLinkMode.value,
|
||||||
attachmentOption,
|
back = {
|
||||||
attachmentBottomSheetState,
|
hideKeyboard(view)
|
||||||
searchText,
|
AudioPlayer.stop()
|
||||||
useLinkPreviews = useLinkPreviews,
|
chatModel.chatId.value = null
|
||||||
linkMode = chatModel.simplexLinkMode.value,
|
chatModel.groupMembers.clear()
|
||||||
back = {
|
},
|
||||||
hideKeyboard(view)
|
info = {
|
||||||
AudioPlayer.stop()
|
if (ModalManager.end.hasModalsOpen()) {
|
||||||
chatModel.chatId.value = null
|
ModalManager.end.closeModals()
|
||||||
chatModel.groupMembers.clear()
|
return@ChatLayout
|
||||||
},
|
|
||||||
info = {
|
|
||||||
if (ModalManager.end.hasModalsOpen()) {
|
|
||||||
ModalManager.end.closeModals()
|
|
||||||
return@ChatLayout
|
|
||||||
}
|
|
||||||
hideKeyboard(view)
|
|
||||||
withBGApi {
|
|
||||||
// The idea is to preload information before showing a modal because large groups can take time to load all members
|
|
||||||
var preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
|
|
||||||
var preloadedCode: String? = null
|
|
||||||
var preloadedLink: Pair<String, GroupMemberRole>? = null
|
|
||||||
if (chat.chatInfo is ChatInfo.Direct) {
|
|
||||||
preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
|
|
||||||
preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
|
|
||||||
} else if (chat.chatInfo is ChatInfo.Group) {
|
|
||||||
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
|
|
||||||
preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
|
|
||||||
}
|
}
|
||||||
ModalManager.end.showModalCloseable(true) { close ->
|
hideKeyboard(view)
|
||||||
val chat = remember { activeChat }.value
|
|
||||||
if (chat?.chatInfo is ChatInfo.Direct) {
|
|
||||||
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
|
|
||||||
var code: String? by remember { mutableStateOf(preloadedCode) }
|
|
||||||
KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) {
|
|
||||||
contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
|
|
||||||
preloadedContactInfo = contactInfo
|
|
||||||
code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
|
|
||||||
preloadedCode = code
|
|
||||||
}
|
|
||||||
ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close)
|
|
||||||
} else if (chat?.chatInfo is ChatInfo.Group) {
|
|
||||||
var link: Pair<String, GroupMemberRole>? by remember(chat.id) { mutableStateOf(preloadedLink) }
|
|
||||||
KeyChangeEffect(chat.id) {
|
|
||||||
setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel)
|
|
||||||
link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
|
|
||||||
preloadedLink = link
|
|
||||||
}
|
|
||||||
GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, {
|
|
||||||
link = it
|
|
||||||
preloadedLink = it
|
|
||||||
}, close)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
|
|
||||||
hideKeyboard(view)
|
|
||||||
withBGApi {
|
|
||||||
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
|
|
||||||
val stats = r?.second
|
|
||||||
val (_, code) = if (member.memberActive) {
|
|
||||||
val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId)
|
|
||||||
member to memCode?.second
|
|
||||||
} else {
|
|
||||||
member to null
|
|
||||||
}
|
|
||||||
setGroupMembers(chatRh, groupInfo, chatModel)
|
|
||||||
ModalManager.end.closeModals()
|
|
||||||
ModalManager.end.showModalCloseable(true) { close ->
|
|
||||||
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
|
|
||||||
GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadPrevMessages = {
|
|
||||||
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
|
|
||||||
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout)
|
|
||||||
val firstId = chatModel.chatItems.value.firstOrNull()?.id
|
|
||||||
if (c != null && firstId != null) {
|
|
||||||
withBGApi {
|
withBGApi {
|
||||||
apiLoadPrevMessages(c, chatModel, firstId, searchText.value)
|
// The idea is to preload information before showing a modal because large groups can take time to load all members
|
||||||
|
var preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
|
||||||
|
var preloadedCode: String? = null
|
||||||
|
var preloadedLink: Pair<String, GroupMemberRole>? = null
|
||||||
|
if (chat.chatInfo is ChatInfo.Direct) {
|
||||||
|
preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
|
||||||
|
preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
|
||||||
|
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||||
|
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
|
||||||
|
preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
|
||||||
|
}
|
||||||
|
ModalManager.end.showModalCloseable(true) { close ->
|
||||||
|
val chat = remember { activeChat }.value
|
||||||
|
if (chat?.chatInfo is ChatInfo.Direct) {
|
||||||
|
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
|
||||||
|
var code: String? by remember { mutableStateOf(preloadedCode) }
|
||||||
|
KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) {
|
||||||
|
contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
|
||||||
|
preloadedContactInfo = contactInfo
|
||||||
|
code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
|
||||||
|
preloadedCode = code
|
||||||
|
}
|
||||||
|
ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close)
|
||||||
|
} else if (chat?.chatInfo is ChatInfo.Group) {
|
||||||
|
var link: Pair<String, GroupMemberRole>? by remember(chat.id) { mutableStateOf(preloadedLink) }
|
||||||
|
KeyChangeEffect(chat.id) {
|
||||||
|
setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel)
|
||||||
|
link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
|
||||||
|
preloadedLink = link
|
||||||
|
}
|
||||||
|
GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, {
|
||||||
|
link = it
|
||||||
|
preloadedLink = it
|
||||||
|
}, close)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
|
||||||
deleteMessage = { itemId, mode ->
|
hideKeyboard(view)
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val cInfo = chat.chatInfo
|
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
|
||||||
val toDeleteItem = chatModel.chatItems.value.firstOrNull { it.id == itemId }
|
val stats = r?.second
|
||||||
val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo)
|
val (_, code) = if (member.memberActive) {
|
||||||
val groupInfo = toModerate?.first
|
val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId)
|
||||||
val groupMember = toModerate?.second
|
member to memCode?.second
|
||||||
val deletedChatItem: ChatItem?
|
} else {
|
||||||
val toChatItem: ChatItem?
|
member to null
|
||||||
if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) {
|
}
|
||||||
val r = chatModel.controller.apiDeleteMemberChatItem(
|
setGroupMembers(chatRh, groupInfo, chatModel)
|
||||||
chatRh,
|
ModalManager.end.closeModals()
|
||||||
groupId = groupInfo.groupId,
|
ModalManager.end.showModalCloseable(true) { close ->
|
||||||
groupMemberId = groupMember.groupMemberId,
|
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
|
||||||
itemId = itemId
|
GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close)
|
||||||
)
|
}
|
||||||
deletedChatItem = r?.first
|
}
|
||||||
toChatItem = r?.second
|
}
|
||||||
} else {
|
},
|
||||||
val r = chatModel.controller.apiDeleteChatItem(
|
loadPrevMessages = {
|
||||||
chatRh,
|
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
|
||||||
|
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout)
|
||||||
|
val firstId = chatModel.chatItems.value.firstOrNull()?.id
|
||||||
|
if (c != null && firstId != null) {
|
||||||
|
withBGApi {
|
||||||
|
apiLoadPrevMessages(c, chatModel, firstId, searchText.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteMessage = { itemId, mode ->
|
||||||
|
withBGApi {
|
||||||
|
val cInfo = chat.chatInfo
|
||||||
|
val toDeleteItem = chatModel.chatItems.value.firstOrNull { it.id == itemId }
|
||||||
|
val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo)
|
||||||
|
val groupInfo = toModerate?.first
|
||||||
|
val groupMember = toModerate?.second
|
||||||
|
val deletedChatItem: ChatItem?
|
||||||
|
val toChatItem: ChatItem?
|
||||||
|
if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) {
|
||||||
|
val r = chatModel.controller.apiDeleteMemberChatItem(
|
||||||
|
chatRh,
|
||||||
|
groupId = groupInfo.groupId,
|
||||||
|
groupMemberId = groupMember.groupMemberId,
|
||||||
|
itemId = itemId
|
||||||
|
)
|
||||||
|
deletedChatItem = r?.first
|
||||||
|
toChatItem = r?.second
|
||||||
|
} else {
|
||||||
|
val r = chatModel.controller.apiDeleteChatItem(
|
||||||
|
chatRh,
|
||||||
|
type = cInfo.chatType,
|
||||||
|
id = cInfo.apiId,
|
||||||
|
itemId = itemId,
|
||||||
|
mode = mode
|
||||||
|
)
|
||||||
|
deletedChatItem = r?.deletedChatItem?.chatItem
|
||||||
|
toChatItem = r?.toChatItem?.chatItem
|
||||||
|
}
|
||||||
|
if (toChatItem == null && deletedChatItem != null) {
|
||||||
|
chatModel.removeChatItem(chatRh, cInfo, deletedChatItem)
|
||||||
|
} else if (toChatItem != null) {
|
||||||
|
chatModel.upsertChatItem(chatRh, cInfo, toChatItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteMessages = { itemIds ->
|
||||||
|
if (itemIds.isNotEmpty()) {
|
||||||
|
val chatInfo = chat.chatInfo
|
||||||
|
withBGApi {
|
||||||
|
val deletedItems: ArrayList<ChatItem> = arrayListOf()
|
||||||
|
for (itemId in itemIds) {
|
||||||
|
val di = chatModel.controller.apiDeleteChatItem(
|
||||||
|
chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal
|
||||||
|
)?.deletedChatItem?.chatItem
|
||||||
|
if (di != null) {
|
||||||
|
deletedItems.add(di)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (di in deletedItems) {
|
||||||
|
chatModel.removeChatItem(chatRh, chatInfo, di)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
receiveFile = { fileId ->
|
||||||
|
withBGApi { chatModel.controller.receiveFile(chatRh, user, fileId) }
|
||||||
|
},
|
||||||
|
cancelFile = { fileId ->
|
||||||
|
withBGApi { chatModel.controller.cancelFile(chatRh, user, fileId) }
|
||||||
|
},
|
||||||
|
joinGroup = { groupId, onComplete ->
|
||||||
|
withBGApi {
|
||||||
|
chatModel.controller.apiJoinGroup(chatRh, groupId)
|
||||||
|
onComplete.invoke()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startCall = out@{ media ->
|
||||||
|
withBGApi {
|
||||||
|
val cInfo = chat.chatInfo
|
||||||
|
if (cInfo is ChatInfo.Direct) {
|
||||||
|
val contactInfo = chatModel.controller.apiContactInfo(chat.remoteHostId, cInfo.contact.contactId)
|
||||||
|
val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi
|
||||||
|
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile)
|
||||||
|
chatModel.showCallView.value = true
|
||||||
|
chatModel.callCommand.add(WCallCommand.Capabilities(media))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endCall = {
|
||||||
|
val call = chatModel.activeCall.value
|
||||||
|
if (call != null) withBGApi { chatModel.callManager.endCall(call) }
|
||||||
|
},
|
||||||
|
acceptCall = { contact ->
|
||||||
|
hideKeyboard(view)
|
||||||
|
withBGApi {
|
||||||
|
val invitation = chatModel.callInvitations.remove(contact.id)
|
||||||
|
?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id }
|
||||||
|
if (invitation == null) {
|
||||||
|
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended))
|
||||||
|
} else {
|
||||||
|
chatModel.callManager.acceptIncomingCall(invitation = invitation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
acceptFeature = { contact, feature, param ->
|
||||||
|
withBGApi {
|
||||||
|
chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openDirectChat = { contactId ->
|
||||||
|
withBGApi {
|
||||||
|
openDirectChat(chatRh, contactId, chatModel)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateContactStats = { contact ->
|
||||||
|
withBGApi {
|
||||||
|
val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
|
||||||
|
if (r != null) {
|
||||||
|
val contactStats = r.first
|
||||||
|
if (contactStats != null)
|
||||||
|
chatModel.updateContactConnectionStats(chatRh, contact, contactStats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateMemberStats = { groupInfo, member ->
|
||||||
|
withBGApi {
|
||||||
|
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
|
||||||
|
if (r != null) {
|
||||||
|
val memStats = r.second
|
||||||
|
if (memStats != null) {
|
||||||
|
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
syncContactConnection = { contact ->
|
||||||
|
withBGApi {
|
||||||
|
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
|
||||||
|
if (cStats != null) {
|
||||||
|
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
syncMemberConnection = { groupInfo, member ->
|
||||||
|
withBGApi {
|
||||||
|
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
|
||||||
|
if (r != null) {
|
||||||
|
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findModelChat = { chatId ->
|
||||||
|
chatModel.getChat(chatId)
|
||||||
|
},
|
||||||
|
findModelMember = { memberId ->
|
||||||
|
chatModel.groupMembers.find { it.id == memberId }
|
||||||
|
},
|
||||||
|
setReaction = { cInfo, cItem, add, reaction ->
|
||||||
|
withBGApi {
|
||||||
|
val updatedCI = chatModel.controller.apiChatItemReaction(
|
||||||
|
rh = chatRh,
|
||||||
type = cInfo.chatType,
|
type = cInfo.chatType,
|
||||||
id = cInfo.apiId,
|
id = cInfo.apiId,
|
||||||
itemId = itemId,
|
itemId = cItem.id,
|
||||||
mode = mode
|
add = add,
|
||||||
|
reaction = reaction
|
||||||
)
|
)
|
||||||
deletedChatItem = r?.deletedChatItem?.chatItem
|
if (updatedCI != null) {
|
||||||
toChatItem = r?.toChatItem?.chatItem
|
chatModel.updateChatItem(cInfo, updatedCI)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (toChatItem == null && deletedChatItem != null) {
|
},
|
||||||
chatModel.removeChatItem(chatRh, cInfo, deletedChatItem)
|
showItemDetails = { cInfo, cItem ->
|
||||||
} else if (toChatItem != null) {
|
suspend fun loadChatItemInfo(): ChatItemInfo? {
|
||||||
chatModel.upsertChatItem(chatRh, cInfo, toChatItem)
|
val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id)
|
||||||
}
|
if (ciInfo != null) {
|
||||||
}
|
if (chat.chatInfo is ChatInfo.Group) {
|
||||||
},
|
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
|
||||||
deleteMessages = { itemIds ->
|
|
||||||
if (itemIds.isNotEmpty()) {
|
|
||||||
val chatInfo = chat.chatInfo
|
|
||||||
withBGApi {
|
|
||||||
val deletedItems: ArrayList<ChatItem> = arrayListOf()
|
|
||||||
for (itemId in itemIds) {
|
|
||||||
val di = chatModel.controller.apiDeleteChatItem(
|
|
||||||
chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal
|
|
||||||
)?.deletedChatItem?.chatItem
|
|
||||||
if (di != null) {
|
|
||||||
deletedItems.add(di)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (di in deletedItems) {
|
return ciInfo
|
||||||
chatModel.removeChatItem(chatRh, chatInfo, di)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
withBGApi {
|
||||||
},
|
var initialCiInfo = loadChatItemInfo() ?: return@withBGApi
|
||||||
receiveFile = { fileId ->
|
ModalManager.end.closeModals()
|
||||||
withBGApi { chatModel.controller.receiveFile(chatRh, user, fileId) }
|
ModalManager.end.showModalCloseable(endButtons = {
|
||||||
},
|
ShareButton {
|
||||||
cancelFile = { fileId ->
|
clipboard.shareText(itemInfoShareText(chatModel, cItem, initialCiInfo, chatModel.controller.appPrefs.developerTools.get()))
|
||||||
withBGApi { chatModel.controller.cancelFile(chatRh, user, fileId) }
|
}
|
||||||
},
|
}) { close ->
|
||||||
joinGroup = { groupId, onComplete ->
|
var ciInfo by remember(cItem.id) { mutableStateOf(initialCiInfo) }
|
||||||
withBGApi {
|
ChatItemInfoView(chatRh, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get())
|
||||||
chatModel.controller.apiJoinGroup(chatRh, groupId)
|
LaunchedEffect(cItem.id) {
|
||||||
onComplete.invoke()
|
withContext(Dispatchers.Default) {
|
||||||
}
|
for (apiResp in controller.messagesChannel) {
|
||||||
},
|
val msg = apiResp.resp
|
||||||
startCall = out@{ media ->
|
if (apiResp.remoteHostId == chatRh &&
|
||||||
withBGApi {
|
msg is CR.ChatItemStatusUpdated &&
|
||||||
val cInfo = chat.chatInfo
|
msg.chatItem.chatItem.id == cItem.id
|
||||||
if (cInfo is ChatInfo.Direct) {
|
) {
|
||||||
val contactInfo = chatModel.controller.apiContactInfo(chat.remoteHostId, cInfo.contact.contactId)
|
ciInfo = loadChatItemInfo() ?: return@withContext
|
||||||
val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi
|
initialCiInfo = ciInfo
|
||||||
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile)
|
}
|
||||||
chatModel.showCallView.value = true
|
|
||||||
chatModel.callCommand.add(WCallCommand.Capabilities(media))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
endCall = {
|
|
||||||
val call = chatModel.activeCall.value
|
|
||||||
if (call != null) withBGApi { chatModel.callManager.endCall(call) }
|
|
||||||
},
|
|
||||||
acceptCall = { contact ->
|
|
||||||
hideKeyboard(view)
|
|
||||||
withBGApi {
|
|
||||||
val invitation = chatModel.callInvitations.remove(contact.id)
|
|
||||||
?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id }
|
|
||||||
if (invitation == null) {
|
|
||||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended))
|
|
||||||
} else {
|
|
||||||
chatModel.callManager.acceptIncomingCall(invitation = invitation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
acceptFeature = { contact, feature, param ->
|
|
||||||
withBGApi {
|
|
||||||
chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openDirectChat = { contactId ->
|
|
||||||
withBGApi {
|
|
||||||
openDirectChat(chatRh, contactId, chatModel)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateContactStats = { contact ->
|
|
||||||
withBGApi {
|
|
||||||
val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
|
|
||||||
if (r != null) {
|
|
||||||
val contactStats = r.first
|
|
||||||
if (contactStats != null)
|
|
||||||
chatModel.updateContactConnectionStats(chatRh, contact, contactStats)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateMemberStats = { groupInfo, member ->
|
|
||||||
withBGApi {
|
|
||||||
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
|
|
||||||
if (r != null) {
|
|
||||||
val memStats = r.second
|
|
||||||
if (memStats != null) {
|
|
||||||
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
syncContactConnection = { contact ->
|
|
||||||
withBGApi {
|
|
||||||
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
|
|
||||||
if (cStats != null) {
|
|
||||||
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
syncMemberConnection = { groupInfo, member ->
|
|
||||||
withBGApi {
|
|
||||||
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
|
|
||||||
if (r != null) {
|
|
||||||
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
findModelChat = { chatId ->
|
|
||||||
chatModel.getChat(chatId)
|
|
||||||
},
|
|
||||||
findModelMember = { memberId ->
|
|
||||||
chatModel.groupMembers.find { it.id == memberId }
|
|
||||||
},
|
|
||||||
setReaction = { cInfo, cItem, add, reaction ->
|
|
||||||
withBGApi {
|
|
||||||
val updatedCI = chatModel.controller.apiChatItemReaction(
|
|
||||||
rh = chatRh,
|
|
||||||
type = cInfo.chatType,
|
|
||||||
id = cInfo.apiId,
|
|
||||||
itemId = cItem.id,
|
|
||||||
add = add,
|
|
||||||
reaction = reaction
|
|
||||||
)
|
|
||||||
if (updatedCI != null) {
|
|
||||||
chatModel.updateChatItem(cInfo, updatedCI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showItemDetails = { cInfo, cItem ->
|
|
||||||
suspend fun loadChatItemInfo(): ChatItemInfo? {
|
|
||||||
val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id)
|
|
||||||
if (ciInfo != null) {
|
|
||||||
if (chat.chatInfo is ChatInfo.Group) {
|
|
||||||
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ciInfo
|
|
||||||
}
|
|
||||||
withBGApi {
|
|
||||||
var initialCiInfo = loadChatItemInfo() ?: return@withBGApi
|
|
||||||
ModalManager.end.closeModals()
|
|
||||||
ModalManager.end.showModalCloseable(endButtons = {
|
|
||||||
ShareButton {
|
|
||||||
clipboard.shareText(itemInfoShareText(chatModel, cItem, initialCiInfo, chatModel.controller.appPrefs.developerTools.get()))
|
|
||||||
}
|
|
||||||
}) { close ->
|
|
||||||
var ciInfo by remember(cItem.id) { mutableStateOf(initialCiInfo) }
|
|
||||||
ChatItemInfoView(chatRh, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get())
|
|
||||||
LaunchedEffect(cItem.id) {
|
|
||||||
withContext(Dispatchers.Default) {
|
|
||||||
for (apiResp in controller.messagesChannel) {
|
|
||||||
val msg = apiResp.resp
|
|
||||||
if (apiResp.remoteHostId == chatRh &&
|
|
||||||
msg is CR.ChatItemStatusUpdated &&
|
|
||||||
msg.chatItem.chatItem.id == cItem.id
|
|
||||||
) {
|
|
||||||
ciInfo = loadChatItemInfo() ?: return@withContext
|
|
||||||
initialCiInfo = ciInfo
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
KeyChangeEffect(chatModel.chatId.value) {
|
||||||
KeyChangeEffect(chatModel.chatId.value) {
|
close()
|
||||||
close()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
addMembers = { groupInfo ->
|
||||||
addMembers = { groupInfo ->
|
hideKeyboard(view)
|
||||||
hideKeyboard(view)
|
withBGApi {
|
||||||
withBGApi {
|
setGroupMembers(chatRh, groupInfo, chatModel)
|
||||||
setGroupMembers(chatRh, groupInfo, chatModel)
|
ModalManager.end.closeModals()
|
||||||
ModalManager.end.closeModals()
|
ModalManager.end.showModalCloseable(true) { close ->
|
||||||
ModalManager.end.showModalCloseable(true) { close ->
|
AddGroupMembersView(chatRh, groupInfo, false, chatModel, close)
|
||||||
AddGroupMembersView(chatRh, groupInfo, false, chatModel, close)
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
openGroupLink = { groupInfo ->
|
||||||
openGroupLink = { groupInfo ->
|
hideKeyboard(view)
|
||||||
hideKeyboard(view)
|
withBGApi {
|
||||||
withBGApi {
|
val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId)
|
||||||
val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId)
|
ModalManager.end.closeModals()
|
||||||
ModalManager.end.closeModals()
|
ModalManager.end.showModalCloseable(true) {
|
||||||
ModalManager.end.showModalCloseable(true) {
|
GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null)
|
||||||
GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null)
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
markRead = { range, unreadCountAfter ->
|
||||||
markRead = { range, unreadCountAfter ->
|
chatModel.markChatItemsRead(chat, range, unreadCountAfter)
|
||||||
chatModel.markChatItemsRead(chat, range, unreadCountAfter)
|
ntfManager.cancelNotificationsForChat(chat.id)
|
||||||
ntfManager.cancelNotificationsForChat(chat.id)
|
withBGApi {
|
||||||
withBGApi {
|
chatModel.controller.apiChatRead(
|
||||||
chatModel.controller.apiChatRead(
|
chatRh,
|
||||||
chatRh,
|
chat.chatInfo.chatType,
|
||||||
chat.chatInfo.chatType,
|
chat.chatInfo.apiId,
|
||||||
chat.chatInfo.apiId,
|
range
|
||||||
range
|
)
|
||||||
)
|
}
|
||||||
}
|
},
|
||||||
},
|
changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) },
|
||||||
changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) },
|
onSearchValueChanged = { value ->
|
||||||
onSearchValueChanged = { value ->
|
if (searchText.value == value) return@ChatLayout
|
||||||
if (searchText.value == value) return@ChatLayout
|
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
|
||||||
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
|
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout
|
||||||
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout
|
withBGApi {
|
||||||
withBGApi {
|
apiFindMessages(c, chatModel, value)
|
||||||
apiFindMessages(c, chatModel, value)
|
searchText.value = value
|
||||||
searchText.value = value
|
}
|
||||||
}
|
},
|
||||||
},
|
onComposed,
|
||||||
onComposed,
|
developerTools = chatModel.controller.appPrefs.developerTools.get(),
|
||||||
developerTools = chatModel.controller.appPrefs.developerTools.get(),
|
showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(),
|
||||||
showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(),
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
is ChatInfo.ContactConnection -> {
|
is ChatInfo.ContactConnection -> {
|
||||||
val close = { chatModel.chatId.value = null }
|
val close = { chatModel.chatId.value = null }
|
||||||
@@ -598,9 +601,19 @@ fun ChatLayout(
|
|||||||
floatingActionButton = { floatingButton.value() },
|
floatingActionButton = { floatingButton.value() },
|
||||||
contentColor = LocalContentColor.current,
|
contentColor = LocalContentColor.current,
|
||||||
drawerContentColor = LocalContentColor.current,
|
drawerContentColor = LocalContentColor.current,
|
||||||
|
backgroundColor = Color.Unspecified
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
|
val wallpaperImage = MaterialTheme.wallpaper.type.image
|
||||||
|
val wallpaperType = MaterialTheme.wallpaper.type
|
||||||
|
val backgroundColor = MaterialTheme.wallpaper.background ?: wallpaperType.defaultBackgroundColor(CurrentColors.value.base, MaterialTheme.colors.background)
|
||||||
|
val tintColor = MaterialTheme.wallpaper.tint ?: wallpaperType.defaultTintColor(CurrentColors.value.base)
|
||||||
BoxWithConstraints(Modifier
|
BoxWithConstraints(Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colors.background)
|
||||||
|
.then(if (wallpaperImage != null)
|
||||||
|
Modifier.drawBehind { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) }
|
||||||
|
else
|
||||||
|
Modifier)
|
||||||
.padding(contentPadding)
|
.padding(contentPadding)
|
||||||
) {
|
) {
|
||||||
ChatItemsList(
|
ChatItemsList(
|
||||||
@@ -1262,7 +1275,7 @@ val MEMBER_IMAGE_SIZE: Dp = 38.dp
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MemberImage(member: GroupMember) {
|
fun MemberImage(member: GroupMember) {
|
||||||
ProfileImage(MEMBER_IMAGE_SIZE, member.memberProfile.image)
|
ProfileImage(MEMBER_IMAGE_SIZE, member.memberProfile.image, backgroundColor = MaterialTheme.colors.background)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import dev.icerock.moko.resources.compose.stringResource
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ComposeContextInvitingContactMemberView() {
|
fun ComposeContextInvitingContactMemberView() {
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.height(60.dp)
|
.height(60.dp)
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import chat.simplex.res.MR
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ComposeFileView(fileName: String, cancelFile: () -> Unit, cancelEnabled: Boolean) {
|
fun ComposeFileView(fileName: String, cancelFile: () -> Unit, cancelEnabled: Boolean) {
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.height(60.dp)
|
.height(60.dp)
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import chat.simplex.common.views.helpers.UploadContent
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Unit, cancelEnabled: Boolean) {
|
fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Unit, cancelEnabled: Boolean) {
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
|
|||||||
+3
-3
@@ -776,7 +776,7 @@ fun ComposeView(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MsgNotAllowedView(reason: String, icon: Painter) {
|
fun MsgNotAllowedView(reason: String, icon: Painter) {
|
||||||
val color = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val color = MaterialTheme.appColors.receivedMessage
|
||||||
Row(Modifier.padding(top = 5.dp).fillMaxWidth().background(color).padding(horizontal = DEFAULT_PADDING_HALF, vertical = DEFAULT_PADDING_HALF * 1.5f), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.padding(top = 5.dp).fillMaxWidth().background(color).padding(horizontal = DEFAULT_PADDING_HALF, vertical = DEFAULT_PADDING_HALF * 1.5f), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Icon(icon, null, tint = MaterialTheme.colors.secondary)
|
Icon(icon, null, tint = MaterialTheme.colors.secondary)
|
||||||
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
|
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
|
||||||
@@ -862,7 +862,7 @@ fun ComposeView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.background(MaterialTheme.colors.background).padding(end = 8.dp),
|
||||||
verticalAlignment = Alignment.Bottom,
|
verticalAlignment = Alignment.Bottom,
|
||||||
) {
|
) {
|
||||||
val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership)
|
val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership)
|
||||||
@@ -974,7 +974,7 @@ fun ComposeView(
|
|||||||
val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) }
|
val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) }
|
||||||
val sendButtonColor =
|
val sendButtonColor =
|
||||||
if (chat.chatInfo.incognito)
|
if (chat.chatInfo.incognito)
|
||||||
if (isSystemInDarkTheme()) Indigo else Indigo.copy(alpha = 0.7F)
|
if (isInDarkTheme()) Indigo else Indigo.copy(alpha = 0.7F)
|
||||||
else MaterialTheme.colors.primary
|
else MaterialTheme.colors.primary
|
||||||
SendMsgView(
|
SendMsgView(
|
||||||
composeState,
|
composeState,
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ fun ComposeVoiceView(
|
|||||||
) {
|
) {
|
||||||
val progress = rememberSaveable { mutableStateOf(0) }
|
val progress = rememberSaveable { mutableStateOf(0) }
|
||||||
val duration = rememberSaveable(recordedDurationMs) { mutableStateOf(recordedDurationMs) }
|
val duration = rememberSaveable(recordedDurationMs) { mutableStateOf(recordedDurationMs) }
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
Box {
|
Box {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
|
|||||||
+2
-2
@@ -30,8 +30,8 @@ fun ContextItemView(
|
|||||||
cancelContextItem: () -> Unit
|
cancelContextItem: () -> Unit
|
||||||
) {
|
) {
|
||||||
val sent = contextItem.chatDir.sent
|
val sent = contextItem.chatDir.sent
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageText(attachment: ImageResource?, lines: Int) {
|
fun MessageText(attachment: ImageResource?, lines: Int) {
|
||||||
|
|||||||
+10
-1
@@ -9,7 +9,6 @@ import SectionSpacer
|
|||||||
import SectionTextFooter
|
import SectionTextFooter
|
||||||
import SectionView
|
import SectionView
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.*
|
import androidx.compose.foundation.lazy.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
@@ -233,6 +232,16 @@ fun GroupChatInfoLayout(
|
|||||||
} else {
|
} else {
|
||||||
SendReceiptsOptionDisabled()
|
SendReceiptsOptionDisabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WallpaperButton {
|
||||||
|
ModalManager.end.showModal {
|
||||||
|
val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } }
|
||||||
|
val c = chat.value
|
||||||
|
if (c != null) {
|
||||||
|
ChatWallpaperEditorModal(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SectionTextFooter(stringResource(MR.strings.only_group_owners_can_change_prefs))
|
SectionTextFooter(stringResource(MR.strings.only_group_owners_can_change_prefs))
|
||||||
SectionDividerSpaced(maxTopPadding = true)
|
SectionDividerSpaced(maxTopPadding = true)
|
||||||
|
|||||||
+6
-9
@@ -123,13 +123,12 @@ private fun GroupPreferencesLayout(
|
|||||||
applyPrefs(preferences.copy(files = RoleGroupPreference(enable = enable, role)))
|
applyPrefs(preferences.copy(files = RoleGroupPreference(enable = enable, role)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO enable simplexLinks preference in 5.8
|
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||||
// SectionDividerSpaced(true, maxBottomPadding = false)
|
val allowSimplexLinks = remember(preferences) { mutableStateOf(preferences.simplexLinks.enable) }
|
||||||
// val allowSimplexLinks = remember(preferences) { mutableStateOf(preferences.simplexLinks.enable) }
|
val simplexLinksRole = remember(preferences) { mutableStateOf(preferences.simplexLinks.role) }
|
||||||
// val simplexLinksRole = remember(preferences) { mutableStateOf(preferences.simplexLinks.role) }
|
FeatureSection(GroupFeature.SimplexLinks, allowSimplexLinks, simplexLinksRole, groupInfo, preferences, onTTLUpdated) { enable, role ->
|
||||||
// FeatureSection(GroupFeature.SimplexLinks, allowSimplexLinks, simplexLinksRole, groupInfo, preferences, onTTLUpdated) { enable, role ->
|
applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role)))
|
||||||
// applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role)))
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||||
val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) }
|
val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) }
|
||||||
@@ -189,8 +188,6 @@ private fun FeatureSection(
|
|||||||
generalGetString(MR.strings.feature_enabled_for),
|
generalGetString(MR.strings.feature_enabled_for),
|
||||||
featureRoles,
|
featureRoles,
|
||||||
enableForRole,
|
enableForRole,
|
||||||
// remove in v5.8
|
|
||||||
enabled = remember { mutableStateOf(false) },
|
|
||||||
onSelected = { value ->
|
onSelected = { value ->
|
||||||
onSelected(enableFeature.value, value)
|
onSelected(enableFeature.value, value)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -83,8 +83,8 @@ fun CIGroupInvitationView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
Surface(
|
Surface(
|
||||||
modifier = if (action && !inProgress.value) Modifier.clickable(onClick = {
|
modifier = if (action && !inProgress.value) Modifier.clickable(onClick = {
|
||||||
inProgress.value = true
|
inProgress.value = true
|
||||||
@@ -110,6 +110,7 @@ fun CIGroupInvitationView(
|
|||||||
.padding(bottom = 4.dp),
|
.padding(bottom = 4.dp),
|
||||||
) {
|
) {
|
||||||
groupInfoView()
|
groupInfoView()
|
||||||
|
val secondaryColor = MaterialTheme.colors.secondary
|
||||||
Column(Modifier.padding(top = 2.dp, start = 5.dp)) {
|
Column(Modifier.padding(top = 2.dp, start = 5.dp)) {
|
||||||
Divider(Modifier.fillMaxWidth().padding(bottom = 4.dp))
|
Divider(Modifier.fillMaxWidth().padding(bottom = 4.dp))
|
||||||
if (action) {
|
if (action) {
|
||||||
@@ -117,7 +118,7 @@ fun CIGroupInvitationView(
|
|||||||
Text(
|
Text(
|
||||||
buildAnnotatedString {
|
buildAnnotatedString {
|
||||||
append(generalGetString(if (chatIncognito) MR.strings.group_invitation_tap_to_join_incognito else MR.strings.group_invitation_tap_to_join))
|
append(generalGetString(if (chatIncognito) MR.strings.group_invitation_tap_to_join_incognito else MR.strings.group_invitation_tap_to_join))
|
||||||
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false)) }
|
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false, secondaryColor = secondaryColor)) }
|
||||||
},
|
},
|
||||||
color = if (inProgress.value)
|
color = if (inProgress.value)
|
||||||
MaterialTheme.colors.secondary
|
MaterialTheme.colors.secondary
|
||||||
@@ -128,7 +129,7 @@ fun CIGroupInvitationView(
|
|||||||
Text(
|
Text(
|
||||||
buildAnnotatedString {
|
buildAnnotatedString {
|
||||||
append(groupInvitationStr())
|
append(groupInvitationStr())
|
||||||
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false)) }
|
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false, secondaryColor = secondaryColor)) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -12,7 +12,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.ui.theme.CurrentColors
|
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.ui.theme.isInDarkTheme
|
import chat.simplex.common.ui.theme.isInDarkTheme
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
@@ -86,7 +85,7 @@ private fun CIMetaText(
|
|||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
}
|
}
|
||||||
if (showViaProxy && meta.sentViaProxy == true) {
|
if (showViaProxy && meta.sentViaProxy == true) {
|
||||||
Icon(painterResource(MR.images.ic_arrow_forward), null, Modifier.height(17.dp), tint = CurrentColors.value.colors.secondary)
|
Icon(painterResource(MR.images.ic_arrow_forward), null, Modifier.height(17.dp), tint = MaterialTheme.colors.secondary)
|
||||||
}
|
}
|
||||||
if (showStatus) {
|
if (showStatus) {
|
||||||
val statusIcon = meta.statusIcon(MaterialTheme.colors.primary, color, paleColor)
|
val statusIcon = meta.statusIcon(MaterialTheme.colors.primary, color, paleColor)
|
||||||
@@ -115,6 +114,7 @@ fun reserveSpaceForMeta(
|
|||||||
meta: CIMeta,
|
meta: CIMeta,
|
||||||
chatTTL: Int?,
|
chatTTL: Int?,
|
||||||
encrypted: Boolean?,
|
encrypted: Boolean?,
|
||||||
|
secondaryColor: Color,
|
||||||
showStatus: Boolean = true,
|
showStatus: Boolean = true,
|
||||||
showEdited: Boolean = true,
|
showEdited: Boolean = true,
|
||||||
showViaProxy: Boolean = false
|
showViaProxy: Boolean = false
|
||||||
@@ -132,7 +132,7 @@ fun reserveSpaceForMeta(
|
|||||||
if (showViaProxy && meta.sentViaProxy == true) {
|
if (showViaProxy && meta.sentViaProxy == true) {
|
||||||
res += iconSpace
|
res += iconSpace
|
||||||
}
|
}
|
||||||
if (showStatus && (meta.statusIcon(CurrentColors.value.colors.secondary) != null || !meta.disappearing)) {
|
if (showStatus && (meta.statusIcon(secondaryColor) != null || !meta.disappearing)) {
|
||||||
res += iconSpace
|
res += iconSpace
|
||||||
}
|
}
|
||||||
if (encrypted != null) {
|
if (encrypted != null) {
|
||||||
|
|||||||
+7
-4
@@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.ui.theme.CurrentColors
|
import chat.simplex.common.ui.theme.CurrentColors
|
||||||
|
import chat.simplex.common.ui.theme.appColors
|
||||||
import chat.simplex.common.views.helpers.AlertManager
|
import chat.simplex.common.views.helpers.AlertManager
|
||||||
import chat.simplex.common.views.helpers.generalGetString
|
import chat.simplex.common.views.helpers.generalGetString
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
@@ -137,7 +138,7 @@ fun DecryptionErrorItemFixButton(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
syncSupported: Boolean
|
syncSupported: Boolean
|
||||||
) {
|
) {
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
Surface(
|
Surface(
|
||||||
Modifier.clickable(onClick = onClick),
|
Modifier.clickable(onClick = onClick),
|
||||||
shape = RoundedCornerShape(18.dp),
|
shape = RoundedCornerShape(18.dp),
|
||||||
@@ -164,10 +165,11 @@ fun DecryptionErrorItemFixButton(
|
|||||||
tint = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
tint = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
||||||
)
|
)
|
||||||
Spacer(Modifier.padding(2.dp))
|
Spacer(Modifier.padding(2.dp))
|
||||||
|
val secondaryColor = MaterialTheme.colors.secondary
|
||||||
Text(
|
Text(
|
||||||
buildAnnotatedString {
|
buildAnnotatedString {
|
||||||
append(generalGetString(MR.strings.fix_connection))
|
append(generalGetString(MR.strings.fix_connection))
|
||||||
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null)) }
|
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null, secondaryColor = secondaryColor)) }
|
||||||
withStyle(reserveTimestampStyle) { append(" ") } // for icon
|
withStyle(reserveTimestampStyle) { append(" ") } // for icon
|
||||||
},
|
},
|
||||||
color = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
color = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
||||||
@@ -184,7 +186,7 @@ fun DecryptionErrorItem(
|
|||||||
ci: ChatItem,
|
ci: ChatItem,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
Surface(
|
Surface(
|
||||||
Modifier.clickable(onClick = onClick),
|
Modifier.clickable(onClick = onClick),
|
||||||
shape = RoundedCornerShape(18.dp),
|
shape = RoundedCornerShape(18.dp),
|
||||||
@@ -195,10 +197,11 @@ fun DecryptionErrorItem(
|
|||||||
Modifier.padding(vertical = 6.dp, horizontal = 12.dp),
|
Modifier.padding(vertical = 6.dp, horizontal = 12.dp),
|
||||||
contentAlignment = Alignment.BottomEnd,
|
contentAlignment = Alignment.BottomEnd,
|
||||||
) {
|
) {
|
||||||
|
val secondaryColor = MaterialTheme.colors.secondary
|
||||||
Text(
|
Text(
|
||||||
buildAnnotatedString {
|
buildAnnotatedString {
|
||||||
withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = Color.Red)) { append(ci.content.text) }
|
withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = Color.Red)) { append(ci.content.text) }
|
||||||
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null)) }
|
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null, secondaryColor = secondaryColor)) }
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp)
|
style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp)
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-4
@@ -154,8 +154,8 @@ private fun VoiceLayout(
|
|||||||
}
|
}
|
||||||
when {
|
when {
|
||||||
hasText -> {
|
hasText -> {
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
Spacer(Modifier.width(6.dp))
|
Spacer(Modifier.width(6.dp))
|
||||||
VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick, receiveFile)
|
VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick, receiveFile)
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -224,8 +224,8 @@ private fun PlayPauseButton(
|
|||||||
longClick: () -> Unit,
|
longClick: () -> Unit,
|
||||||
icon: ImageResource = MR.images.ic_play_arrow_filled,
|
icon: ImageResource = MR.images.ic_play_arrow_filled,
|
||||||
) {
|
) {
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
Surface(
|
Surface(
|
||||||
Modifier.drawRingModifier(angle, strokeColor, strokeWidth),
|
Modifier.drawRingModifier(angle, strokeColor, strokeWidth),
|
||||||
color = if (sent) sentColor else receivedColor,
|
color = if (sent) sentColor else receivedColor,
|
||||||
|
|||||||
+32
-34
@@ -819,40 +819,38 @@ expect fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager)
|
|||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewChatItemView() {
|
fun PreviewChatItemView(
|
||||||
SimpleXTheme {
|
chatItem: ChatItem = ChatItem.getSampleData(1, CIDirection.DirectSnd(), Clock.System.now(), "hello")
|
||||||
ChatItemView(
|
) {
|
||||||
rhId = null,
|
ChatItemView(
|
||||||
ChatInfo.Direct.sampleData,
|
rhId = null,
|
||||||
ChatItem.getSampleData(
|
ChatInfo.Direct.sampleData,
|
||||||
1, CIDirection.DirectSnd(), Clock.System.now(), "hello"
|
chatItem,
|
||||||
),
|
useLinkPreviews = true,
|
||||||
useLinkPreviews = true,
|
linkMode = SimplexLinkMode.DESCRIPTION,
|
||||||
linkMode = SimplexLinkMode.DESCRIPTION,
|
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
||||||
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
revealed = remember { mutableStateOf(false) },
|
||||||
revealed = remember { mutableStateOf(false) },
|
range = 0..1,
|
||||||
range = 0..1,
|
deleteMessage = { _, _ -> },
|
||||||
deleteMessage = { _, _ -> },
|
deleteMessages = { _ -> },
|
||||||
deleteMessages = { _ -> },
|
receiveFile = { _ -> },
|
||||||
receiveFile = { _ -> },
|
cancelFile = {},
|
||||||
cancelFile = {},
|
joinGroup = { _, _ -> },
|
||||||
joinGroup = { _, _ -> },
|
acceptCall = { _ -> },
|
||||||
acceptCall = { _ -> },
|
scrollToItem = {},
|
||||||
scrollToItem = {},
|
acceptFeature = { _, _, _ -> },
|
||||||
acceptFeature = { _, _, _ -> },
|
openDirectChat = { _ -> },
|
||||||
openDirectChat = { _ -> },
|
updateContactStats = { },
|
||||||
updateContactStats = { },
|
updateMemberStats = { _, _ -> },
|
||||||
updateMemberStats = { _, _ -> },
|
syncContactConnection = { },
|
||||||
syncContactConnection = { },
|
syncMemberConnection = { _, _ -> },
|
||||||
syncMemberConnection = { _, _ -> },
|
findModelChat = { null },
|
||||||
findModelChat = { null },
|
findModelMember = { null },
|
||||||
findModelMember = { null },
|
setReaction = { _, _, _, _ -> },
|
||||||
setReaction = { _, _, _, _ -> },
|
showItemDetails = { _, _ -> },
|
||||||
showItemDetails = { _, _ -> },
|
developerTools = false,
|
||||||
developerTools = false,
|
showViaProxy = false,
|
||||||
showViaProxy = false
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
|
|||||||
+2
-2
@@ -18,8 +18,8 @@ import chat.simplex.common.ui.theme.*
|
|||||||
@Composable
|
@Composable
|
||||||
fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showViaProxy: Boolean) {
|
fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showViaProxy: Boolean) {
|
||||||
val sent = ci.chatDir.sent
|
val sent = ci.chatDir.sent
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(18.dp),
|
shape = RoundedCornerShape(18.dp),
|
||||||
color = if (sent) sentColor else receivedColor,
|
color = if (sent) sentColor else receivedColor,
|
||||||
|
|||||||
+15
-11
@@ -46,9 +46,6 @@ fun FramedItemView(
|
|||||||
return if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.membership else null
|
return if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.membership else null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Color.toQuote(): Color = if (isInDarkTheme()) lighter(0.12f) else darker(0.12f)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ciQuotedMsgTextView(qi: CIQuote, lines: Int) {
|
fun ciQuotedMsgTextView(qi: CIQuote, lines: Int) {
|
||||||
MarkdownText(
|
MarkdownText(
|
||||||
@@ -89,11 +86,11 @@ fun FramedItemView(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FramedItemHeader(caption: String, italic: Boolean, icon: Painter? = null, pad: Boolean = false) {
|
fun FramedItemHeader(caption: String, italic: Boolean, icon: Painter? = null, pad: Boolean = false) {
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentQuote
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedQuote
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.background(if (sent) sentColor.toQuote() else receivedColor.toQuote())
|
.background(if (sent) sentColor else receivedColor)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 8.dp, top = 6.dp, end = 12.dp, bottom = if (pad || (ci.quotedItem == null && ci.meta.itemForwarded == null)) 6.dp else 0.dp),
|
.padding(start = 8.dp, top = 6.dp, end = 12.dp, bottom = if (pad || (ci.quotedItem == null && ci.meta.itemForwarded == null)) 6.dp else 0.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
@@ -122,11 +119,11 @@ fun FramedItemView(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ciQuoteView(qi: CIQuote) {
|
fun ciQuoteView(qi: CIQuote) {
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentQuote
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedQuote
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.background(if (sent) sentColor.toQuote() else receivedColor.toQuote())
|
.background(if (sent) sentColor else receivedColor)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onLongClick = { showMenu.value = true },
|
onLongClick = { showMenu.value = true },
|
||||||
@@ -188,10 +185,17 @@ fun FramedItemView(
|
|||||||
val transparentBackground = (ci.content.msgContent is MsgContent.MCImage || ci.content.msgContent is MsgContent.MCVideo) &&
|
val transparentBackground = (ci.content.msgContent is MsgContent.MCImage || ci.content.msgContent is MsgContent.MCVideo) &&
|
||||||
!ci.meta.isLive && ci.content.text.isEmpty() && ci.quotedItem == null && ci.meta.itemForwarded == null
|
!ci.meta.isLive && ci.content.text.isEmpty() && ci.quotedItem == null && ci.meta.itemForwarded == null
|
||||||
|
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
Box(Modifier
|
Box(Modifier
|
||||||
.clip(RoundedCornerShape(18.dp))
|
.clip(RoundedCornerShape(18.dp))
|
||||||
|
.background(
|
||||||
|
when {
|
||||||
|
transparentBackground -> Color.Transparent
|
||||||
|
sent -> MaterialTheme.colors.background
|
||||||
|
else -> MaterialTheme.colors.background
|
||||||
|
}
|
||||||
|
)
|
||||||
.background(
|
.background(
|
||||||
when {
|
when {
|
||||||
transparentBackground -> Color.Transparent
|
transparentBackground -> Color.Transparent
|
||||||
|
|||||||
+2
-3
@@ -17,8 +17,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.model.ChatItem
|
import chat.simplex.common.model.ChatItem
|
||||||
import chat.simplex.common.model.MsgErrorType
|
import chat.simplex.common.model.MsgErrorType
|
||||||
import chat.simplex.common.ui.theme.CurrentColors
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.ui.theme.SimpleXTheme
|
|
||||||
import chat.simplex.common.views.helpers.AlertManager
|
import chat.simplex.common.views.helpers.AlertManager
|
||||||
import chat.simplex.common.views.helpers.generalGetString
|
import chat.simplex.common.views.helpers.generalGetString
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
@@ -51,7 +50,7 @@ fun IntegrityErrorItemView(msgError: MsgErrorType, ci: ChatItem, timedMessagesTT
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CIMsgError(ci: ChatItem, timedMessagesTTL: Int?, onClick: () -> Unit) {
|
fun CIMsgError(ci: ChatItem, timedMessagesTTL: Int?, onClick: () -> Unit) {
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
Surface(
|
Surface(
|
||||||
Modifier.clickable(onClick = onClick),
|
Modifier.clickable(onClick = onClick),
|
||||||
shape = RoundedCornerShape(18.dp),
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
|||||||
+2
-2
@@ -21,8 +21,8 @@ import kotlinx.datetime.Clock
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, revealed: MutableState<Boolean>, showViaProxy: Boolean) {
|
fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, revealed: MutableState<Boolean>, showViaProxy: Boolean) {
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
val receivedColor = MaterialTheme.appColors.receivedMessage
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(18.dp),
|
shape = RoundedCornerShape(18.dp),
|
||||||
color = if (ci.chatDir.sent) sentColor else receivedColor,
|
color = if (ci.chatDir.sent) sentColor else receivedColor,
|
||||||
|
|||||||
+1
-1
@@ -78,7 +78,7 @@ fun MarkdownText (
|
|||||||
val reserve = if (textLayoutDirection != LocalLayoutDirection.current && meta != null) {
|
val reserve = if (textLayoutDirection != LocalLayoutDirection.current && meta != null) {
|
||||||
"\n"
|
"\n"
|
||||||
} else if (meta != null) {
|
} else if (meta != null) {
|
||||||
reserveSpaceForMeta(meta, chatTTL, null, showViaProxy = showViaProxy)
|
reserveSpaceForMeta(meta, chatTTL, null, secondaryColor = MaterialTheme.colors.secondary, showViaProxy = showViaProxy)
|
||||||
} else {
|
} else {
|
||||||
" "
|
" "
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -486,6 +486,8 @@ fun deleteChatDatabaseFilesAndState() {
|
|||||||
tmpDir.deleteRecursively()
|
tmpDir.deleteRecursively()
|
||||||
getMigrationTempFilesDirectory().deleteRecursively()
|
getMigrationTempFilesDirectory().deleteRecursively()
|
||||||
tmpDir.mkdir()
|
tmpDir.mkdir()
|
||||||
|
wallpapersDir.deleteRecursively()
|
||||||
|
wallpapersDir.mkdirs()
|
||||||
DatabaseUtils.ksDatabasePassword.remove()
|
DatabaseUtils.ksDatabasePassword.remove()
|
||||||
controller.appPrefs.storeDBPassphrase.set(true)
|
controller.appPrefs.storeDBPassphrase.set(true)
|
||||||
controller.ctrl = null
|
controller.ctrl = null
|
||||||
|
|||||||
+7
-1
@@ -2,12 +2,14 @@ package chat.simplex.common.views.helpers
|
|||||||
|
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.*
|
import androidx.compose.foundation.shape.*
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
@@ -51,7 +53,8 @@ fun ProfileImage(
|
|||||||
size: Dp,
|
size: Dp,
|
||||||
image: String? = null,
|
image: String? = null,
|
||||||
icon: ImageResource = MR.images.ic_account_circle_filled,
|
icon: ImageResource = MR.images.ic_account_circle_filled,
|
||||||
color: Color = MaterialTheme.colors.secondaryVariant
|
color: Color = MaterialTheme.colors.secondaryVariant,
|
||||||
|
backgroundColor: Color? = null
|
||||||
) {
|
) {
|
||||||
Box(Modifier.size(size)) {
|
Box(Modifier.size(size)) {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
@@ -61,6 +64,9 @@ fun ProfileImage(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (iconToReplace != null) {
|
if (iconToReplace != null) {
|
||||||
|
if (backgroundColor != null) {
|
||||||
|
Box(Modifier.size(size * 0.7f).align(Alignment.Center).background(backgroundColor, CircleShape))
|
||||||
|
}
|
||||||
Icon(
|
Icon(
|
||||||
iconToReplace,
|
iconToReplace,
|
||||||
contentDescription = stringResource(MR.strings.icon_descr_profile_image_placeholder),
|
contentDescription = stringResource(MR.strings.icon_descr_profile_image_placeholder),
|
||||||
|
|||||||
+415
@@ -0,0 +1,415 @@
|
|||||||
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.*
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.ui.theme.ThemeManager.colorFromReadableHex
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.ImageResource
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.math.*
|
||||||
|
|
||||||
|
enum class PresetWallpaper(
|
||||||
|
val res: ImageResource,
|
||||||
|
val filename: String,
|
||||||
|
val text: StringResource,
|
||||||
|
val scale: Float,
|
||||||
|
val background: Map<DefaultTheme, Color>,
|
||||||
|
val tint: Map<DefaultTheme, Color>,
|
||||||
|
val colors: Map<DefaultTheme, ThemeColors>,
|
||||||
|
) {
|
||||||
|
CATS(MR.images.wallpaper_cats, "cats", MR.strings.wallpaper_cats, 0.63f,
|
||||||
|
wallpaperBackgrounds(light = "#ffF8F6EA"),
|
||||||
|
tint = mapOf(
|
||||||
|
DefaultTheme.LIGHT to "#ffefdca6".colorFromReadableHex(),
|
||||||
|
DefaultTheme.DARK to "#ff4b3b0e".colorFromReadableHex(),
|
||||||
|
DefaultTheme.SIMPLEX to "#ff51400f".colorFromReadableHex(),
|
||||||
|
DefaultTheme.BLACK to "#ff4b3b0e".colorFromReadableHex()
|
||||||
|
),
|
||||||
|
mapOf(
|
||||||
|
DefaultTheme.LIGHT to ThemeColors(
|
||||||
|
sentMessage = "#fffffaed",
|
||||||
|
sentQuote = "#fffaf0d6",
|
||||||
|
receivedMessage = "#ffF8F7F4",
|
||||||
|
receivedQuote = "#ffefede9",
|
||||||
|
),
|
||||||
|
DefaultTheme.DARK to ThemeColors(
|
||||||
|
sentMessage = "#ff2f2919",
|
||||||
|
sentQuote = "#ff473a1d",
|
||||||
|
receivedMessage = "#ff272624",
|
||||||
|
receivedQuote = "#ff373633",
|
||||||
|
),
|
||||||
|
DefaultTheme.SIMPLEX to ThemeColors(
|
||||||
|
sentMessage = "#ff41371b",
|
||||||
|
sentQuote = "#ff654f1c",
|
||||||
|
receivedMessage = "#ff272624",
|
||||||
|
receivedQuote = "#ff373633",
|
||||||
|
),
|
||||||
|
DefaultTheme.BLACK to ThemeColors(
|
||||||
|
sentMessage = "#ff41371b",
|
||||||
|
sentQuote = "#ff654f1c",
|
||||||
|
receivedMessage = "#ff1f1e1b",
|
||||||
|
receivedQuote = "#ff2f2d27",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
FLOWERS(MR.images.wallpaper_flowers, "flowers", MR.strings.wallpaper_flowers, 0.53f,
|
||||||
|
wallpaperBackgrounds(light = "#ffE2FFE4"),
|
||||||
|
tint = mapOf(
|
||||||
|
DefaultTheme.LIGHT to "#ff9CEA59".colorFromReadableHex(),
|
||||||
|
DefaultTheme.DARK to "#ff31560D".colorFromReadableHex(),
|
||||||
|
DefaultTheme.SIMPLEX to "#ff36600f".colorFromReadableHex(),
|
||||||
|
DefaultTheme.BLACK to "#ff31560D".colorFromReadableHex()
|
||||||
|
),
|
||||||
|
mapOf(
|
||||||
|
DefaultTheme.LIGHT to ThemeColors(
|
||||||
|
sentMessage = "#fff1ffe5",
|
||||||
|
sentQuote = "#ffdcf9c4",
|
||||||
|
receivedMessage = "#ffF4F8F2",
|
||||||
|
receivedQuote = "#ffe7ece7",
|
||||||
|
),
|
||||||
|
DefaultTheme.DARK to ThemeColors(
|
||||||
|
sentMessage = "#ff163521",
|
||||||
|
sentQuote = "#ff1B5330",
|
||||||
|
receivedMessage = "#ff242523",
|
||||||
|
receivedQuote = "#ff353733",
|
||||||
|
),
|
||||||
|
DefaultTheme.SIMPLEX to ThemeColors(
|
||||||
|
sentMessage = "#ff184739",
|
||||||
|
sentQuote = "#ff1F6F4B",
|
||||||
|
receivedMessage = "#ff242523",
|
||||||
|
receivedQuote = "#ff353733",
|
||||||
|
),
|
||||||
|
DefaultTheme.BLACK to ThemeColors(
|
||||||
|
sentMessage = "#ff184739",
|
||||||
|
sentQuote = "#ff1F6F4B",
|
||||||
|
receivedMessage = "#ff1c1f1a",
|
||||||
|
receivedQuote = "#ff282b25",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
HEARTS(MR.images.wallpaper_hearts, "hearts", MR.strings.wallpaper_hearts, 0.59f,
|
||||||
|
wallpaperBackgrounds(light = "#ffFDECEC"),
|
||||||
|
tint = mapOf(
|
||||||
|
DefaultTheme.LIGHT to "#fffde0e0".colorFromReadableHex(),
|
||||||
|
DefaultTheme.DARK to "#ff3c0f0f".colorFromReadableHex(),
|
||||||
|
DefaultTheme.SIMPLEX to "#ff411010".colorFromReadableHex(),
|
||||||
|
DefaultTheme.BLACK to "#ff3C0F0F".colorFromReadableHex()
|
||||||
|
),
|
||||||
|
mapOf(
|
||||||
|
DefaultTheme.LIGHT to ThemeColors(
|
||||||
|
sentMessage = "#fffff4f4",
|
||||||
|
sentQuote = "#ffffdfdf",
|
||||||
|
receivedMessage = "#fff8f6f6",
|
||||||
|
receivedQuote = "#ffefebeb",
|
||||||
|
),
|
||||||
|
DefaultTheme.DARK to ThemeColors(
|
||||||
|
sentMessage = "#ff301515",
|
||||||
|
sentQuote = "#ff4C1818",
|
||||||
|
receivedMessage = "#ff242121",
|
||||||
|
receivedQuote = "#ff3b3535",
|
||||||
|
),
|
||||||
|
DefaultTheme.SIMPLEX to ThemeColors(
|
||||||
|
sentMessage = "#ff491A28",
|
||||||
|
sentQuote = "#ff761F29",
|
||||||
|
receivedMessage = "#ff242121",
|
||||||
|
receivedQuote = "#ff3b3535",
|
||||||
|
),
|
||||||
|
DefaultTheme.BLACK to ThemeColors(
|
||||||
|
sentMessage = "#ff491A28",
|
||||||
|
sentQuote = "#ff761F29",
|
||||||
|
receivedMessage = "#ff1f1b1b",
|
||||||
|
receivedQuote = "#ff2e2626",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
KIDS(MR.images.wallpaper_kids, "kids", MR.strings.wallpaper_kids, 0.53f,
|
||||||
|
wallpaperBackgrounds(light = "#ffdbfdfb"),
|
||||||
|
tint = mapOf(
|
||||||
|
DefaultTheme.LIGHT to "#ffadeffc".colorFromReadableHex(),
|
||||||
|
DefaultTheme.DARK to "#ff16404B".colorFromReadableHex(),
|
||||||
|
DefaultTheme.SIMPLEX to "#ff184753".colorFromReadableHex(),
|
||||||
|
DefaultTheme.BLACK to "#ff16404B".colorFromReadableHex()
|
||||||
|
),
|
||||||
|
mapOf(
|
||||||
|
DefaultTheme.LIGHT to ThemeColors(
|
||||||
|
sentMessage = "#ffeafeff",
|
||||||
|
sentQuote = "#ffcbf4f7",
|
||||||
|
receivedMessage = "#fff3fafa",
|
||||||
|
receivedQuote = "#ffe4efef",
|
||||||
|
),
|
||||||
|
DefaultTheme.DARK to ThemeColors(
|
||||||
|
sentMessage = "#ff16302F",
|
||||||
|
sentQuote = "#ff1a4a49",
|
||||||
|
receivedMessage = "#ff252626",
|
||||||
|
receivedQuote = "#ff373A39",
|
||||||
|
),
|
||||||
|
DefaultTheme.SIMPLEX to ThemeColors(
|
||||||
|
sentMessage = "#ff1a4745",
|
||||||
|
sentQuote = "#ff1d6b69",
|
||||||
|
receivedMessage = "#ff252626",
|
||||||
|
receivedQuote = "#ff373a39",
|
||||||
|
),
|
||||||
|
DefaultTheme.BLACK to ThemeColors(
|
||||||
|
sentMessage = "#ff1a4745",
|
||||||
|
sentQuote = "#ff1d6b69",
|
||||||
|
receivedMessage = "#ff1e1f1f",
|
||||||
|
receivedQuote = "#ff262b29",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SCHOOL(MR.images.wallpaper_school, "school", MR.strings.wallpaper_school, 0.53f,
|
||||||
|
wallpaperBackgrounds(light = "#ffE7F5FF"),
|
||||||
|
tint = mapOf(
|
||||||
|
DefaultTheme.LIGHT to "#ffCEEBFF".colorFromReadableHex(),
|
||||||
|
DefaultTheme.DARK to "#ff0F293B".colorFromReadableHex(),
|
||||||
|
DefaultTheme.SIMPLEX to "#ff112f43".colorFromReadableHex(),
|
||||||
|
DefaultTheme.BLACK to "#ff0F293B".colorFromReadableHex()
|
||||||
|
),
|
||||||
|
mapOf(
|
||||||
|
DefaultTheme.LIGHT to ThemeColors(
|
||||||
|
sentMessage = "#ffeef9ff",
|
||||||
|
sentQuote = "#ffD6EDFA",
|
||||||
|
receivedMessage = "#ffF3F5F9",
|
||||||
|
receivedQuote = "#ffe4e8ee",
|
||||||
|
),
|
||||||
|
DefaultTheme.DARK to ThemeColors(
|
||||||
|
sentMessage = "#ff172833",
|
||||||
|
sentQuote = "#ff1C3E4F",
|
||||||
|
receivedMessage = "#ff26282c",
|
||||||
|
receivedQuote = "#ff393c40",
|
||||||
|
),
|
||||||
|
DefaultTheme.SIMPLEX to ThemeColors(
|
||||||
|
sentMessage = "#ff1A3C5D",
|
||||||
|
sentQuote = "#ff235b80",
|
||||||
|
receivedMessage = "#ff26282c",
|
||||||
|
receivedQuote = "#ff393c40",
|
||||||
|
),
|
||||||
|
DefaultTheme.BLACK to ThemeColors(
|
||||||
|
sentMessage = "#ff1A3C5D",
|
||||||
|
sentQuote = "#ff235b80",
|
||||||
|
receivedMessage = "#ff1d1e22",
|
||||||
|
receivedQuote = "#ff292b2f",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
TRAVEL(MR.images.wallpaper_travel, "travel", MR.strings.wallpaper_travel, 0.68f,
|
||||||
|
wallpaperBackgrounds(light = "#fff9eeff"),
|
||||||
|
tint = mapOf(
|
||||||
|
DefaultTheme.LIGHT to "#ffeedbfe".colorFromReadableHex(),
|
||||||
|
DefaultTheme.DARK to "#ff311E48".colorFromReadableHex(),
|
||||||
|
DefaultTheme.SIMPLEX to "#ff35204e".colorFromReadableHex(),
|
||||||
|
DefaultTheme.BLACK to "#ff311E48".colorFromReadableHex()
|
||||||
|
),
|
||||||
|
mapOf(
|
||||||
|
DefaultTheme.LIGHT to ThemeColors(
|
||||||
|
sentMessage = "#fffcf6ff",
|
||||||
|
sentQuote = "#fff2e0fc",
|
||||||
|
receivedMessage = "#ffF6F4F7",
|
||||||
|
receivedQuote = "#ffede9ee",
|
||||||
|
),
|
||||||
|
DefaultTheme.DARK to ThemeColors(
|
||||||
|
sentMessage = "#ff33263B",
|
||||||
|
sentQuote = "#ff53385E",
|
||||||
|
receivedMessage = "#ff272528",
|
||||||
|
receivedQuote = "#ff3B373E",
|
||||||
|
),
|
||||||
|
DefaultTheme.SIMPLEX to ThemeColors(
|
||||||
|
sentMessage = "#ff3C255D",
|
||||||
|
sentQuote = "#ff623485",
|
||||||
|
receivedMessage = "#ff26273B",
|
||||||
|
receivedQuote = "#ff3A394F",
|
||||||
|
),
|
||||||
|
DefaultTheme.BLACK to ThemeColors(
|
||||||
|
sentMessage = "#ff3C255D",
|
||||||
|
sentQuote = "#ff623485",
|
||||||
|
receivedMessage = "#ff231f23",
|
||||||
|
receivedQuote = "#ff2c2931",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
fun toType(base: DefaultTheme, scale: Float? = null): WallpaperType =
|
||||||
|
WallpaperType.Preset(
|
||||||
|
filename,
|
||||||
|
scale ?: appPrefs.themeOverrides.get().firstOrNull { it.wallpaper != null && it.wallpaper.preset == filename && it.base == base }?.wallpaper?.scale ?: 1f
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun from(filename: String): PresetWallpaper? =
|
||||||
|
entries.firstOrNull { it.filename == filename }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun wallpaperBackgrounds(light: String): Map<DefaultTheme, Color> =
|
||||||
|
mapOf(
|
||||||
|
DefaultTheme.LIGHT to light.colorFromReadableHex(),
|
||||||
|
DefaultTheme.DARK to "#ff121212".colorFromReadableHex(),
|
||||||
|
DefaultTheme.SIMPLEX to "#ff111528".colorFromReadableHex(),
|
||||||
|
DefaultTheme.BLACK to "#ff070707".colorFromReadableHex()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class WallpaperScaleType(val contentScale: ContentScale, val text: StringResource) {
|
||||||
|
@SerialName("fill") FILL(ContentScale.Crop, MR.strings.wallpaper_scale_fill),
|
||||||
|
@SerialName("fit") FIT(ContentScale.Fit, MR.strings.wallpaper_scale_fit),
|
||||||
|
@SerialName("repeat") REPEAT(ContentScale.Fit, MR.strings.wallpaper_scale_repeat),
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class WallpaperType {
|
||||||
|
abstract val scale: Float?
|
||||||
|
|
||||||
|
val image by lazy {
|
||||||
|
val filename = when (this) {
|
||||||
|
is Preset -> filename
|
||||||
|
is Image -> filename
|
||||||
|
else -> return@lazy null
|
||||||
|
}
|
||||||
|
if (filename == "") return@lazy null
|
||||||
|
if (cachedImages[filename] != null) {
|
||||||
|
cachedImages[filename]
|
||||||
|
} else {
|
||||||
|
val res = if (this is Preset) {
|
||||||
|
(PresetWallpaper.from(filename) ?: PresetWallpaper.CATS).res.toComposeImageBitmap()!!
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// In case of unintentional image deletion don't crash the app
|
||||||
|
File(getWallpaperFilePath(filename)).inputStream().use { loadImageBitmap(it) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error while loading wallpaper file: ${e.stackTraceToString()}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res?.prepareToDraw()
|
||||||
|
cachedImages[filename] = res ?: return@lazy null
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sameType(other: WallpaperType?): Boolean =
|
||||||
|
if (this is Preset && other is Preset) this.filename == other.filename
|
||||||
|
else this.javaClass == other?.javaClass
|
||||||
|
|
||||||
|
fun samePreset(other: PresetWallpaper?): Boolean = this is Preset && filename == other?.filename
|
||||||
|
|
||||||
|
data class Preset(
|
||||||
|
val filename: String,
|
||||||
|
override val scale: Float?,
|
||||||
|
): WallpaperType() {
|
||||||
|
val predefinedImageScale = PresetWallpaper.from(filename)?.scale ?: 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Image(
|
||||||
|
val filename: String,
|
||||||
|
override val scale: Float?,
|
||||||
|
val scaleType: WallpaperScaleType?,
|
||||||
|
): WallpaperType()
|
||||||
|
|
||||||
|
object Empty: WallpaperType() {
|
||||||
|
override val scale: Float?
|
||||||
|
get() = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaultBackgroundColor(theme: DefaultTheme, materialBackground: Color): Color =
|
||||||
|
if (this is Preset) {
|
||||||
|
(PresetWallpaper.from(filename) ?: PresetWallpaper.CATS).background[theme]!!
|
||||||
|
} else {
|
||||||
|
materialBackground
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaultTintColor(theme: DefaultTheme): Color =
|
||||||
|
if (this is Preset) {
|
||||||
|
(PresetWallpaper.from(filename) ?: PresetWallpaper.CATS).tint[theme]!!
|
||||||
|
} else if (this is Image && scaleType == WallpaperScaleType.REPEAT) {
|
||||||
|
Color.Transparent
|
||||||
|
} else {
|
||||||
|
Color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var cachedImages: MutableMap<String, ImageBitmap> = mutableMapOf()
|
||||||
|
|
||||||
|
fun from(wallpaper: ThemeWallpaper?): WallpaperType? {
|
||||||
|
return if (wallpaper == null) {
|
||||||
|
null
|
||||||
|
} else if (wallpaper.preset != null) {
|
||||||
|
Preset(wallpaper.preset, wallpaper.scale)
|
||||||
|
} else if (wallpaper.imageFile != null) {
|
||||||
|
Image(wallpaper.imageFile, wallpaper.scale, wallpaper.scaleType)
|
||||||
|
} else {
|
||||||
|
Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color) = clipRect {
|
||||||
|
val quality = FilterQuality.High
|
||||||
|
fun repeat(imageScale: Float) {
|
||||||
|
val scale = imageScale * density
|
||||||
|
for (h in 0..(size.height / image.height / scale).roundToInt()) {
|
||||||
|
for (w in 0..(size.width / image.width / scale).roundToInt()) {
|
||||||
|
drawImage(
|
||||||
|
image,
|
||||||
|
dstOffset = IntOffset(x = (w * image.width * scale).roundToInt(), y = (h * image.height * scale).roundToInt()),
|
||||||
|
dstSize = IntSize((image.width * scale).roundToInt(), (image.height * scale).roundToInt()),
|
||||||
|
colorFilter = ColorFilter.tint(tint, BlendMode.SrcIn),
|
||||||
|
filterQuality = quality
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawRect(background)
|
||||||
|
when (imageType) {
|
||||||
|
is WallpaperType.Preset -> repeat((imageType.scale ?: 1f) * imageType.predefinedImageScale)
|
||||||
|
is WallpaperType.Image -> when (val scaleType = imageType.scaleType ?: WallpaperScaleType.FILL) {
|
||||||
|
WallpaperScaleType.REPEAT -> repeat(imageType.scale ?: 1f)
|
||||||
|
WallpaperScaleType.FILL, WallpaperScaleType.FIT -> {
|
||||||
|
val scale = scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height))
|
||||||
|
val scaledWidth = (image.width * scale.scaleX).roundToInt()
|
||||||
|
val scaledHeight = (image.height * scale.scaleY).roundToInt()
|
||||||
|
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||||
|
if (scaleType == WallpaperScaleType.FIT) {
|
||||||
|
if (scaledWidth < size.width) {
|
||||||
|
// has black lines at left and right sides
|
||||||
|
var x = (size.width - scaledWidth) / 2
|
||||||
|
while (x > 0) {
|
||||||
|
drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||||
|
x -= scaledWidth
|
||||||
|
}
|
||||||
|
x = size.width - (size.width - scaledWidth) / 2
|
||||||
|
while (x < size.width) {
|
||||||
|
drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||||
|
x += scaledWidth
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// has black lines at top and bottom sides
|
||||||
|
var y = (size.height - scaledHeight) / 2
|
||||||
|
while (y > 0) {
|
||||||
|
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||||
|
y -= scaledHeight
|
||||||
|
}
|
||||||
|
y = size.height - (size.height - scaledHeight) / 2
|
||||||
|
while (y < size.height) {
|
||||||
|
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||||
|
y += scaledHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawRect(tint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is WallpaperType.Empty -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -51,7 +51,7 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co
|
|||||||
@Composable
|
@Composable
|
||||||
fun AppBarTitle(title: String, hostDevice: Pair<Long?, String>? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f) {
|
fun AppBarTitle(title: String, hostDevice: Pair<Long?, String>? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f) {
|
||||||
val theme = CurrentColors.collectAsState()
|
val theme = CurrentColors.collectAsState()
|
||||||
val titleColor = CurrentColors.collectAsState().value.appColors.title
|
val titleColor = MaterialTheme.appColors.title
|
||||||
val brush = if (theme.value.base == DefaultTheme.SIMPLEX)
|
val brush = if (theme.value.base == DefaultTheme.SIMPLEX)
|
||||||
Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f))
|
Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f))
|
||||||
else // color is not updated when changing themes if I pass null here
|
else // color is not updated when changing themes if I pass null here
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ suspend fun getLinkPreview(url: String): LinkPreview? {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit, cancelEnabled: Boolean) {
|
fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit, cancelEnabled: Boolean) {
|
||||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
val sentColor = MaterialTheme.appColors.sentMessage
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().padding(top = 8.dp).background(sentColor),
|
Modifier.fillMaxWidth().padding(top = 8.dp).background(sentColor),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
|||||||
+4
-1
@@ -41,9 +41,12 @@ enum class ModalPlacement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ModalData {
|
class ModalData {
|
||||||
private val state = mutableMapOf<String, MutableState<Any>>()
|
private val state = mutableMapOf<String, MutableState<Any?>>()
|
||||||
fun <T> stateGetOrPut (key: String, default: () -> T): MutableState<T> =
|
fun <T> stateGetOrPut (key: String, default: () -> T): MutableState<T> =
|
||||||
state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState<T>
|
state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState<T>
|
||||||
|
|
||||||
|
fun <T> stateGetOrPutNullable (key: String, default: () -> T?): MutableState<T?> =
|
||||||
|
state.getOrPut(key) { mutableStateOf(default() as Any?) } as MutableState<T?>
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModalManager(private val placement: ModalPlacement? = null) {
|
class ModalManager(private val placement: ModalPlacement? = null) {
|
||||||
|
|||||||
+463
@@ -0,0 +1,463 @@
|
|||||||
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
|
import SectionBottomSpacer
|
||||||
|
import SectionItemView
|
||||||
|
import SectionSpacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.usersettings.*
|
||||||
|
import chat.simplex.common.views.usersettings.AppearanceScope.WallpaperPresetSelector
|
||||||
|
import chat.simplex.common.views.usersettings.AppearanceScope.editColor
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ModalData.UserWallpaperEditor(
|
||||||
|
theme: ThemeModeOverride,
|
||||||
|
applyToMode: DefaultThemeMode?,
|
||||||
|
globalThemeUsed: MutableState<Boolean>,
|
||||||
|
save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit
|
||||||
|
) {
|
||||||
|
ColumnWithScrollBar(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } }
|
||||||
|
var showMore by remember { stateGetOrPut("showMore") { false } }
|
||||||
|
val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } }
|
||||||
|
val currentTheme by CurrentColors.collectAsState()
|
||||||
|
|
||||||
|
AppBarTitle(stringResource(MR.strings.settings_section_title_user_theme))
|
||||||
|
val wallpaperImage = MaterialTheme.wallpaper.type.image
|
||||||
|
val wallpaperType = MaterialTheme.wallpaper.type
|
||||||
|
|
||||||
|
val onTypeCopyFromSameTheme = { type: WallpaperType? ->
|
||||||
|
if (type is WallpaperType.Image && chatModel.remoteHostId() != null) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
ThemeManager.copyFromSameThemeOverrides(type, null, themeModeOverride)
|
||||||
|
withBGApi { save(applyToMode.value, themeModeOverride.value) }
|
||||||
|
globalThemeUsed.value = false
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val preApplyGlobalIfNeeded = { type: WallpaperType? ->
|
||||||
|
if (globalThemeUsed.value) {
|
||||||
|
onTypeCopyFromSameTheme(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val onTypeChange: (WallpaperType?) -> Unit = { type: WallpaperType? ->
|
||||||
|
if (globalThemeUsed.value) {
|
||||||
|
preApplyGlobalIfNeeded(type)
|
||||||
|
// Saves copied static image instead of original from global theme
|
||||||
|
ThemeManager.applyWallpaper(themeModeOverride.value.type, themeModeOverride)
|
||||||
|
} else {
|
||||||
|
ThemeManager.applyWallpaper(type, themeModeOverride)
|
||||||
|
}
|
||||||
|
withBGApi { save(applyToMode.value, themeModeOverride.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val importWallpaperLauncher = rememberFileChooserLauncher(true) { to: URI? ->
|
||||||
|
if (to != null) {
|
||||||
|
val filename = saveWallpaperFile(to)
|
||||||
|
if (filename != null) {
|
||||||
|
onTypeChange(WallpaperType.Image(filename, 1f, WallpaperScaleType.FILL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentColors = { type: WallpaperType? ->
|
||||||
|
// If applying for :
|
||||||
|
// - all themes: no overrides needed
|
||||||
|
// - specific user: only user overrides for currently selected theme are needed, because they will NOT be copied when other wallpaper is selected
|
||||||
|
val perUserOverride = if (wallpaperType.sameType(type)) chatModel.currentUser.value?.uiThemes else null
|
||||||
|
ThemeManager.currentColors(type, null, perUserOverride, appPrefs.themeOverrides.get())
|
||||||
|
}
|
||||||
|
val onChooseType: (WallpaperType?) -> Unit = { type: WallpaperType? ->
|
||||||
|
when {
|
||||||
|
// don't have image in parent or already selected wallpaper with custom image
|
||||||
|
type is WallpaperType.Image && chatModel.remoteHostId() != null -> { /* do nothing */ }
|
||||||
|
type is WallpaperType.Image && (wallpaperType is WallpaperType.Image || currentColors(type).wallpaper.type.image == null) -> withLongRunningApi { importWallpaperLauncher.launch("image/*") }
|
||||||
|
type is WallpaperType.Image -> onTypeCopyFromSameTheme(currentColors(type).wallpaper.type)
|
||||||
|
themeModeOverride.value.type != type || currentTheme.wallpaper.type != type -> onTypeCopyFromSameTheme(type)
|
||||||
|
else -> onTypeChange(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val editColor = { name: ThemeColor ->
|
||||||
|
editColor(
|
||||||
|
name,
|
||||||
|
wallpaperType,
|
||||||
|
wallpaperImage,
|
||||||
|
onColorChange = { color ->
|
||||||
|
preApplyGlobalIfNeeded(themeModeOverride.value.type)
|
||||||
|
ThemeManager.applyThemeColor(name, color, themeModeOverride)
|
||||||
|
withBGApi { save(applyToMode.value, themeModeOverride.value) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
WallpaperPresetSelector(
|
||||||
|
selectedWallpaper = wallpaperType,
|
||||||
|
baseTheme = currentTheme.base,
|
||||||
|
currentColors = { type ->
|
||||||
|
// If applying for :
|
||||||
|
// - all themes: no overrides needed
|
||||||
|
// - specific user: only user overrides for currently selected theme are needed, because they will NOT be copied when other wallpaper is selected
|
||||||
|
val perUserOverride = if (wallpaperType.sameType(type)) chatModel.currentUser.value?.uiThemes else null
|
||||||
|
ThemeManager.currentColors(type, null, perUserOverride, appPrefs.themeOverrides.get())
|
||||||
|
},
|
||||||
|
onChooseType = onChooseType
|
||||||
|
)
|
||||||
|
|
||||||
|
WallpaperSetupView(
|
||||||
|
themeModeOverride.value.type,
|
||||||
|
CurrentColors.collectAsState().value.base,
|
||||||
|
currentTheme.wallpaper,
|
||||||
|
currentTheme.appColors.sentMessage,
|
||||||
|
currentTheme.appColors.sentQuote,
|
||||||
|
currentTheme.appColors.receivedMessage,
|
||||||
|
currentTheme.appColors.receivedQuote,
|
||||||
|
editColor = { name -> editColor(name) },
|
||||||
|
onTypeChange = onTypeChange,
|
||||||
|
)
|
||||||
|
|
||||||
|
SectionSpacer()
|
||||||
|
|
||||||
|
if (!globalThemeUsed.value) {
|
||||||
|
ResetToGlobalThemeButton {
|
||||||
|
themeModeOverride.value = ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
globalThemeUsed.value = true
|
||||||
|
withBGApi { save(applyToMode.value, null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDefaultThemeButton {
|
||||||
|
globalThemeUsed.value = false
|
||||||
|
val lightBase = DefaultTheme.LIGHT
|
||||||
|
val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else if (appPrefs.systemDarkTheme.get() == DefaultTheme.BLACK.themeName) DefaultTheme.BLACK else DefaultTheme.SIMPLEX
|
||||||
|
val mode = themeModeOverride.value.mode
|
||||||
|
withBGApi {
|
||||||
|
// Saving for both modes in one place by changing mode once per save
|
||||||
|
if (applyToMode.value == null) {
|
||||||
|
val oppositeMode = if (mode == DefaultThemeMode.LIGHT) DefaultThemeMode.DARK else DefaultThemeMode.LIGHT
|
||||||
|
save(oppositeMode, ThemeModeOverride.withFilledAppDefaults(oppositeMode, if (oppositeMode == DefaultThemeMode.LIGHT) lightBase else darkBase))
|
||||||
|
}
|
||||||
|
themeModeOverride.value = ThemeModeOverride.withFilledAppDefaults(mode, if (mode == DefaultThemeMode.LIGHT) lightBase else darkBase)
|
||||||
|
save(themeModeOverride.value.mode, themeModeOverride.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyChangeEffect(theme.mode) {
|
||||||
|
themeModeOverride.value = theme
|
||||||
|
if (applyToMode.value != null) {
|
||||||
|
applyToMode.value = theme.mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies updated global theme if current one tracks global theme
|
||||||
|
KeyChangeEffect(CurrentColors.collectAsState().value) {
|
||||||
|
if (globalThemeUsed.value) {
|
||||||
|
themeModeOverride.value = ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
globalThemeUsed.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionSpacer()
|
||||||
|
|
||||||
|
if (showMore) {
|
||||||
|
val values by remember { mutableStateOf(
|
||||||
|
listOf(
|
||||||
|
null to generalGetString(MR.strings.chat_theme_apply_to_all_modes),
|
||||||
|
DefaultThemeMode.LIGHT to generalGetString(MR.strings.chat_theme_apply_to_light_mode),
|
||||||
|
DefaultThemeMode.DARK to generalGetString(MR.strings.chat_theme_apply_to_dark_mode),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ExposedDropDownSettingRow(
|
||||||
|
generalGetString(MR.strings.chat_theme_apply_to_mode),
|
||||||
|
values,
|
||||||
|
applyToMode,
|
||||||
|
icon = null,
|
||||||
|
enabled = remember { mutableStateOf(true) },
|
||||||
|
onSelected = {
|
||||||
|
applyToMode.value = it
|
||||||
|
if (it != null && it != CurrentColors.value.base.mode) {
|
||||||
|
val lightBase = DefaultTheme.LIGHT
|
||||||
|
val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else if (appPrefs.systemDarkTheme.get() == DefaultTheme.BLACK.themeName) DefaultTheme.BLACK else DefaultTheme.SIMPLEX
|
||||||
|
ThemeManager.applyTheme(if (it == DefaultThemeMode.LIGHT) lightBase.themeName else darkBase.themeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SectionSpacer()
|
||||||
|
|
||||||
|
AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor)
|
||||||
|
} else {
|
||||||
|
AdvancedSettingsButton { showMore = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionBottomSpacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ModalData.ChatWallpaperEditor(
|
||||||
|
theme: ThemeModeOverride,
|
||||||
|
applyToMode: DefaultThemeMode?,
|
||||||
|
globalThemeUsed: MutableState<Boolean>,
|
||||||
|
save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit
|
||||||
|
) {
|
||||||
|
ColumnWithScrollBar(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } }
|
||||||
|
var showMore by remember { stateGetOrPut("showMore") { false } }
|
||||||
|
val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } }
|
||||||
|
val currentTheme by remember(themeModeOverride.value, CurrentColors.collectAsState().value) {
|
||||||
|
mutableStateOf(
|
||||||
|
ThemeManager.currentColors(null, if (themeModeOverride.value == ThemeModeOverride()) null else themeModeOverride.value, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppBarTitle(stringResource(MR.strings.settings_section_title_chat_theme))
|
||||||
|
|
||||||
|
val onTypeCopyFromSameTheme: (WallpaperType?) -> Boolean = { type ->
|
||||||
|
if (type is WallpaperType.Image && chatModel.remoteHostId() != null) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
val success = ThemeManager.copyFromSameThemeOverrides(type, chatModel.currentUser.value?.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight), themeModeOverride)
|
||||||
|
if (success) {
|
||||||
|
withBGApi { save(applyToMode.value, themeModeOverride.value) }
|
||||||
|
globalThemeUsed.value = false
|
||||||
|
}
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val preApplyGlobalIfNeeded = { type: WallpaperType? ->
|
||||||
|
if (globalThemeUsed.value) {
|
||||||
|
onTypeCopyFromSameTheme(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val onTypeChange: (WallpaperType?) -> Unit = { type ->
|
||||||
|
if (globalThemeUsed.value) {
|
||||||
|
preApplyGlobalIfNeeded(type)
|
||||||
|
// Saves copied static image instead of original from global theme
|
||||||
|
ThemeManager.applyWallpaper(themeModeOverride.value.type, themeModeOverride)
|
||||||
|
} else {
|
||||||
|
ThemeManager.applyWallpaper(type, themeModeOverride)
|
||||||
|
}
|
||||||
|
withBGApi { save(applyToMode.value, themeModeOverride.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val editColor: (ThemeColor) -> Unit = { name: ThemeColor ->
|
||||||
|
ModalManager.end.showModal {
|
||||||
|
val currentTheme by remember(themeModeOverride.value, CurrentColors.collectAsState().value) {
|
||||||
|
mutableStateOf(
|
||||||
|
ThemeManager.currentColors(null, themeModeOverride.value, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val initialColor: Color = when (name) {
|
||||||
|
ThemeColor.WALLPAPER_BACKGROUND -> currentTheme.wallpaper.background ?: Color.Transparent
|
||||||
|
ThemeColor.WALLPAPER_TINT -> currentTheme.wallpaper.tint ?: Color.Transparent
|
||||||
|
ThemeColor.PRIMARY -> currentTheme.colors.primary
|
||||||
|
ThemeColor.PRIMARY_VARIANT -> currentTheme.colors.primaryVariant
|
||||||
|
ThemeColor.SECONDARY -> currentTheme.colors.secondary
|
||||||
|
ThemeColor.SECONDARY_VARIANT -> currentTheme.colors.secondaryVariant
|
||||||
|
ThemeColor.BACKGROUND -> currentTheme.colors.background
|
||||||
|
ThemeColor.SURFACE -> currentTheme.colors.surface
|
||||||
|
ThemeColor.TITLE -> currentTheme.appColors.title
|
||||||
|
ThemeColor.PRIMARY_VARIANT2 -> currentTheme.appColors.primaryVariant2
|
||||||
|
ThemeColor.SENT_MESSAGE -> currentTheme.appColors.sentMessage
|
||||||
|
ThemeColor.SENT_QUOTE -> currentTheme.appColors.sentQuote
|
||||||
|
ThemeColor.RECEIVED_MESSAGE -> currentTheme.appColors.receivedMessage
|
||||||
|
ThemeColor.RECEIVED_QUOTE -> currentTheme.appColors.receivedQuote
|
||||||
|
}
|
||||||
|
AppearanceScope.ColorEditor(
|
||||||
|
name,
|
||||||
|
initialColor,
|
||||||
|
CurrentColors.collectAsState().value.base,
|
||||||
|
themeModeOverride.value.type,
|
||||||
|
themeModeOverride.value.type?.image,
|
||||||
|
currentTheme.wallpaper.background,
|
||||||
|
currentTheme.wallpaper.tint,
|
||||||
|
currentColors = {
|
||||||
|
ThemeManager.currentColors(null, themeModeOverride.value, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get())
|
||||||
|
},
|
||||||
|
onColorChange = { color ->
|
||||||
|
preApplyGlobalIfNeeded(themeModeOverride.value.type)
|
||||||
|
ThemeManager.applyThemeColor(name, color, themeModeOverride)
|
||||||
|
withBGApi { save(applyToMode.value, themeModeOverride.value) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val importWallpaperLauncher = rememberFileChooserLauncher(true) { to: URI? ->
|
||||||
|
if (to != null) {
|
||||||
|
val filename = saveWallpaperFile(to)
|
||||||
|
if (filename != null) {
|
||||||
|
// Delete only non-user image
|
||||||
|
if (!globalThemeUsed.value) {
|
||||||
|
removeWallpaperFile((themeModeOverride.value.type as? WallpaperType.Image)?.filename)
|
||||||
|
}
|
||||||
|
globalThemeUsed.value = false
|
||||||
|
onTypeChange(WallpaperType.Image(filename, 1f, WallpaperScaleType.FILL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentColors = { type: WallpaperType? ->
|
||||||
|
ThemeManager.currentColors(type, if (type?.sameType(themeModeOverride.value.type) == true) themeModeOverride.value else null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
WallpaperPresetSelector(
|
||||||
|
selectedWallpaper = currentTheme.wallpaper.type,
|
||||||
|
activeBackgroundColor = currentTheme.wallpaper.background,
|
||||||
|
activeTintColor = currentTheme.wallpaper.tint,
|
||||||
|
baseTheme = CurrentColors.collectAsState().value.base,
|
||||||
|
currentColors = { type -> currentColors(type) },
|
||||||
|
onChooseType = { type ->
|
||||||
|
when {
|
||||||
|
type is WallpaperType.Image && chatModel.remoteHostId() != null -> { /* do nothing */ }
|
||||||
|
type is WallpaperType.Image && ((themeModeOverride.value.type is WallpaperType.Image && !globalThemeUsed.value) || currentColors(type).wallpaper.type.image == null) -> {
|
||||||
|
withLongRunningApi { importWallpaperLauncher.launch("image/*") }
|
||||||
|
}
|
||||||
|
type is WallpaperType.Image -> {
|
||||||
|
if (!onTypeCopyFromSameTheme(currentColors(type).wallpaper.type)) {
|
||||||
|
withLongRunningApi { importWallpaperLauncher.launch("image/*") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
globalThemeUsed.value || themeModeOverride.value.type != type -> {
|
||||||
|
onTypeCopyFromSameTheme(type)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
onTypeChange(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
WallpaperSetupView(
|
||||||
|
themeModeOverride.value.type,
|
||||||
|
CurrentColors.collectAsState().value.base,
|
||||||
|
currentTheme.wallpaper,
|
||||||
|
currentTheme.appColors.sentMessage,
|
||||||
|
currentTheme.appColors.sentQuote,
|
||||||
|
currentTheme.appColors.receivedMessage,
|
||||||
|
currentTheme.appColors.receivedQuote,
|
||||||
|
editColor = editColor,
|
||||||
|
onTypeChange = onTypeChange,
|
||||||
|
)
|
||||||
|
|
||||||
|
SectionSpacer()
|
||||||
|
|
||||||
|
if (!globalThemeUsed.value) {
|
||||||
|
ResetToGlobalThemeButton {
|
||||||
|
themeModeOverride.value = ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
globalThemeUsed.value = true
|
||||||
|
withBGApi { save(applyToMode.value, null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDefaultThemeButton {
|
||||||
|
globalThemeUsed.value = false
|
||||||
|
val lightBase = DefaultTheme.LIGHT
|
||||||
|
val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else if (appPrefs.systemDarkTheme.get() == DefaultTheme.BLACK.themeName) DefaultTheme.BLACK else DefaultTheme.SIMPLEX
|
||||||
|
val mode = themeModeOverride.value.mode
|
||||||
|
withBGApi {
|
||||||
|
// Saving for both modes in one place by changing mode once per save
|
||||||
|
if (applyToMode.value == null) {
|
||||||
|
val oppositeMode = if (mode == DefaultThemeMode.LIGHT) DefaultThemeMode.DARK else DefaultThemeMode.LIGHT
|
||||||
|
save(oppositeMode, ThemeModeOverride.withFilledAppDefaults(oppositeMode, if (oppositeMode == DefaultThemeMode.LIGHT) lightBase else darkBase))
|
||||||
|
}
|
||||||
|
themeModeOverride.value = ThemeModeOverride.withFilledAppDefaults(mode, if (mode == DefaultThemeMode.LIGHT) lightBase else darkBase)
|
||||||
|
save(themeModeOverride.value.mode, themeModeOverride.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyChangeEffect(theme.mode) {
|
||||||
|
themeModeOverride.value = theme
|
||||||
|
if (applyToMode.value != null) {
|
||||||
|
applyToMode.value = theme.mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies updated global theme if current one tracks global theme
|
||||||
|
KeyChangeEffect(CurrentColors.collectAsState()) {
|
||||||
|
if (globalThemeUsed.value) {
|
||||||
|
themeModeOverride.value = ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
|
globalThemeUsed.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionSpacer()
|
||||||
|
|
||||||
|
if (showMore) {
|
||||||
|
val values by remember { mutableStateOf(
|
||||||
|
listOf(
|
||||||
|
null to generalGetString(MR.strings.chat_theme_apply_to_all_modes),
|
||||||
|
DefaultThemeMode.LIGHT to generalGetString(MR.strings.chat_theme_apply_to_light_mode),
|
||||||
|
DefaultThemeMode.DARK to generalGetString(MR.strings.chat_theme_apply_to_dark_mode),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ExposedDropDownSettingRow(
|
||||||
|
generalGetString(MR.strings.chat_theme_apply_to_mode),
|
||||||
|
values,
|
||||||
|
applyToMode,
|
||||||
|
icon = null,
|
||||||
|
enabled = remember { mutableStateOf(true) },
|
||||||
|
onSelected = {
|
||||||
|
applyToMode.value = it
|
||||||
|
if (it != null && it != CurrentColors.value.base.mode) {
|
||||||
|
val lightBase = DefaultTheme.LIGHT
|
||||||
|
val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else if (appPrefs.systemDarkTheme.get() == DefaultTheme.BLACK.themeName) DefaultTheme.BLACK else DefaultTheme.SIMPLEX
|
||||||
|
ThemeManager.applyTheme(if (it == DefaultThemeMode.LIGHT) lightBase.themeName else darkBase.themeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SectionSpacer()
|
||||||
|
|
||||||
|
AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor)
|
||||||
|
} else {
|
||||||
|
AdvancedSettingsButton { showMore = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionBottomSpacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ResetToGlobalThemeButton(onClick: () -> Unit) {
|
||||||
|
SectionItemView(onClick) {
|
||||||
|
Text(stringResource(MR.strings.chat_theme_reset_to_global_theme), color = MaterialTheme.colors.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SetDefaultThemeButton(onClick: () -> Unit) {
|
||||||
|
SectionItemView(onClick) {
|
||||||
|
Text(stringResource(MR.strings.chat_theme_set_default_theme), color = MaterialTheme.colors.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AdvancedSettingsButton(onClick: () -> Unit) {
|
||||||
|
SettingsActionItem(
|
||||||
|
painterResource(MR.images.ic_arrow_downward),
|
||||||
|
stringResource(MR.strings.wallpaper_advanced_settings),
|
||||||
|
click = onClick
|
||||||
|
)
|
||||||
|
}
|
||||||
+57
-1
@@ -19,6 +19,7 @@ import kotlinx.serialization.encodeToString
|
|||||||
import java.io.*
|
import java.io.*
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
@@ -280,6 +281,38 @@ fun saveFileFromUri(uri: URI, withAlertOnException: Boolean = true): CryptoFile?
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveWallpaperFile(uri: URI): String? {
|
||||||
|
val destFileName = generateNewFileName("wallpaper", "jpg", File(getWallpaperFilePath("")))
|
||||||
|
val destFile = File(getWallpaperFilePath(destFileName))
|
||||||
|
try {
|
||||||
|
val inputStream = uri.inputStream()
|
||||||
|
Files.copy(inputStream!!, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error saving wallpaper file: ${e.stackTraceToString()}")
|
||||||
|
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), e.stackTraceToString())
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return destFile.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveWallpaperFile(image: ImageBitmap): String {
|
||||||
|
val destFileName = generateNewFileName("wallpaper", "jpg", File(getWallpaperFilePath("")))
|
||||||
|
val destFile = File(getWallpaperFilePath(destFileName))
|
||||||
|
val dataResized = resizeImageToDataSize(image, false, maxDataSize = 5_000_000)
|
||||||
|
val output = FileOutputStream(destFile)
|
||||||
|
dataResized.use {
|
||||||
|
it.writeTo(output)
|
||||||
|
}
|
||||||
|
return destFile.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeWallpaperFile(fileName: String? = null) {
|
||||||
|
File(getWallpaperFilePath("_")).parentFile.listFiles()?.forEach {
|
||||||
|
if (it.name == fileName) it.delete()
|
||||||
|
}
|
||||||
|
WallpaperType.cachedImages.remove(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> createTmpFileAndDelete(onCreated: (File) -> T): T {
|
fun <T> createTmpFileAndDelete(onCreated: (File) -> T): T {
|
||||||
val tmpFile = File(tmpDir, UUID.randomUUID().toString())
|
val tmpFile = File(tmpDir, UUID.randomUUID().toString())
|
||||||
tmpFile.deleteOnExit()
|
tmpFile.deleteOnExit()
|
||||||
@@ -550,10 +583,33 @@ fun KeyChangeEffect(
|
|||||||
val initialKey = remember { key1 }
|
val initialKey = remember { key1 }
|
||||||
val initialKey2 = remember { key2 }
|
val initialKey2 = remember { key2 }
|
||||||
var anyChange by remember { mutableStateOf(false) }
|
var anyChange by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(key1) {
|
LaunchedEffect(key1, key2) {
|
||||||
if (anyChange || key1 != initialKey || key2 != initialKey2) {
|
if (anyChange || key1 != initialKey || key2 != initialKey2) {
|
||||||
block()
|
block()
|
||||||
anyChange = true
|
anyChange = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the [block] only after initial value of the [key1], or [key2], or [key3] changes, not after initial launch
|
||||||
|
* */
|
||||||
|
@Composable
|
||||||
|
@NonRestartableComposable
|
||||||
|
fun KeyChangeEffect(
|
||||||
|
key1: Any?,
|
||||||
|
key2: Any?,
|
||||||
|
key3: Any?,
|
||||||
|
block: suspend CoroutineScope.() -> Unit
|
||||||
|
) {
|
||||||
|
val initialKey = remember { key1 }
|
||||||
|
val initialKey2 = remember { key2 }
|
||||||
|
val initialKey3 = remember { key3 }
|
||||||
|
var anyChange by remember { mutableStateOf(false) }
|
||||||
|
LaunchedEffect(key1, key2, key3) {
|
||||||
|
if (anyChange || key1 != initialKey || key2 != initialKey2 || key3 != initialKey3) {
|
||||||
|
block()
|
||||||
|
anyChange = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) {
|
|||||||
} else {
|
} else {
|
||||||
val r: LAResult = if (passcode.value == authRequest.password) {
|
val r: LAResult = if (passcode.value == authRequest.password) {
|
||||||
if (authRequest.selfDestruct && sdPassword != null && controller.ctrl == -1L) {
|
if (authRequest.selfDestruct && sdPassword != null && controller.ctrl == -1L) {
|
||||||
initChatControllerAndRunMigrations()
|
initChatControllerOnStart()
|
||||||
}
|
}
|
||||||
LAResult.Success
|
LAResult.Success
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -669,7 +669,7 @@ private suspend fun MutableState<MigrationToState?>.cleanUpOnBack(chatReceiver:
|
|||||||
if (state is MigrationToState.ArchiveImportFailed) {
|
if (state is MigrationToState.ArchiveImportFailed) {
|
||||||
// Original database is not exist, nothing is set up correctly for showing to a user yet. Return to clean state
|
// Original database is not exist, nothing is set up correctly for showing to a user yet. Return to clean state
|
||||||
deleteChatDatabaseFilesAndState()
|
deleteChatDatabaseFilesAndState()
|
||||||
initChatControllerAndRunMigrations()
|
initChatControllerOnStart()
|
||||||
} else if (state is MigrationToState.DownloadProgress && state.ctrl != null) {
|
} else if (state is MigrationToState.DownloadProgress && state.ctrl != null) {
|
||||||
stopArchiveDownloading(state.fileId, state.ctrl)
|
stopArchiveDownloading(state.fileId, state.ctrl)
|
||||||
}
|
}
|
||||||
|
|||||||
+853
-119
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -144,7 +144,7 @@ fun SettingsLayout(
|
|||||||
SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped, extraPadding = true)
|
||||||
SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true)
|
||||||
SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true)
|
||||||
SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it, showSettingsModal) }, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }, extraPadding = true)
|
||||||
DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
|
DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
|
||||||
}
|
}
|
||||||
SectionDividerSpaced()
|
SectionDividerSpaced()
|
||||||
|
|||||||
@@ -847,7 +847,6 @@
|
|||||||
<string name="auth_open_chat_profiles">افتح ملفات تعريف الدردشة</string>
|
<string name="auth_open_chat_profiles">افتح ملفات تعريف الدردشة</string>
|
||||||
<string name="revoke_file__confirm">اسحب الوصول</string>
|
<string name="revoke_file__confirm">اسحب الوصول</string>
|
||||||
<string name="save_archive">حفظ الأرشيف</string>
|
<string name="save_archive">حفظ الأرشيف</string>
|
||||||
<string name="save_color">حفظ اللون</string>
|
|
||||||
<string name="reveal_verb">كشف</string>
|
<string name="reveal_verb">كشف</string>
|
||||||
<string name="stop_rcv_file__message">سيتم إيقاف استلام الملف.</string>
|
<string name="stop_rcv_file__message">سيتم إيقاف استلام الملف.</string>
|
||||||
<string name="reject_contact_button">رفض</string>
|
<string name="reject_contact_button">رفض</string>
|
||||||
|
|||||||
@@ -746,7 +746,7 @@
|
|||||||
<string name="private_routing_explanation">To protect your IP address, private routing uses your SMP servers to deliver messages.</string>
|
<string name="private_routing_explanation">To protect your IP address, private routing uses your SMP servers to deliver messages.</string>
|
||||||
<string name="appearance_settings">Appearance</string>
|
<string name="appearance_settings">Appearance</string>
|
||||||
<string name="customize_theme_title">Customize theme</string>
|
<string name="customize_theme_title">Customize theme</string>
|
||||||
<string name="theme_colors_section_title">THEME COLORS</string>
|
<string name="theme_colors_section_title">INTERFACE COLORS</string>
|
||||||
<string name="app_version_title">App version</string>
|
<string name="app_version_title">App version</string>
|
||||||
<string name="app_version_name">App version: v%s</string>
|
<string name="app_version_name">App version: v%s</string>
|
||||||
<string name="app_version_code">App build: %s</string>
|
<string name="app_version_code">App build: %s</string>
|
||||||
@@ -1072,6 +1072,9 @@
|
|||||||
<string name="settings_section_title_icon">APP ICON</string>
|
<string name="settings_section_title_icon">APP ICON</string>
|
||||||
<string name="settings_section_title_themes">THEMES</string>
|
<string name="settings_section_title_themes">THEMES</string>
|
||||||
<string name="settings_section_title_profile_images">Profile images</string>
|
<string name="settings_section_title_profile_images">Profile images</string>
|
||||||
|
<string name="settings_section_title_chat_theme">Chat theme</string>
|
||||||
|
<string name="settings_section_title_user_theme">Profile theme</string>
|
||||||
|
<string name="settings_section_title_chat_colors">Chat colors</string>
|
||||||
<string name="settings_section_title_messages">MESSAGES AND FILES</string>
|
<string name="settings_section_title_messages">MESSAGES AND FILES</string>
|
||||||
<string name="settings_section_title_private_message_routing">PRIVATE MESSAGE ROUTING</string>
|
<string name="settings_section_title_private_message_routing">PRIVATE MESSAGE ROUTING</string>
|
||||||
<string name="settings_section_title_calls">CALLS</string>
|
<string name="settings_section_title_calls">CALLS</string>
|
||||||
@@ -1201,6 +1204,7 @@
|
|||||||
<string name="incompatible_database_version">Incompatible database version</string>
|
<string name="incompatible_database_version">Incompatible database version</string>
|
||||||
<string name="confirm_database_upgrades">Confirm database upgrades</string>
|
<string name="confirm_database_upgrades">Confirm database upgrades</string>
|
||||||
<string name="terminal_always_visible">Show console in new window</string>
|
<string name="terminal_always_visible">Show console in new window</string>
|
||||||
|
<string name="chat_list_always_visible">Show chat list in new window</string>
|
||||||
<string name="invalid_migration_confirmation">Invalid migration confirmation</string>
|
<string name="invalid_migration_confirmation">Invalid migration confirmation</string>
|
||||||
<string name="upgrade_and_open_chat">Upgrade and open chat</string>
|
<string name="upgrade_and_open_chat">Upgrade and open chat</string>
|
||||||
<string name="downgrade_and_open_chat">Downgrade and open chat</string>
|
<string name="downgrade_and_open_chat">Downgrade and open chat</string>
|
||||||
@@ -1543,23 +1547,30 @@
|
|||||||
<string name="incognito_info_share">When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.</string>
|
<string name="incognito_info_share">When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.</string>
|
||||||
|
|
||||||
<!-- Default themes -->
|
<!-- Default themes -->
|
||||||
|
<string name="color_mode_system">System</string>
|
||||||
|
<string name="color_mode_light">Light</string>
|
||||||
|
<string name="color_mode_dark">Dark</string>
|
||||||
<string name="theme_system">System</string>
|
<string name="theme_system">System</string>
|
||||||
<string name="theme_light">Light</string>
|
<string name="theme_light">Light</string>
|
||||||
<string name="theme_dark">Dark</string>
|
<string name="theme_dark">Dark</string>
|
||||||
<string name="theme_simplex">SimpleX</string>
|
<string name="theme_simplex">SimpleX</string>
|
||||||
|
<string name="theme_black">Black</string>
|
||||||
|
|
||||||
<!-- Languages -->
|
<!-- Languages -->
|
||||||
<string name="language_system">System</string>
|
<string name="language_system">System</string>
|
||||||
|
|
||||||
<!-- Appearance.kt -->
|
<!-- Appearance.kt -->
|
||||||
<string name="theme">Theme</string>
|
<string name="theme">Theme</string>
|
||||||
|
<string name="color_mode">Color mode</string>
|
||||||
<string name="dark_theme">Dark theme</string>
|
<string name="dark_theme">Dark theme</string>
|
||||||
<string name="save_color">Save color</string>
|
<string name="dark_mode_colors">Dark mode colors</string>
|
||||||
<string name="import_theme">Import theme</string>
|
<string name="import_theme">Import theme</string>
|
||||||
<string name="import_theme_error">Import theme error</string>
|
<string name="import_theme_error">Import theme error</string>
|
||||||
<string name="import_theme_error_desc">Make sure the file has correct YAML syntax. Export theme to have an example of the theme file structure.</string>
|
<string name="import_theme_error_desc">Make sure the file has correct YAML syntax. Export theme to have an example of the theme file structure.</string>
|
||||||
<string name="export_theme">Export theme</string>
|
<string name="export_theme">Export theme</string>
|
||||||
<string name="reset_color">Reset colors</string>
|
<string name="reset_color">Reset colors</string>
|
||||||
|
<string name="reset_single_color">Reset color</string>
|
||||||
|
<string name="theme_destination_all_profiles">All chat profiles</string>
|
||||||
<string name="color_primary">Accent</string>
|
<string name="color_primary">Accent</string>
|
||||||
<string name="color_primary_variant">Additional accent</string>
|
<string name="color_primary_variant">Additional accent</string>
|
||||||
<string name="color_secondary">Secondary</string>
|
<string name="color_secondary">Secondary</string>
|
||||||
@@ -1567,8 +1578,36 @@
|
|||||||
<string name="color_background">Background</string>
|
<string name="color_background">Background</string>
|
||||||
<string name="color_surface">Menus & alerts</string>
|
<string name="color_surface">Menus & alerts</string>
|
||||||
<string name="color_title">Title</string>
|
<string name="color_title">Title</string>
|
||||||
|
<string name="color_primary_variant2">Additional accent 2</string>
|
||||||
<string name="color_sent_message">Sent message</string>
|
<string name="color_sent_message">Sent message</string>
|
||||||
|
<string name="color_sent_quote">Sent reply</string>
|
||||||
<string name="color_received_message">Received message</string>
|
<string name="color_received_message">Received message</string>
|
||||||
|
<string name="color_received_quote">Received reply</string>
|
||||||
|
<string name="color_wallpaper_background">Wallpaper background</string>
|
||||||
|
<string name="color_wallpaper_tint">Wallpaper accent</string>
|
||||||
|
<string name="theme_remove_image">Remove image</string>
|
||||||
|
|
||||||
|
<!-- Backgrounds -->
|
||||||
|
<string name="wallpaper_cats">Cats</string>
|
||||||
|
<string name="wallpaper_flowers">Flowers</string>
|
||||||
|
<string name="wallpaper_hearts">Hearts</string>
|
||||||
|
<string name="wallpaper_kids">Kids</string>
|
||||||
|
<string name="wallpaper_school">School</string>
|
||||||
|
<string name="wallpaper_travel">Travel</string>
|
||||||
|
<string name="wallpaper_preview_hello_alice">Good afternoon!</string>
|
||||||
|
<string name="wallpaper_preview_hello_bob">Good morning!</string>
|
||||||
|
<string name="wallpaper_scale">Scale</string>
|
||||||
|
<string name="wallpaper_scale_repeat">Repeat</string>
|
||||||
|
<string name="wallpaper_scale_fill">Fill</string>
|
||||||
|
<string name="wallpaper_scale_fit">Fit</string>
|
||||||
|
<string name="wallpaper_advanced_settings">Advanced settings</string>
|
||||||
|
<string name="chat_theme_reset_to_global_theme">Reset to global theme</string>
|
||||||
|
<string name="chat_theme_set_default_theme">Set default theme</string>
|
||||||
|
<string name="chat_theme_apply_to_mode">Apply to</string>
|
||||||
|
<string name="chat_theme_apply_to_all_modes">All color modes</string>
|
||||||
|
<string name="chat_theme_apply_to_light_mode">Light mode</string>
|
||||||
|
<string name="chat_theme_apply_to_dark_mode">Dark mode</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Preferences.kt -->
|
<!-- Preferences.kt -->
|
||||||
<string name="chat_preferences_you_allow">You allow</string>
|
<string name="chat_preferences_you_allow">You allow</string>
|
||||||
|
|||||||
@@ -1150,7 +1150,6 @@
|
|||||||
<string name="network_options_revert">Отмени промените</string>
|
<string name="network_options_revert">Отмени промените</string>
|
||||||
<string name="network_options_save">Запази</string>
|
<string name="network_options_save">Запази</string>
|
||||||
<string name="reset_color">Нулирай цветовете</string>
|
<string name="reset_color">Нулирай цветовете</string>
|
||||||
<string name="save_color">Запази цвета</string>
|
|
||||||
<string name="color_secondary">Вторичен</string>
|
<string name="color_secondary">Вторичен</string>
|
||||||
<string name="custom_time_picker_select">Избери</string>
|
<string name="custom_time_picker_select">Избери</string>
|
||||||
<string name="to_start_a_new_chat_help_header">За да започнете нов чат</string>
|
<string name="to_start_a_new_chat_help_header">За да започнете нов чат</string>
|
||||||
|
|||||||
@@ -55,7 +55,6 @@
|
|||||||
<string name="update_network_settings_question">Aktualizovat nastavení sítě\?</string>
|
<string name="update_network_settings_question">Aktualizovat nastavení sítě\?</string>
|
||||||
<string name="incognito">Inkognito</string>
|
<string name="incognito">Inkognito</string>
|
||||||
<string name="incognito_random_profile">Váš náhodný profil</string>
|
<string name="incognito_random_profile">Váš náhodný profil</string>
|
||||||
<string name="save_color">Uložit barvu</string>
|
|
||||||
<string name="reset_color">Obnovit barvu</string>
|
<string name="reset_color">Obnovit barvu</string>
|
||||||
<string name="color_primary">Zbarvení</string>
|
<string name="color_primary">Zbarvení</string>
|
||||||
<string name="chat_preferences_you_allow">Povolujete</string>
|
<string name="chat_preferences_you_allow">Povolujete</string>
|
||||||
|
|||||||
@@ -842,7 +842,6 @@
|
|||||||
<string name="theme_dark">Dunkel</string>
|
<string name="theme_dark">Dunkel</string>
|
||||||
<!-- Appearance.kt -->
|
<!-- Appearance.kt -->
|
||||||
<string name="theme">Design</string>
|
<string name="theme">Design</string>
|
||||||
<string name="save_color">Farbe speichern</string>
|
|
||||||
<string name="reset_color">Farben zurücksetzen</string>
|
<string name="reset_color">Farben zurücksetzen</string>
|
||||||
<string name="color_primary">Akzent</string>
|
<string name="color_primary">Akzent</string>
|
||||||
<!-- Preferences.kt -->
|
<!-- Preferences.kt -->
|
||||||
|
|||||||
@@ -607,7 +607,6 @@
|
|||||||
<string name="secret_text">secreto</string>
|
<string name="secret_text">secreto</string>
|
||||||
<string name="open_simplex_chat_to_accept_call">Abrir SimpleX Chat para aceptar llamada</string>
|
<string name="open_simplex_chat_to_accept_call">Abrir SimpleX Chat para aceptar llamada</string>
|
||||||
<string name="network_options_reset_to_defaults">Restablecer valores por defecto</string>
|
<string name="network_options_reset_to_defaults">Restablecer valores por defecto</string>
|
||||||
<string name="save_color">Guardar color</string>
|
|
||||||
<string name="icon_descr_server_status_pending">Pendiente</string>
|
<string name="icon_descr_server_status_pending">Pendiente</string>
|
||||||
<string name="periodic_notifications">Notificaciones periódicas</string>
|
<string name="periodic_notifications">Notificaciones periódicas</string>
|
||||||
<string name="store_passphrase_securely">Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes.</string>
|
<string name="store_passphrase_securely">Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes.</string>
|
||||||
|
|||||||
@@ -873,7 +873,6 @@
|
|||||||
<string name="v4_6_chinese_spanish_interface_descr">Kiitos käyttäjille – osallistu Weblaten kautta!</string>
|
<string name="v4_6_chinese_spanish_interface_descr">Kiitos käyttäjille – osallistu Weblaten kautta!</string>
|
||||||
<string name="profile_password">Profiilin salasana</string>
|
<string name="profile_password">Profiilin salasana</string>
|
||||||
<string name="reset_color">Oletusvärit</string>
|
<string name="reset_color">Oletusvärit</string>
|
||||||
<string name="save_color">Tallenna väri</string>
|
|
||||||
<string name="prohibit_sending_voice_messages">Estä ääniviestien lähettäminen.</string>
|
<string name="prohibit_sending_voice_messages">Estä ääniviestien lähettäminen.</string>
|
||||||
<string name="v4_4_disappearing_messages_desc">Lähetetyt viestit poistetaan asetetun ajan kuluttua.</string>
|
<string name="v4_4_disappearing_messages_desc">Lähetetyt viestit poistetaan asetetun ajan kuluttua.</string>
|
||||||
<string name="add_contact_or_create_group">Aloita uusi keskustelu</string>
|
<string name="add_contact_or_create_group">Aloita uusi keskustelu</string>
|
||||||
|
|||||||
@@ -783,7 +783,6 @@
|
|||||||
<string name="theme_light">Clair</string>
|
<string name="theme_light">Clair</string>
|
||||||
<string name="theme_dark">Sombre</string>
|
<string name="theme_dark">Sombre</string>
|
||||||
<string name="theme">Thème</string>
|
<string name="theme">Thème</string>
|
||||||
<string name="save_color">Enregistrer la couleur</string>
|
|
||||||
<string name="reset_color">Réinitialisation des couleurs</string>
|
<string name="reset_color">Réinitialisation des couleurs</string>
|
||||||
<string name="color_primary">Principale</string>
|
<string name="color_primary">Principale</string>
|
||||||
<string name="chat_preferences_you_allow">Vous autorisez</string>
|
<string name="chat_preferences_you_allow">Vous autorisez</string>
|
||||||
|
|||||||
@@ -69,7 +69,6 @@
|
|||||||
<string name="personal_welcome">स्वागत %1$s!</string>
|
<string name="personal_welcome">स्वागत %1$s!</string>
|
||||||
<string name="callstate_starting">शुरुआत</string>
|
<string name="callstate_starting">शुरुआत</string>
|
||||||
<string name="send_verb">भेजना</string>
|
<string name="send_verb">भेजना</string>
|
||||||
<string name="save_color">रंग बचाओ</string>
|
|
||||||
<string name="share_verb">साझा करना</string>
|
<string name="share_verb">साझा करना</string>
|
||||||
<string name="reject_contact_button">अस्वीकार</string>
|
<string name="reject_contact_button">अस्वीकार</string>
|
||||||
<string name="network_use_onion_hosts_required">आवश्यक</string>
|
<string name="network_use_onion_hosts_required">आवश्यक</string>
|
||||||
|
|||||||
@@ -1196,7 +1196,6 @@
|
|||||||
<string name="scan_code">Kód beolvasása</string>
|
<string name="scan_code">Kód beolvasása</string>
|
||||||
<string name="open_port_in_firewall_title">Port megnyitása a tűzfalon</string>
|
<string name="open_port_in_firewall_title">Port megnyitása a tűzfalon</string>
|
||||||
<string name="callstate_starting">indítás…</string>
|
<string name="callstate_starting">indítás…</string>
|
||||||
<string name="save_color">Szín mentése</string>
|
|
||||||
<string name="settings_shutdown">Leállítás</string>
|
<string name="settings_shutdown">Leállítás</string>
|
||||||
<string name="icon_descr_sent_msg_status_sent">elküldve</string>
|
<string name="icon_descr_sent_msg_status_sent">elküldve</string>
|
||||||
<string name="network_socks_toggle_use_socks_proxy">SOCKS proxy használata</string>
|
<string name="network_socks_toggle_use_socks_proxy">SOCKS proxy használata</string>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 168 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
@@ -847,7 +847,6 @@
|
|||||||
<string name="prohibit_sending_voice_messages">Proibisci l\'invio di messaggi vocali.</string>
|
<string name="prohibit_sending_voice_messages">Proibisci l\'invio di messaggi vocali.</string>
|
||||||
<string name="feature_received_prohibited">ricevuto, vietato</string>
|
<string name="feature_received_prohibited">ricevuto, vietato</string>
|
||||||
<string name="reset_color">Ripristina i colori</string>
|
<string name="reset_color">Ripristina i colori</string>
|
||||||
<string name="save_color">Salva colore</string>
|
|
||||||
<string name="accept_feature_set_1_day">Imposta 1 giorno</string>
|
<string name="accept_feature_set_1_day">Imposta 1 giorno</string>
|
||||||
<string name="set_group_preferences">Imposta le preferenze del gruppo</string>
|
<string name="set_group_preferences">Imposta le preferenze del gruppo</string>
|
||||||
<string name="theme">Tema</string>
|
<string name="theme">Tema</string>
|
||||||
|
|||||||
@@ -898,7 +898,6 @@
|
|||||||
<string name="save_passphrase_and_open_chat">שמור סיסמה ופתח את הצ׳אט</string>
|
<string name="save_passphrase_and_open_chat">שמור סיסמה ופתח את הצ׳אט</string>
|
||||||
<string name="save_archive">שמור ארכיון</string>
|
<string name="save_archive">שמור ארכיון</string>
|
||||||
<string name="select_contacts">בחירת אנשי קשר</string>
|
<string name="select_contacts">בחירת אנשי קשר</string>
|
||||||
<string name="save_color">שמור צבע</string>
|
|
||||||
<string name="v5_1_self_destruct_passcode">קוד גישה להשמדה עצמית</string>
|
<string name="v5_1_self_destruct_passcode">קוד גישה להשמדה עצמית</string>
|
||||||
<string name="custom_time_unit_seconds">שניות</string>
|
<string name="custom_time_unit_seconds">שניות</string>
|
||||||
<string name="custom_time_picker_select">אישור</string>
|
<string name="custom_time_picker_select">אישור</string>
|
||||||
|
|||||||
@@ -792,7 +792,6 @@
|
|||||||
<string name="network_options_save">保存</string>
|
<string name="network_options_save">保存</string>
|
||||||
<string name="update_network_settings_question">ネットワーク設定を更新しますか?</string>
|
<string name="update_network_settings_question">ネットワーク設定を更新しますか?</string>
|
||||||
<string name="updating_settings_will_reconnect_client_to_all_servers">設定を更新すると、全サーバにクライントの再接続が行われます。</string>
|
<string name="updating_settings_will_reconnect_client_to_all_servers">設定を更新すると、全サーバにクライントの再接続が行われます。</string>
|
||||||
<string name="save_color">色を保存</string>
|
|
||||||
<string name="chat_preferences_you_allow">あなたが次を許可しています:</string>
|
<string name="chat_preferences_you_allow">あなたが次を許可しています:</string>
|
||||||
<string name="chat_preferences_yes">オン</string>
|
<string name="chat_preferences_yes">オン</string>
|
||||||
<string name="set_group_preferences">グループ設定を行う</string>
|
<string name="set_group_preferences">グループ設定を行う</string>
|
||||||
|
|||||||
@@ -757,7 +757,6 @@
|
|||||||
<string name="save_and_notify_contact">저장하고 대화 상대에게 알리기</string>
|
<string name="save_and_notify_contact">저장하고 대화 상대에게 알리기</string>
|
||||||
<string name="remove_passphrase">지우기</string>
|
<string name="remove_passphrase">지우기</string>
|
||||||
<string name="save_archive">아카이브 저장하기</string>
|
<string name="save_archive">아카이브 저장하기</string>
|
||||||
<string name="save_color">색상 저장하기</string>
|
|
||||||
<string name="save_passphrase_in_keychain">암호 저장소에 비밀번호 저장하기</string>
|
<string name="save_passphrase_in_keychain">암호 저장소에 비밀번호 저장하기</string>
|
||||||
<string name="restore_database">데이터베이스 백업 복원하기</string>
|
<string name="restore_database">데이터베이스 백업 복원하기</string>
|
||||||
<string name="restore_database_alert_desc">데이터베이스 백업을 복원한 후 이전 비밀번호를 입력해 주세요. 이 작업은 되돌릴 수 없어요.</string>
|
<string name="restore_database_alert_desc">데이터베이스 백업을 복원한 후 이전 비밀번호를 입력해 주세요. 이 작업은 되돌릴 수 없어요.</string>
|
||||||
|
|||||||
@@ -189,7 +189,6 @@
|
|||||||
<string name="dont_show_again">Daugiau neberodyti</string>
|
<string name="dont_show_again">Daugiau neberodyti</string>
|
||||||
<string name="theme_dark">Tamsus</string>
|
<string name="theme_dark">Tamsus</string>
|
||||||
<string name="reset_color">Atstatyti spalvas</string>
|
<string name="reset_color">Atstatyti spalvas</string>
|
||||||
<string name="save_color">Įrašyti spalvą</string>
|
|
||||||
<string name="group_preferences">Grupės parinktys</string>
|
<string name="group_preferences">Grupės parinktys</string>
|
||||||
<string name="full_deletion">Ištrinti visiems</string>
|
<string name="full_deletion">Ištrinti visiems</string>
|
||||||
<string name="direct_messages">Tiesioginės žinutės</string>
|
<string name="direct_messages">Tiesioginės žinutės</string>
|
||||||
|
|||||||
@@ -323,7 +323,6 @@
|
|||||||
<string name="network_options_revert">പഴയപടിയാക്കുക</string>
|
<string name="network_options_revert">പഴയപടിയാക്കുക</string>
|
||||||
<string name="theme_system">സംവിധാനം</string>
|
<string name="theme_system">സംവിധാനം</string>
|
||||||
<string name="language_system">സംവിധാനം</string>
|
<string name="language_system">സംവിധാനം</string>
|
||||||
<string name="save_color">നിറം സംരക്ഷിക്കുക</string>
|
|
||||||
<string name="color_title">ശീർഷകം</string>
|
<string name="color_title">ശീർഷകം</string>
|
||||||
<string name="color_secondary">രണ്ടാംതരമായ</string>
|
<string name="color_secondary">രണ്ടാംതരമായ</string>
|
||||||
<string name="chat_preferences_yes">അതെ</string>
|
<string name="chat_preferences_yes">അതെ</string>
|
||||||
|
|||||||
@@ -814,7 +814,6 @@
|
|||||||
<string name="incognito_info_share">Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen.</string>
|
<string name="incognito_info_share">Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen.</string>
|
||||||
<string name="theme">Thema</string>
|
<string name="theme">Thema</string>
|
||||||
<string name="reset_color">Kleuren resetten</string>
|
<string name="reset_color">Kleuren resetten</string>
|
||||||
<string name="save_color">Kleur opslaan</string>
|
|
||||||
<string name="theme_system">Systeem</string>
|
<string name="theme_system">Systeem</string>
|
||||||
<string name="chat_preferences_yes">ja</string>
|
<string name="chat_preferences_yes">ja</string>
|
||||||
<string name="feature_received_prohibited">gekregen, verboden</string>
|
<string name="feature_received_prohibited">gekregen, verboden</string>
|
||||||
|
|||||||
@@ -799,7 +799,6 @@
|
|||||||
<string name="prohibit_sending_disappearing_messages">Zabroń wysyłania znikających wiadomości.</string>
|
<string name="prohibit_sending_disappearing_messages">Zabroń wysyłania znikających wiadomości.</string>
|
||||||
<string name="feature_received_prohibited">otrzymane, zabronione</string>
|
<string name="feature_received_prohibited">otrzymane, zabronione</string>
|
||||||
<string name="reset_color">Resetuj kolory</string>
|
<string name="reset_color">Resetuj kolory</string>
|
||||||
<string name="save_color">Zapisz kolor</string>
|
|
||||||
<string name="accept_feature_set_1_day">Ustaw 1 dzień</string>
|
<string name="accept_feature_set_1_day">Ustaw 1 dzień</string>
|
||||||
<string name="theme_system">System</string>
|
<string name="theme_system">System</string>
|
||||||
<string name="language_system">System</string>
|
<string name="language_system">System</string>
|
||||||
|
|||||||
@@ -485,7 +485,6 @@
|
|||||||
<string name="network_options_revert">Reverter</string>
|
<string name="network_options_revert">Reverter</string>
|
||||||
<string name="network_options_save">Salvar</string>
|
<string name="network_options_save">Salvar</string>
|
||||||
<string name="reset_color">Redefinir cores</string>
|
<string name="reset_color">Redefinir cores</string>
|
||||||
<string name="save_color">Salvar cor</string>
|
|
||||||
<string name="v4_5_italian_interface">interface italiana</string>
|
<string name="v4_5_italian_interface">interface italiana</string>
|
||||||
<string name="periodic_notifications">Notificações periódicas</string>
|
<string name="periodic_notifications">Notificações periódicas</string>
|
||||||
<string name="use_camera_button">Câmera</string>
|
<string name="use_camera_button">Câmera</string>
|
||||||
|
|||||||
@@ -370,7 +370,6 @@
|
|||||||
<string name="invite_prohibited_description">Você está a tentar convidar um contato com quem partilhou um perfil anónimo para o grupo no qual voçê está a usar o seu perfil principal</string>
|
<string name="invite_prohibited_description">Você está a tentar convidar um contato com quem partilhou um perfil anónimo para o grupo no qual voçê está a usar o seu perfil principal</string>
|
||||||
<string name="info_row_connection">Conexão</string>
|
<string name="info_row_connection">Conexão</string>
|
||||||
<string name="incognito_info_protects">O modo anónimo protege a privacidade do nome e da imagem do seu perfil principal — para cada novo contato um novo perfil aleatório é criado.</string>
|
<string name="incognito_info_protects">O modo anónimo protege a privacidade do nome e da imagem do seu perfil principal — para cada novo contato um novo perfil aleatório é criado.</string>
|
||||||
<string name="save_color">Salvar cor</string>
|
|
||||||
<string name="description_you_shared_one_time_link">você partilhou ligação de utilização única</string>
|
<string name="description_you_shared_one_time_link">você partilhou ligação de utilização única</string>
|
||||||
<string name="description_you_shared_one_time_link_incognito">você partilhou ligação anónima de utilização única</string>
|
<string name="description_you_shared_one_time_link_incognito">você partilhou ligação anónima de utilização única</string>
|
||||||
<string name="invalid_connection_link">Ligação de conexão inválida</string>
|
<string name="invalid_connection_link">Ligação de conexão inválida</string>
|
||||||
|
|||||||
@@ -848,7 +848,6 @@
|
|||||||
<string name="theme_dark">Темная</string>
|
<string name="theme_dark">Темная</string>
|
||||||
<!-- Appearance.kt -->
|
<!-- Appearance.kt -->
|
||||||
<string name="theme">Тема</string>
|
<string name="theme">Тема</string>
|
||||||
<string name="save_color">Сохранить цвет</string>
|
|
||||||
<string name="reset_color">Сбросить цвета</string>
|
<string name="reset_color">Сбросить цвета</string>
|
||||||
<string name="color_primary">Акцент</string>
|
<string name="color_primary">Акцент</string>
|
||||||
<!-- Preferences.kt -->
|
<!-- Preferences.kt -->
|
||||||
|
|||||||
@@ -855,7 +855,6 @@
|
|||||||
<string name="network_options_revert">เปลี่ยนกลับ</string>
|
<string name="network_options_revert">เปลี่ยนกลับ</string>
|
||||||
<string name="network_options_save">บันทึก</string>
|
<string name="network_options_save">บันทึก</string>
|
||||||
<string name="reset_color">รีเซ็ตสี</string>
|
<string name="reset_color">รีเซ็ตสี</string>
|
||||||
<string name="save_color">บันทึกสี</string>
|
|
||||||
<string name="feature_received_prohibited">ได้รับ, ห้าม</string>
|
<string name="feature_received_prohibited">ได้รับ, ห้าม</string>
|
||||||
<string name="v4_4_live_messages_desc">ผู้รับจะเห็นการอัปเดตเมื่อคุณพิมพ์</string>
|
<string name="v4_4_live_messages_desc">ผู้รับจะเห็นการอัปเดตเมื่อคุณพิมพ์</string>
|
||||||
<string name="v4_5_reduced_battery_usage">ลดการใช้แบตเตอรี่</string>
|
<string name="v4_5_reduced_battery_usage">ลดการใช้แบตเตอรี่</string>
|
||||||
|
|||||||
@@ -122,7 +122,6 @@
|
|||||||
<string name="theme_simplex">SimpleX</string>
|
<string name="theme_simplex">SimpleX</string>
|
||||||
<string name="dark_theme">Koyu tema</string>
|
<string name="dark_theme">Koyu tema</string>
|
||||||
<string name="theme">Tema</string>
|
<string name="theme">Tema</string>
|
||||||
<string name="save_color">Rengi kaydet</string>
|
|
||||||
<string name="import_theme">Temayı içe aktar</string>
|
<string name="import_theme">Temayı içe aktar</string>
|
||||||
<string name="import_theme_error">Temayı içe aktarırken hata oluştu</string>
|
<string name="import_theme_error">Temayı içe aktarırken hata oluştu</string>
|
||||||
<string name="import_theme_error_desc">Dosyanın doğru YAML sözdizimine sahip olduğundan emin olun. Tema dosyası yapısının bir örneğine sahip olmak için temayı dışa aktarın.</string>
|
<string name="import_theme_error_desc">Dosyanın doğru YAML sözdizimine sahip olduğundan emin olun. Tema dosyası yapısının bir örneğine sahip olmak için temayı dışa aktarın.</string>
|
||||||
|
|||||||
@@ -450,7 +450,6 @@
|
|||||||
<string name="change_member_role_question">Змінити роль у групі\?</string>
|
<string name="change_member_role_question">Змінити роль у групі\?</string>
|
||||||
<string name="error_removing_member">Помилка при вилученні учасника</string>
|
<string name="error_removing_member">Помилка при вилученні учасника</string>
|
||||||
<string name="group_main_profile_sent">Ваш профіль чату буде відправлений учасникам групи</string>
|
<string name="group_main_profile_sent">Ваш профіль чату буде відправлений учасникам групи</string>
|
||||||
<string name="save_color">Зберегти колір</string>
|
|
||||||
<string name="full_deletion">Видалення для всіх</string>
|
<string name="full_deletion">Видалення для всіх</string>
|
||||||
<string name="voice_messages">Голосові повідомлення</string>
|
<string name="voice_messages">Голосові повідомлення</string>
|
||||||
<string name="voice_prohibited_in_this_chat">Голосові повідомлення заборонені в цьому чаті.</string>
|
<string name="voice_prohibited_in_this_chat">Голосові повідомлення заборонені в цьому чаті.</string>
|
||||||
|
|||||||
@@ -831,7 +831,6 @@
|
|||||||
<string name="network_option_seconds_label">秒</string>
|
<string name="network_option_seconds_label">秒</string>
|
||||||
<string name="network_options_revert">恢复</string>
|
<string name="network_options_revert">恢复</string>
|
||||||
<string name="reset_color">重置颜色</string>
|
<string name="reset_color">重置颜色</string>
|
||||||
<string name="save_color">保存颜色</string>
|
|
||||||
<string name="v4_5_reduced_battery_usage">减少电池使用量</string>
|
<string name="v4_5_reduced_battery_usage">减少电池使用量</string>
|
||||||
<string name="v4_5_private_filenames_descr">为了保护时区,图像/语音文件使用 UTC。</string>
|
<string name="v4_5_private_filenames_descr">为了保护时区,图像/语音文件使用 UTC。</string>
|
||||||
<string name="use_chat">使用聊天</string>
|
<string name="use_chat">使用聊天</string>
|
||||||
|
|||||||
@@ -827,7 +827,6 @@
|
|||||||
<string name="error_saving_group_profile">儲存群組檔案時出錯</string>
|
<string name="error_saving_group_profile">儲存群組檔案時出錯</string>
|
||||||
<string name="network_options_revert">恢復</string>
|
<string name="network_options_revert">恢復</string>
|
||||||
<string name="theme">主題</string>
|
<string name="theme">主題</string>
|
||||||
<string name="save_color">儲存顏色</string>
|
|
||||||
<string name="chat_preferences_you_allow">你允許</string>
|
<string name="chat_preferences_you_allow">你允許</string>
|
||||||
<string name="set_group_preferences">修改群組內的設定</string>
|
<string name="set_group_preferences">修改群組內的設定</string>
|
||||||
<string name="direct_messages">私訊</string>
|
<string name="direct_messages">私訊</string>
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package chat.simplex.app
|
||||||
|
|
||||||
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
// use this command for testing:
|
||||||
|
// ./gradlew desktopTest
|
||||||
|
class ThemesTest {
|
||||||
|
@Test
|
||||||
|
fun testSkipDuplicates() {
|
||||||
|
val r = ArrayList<ThemeOverrides>()
|
||||||
|
r.add(ThemeOverrides("UUID", DefaultTheme.DARK))
|
||||||
|
r.add(ThemeOverrides("UUID", DefaultTheme.DARK))
|
||||||
|
r.add(ThemeOverrides("UUID", DefaultTheme.LIGHT))
|
||||||
|
r.add(ThemeOverrides("UUID2", DefaultTheme.DARK))
|
||||||
|
r.add(ThemeOverrides("UUID3", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper()))
|
||||||
|
r.add(ThemeOverrides("UUID4", DefaultTheme.LIGHT, wallpaper = null))
|
||||||
|
r.add(ThemeOverrides("UUID5", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something")))
|
||||||
|
r.add(ThemeOverrides("UUID5", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something2")))
|
||||||
|
r.add(ThemeOverrides("UUID6", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something2")))
|
||||||
|
r.add(ThemeOverrides("UUID7", DefaultTheme.DARK, wallpaper = ThemeWallpaper(preset = "something2")))
|
||||||
|
r.add(ThemeOverrides("UUID8", DefaultTheme.DARK, wallpaper = ThemeWallpaper(imageFile = "image")))
|
||||||
|
r.add(ThemeOverrides("UUID9", DefaultTheme.DARK, wallpaper = ThemeWallpaper(imageFile = "image2")))
|
||||||
|
r.add(ThemeOverrides("UUID10", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(imageFile = "image")))
|
||||||
|
assertEquals(
|
||||||
|
r.skipDuplicates(), listOf(
|
||||||
|
ThemeOverrides("UUID", DefaultTheme.DARK),
|
||||||
|
ThemeOverrides("UUID3", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper()),
|
||||||
|
ThemeOverrides("UUID5", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something")),
|
||||||
|
ThemeOverrides("UUID6", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something2")),
|
||||||
|
ThemeOverrides("UUID7", DefaultTheme.DARK, wallpaper = ThemeWallpaper(preset = "something2")),
|
||||||
|
ThemeOverrides("UUID8", DefaultTheme.DARK, wallpaper = ThemeWallpaper(imageFile = "image")),
|
||||||
|
ThemeOverrides("UUID10", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(imageFile = "image"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,7 @@ import androidx.compose.desktop.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ fun initApp() {
|
|||||||
}
|
}
|
||||||
applyAppLocale()
|
applyAppLocale()
|
||||||
if (DatabaseUtils.ksSelfDestructPassword.get() == null) {
|
if (DatabaseUtils.ksSelfDestructPassword.get() == null) {
|
||||||
initChatControllerAndRunMigrations()
|
initChatControllerOnStart()
|
||||||
}
|
}
|
||||||
// LALAL
|
// LALAL
|
||||||
//testCrypto()
|
//testCrypto()
|
||||||
|
|||||||
+2
@@ -13,8 +13,10 @@ actual val dataDir: File = File(desktopPlatform.dataPath)
|
|||||||
actual val tmpDir: File = File(System.getProperty("java.io.tmpdir") + File.separator + "simplex").also { it.deleteOnExit() }
|
actual val tmpDir: File = File(System.getProperty("java.io.tmpdir") + File.separator + "simplex").also { it.deleteOnExit() }
|
||||||
actual val filesDir: File = File(dataDir.absolutePath + File.separator + "simplex_v1_files")
|
actual val filesDir: File = File(dataDir.absolutePath + File.separator + "simplex_v1_files")
|
||||||
actual val appFilesDir: File = filesDir
|
actual val appFilesDir: File = filesDir
|
||||||
|
actual val wallpapersDir: File = File(dataDir.absolutePath + File.separator + "simplex_v1_assets" + File.separator + "wallpapers").also { it.mkdirs() }
|
||||||
actual val coreTmpDir: File = File(dataDir.absolutePath + File.separator + "tmp")
|
actual val coreTmpDir: File = File(dataDir.absolutePath + File.separator + "tmp")
|
||||||
actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "simplex_v1"
|
actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "simplex_v1"
|
||||||
|
actual val preferencesDir = File(desktopPlatform.configPath).also { it.parentFile.mkdirs() }
|
||||||
|
|
||||||
actual val chatDatabaseFileName: String = "simplex_v1_chat.db"
|
actual val chatDatabaseFileName: String = "simplex_v1_chat.db"
|
||||||
actual val agentDatabaseFileName: String = "simplex_v1_agent.db"
|
actual val agentDatabaseFileName: String = "simplex_v1_agent.db"
|
||||||
|
|||||||
+17
-1
@@ -1,12 +1,17 @@
|
|||||||
package chat.simplex.common.platform
|
package chat.simplex.common.platform
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import chat.simplex.common.simplexWindowState
|
import chat.simplex.common.simplexWindowState
|
||||||
|
import chat.simplex.common.ui.theme.reactOnDarkThemeChanges
|
||||||
|
import com.jthemedetecor.OsThemeDetector
|
||||||
import com.russhwolf.settings.*
|
import com.russhwolf.settings.*
|
||||||
|
import dev.icerock.moko.resources.ImageResource
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import dev.icerock.moko.resources.desc.desc
|
import dev.icerock.moko.resources.desc.desc
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -18,7 +23,15 @@ actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle)
|
|||||||
|
|
||||||
actual fun StringResource.localized(): String = desc().toString()
|
actual fun StringResource.localized(): String = desc().toString()
|
||||||
|
|
||||||
actual fun isInNightMode() = false
|
private val detector: OsThemeDetector = OsThemeDetector.getDetector()
|
||||||
|
actual fun isInNightMode() = try {
|
||||||
|
detector.isDark
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
Log.e(TAG, e.stackTraceToString())
|
||||||
|
/* On Mac this code can produce exception */
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
private val settingsFile =
|
private val settingsFile =
|
||||||
File(desktopPlatform.configPath + File.separator + "settings.properties")
|
File(desktopPlatform.configPath + File.separator + "settings.properties")
|
||||||
@@ -58,3 +71,6 @@ actual fun isRtl(text: CharSequence): Boolean {
|
|||||||
dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC
|
dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual fun ImageResource.toComposeImageBitmap(): ImageBitmap? =
|
||||||
|
image.toComposeImageBitmap()
|
||||||
|
|||||||
+4
@@ -10,6 +10,10 @@ private val detector: OsThemeDetector = OsThemeDetector.getDetector()
|
|||||||
registerListener(::reactOnDarkThemeChanges)
|
registerListener(::reactOnDarkThemeChanges)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: explore possibility to use
|
||||||
|
//@Composable
|
||||||
|
//actual fun isSystemInDarkTheme(): Boolean = androidx.compose.foundation.isSystemInDarkTheme()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun isSystemInDarkTheme(): Boolean = try {
|
actual fun isSystemInDarkTheme(): Boolean = try {
|
||||||
detector.isDark
|
detector.isDark
|
||||||
|
|||||||
-5
@@ -25,11 +25,6 @@ val connections = ArrayList<WebSocket>()
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun ActiveCallView() {
|
actual fun ActiveCallView() {
|
||||||
val endCall = {
|
|
||||||
val call = chatModel.activeCall.value
|
|
||||||
if (call != null) withBGApi { chatModel.callManager.endCall(call) }
|
|
||||||
}
|
|
||||||
BackHandler(onBack = endCall)
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
WebRTCController(chatModel.callCommand) { apiMsg ->
|
WebRTCController(chatModel.callCommand) { apiMsg ->
|
||||||
Log.d(TAG, "received from WebRTCController: $apiMsg")
|
Log.d(TAG, "received from WebRTCController: $apiMsg")
|
||||||
|
|||||||
+6
-18
@@ -4,34 +4,23 @@ import SectionBottomSpacer
|
|||||||
import SectionDividerSpaced
|
import SectionDividerSpaced
|
||||||
import SectionView
|
import SectionView
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.ChatModel
|
||||||
import chat.simplex.common.model.SharedPreference
|
import chat.simplex.common.model.SharedPreference
|
||||||
import chat.simplex.common.platform.ColumnWithScrollBar
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.platform.defaultLocale
|
|
||||||
import chat.simplex.common.ui.theme.ThemeColor
|
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor
|
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) {
|
actual fun AppearanceView(m: ChatModel) {
|
||||||
AppearanceScope.AppearanceLayout(
|
AppearanceScope.AppearanceLayout(
|
||||||
m.controller.appPrefs.appLanguage,
|
m.controller.appPrefs.appLanguage,
|
||||||
m.controller.appPrefs.systemDarkTheme,
|
m.controller.appPrefs.systemDarkTheme,
|
||||||
showSettingsModal = showSettingsModal,
|
|
||||||
editColor = { name, initialColor ->
|
|
||||||
ModalManager.start.showModalCloseable { close ->
|
|
||||||
ColorEditor(name, initialColor, close)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,8 +28,6 @@ actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatMod
|
|||||||
fun AppearanceScope.AppearanceLayout(
|
fun AppearanceScope.AppearanceLayout(
|
||||||
languagePref: SharedPreference<String?>,
|
languagePref: SharedPreference<String?>,
|
||||||
systemDarkTheme: SharedPreference<String?>,
|
systemDarkTheme: SharedPreference<String?>,
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
|
||||||
editColor: (ThemeColor, Color) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
@@ -63,10 +50,11 @@ fun AppearanceScope.AppearanceLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SectionDividerSpaced(maxTopPadding = true)
|
SectionDividerSpaced(maxTopPadding = true)
|
||||||
ProfileImageSection()
|
ThemesSection(systemDarkTheme)
|
||||||
|
|
||||||
SectionDividerSpaced(maxTopPadding = true)
|
SectionDividerSpaced(maxTopPadding = true)
|
||||||
ThemesSection(systemDarkTheme, showSettingsModal, editColor)
|
ProfileImageSection()
|
||||||
|
|
||||||
SectionBottomSpacer()
|
SectionBottomSpacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import java.io.File
|
|||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
initHaskell()
|
initHaskell()
|
||||||
|
runMigrations()
|
||||||
initApp()
|
initApp()
|
||||||
tmpDir.deleteRecursively()
|
tmpDir.deleteRecursively()
|
||||||
tmpDir.mkdir()
|
tmpDir.mkdir()
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ android.enableJetifier=true
|
|||||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||||
kotlin.jvm.target=11
|
kotlin.jvm.target=11
|
||||||
|
|
||||||
android.version_name=5.8-beta.2
|
android.version_name=5.8-beta.3
|
||||||
android.version_code=210
|
android.version_code=214
|
||||||
|
|
||||||
desktop.version_name=5.8-beta.2
|
desktop.version_name=5.8-beta.3
|
||||||
desktop.version_code=47
|
desktop.version_code=49
|
||||||
|
|
||||||
kotlin.version=1.9.23
|
kotlin.version=1.9.23
|
||||||
gradle.plugin.version=8.2.0
|
gradle.plugin.version=8.2.0
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ import Simplex.Chat.Messages.CIContent
|
|||||||
import Simplex.Chat.Protocol (MsgContent (..))
|
import Simplex.Chat.Protocol (MsgContent (..))
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
import Simplex.Chat.Types.Shared
|
import Simplex.Chat.Types.Shared
|
||||||
|
import Simplex.Messaging.Agent.Protocol (AgentErrorType (..))
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Util ((<$?>))
|
import Simplex.Messaging.Protocol (BrokerErrorType (..))
|
||||||
|
import Simplex.Messaging.Util (tshow, (<$?>))
|
||||||
|
|
||||||
data DirectoryEvent
|
data DirectoryEvent
|
||||||
= DEContactConnected Contact
|
= DEContactConnected Contact
|
||||||
@@ -53,6 +55,7 @@ data DirectoryEvent
|
|||||||
| DEItemEditIgnored Contact
|
| DEItemEditIgnored Contact
|
||||||
| DEItemDeleteIgnored Contact
|
| DEItemDeleteIgnored Contact
|
||||||
| DEContactCommand Contact ChatItemId ADirectoryCmd
|
| DEContactCommand Contact ChatItemId ADirectoryCmd
|
||||||
|
| DELogChatResponse Text
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
crDirectoryEvent :: ChatResponse -> Maybe DirectoryEvent
|
crDirectoryEvent :: ChatResponse -> Maybe DirectoryEvent
|
||||||
@@ -77,6 +80,13 @@ crDirectoryEvent = \case
|
|||||||
where
|
where
|
||||||
ciId = chatItemId' ci
|
ciId = chatItemId' ci
|
||||||
err = ADC SDRUser DCUnknownCommand
|
err = ADC SDRUser DCUnknownCommand
|
||||||
|
CRMessageError {severity, errorMessage} -> Just $ DELogChatResponse $ "message error: " <> severity <> ", " <> errorMessage
|
||||||
|
CRChatCmdError {chatError} -> Just $ DELogChatResponse $ "chat cmd error: " <> tshow chatError
|
||||||
|
CRChatError {chatError} -> case chatError of
|
||||||
|
ChatErrorAgent {agentError = BROKER _ NETWORK} -> Nothing
|
||||||
|
ChatErrorAgent {agentError = BROKER _ TIMEOUT} -> Nothing
|
||||||
|
_ -> Just $ DELogChatResponse $ "chat error: " <> tshow chatError
|
||||||
|
CRChatErrors {chatErrors} -> Just $ DELogChatResponse $ "chat errors: " <> T.intercalate ", " (map tshow chatErrors)
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
data DirectoryRole = DRUser | DRSuperUser
|
data DirectoryRole = DRUser | DRSuperUser
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi
|
|||||||
case sUser of
|
case sUser of
|
||||||
SDRUser -> deUserCommand env ct ciId cmd
|
SDRUser -> deUserCommand env ct ciId cmd
|
||||||
SDRSuperUser -> deSuperUserCommand ct ciId cmd
|
SDRSuperUser -> deSuperUserCommand ct ciId cmd
|
||||||
|
DELogChatResponse r -> logInfo r
|
||||||
where
|
where
|
||||||
withSuperUsers action = void . forkIO $ forM_ superUsers $ \KnownContact {contactId} -> action contactId
|
withSuperUsers action = void . forkIO $ forM_ superUsers $ \KnownContact {contactId} -> action contactId
|
||||||
notifySuperUsers s = withSuperUsers $ \contactId -> sendMessage' cc contactId s
|
notifySuperUsers s = withSuperUsers $ \contactId -> sendMessage' cc contactId s
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ import Data.Text (Text)
|
|||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Util (ifM)
|
import Simplex.Messaging.Util (ifM)
|
||||||
import System.IO (Handle, IOMode (..), openFile, BufferMode (..), hSetBuffering)
|
import System.Directory (doesFileExist, renameFile)
|
||||||
import System.Directory (renameFile, doesFileExist)
|
import System.IO (BufferMode (..), Handle, IOMode (..), hSetBuffering, openFile)
|
||||||
|
|
||||||
data DirectoryStore = DirectoryStore
|
data DirectoryStore = DirectoryStore
|
||||||
{ groupRegs :: TVar [GroupReg],
|
{ groupRegs :: TVar [GroupReg],
|
||||||
@@ -112,7 +112,7 @@ addGroupReg st ct GroupInfo {groupId} grStatus = do
|
|||||||
let ugrId = 1 + foldl' maxUgrId 0 grs
|
let ugrId = 1 + foldl' maxUgrId 0 grs
|
||||||
grData' = grData {userGroupRegId_ = ugrId}
|
grData' = grData {userGroupRegId_ = ugrId}
|
||||||
gr' = gr {userGroupRegId = ugrId}
|
gr' = gr {userGroupRegId = ugrId}
|
||||||
in (grData', gr' : grs)
|
in (grData', gr' : grs)
|
||||||
ctId = contactId' ct
|
ctId = contactId' ct
|
||||||
maxUgrId mx GroupReg {dbContactId, userGroupRegId}
|
maxUgrId mx GroupReg {dbContactId, userGroupRegId}
|
||||||
| dbContactId == ctId && userGroupRegId > mx = userGroupRegId
|
| dbContactId == ctId && userGroupRegId > mx = userGroupRegId
|
||||||
@@ -311,14 +311,15 @@ readDirectoryData f =
|
|||||||
Right r -> case r of
|
Right r -> case r of
|
||||||
GRCreate gr@GroupRegData {dbGroupId_ = gId} -> do
|
GRCreate gr@GroupRegData {dbGroupId_ = gId} -> do
|
||||||
when (isJust $ M.lookup gId m) $
|
when (isJust $ M.lookup gId m) $
|
||||||
putStrLn $ "Warning: duplicate group with ID " <> show gId <> ", group replaced."
|
putStrLn $
|
||||||
|
"Warning: duplicate group with ID " <> show gId <> ", group replaced."
|
||||||
pure $ M.insert gId gr m
|
pure $ M.insert gId gr m
|
||||||
GRUpdateStatus gId groupRegStatus_ -> case M.lookup gId m of
|
GRUpdateStatus gId groupRegStatus_ -> case M.lookup gId m of
|
||||||
Just gr -> pure $ M.insert gId gr {groupRegStatus_} m
|
Just gr -> pure $ M.insert gId gr {groupRegStatus_} m
|
||||||
Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <>", status update ignored.")
|
Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <> ", status update ignored.")
|
||||||
GRUpdateOwner gId grOwnerId -> case M.lookup gId m of
|
GRUpdateOwner gId grOwnerId -> case M.lookup gId m of
|
||||||
Just gr -> pure $ M.insert gId gr {dbOwnerMemberId_ = Just grOwnerId} m
|
Just gr -> pure $ M.insert gId gr {dbOwnerMemberId_ = Just grOwnerId} m
|
||||||
Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <>", owner update ignored.")
|
Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <> ", owner update ignored.")
|
||||||
|
|
||||||
writeDirectoryData :: FilePath -> [GroupRegData] -> IO Handle
|
writeDirectoryData :: FilePath -> [GroupRegData] -> IO Handle
|
||||||
writeDirectoryData f grs = do
|
writeDirectoryData f grs = do
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
|||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/simplex-chat/simplexmq.git
|
location: https://github.com/simplex-chat/simplexmq.git
|
||||||
tag: e3f5d244c1a435593e33adc023bf1f920f379f8d
|
tag: bd67844169d2206d8543c01e6ed966315115b0e3
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
|
|||||||
+22
-3
@@ -1,5 +1,5 @@
|
|||||||
name: simplex-chat
|
name: simplex-chat
|
||||||
version: 5.8.0.2
|
version: 5.8.0.3
|
||||||
#synopsis:
|
#synopsis:
|
||||||
#description:
|
#description:
|
||||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||||
@@ -152,12 +152,31 @@ tests:
|
|||||||
ghc-options:
|
ghc-options:
|
||||||
# - -haddock
|
# - -haddock
|
||||||
- -O2
|
- -O2
|
||||||
- -Wall
|
- -Weverything
|
||||||
|
- -Wno-missing-exported-signatures
|
||||||
|
- -Wno-missing-import-lists
|
||||||
|
- -Wno-missed-specialisations
|
||||||
|
- -Wno-all-missed-specialisations
|
||||||
|
- -Wno-unsafe
|
||||||
|
- -Wno-safe
|
||||||
|
- -Wno-missing-local-signatures
|
||||||
|
- -Wno-missing-kind-signatures
|
||||||
|
- -Wno-missing-deriving-strategies
|
||||||
|
- -Wno-monomorphism-restriction
|
||||||
|
- -Wno-prepositive-qualified-module
|
||||||
|
- -Wno-unused-packages
|
||||||
|
- -Wno-implicit-prelude
|
||||||
|
- -Wno-missing-safe-haskell-mode
|
||||||
|
- -Wno-missing-export-lists
|
||||||
|
- -Wno-partial-fields
|
||||||
- -Wcompat
|
- -Wcompat
|
||||||
|
- -Werror=incomplete-record-updates
|
||||||
- -Werror=incomplete-patterns
|
- -Werror=incomplete-patterns
|
||||||
|
- -Werror=missing-methods
|
||||||
|
- -Werror=incomplete-uni-patterns
|
||||||
|
- -Werror=tabs
|
||||||
- -Wredundant-constraints
|
- -Wredundant-constraints
|
||||||
- -Wincomplete-record-updates
|
- -Wincomplete-record-updates
|
||||||
- -Wincomplete-uni-patterns
|
|
||||||
- -Wunused-type-patterns
|
- -Wunused-type-patterns
|
||||||
|
|
||||||
default-extensions:
|
default-extensions:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"https://github.com/simplex-chat/simplexmq.git"."e3f5d244c1a435593e33adc023bf1f920f379f8d" = "1klin78kgvgzdvf64nahn3280m7hw5f8wzrca43cmyajm2qp3wfs";
|
"https://github.com/simplex-chat/simplexmq.git"."bd67844169d2206d8543c01e6ed966315115b0e3" = "1g218q15hrg21h8gyidavfys5zx8dzmxq7iwfm5bfaw71grpd7pn";
|
||||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user