mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e67feeea22 | |||
| da65474452 | |||
| d0a7e14a96 | |||
| bd4745775d | |||
| af144c6208 | |||
| 74206a947b | |||
| 457e12880c | |||
| 2d588949b1 | |||
| 84f7f901ea | |||
| 90ed503ee0 | |||
| 28105038d4 | |||
| fd60a2402a | |||
| ef0f21a11c | |||
| d0e40c1f1d | |||
| 8396e70e7b | |||
| 6128a24869 | |||
| 0329a6a7d3 | |||
| 2da89c2cf1 | |||
| 601ddf97ce | |||
| 128d031ced | |||
| 3b0205b25f | |||
| d4a47f1cce | |||
| 2998e3af3f | |||
| 5ff838d63e | |||
| bdaec30fa0 | |||
| 5a8bf9106e | |||
| 97df069730 | |||
| 9a1166f097 | |||
| 7a741e7ac4 | |||
| 165143a111 | |||
| ceb17b23b4 | |||
| 3c8c9d8b52 | |||
| 4162bccc46 | |||
| 24090fe350 | |||
| 37b78edb91 |
@@ -131,10 +131,6 @@ jobs:
|
|||||||
echo " extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include" >> cabal.project.local
|
echo " extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include" >> cabal.project.local
|
||||||
echo " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local
|
echo " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local
|
||||||
echo " flags: +openssl" >> cabal.project.local
|
echo " flags: +openssl" >> cabal.project.local
|
||||||
echo "" >> cabal.project.local
|
|
||||||
echo "package rocksdb-haskell-jprupp" >> cabal.project.local
|
|
||||||
echo " extra-include-dirs: /opt/homebrew/opt/rocksdb/include" >> cabal.project.local
|
|
||||||
echo " extra-lib-dirs: /opt/homebrew/opt/rocksdb/lib" >> cabal.project.local
|
|
||||||
|
|
||||||
- name: Unix prepare cabal.project.local for Mac
|
- name: Unix prepare cabal.project.local for Mac
|
||||||
if: matrix.os == 'macos-13'
|
if: matrix.os == 'macos-13'
|
||||||
@@ -149,22 +145,14 @@ jobs:
|
|||||||
echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local
|
echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local
|
||||||
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
|
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
|
||||||
echo " flags: +openssl" >> cabal.project.local
|
echo " flags: +openssl" >> cabal.project.local
|
||||||
echo "" >> cabal.project.local
|
|
||||||
echo "package rocksdb-haskell-jprupp" >> cabal.project.local
|
|
||||||
echo " extra-include-dirs: /opt/homebrew/opt/rocksdb/include" >> cabal.project.local
|
|
||||||
echo " extra-lib-dirs: /opt/homebrew/opt/rocksdb/lib" >> cabal.project.local
|
|
||||||
|
|
||||||
- name: Install AppImage dependencies
|
- name: Install AppImage dependencies
|
||||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04'
|
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04'
|
||||||
run: sudo apt install -y desktop-file-utils
|
run: sudo apt install -y desktop-file-utils
|
||||||
|
|
||||||
- name: Install package dependencies
|
|
||||||
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04'
|
|
||||||
run: sudo apt install -y librocksdb-dev
|
|
||||||
|
|
||||||
- name: Install pkg-config for Mac
|
- name: Install pkg-config for Mac
|
||||||
if: matrix.os == 'macos-latest' || matrix.os == 'macos-13'
|
if: matrix.os == 'macos-latest' || matrix.os == 'macos-13'
|
||||||
run: brew install pkg-config rocksdb
|
run: brew install pkg-config
|
||||||
|
|
||||||
- name: Unix prepare cabal.project.local for Ubuntu
|
- name: Unix prepare cabal.project.local for Ubuntu
|
||||||
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04'
|
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04'
|
||||||
@@ -334,7 +322,6 @@ jobs:
|
|||||||
git
|
git
|
||||||
perl
|
perl
|
||||||
make
|
make
|
||||||
mingw-w64-x86_64-rocksdb
|
|
||||||
pacboy: >-
|
pacboy: >-
|
||||||
toolchain:p
|
toolchain:p
|
||||||
cmake:p
|
cmake:p
|
||||||
@@ -354,10 +341,6 @@ jobs:
|
|||||||
echo " flags: +openssl" >> cabal.project.local
|
echo " flags: +openssl" >> cabal.project.local
|
||||||
echo " extra-include-dirs: $openssl_windows_style_path\include" >> cabal.project.local
|
echo " extra-include-dirs: $openssl_windows_style_path\include" >> cabal.project.local
|
||||||
echo " extra-lib-dirs: $openssl_windows_style_path" >> cabal.project.local
|
echo " extra-lib-dirs: $openssl_windows_style_path" >> cabal.project.local
|
||||||
echo "" >> cabal.project.local
|
|
||||||
echo "package rocksdb-haskell-jprupp" >> cabal.project.local
|
|
||||||
echo " extra-include-dirs: /mingw64/include" >> cabal.project.local
|
|
||||||
echo " extra-lib-dirs: /mingw64/lib" >> cabal.project.local
|
|
||||||
|
|
||||||
rm -rf dist-newstyle/src/direct-sq*
|
rm -rf dist-newstyle/src/direct-sq*
|
||||||
sed -i "s/, unix /--, unix /" simplex-chat.cabal
|
sed -i "s/, unix /--, unix /" simplex-chat.cabal
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ RUN cp ./scripts/cabal.project.local.linux ./cabal.project.local
|
|||||||
|
|
||||||
# Compile simplex-chat
|
# Compile simplex-chat
|
||||||
RUN cabal update
|
RUN cabal update
|
||||||
RUN cabal build exe:simplex-chat
|
RUN cabal build exe:simplex-chat --constraint 'simplexmq +client_library'
|
||||||
|
|
||||||
# Strip the binary from debug symbols to reduce size
|
# Strip the binary from debug symbols to reduce size
|
||||||
RUN bin=$(find /project/dist-newstyle -name "simplex-chat" -type f -executable) && \
|
RUN bin=$(find /project/dist-newstyle -name "simplex-chat" -type f -executable) && \
|
||||||
|
|||||||
@@ -149,9 +149,9 @@
|
|||||||
6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; };
|
6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; };
|
||||||
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; };
|
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; };
|
||||||
643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B402CCBEB080083A2CF /* libgmpxx.a */; };
|
643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B402CCBEB080083A2CF /* libgmpxx.a */; };
|
||||||
643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */; };
|
643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a */; };
|
||||||
643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B422CCBEB080083A2CF /* libffi.a */; };
|
643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B422CCBEB080083A2CF /* libffi.a */; };
|
||||||
643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */; };
|
643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a */; };
|
||||||
643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B442CCBEB080083A2CF /* libgmp.a */; };
|
643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B442CCBEB080083A2CF /* libgmp.a */; };
|
||||||
6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; };
|
6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; };
|
||||||
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; };
|
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; };
|
||||||
@@ -492,9 +492,9 @@
|
|||||||
6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = "<group>"; };
|
6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = "<group>"; };
|
||||||
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = "<group>"; };
|
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = "<group>"; };
|
||||||
643B3B402CCBEB080083A2CF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = "<group>"; };
|
643B3B402CCBEB080083A2CF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = "<group>"; };
|
||||||
643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; sourceTree = "<group>"; };
|
643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||||
643B3B422CCBEB080083A2CF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = "<group>"; };
|
643B3B422CCBEB080083A2CF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = "<group>"; };
|
||||||
643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; sourceTree = "<group>"; };
|
643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a"; sourceTree = "<group>"; };
|
||||||
643B3B442CCBEB080083A2CF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = "<group>"; };
|
643B3B442CCBEB080083A2CF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = "<group>"; };
|
||||||
6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = "<group>"; };
|
6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = "<group>"; };
|
||||||
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = "<group>"; };
|
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = "<group>"; };
|
||||||
@@ -663,8 +663,8 @@
|
|||||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||||
643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */,
|
643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a in Frameworks */,
|
||||||
643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */,
|
643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -815,8 +815,8 @@
|
|||||||
643B3B422CCBEB080083A2CF /* libffi.a */,
|
643B3B422CCBEB080083A2CF /* libffi.a */,
|
||||||
643B3B442CCBEB080083A2CF /* libgmp.a */,
|
643B3B442CCBEB080083A2CF /* libgmp.a */,
|
||||||
643B3B402CCBEB080083A2CF /* libgmpxx.a */,
|
643B3B402CCBEB080083A2CF /* libgmpxx.a */,
|
||||||
643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */,
|
643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a */,
|
||||||
643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */,
|
643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a */,
|
||||||
5CA059C2279559F40002BEB4 /* Shared */,
|
5CA059C2279559F40002BEB4 /* Shared */,
|
||||||
5CDCAD462818589900503DA2 /* SimpleX NSE */,
|
5CDCAD462818589900503DA2 /* SimpleX NSE */,
|
||||||
CEE723A82C3BD3D70009AE93 /* SimpleX SE */,
|
CEE723A82C3BD3D70009AE93 /* SimpleX SE */,
|
||||||
@@ -912,10 +912,9 @@
|
|||||||
5CB924DF27A8678B00ACCCDD /* UserSettings */ = {
|
5CB924DF27A8678B00ACCCDD /* UserSettings */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
643B3B4C2CCFD34B0083A2CF /* NetworkAndServers */,
|
||||||
5CB924D627A8563F00ACCCDD /* SettingsView.swift */,
|
5CB924D627A8563F00ACCCDD /* SettingsView.swift */,
|
||||||
5CB346E62868D76D001FD2EF /* NotificationsView.swift */,
|
5CB346E62868D76D001FD2EF /* NotificationsView.swift */,
|
||||||
5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */,
|
|
||||||
5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */,
|
|
||||||
5CADE79929211BB900072E13 /* PreferencesView.swift */,
|
5CADE79929211BB900072E13 /* PreferencesView.swift */,
|
||||||
5C5DB70D289ABDD200730FFF /* AppearanceSettings.swift */,
|
5C5DB70D289ABDD200730FFF /* AppearanceSettings.swift */,
|
||||||
5C05DF522840AA1D00C683F9 /* CallSettings.swift */,
|
5C05DF522840AA1D00C683F9 /* CallSettings.swift */,
|
||||||
@@ -923,9 +922,6 @@
|
|||||||
5CC036DF29C488D500C0EF20 /* HiddenProfileView.swift */,
|
5CC036DF29C488D500C0EF20 /* HiddenProfileView.swift */,
|
||||||
5C577F7C27C83AA10006112D /* MarkdownHelp.swift */,
|
5C577F7C27C83AA10006112D /* MarkdownHelp.swift */,
|
||||||
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */,
|
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */,
|
||||||
5C93292E29239A170090FFF9 /* ProtocolServersView.swift */,
|
|
||||||
5C93293029239BED0090FFF9 /* ProtocolServerView.swift */,
|
|
||||||
5C9329402929248A0090FFF9 /* ScanProtocolServer.swift */,
|
|
||||||
5CB2084E28DA4B4800D024EC /* RTCServers.swift */,
|
5CB2084E28DA4B4800D024EC /* RTCServers.swift */,
|
||||||
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */,
|
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */,
|
||||||
18415845648CA4F5A8BCA272 /* UserProfilesView.swift */,
|
18415845648CA4F5A8BCA272 /* UserProfilesView.swift */,
|
||||||
@@ -1056,6 +1052,18 @@
|
|||||||
path = Database;
|
path = Database;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
643B3B4C2CCFD34B0083A2CF /* NetworkAndServers */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5C9329402929248A0090FFF9 /* ScanProtocolServer.swift */,
|
||||||
|
5C93293029239BED0090FFF9 /* ProtocolServerView.swift */,
|
||||||
|
5C93292E29239A170090FFF9 /* ProtocolServersView.swift */,
|
||||||
|
5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */,
|
||||||
|
5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */,
|
||||||
|
);
|
||||||
|
path = NetworkAndServers;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
6440CA01288AEC770062C672 /* Group */ = {
|
6440CA01288AEC770062C672 /* Group */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.*
|
import android.os.*
|
||||||
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.ui.platform.ClipboardManager
|
import androidx.compose.ui.platform.ClipboardManager
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import chat.simplex.app.model.NtfManager
|
import chat.simplex.app.model.NtfManager
|
||||||
@@ -13,7 +15,6 @@ import chat.simplex.app.model.NtfManager.getUserIdFromIntent
|
|||||||
import chat.simplex.common.*
|
import chat.simplex.common.*
|
||||||
import chat.simplex.common.helpers.*
|
import chat.simplex.common.helpers.*
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.chatlist.*
|
import chat.simplex.common.views.chatlist.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
@@ -24,13 +25,21 @@ import kotlinx.coroutines.*
|
|||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
class MainActivity: FragmentActivity() {
|
class MainActivity: FragmentActivity() {
|
||||||
|
companion object {
|
||||||
|
const val OLD_ANDROID_UI_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
mainActivity = WeakReference(this)
|
mainActivity = WeakReference(this)
|
||||||
platform.androidSetNightModeIfSupported()
|
platform.androidSetNightModeIfSupported()
|
||||||
val c = CurrentColors.value.colors
|
val c = CurrentColors.value.colors
|
||||||
platform.androidSetStatusAndNavBarColors(c.isLight, c.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get())
|
platform.androidSetStatusAndNavigationBarAppearance(c.isLight, c.isLight)
|
||||||
applyAppLocale(ChatModel.controller.appPrefs.appLanguage)
|
applyAppLocale(ChatModel.controller.appPrefs.appLanguage)
|
||||||
|
// This flag makes status bar and navigation bar fully transparent. But on API level < 30 it breaks insets entirely
|
||||||
|
// https://issuetracker.google.com/issues/236862874
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||||
|
}
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// testJson()
|
// testJson()
|
||||||
// When call ended and orientation changes, it re-process old intent, it's unneeded.
|
// When call ended and orientation changes, it re-process old intent, it's unneeded.
|
||||||
@@ -47,6 +56,7 @@ class MainActivity: FragmentActivity() {
|
|||||||
WindowManager.LayoutParams.FLAG_SECURE
|
WindowManager.LayoutParams.FLAG_SECURE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
AppScreen()
|
AppScreen()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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.os.*
|
import android.os.*
|
||||||
|
import android.view.View
|
||||||
import androidx.compose.animation.core.*
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -16,6 +17,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
|
import chat.simplex.app.MainActivity.Companion.OLD_ANDROID_UI_FLAGS
|
||||||
import chat.simplex.app.model.NtfManager
|
import chat.simplex.app.model.NtfManager
|
||||||
import chat.simplex.app.model.NtfManager.AcceptCallAction
|
import chat.simplex.app.model.NtfManager.AcceptCallAction
|
||||||
import chat.simplex.app.views.call.CallActivity
|
import chat.simplex.app.views.call.CallActivity
|
||||||
@@ -26,7 +28,6 @@ import chat.simplex.common.model.ChatModel.withChats
|
|||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.call.*
|
import chat.simplex.common.views.call.*
|
||||||
import chat.simplex.common.views.chatlist.statusBarColorAfterCall
|
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
import chat.simplex.common.views.onboarding.OnboardingStage
|
||||||
import com.jakewharton.processphoenix.ProcessPhoenix
|
import com.jakewharton.processphoenix.ProcessPhoenix
|
||||||
@@ -274,79 +275,32 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
uiModeManager.setApplicationNightMode(mode)
|
uiModeManager.setApplicationNightMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun androidSetDrawerStatusAndNavBarColor(
|
override fun androidSetStatusAndNavigationBarAppearance(isLightStatusBar: Boolean, isLightNavBar: Boolean, blackNavBar: Boolean, themeBackgroundColor: Color) {
|
||||||
isLight: Boolean,
|
|
||||||
drawerShadingColor: Color,
|
|
||||||
toolbarOnTop: Boolean,
|
|
||||||
navBarColor: Color,
|
|
||||||
) {
|
|
||||||
val window = mainActivity.get()?.window ?: return
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
val windowInsetController = ViewCompat.getWindowInsetsController(window.decorView)
|
|
||||||
// Blend status bar color to the animated color
|
|
||||||
val colors = CurrentColors.value.colors
|
|
||||||
val baseBackgroundColor = if (toolbarOnTop) colors.background.mixWith(colors.onBackground, 0.97f) else colors.background
|
|
||||||
var statusBar = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb()
|
|
||||||
var statusBarLight = isLight
|
|
||||||
|
|
||||||
// SimplexGreen while in call
|
|
||||||
if (window.statusBarColor == SimplexGreen.toArgb()) {
|
|
||||||
statusBarColorAfterCall.intValue = statusBar
|
|
||||||
statusBar = SimplexGreen.toArgb()
|
|
||||||
statusBarLight = false
|
|
||||||
}
|
|
||||||
window.statusBarColor = statusBar
|
|
||||||
val navBar = navBarColor.toArgb()
|
|
||||||
if (windowInsetController?.isAppearanceLightStatusBars != statusBarLight) {
|
|
||||||
windowInsetController?.isAppearanceLightStatusBars = statusBarLight
|
|
||||||
}
|
|
||||||
if (window.navigationBarColor != navBar) {
|
|
||||||
window.navigationBarColor = navBar
|
|
||||||
}
|
|
||||||
if (windowInsetController?.isAppearanceLightNavigationBars != isLight) {
|
|
||||||
windowInsetController?.isAppearanceLightNavigationBars = isLight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun androidSetStatusAndNavBarColors(isLight: Boolean, backgroundColor: Color, hasTop: Boolean, hasBottom: Boolean) {
|
|
||||||
val window = mainActivity.get()?.window ?: return
|
val window = mainActivity.get()?.window ?: return
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
|
val statusLight = isLightStatusBar && chatModel.activeCall.value == null
|
||||||
|
val navBarLight = isLightNavBar || windowOrientation() == WindowOrientation.LANDSCAPE
|
||||||
val windowInsetController = ViewCompat.getWindowInsetsController(window.decorView)
|
val windowInsetController = ViewCompat.getWindowInsetsController(window.decorView)
|
||||||
|
if (windowInsetController?.isAppearanceLightStatusBars != statusLight) {
|
||||||
var statusBar = (if (hasTop && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) {
|
windowInsetController?.isAppearanceLightStatusBars = statusLight
|
||||||
backgroundColor.mixWith(CurrentColors.value.colors.onBackground, 0.97f)
|
}
|
||||||
|
window.navigationBarColor = Color.Transparent.toArgb()
|
||||||
|
if (windowInsetController?.isAppearanceLightNavigationBars != navBarLight) {
|
||||||
|
windowInsetController?.isAppearanceLightNavigationBars = navBarLight
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
window.decorView.systemUiVisibility = if (statusLight && navBarLight) {
|
||||||
|
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or OLD_ANDROID_UI_FLAGS
|
||||||
|
} else if (statusLight) {
|
||||||
|
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or OLD_ANDROID_UI_FLAGS
|
||||||
|
} else if (navBarLight) {
|
||||||
|
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or OLD_ANDROID_UI_FLAGS
|
||||||
} else {
|
} else {
|
||||||
if (CurrentColors.value.base == DefaultTheme.SIMPLEX) {
|
OLD_ANDROID_UI_FLAGS
|
||||||
backgroundColor.lighter(0.4f)
|
}
|
||||||
|
window.navigationBarColor = if (blackNavBar) Color.Black.toArgb() else themeBackgroundColor.toArgb()
|
||||||
} else {
|
} else {
|
||||||
backgroundColor
|
window.navigationBarColor = Color.Transparent.toArgb()
|
||||||
}
|
|
||||||
}).toArgb()
|
|
||||||
var statusBarLight = isLight
|
|
||||||
|
|
||||||
// SimplexGreen while in call
|
|
||||||
if (window.statusBarColor == SimplexGreen.toArgb()) {
|
|
||||||
statusBarColorAfterCall.intValue = statusBar
|
|
||||||
statusBar = SimplexGreen.toArgb()
|
|
||||||
statusBarLight = false
|
|
||||||
}
|
|
||||||
val navBar = (if (hasBottom && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) {
|
|
||||||
backgroundColor.mixWith(CurrentColors.value.colors.onBackground, 0.97f)
|
|
||||||
} else {
|
|
||||||
backgroundColor
|
|
||||||
}).toArgb()
|
|
||||||
if (window.statusBarColor != statusBar) {
|
|
||||||
window.statusBarColor = statusBar
|
|
||||||
}
|
|
||||||
if (windowInsetController?.isAppearanceLightStatusBars != statusBarLight) {
|
|
||||||
windowInsetController?.isAppearanceLightStatusBars = statusBarLight
|
|
||||||
}
|
|
||||||
if (window.navigationBarColor != navBar) {
|
|
||||||
window.navigationBarColor = navBar
|
|
||||||
}
|
|
||||||
if (windowInsetController?.isAppearanceLightNavigationBars != isLight) {
|
|
||||||
windowInsetController?.isAppearanceLightNavigationBars = isLight
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,6 +355,8 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val androidApiLevel: Int get() = Build.VERSION.SDK_INT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ kotlin {
|
|||||||
implementation("androidx.activity:activity-compose:1.9.1")
|
implementation("androidx.activity:activity-compose:1.9.1")
|
||||||
val workVersion = "2.9.1"
|
val workVersion = "2.9.1"
|
||||||
implementation("androidx.work:work-runtime-ktx:$workVersion")
|
implementation("androidx.work:work-runtime-ktx:$workVersion")
|
||||||
implementation("com.google.accompanist:accompanist-insets:0.30.1")
|
|
||||||
|
|
||||||
// Video support
|
// Video support
|
||||||
implementation("com.google.android.exoplayer:exoplayer:2.19.1")
|
implementation("com.google.android.exoplayer:exoplayer:2.19.1")
|
||||||
|
|||||||
+1
-13
@@ -3,25 +3,13 @@ package chat.simplex.common.platform
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import com.google.accompanist.insets.navigationBarsWithImePadding
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
actual fun Modifier.navigationBarsWithImePadding(): Modifier = navigationBarsWithImePadding()
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
actual fun ProvideWindowInsets(
|
|
||||||
consumeWindowInsets: Boolean,
|
|
||||||
windowInsetsAnimationsEnabled: Boolean,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
com.google.accompanist.insets.ProvideWindowInsets(content = content)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun Modifier.desktopOnExternalDrag(
|
actual fun Modifier.desktopOnExternalDrag(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
onFiles: (List<File>) -> Unit,
|
onFiles: (List<File>) -> Unit,
|
||||||
onImage: (Painter) -> Unit,
|
onImage: (File) -> Unit,
|
||||||
onText: (String) -> Unit
|
onText: (String) -> Unit
|
||||||
): Modifier = this
|
): Modifier = this
|
||||||
|
|
||||||
|
|||||||
+16
-5
@@ -6,8 +6,7 @@ import android.graphics.drawable.ColorDrawable
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.OnReceiveContentListener
|
import android.view.*
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.inputmethod.*
|
import android.view.inputmethod.*
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
@@ -26,6 +25,7 @@ import androidx.compose.ui.unit.LayoutDirection
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.graphics.drawable.DrawableCompat
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
|
import androidx.core.view.children
|
||||||
import androidx.core.view.inputmethod.EditorInfoCompat
|
import androidx.core.view.inputmethod.EditorInfoCompat
|
||||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
import androidx.core.view.inputmethod.InputConnectionCompat
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
@@ -94,8 +94,8 @@ actual fun PlatformTextField(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||||
AndroidView(modifier = Modifier, factory = {
|
AndroidView(modifier = Modifier, factory = { context ->
|
||||||
val editText = @SuppressLint("AppCompatCustomView") object: EditText(it) {
|
val editText = @SuppressLint("AppCompatCustomView") object: EditText(context) {
|
||||||
override fun setOnReceiveContentListener(
|
override fun setOnReceiveContentListener(
|
||||||
mimeTypes: Array<out String>?,
|
mimeTypes: Array<out String>?,
|
||||||
listener: OnReceiveContentListener?
|
listener: OnReceiveContentListener?
|
||||||
@@ -140,6 +140,13 @@ actual fun PlatformTextField(
|
|||||||
Log.e(TAG, e.stackTraceToString())
|
Log.e(TAG, e.stackTraceToString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
editText.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
|
||||||
|
// shows keyboard when user had search field on ChatView focused before clicking on this text field
|
||||||
|
// it still produce weird animation of closing/opening keyboard but the solution is to replace this Android EditText with Compose BasicTextField
|
||||||
|
if (hasFocus) {
|
||||||
|
showKeyboard = true
|
||||||
|
}
|
||||||
|
}
|
||||||
editText.doOnTextChanged { text, _, _, _ ->
|
editText.doOnTextChanged { text, _, _, _ ->
|
||||||
if (!composeState.value.inProgress) {
|
if (!composeState.value.inProgress) {
|
||||||
onMessageChange(text.toString())
|
onMessageChange(text.toString())
|
||||||
@@ -148,8 +155,12 @@ actual fun PlatformTextField(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
editText.doAfterTextChanged { text -> if (composeState.value.preview is ComposePreview.VoicePreview && text.toString() != "") editText.setText("") }
|
editText.doAfterTextChanged { text -> if (composeState.value.preview is ComposePreview.VoicePreview && text.toString() != "") editText.setText("") }
|
||||||
editText
|
val workaround = WorkaroundFocusSearchLayout(context)
|
||||||
|
workaround.addView(editText)
|
||||||
|
workaround.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
workaround
|
||||||
}) {
|
}) {
|
||||||
|
val it = it.children.first() as EditText
|
||||||
it.setTextColor(textColor.toArgb())
|
it.setTextColor(textColor.toArgb())
|
||||||
it.setHintTextColor(hintColor.toArgb())
|
it.setHintTextColor(hintColor.toArgb())
|
||||||
it.hint = placeholder
|
it.hint = placeholder
|
||||||
|
|||||||
+7
-3
@@ -6,11 +6,11 @@ import android.content.Context
|
|||||||
import android.content.SharedPreferences
|
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.foundation.layout.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.*
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
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
|
||||||
@@ -50,7 +50,11 @@ actual fun windowOrientation(): WindowOrientation = when (mainActivity.get()?.re
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun windowWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp
|
actual fun windowWidth(): Dp {
|
||||||
|
val direction = LocalLayoutDirection.current
|
||||||
|
val cutout = WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||||
|
return LocalConfiguration.current.screenWidthDp.dp - cutout.calculateStartPadding(direction) - cutout.calculateEndPadding(direction)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun windowHeight(): Dp = LocalConfiguration.current.screenHeightDp.dp
|
actual fun windowHeight(): Dp = LocalConfiguration.current.screenHeightDp.dp
|
||||||
|
|||||||
+118
-19
@@ -7,10 +7,12 @@ import androidx.compose.foundation.lazy.*
|
|||||||
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
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
|
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||||
|
import chat.simplex.common.views.chatlist.NavigationBarBackground
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
@@ -25,25 +27,74 @@ actual fun LazyColumnWithScrollBar(
|
|||||||
horizontalAlignment: Alignment.Horizontal,
|
horizontalAlignment: Alignment.Horizontal,
|
||||||
flingBehavior: FlingBehavior,
|
flingBehavior: FlingBehavior,
|
||||||
userScrollEnabled: Boolean,
|
userScrollEnabled: Boolean,
|
||||||
|
additionalBarOffset: State<Dp>?,
|
||||||
|
fillMaxSize: Boolean,
|
||||||
content: LazyListScope.() -> Unit
|
content: LazyListScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
val state = state ?: LocalAppBarHandler.current?.listState ?: rememberLazyListState()
|
val handler = LocalAppBarHandler.current
|
||||||
val connection = LocalAppBarHandler.current?.connection
|
require(handler != null) { "Using LazyColumnWithScrollBar and without AppBarHandler is an error. Use LazyColumnWithScrollBarNoAppBar instead" }
|
||||||
|
|
||||||
|
val state = state ?: handler.listState
|
||||||
|
val connection = handler.connection
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
if (reverseLayout) {
|
||||||
|
snapshotFlow { state.layoutInfo.visibleItemsInfo.lastOrNull()?.offset ?: 0 }
|
||||||
|
.collect { scrollPosition ->
|
||||||
|
connection.appBarOffset = if (state.layoutInfo.visibleItemsInfo.lastOrNull()?.index == state.layoutInfo.totalItemsCount - 1) {
|
||||||
|
state.layoutInfo.viewportEndOffset - scrollPosition.toFloat() - state.layoutInfo.afterContentPadding
|
||||||
|
} else {
|
||||||
|
// show always when last item is not visible
|
||||||
|
-1000f
|
||||||
|
}
|
||||||
|
//Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
snapshotFlow { state.firstVisibleItemScrollOffset }
|
snapshotFlow { state.firstVisibleItemScrollOffset }
|
||||||
.filter { state.firstVisibleItemIndex == 0 }
|
.filter { state.firstVisibleItemIndex == 0 }
|
||||||
.collect { scrollPosition ->
|
.collect { scrollPosition ->
|
||||||
val offset = connection?.appBarOffset
|
val offset = connection.appBarOffset
|
||||||
if (offset != null && (offset + scrollPosition).absoluteValue > 1) {
|
if ((offset + scrollPosition + state.layoutInfo.afterContentPadding).absoluteValue > 1) {
|
||||||
connection.appBarOffset = -scrollPosition.toFloat()
|
connection.appBarOffset = -scrollPosition.toFloat()
|
||||||
//Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}")
|
//Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (connection != null) {
|
}
|
||||||
LazyColumn(modifier.nestedScroll(connection), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
|
LazyColumn(
|
||||||
|
if (fillMaxSize) {
|
||||||
|
Modifier.fillMaxSize().copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).then(modifier).nestedScroll(connection)
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
|
Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).then(modifier).nestedScroll(connection)
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
contentPadding,
|
||||||
|
reverseLayout,
|
||||||
|
verticalArrangement,
|
||||||
|
horizontalAlignment,
|
||||||
|
flingBehavior,
|
||||||
|
userScrollEnabled
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun LazyColumnWithScrollBarNoAppBar(
|
||||||
|
modifier: Modifier,
|
||||||
|
state: LazyListState?,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
reverseLayout: Boolean,
|
||||||
|
verticalArrangement: Arrangement.Vertical,
|
||||||
|
horizontalAlignment: Alignment.Horizontal,
|
||||||
|
flingBehavior: FlingBehavior,
|
||||||
|
userScrollEnabled: Boolean,
|
||||||
|
additionalBarOffset: State<Dp>?,
|
||||||
|
content: LazyListScope.() -> Unit
|
||||||
|
) {
|
||||||
|
val state = state ?: rememberLazyListState()
|
||||||
|
LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled) {
|
||||||
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,32 +105,80 @@ actual fun ColumnWithScrollBar(
|
|||||||
horizontalAlignment: Alignment.Horizontal,
|
horizontalAlignment: Alignment.Horizontal,
|
||||||
state: ScrollState?,
|
state: ScrollState?,
|
||||||
maxIntrinsicSize: Boolean,
|
maxIntrinsicSize: Boolean,
|
||||||
|
fillMaxSize: Boolean,
|
||||||
content: @Composable() (ColumnScope.() -> Unit)
|
content: @Composable() (ColumnScope.() -> Unit)
|
||||||
) {
|
) {
|
||||||
val state = state ?: LocalAppBarHandler.current?.scrollState ?: rememberScrollState()
|
val handler = LocalAppBarHandler.current
|
||||||
val connection = LocalAppBarHandler.current?.connection
|
require(handler != null) { "Using ColumnWithScrollBar and without AppBarHandler is an error. Use ColumnWithScrollBarNoAppBar instead" }
|
||||||
|
|
||||||
|
val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier).imePadding() else modifier.imePadding()
|
||||||
|
val state = state ?: handler.scrollState
|
||||||
|
val connection = handler.connection
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
snapshotFlow { state.value }
|
snapshotFlow { state.value }
|
||||||
.collect { scrollPosition ->
|
.collect { scrollPosition ->
|
||||||
val offset = connection?.appBarOffset
|
val offset = connection.appBarOffset
|
||||||
if (offset != null && (offset + scrollPosition).absoluteValue > 1) {
|
if ((offset + scrollPosition).absoluteValue > 1) {
|
||||||
connection.appBarOffset = -scrollPosition.toFloat()
|
connection.appBarOffset = -scrollPosition.toFloat()
|
||||||
// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}")
|
// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (connection != null) {
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
Box(Modifier.fillMaxHeight()) {
|
||||||
Column(
|
Column(
|
||||||
if (maxIntrinsicSize) {
|
if (maxIntrinsicSize) {
|
||||||
modifier.nestedScroll(connection).verticalScroll(state).height(IntrinsicSize.Max)
|
Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).then(modifier).nestedScroll(connection).verticalScroll(state).height(IntrinsicSize.Max)
|
||||||
} else {
|
} else {
|
||||||
modifier.nestedScroll(connection).verticalScroll(state)
|
Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).then(modifier).nestedScroll(connection).verticalScroll(state)
|
||||||
}, verticalArrangement, horizontalAlignment, content)
|
}, verticalArrangement, horizontalAlignment
|
||||||
|
) {
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
Spacer(Modifier.padding(top = DEFAULT_PADDING + 5.dp).windowInsetsTopHeight(WindowInsets.statusBars))
|
||||||
|
content()
|
||||||
|
Spacer(Modifier.navigationBarsPadding().padding(bottom = AppBarHeight * fontSizeSqrtMultiplier))
|
||||||
} else {
|
} else {
|
||||||
Column(if (maxIntrinsicSize) {
|
Spacer(Modifier.statusBarsPadding().padding(top = AppBarHeight * fontSizeSqrtMultiplier))
|
||||||
|
content()
|
||||||
|
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!oneHandUI.value) {
|
||||||
|
NavigationBarBackground(false, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun ColumnWithScrollBarNoAppBar(
|
||||||
|
modifier: Modifier,
|
||||||
|
verticalArrangement: Arrangement.Vertical,
|
||||||
|
horizontalAlignment: Alignment.Horizontal,
|
||||||
|
state: ScrollState?,
|
||||||
|
maxIntrinsicSize: Boolean,
|
||||||
|
content: @Composable() (ColumnScope.() -> Unit)
|
||||||
|
) {
|
||||||
|
val modifier = modifier.imePadding()
|
||||||
|
val state = state ?: rememberScrollState()
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
Box(Modifier.fillMaxHeight()) {
|
||||||
|
Column(
|
||||||
|
if (maxIntrinsicSize) {
|
||||||
modifier.verticalScroll(state).height(IntrinsicSize.Max)
|
modifier.verticalScroll(state).height(IntrinsicSize.Max)
|
||||||
} else {
|
} else {
|
||||||
modifier.verticalScroll(state)
|
modifier.verticalScroll(state)
|
||||||
}, verticalArrangement, horizontalAlignment, content)
|
}, verticalArrangement, horizontalAlignment
|
||||||
|
) {
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))
|
||||||
|
content()
|
||||||
|
} else {
|
||||||
|
content()
|
||||||
|
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!oneHandUI.value) {
|
||||||
|
NavigationBarBackground(false, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-22
@@ -3,17 +3,18 @@ package chat.simplex.common.platform
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.graphics.Rect
|
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.ime
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import chat.simplex.common.AppScreen
|
import chat.simplex.common.AppScreen
|
||||||
import chat.simplex.common.model.clear
|
import chat.simplex.common.model.clear
|
||||||
import chat.simplex.common.ui.theme.SimpleXTheme
|
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import androidx.compose.ui.platform.LocalContext as LocalContext1
|
import androidx.compose.ui.platform.LocalContext as LocalContext1
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
@@ -43,28 +44,13 @@ actual fun LocalMultiplatformView(): Any? = LocalView.current
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun getKeyboardState(): State<KeyboardState> {
|
actual fun getKeyboardState(): State<KeyboardState> {
|
||||||
val keyboardState = remember { mutableStateOf(KeyboardState.Closed) }
|
val density = LocalDensity.current
|
||||||
val view = LocalView.current
|
val ime = WindowInsets.ime
|
||||||
DisposableEffect(view) {
|
return remember {
|
||||||
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
|
derivedStateOf {
|
||||||
val rect = Rect()
|
if (ime.getBottom(density) == 0) KeyboardState.Closed else KeyboardState.Opened
|
||||||
view.getWindowVisibleDisplayFrame(rect)
|
|
||||||
val screenHeight = view.rootView.height
|
|
||||||
val keypadHeight = screenHeight - rect.bottom
|
|
||||||
keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
|
|
||||||
KeyboardState.Opened
|
|
||||||
} else {
|
|
||||||
KeyboardState.Closed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
|
|
||||||
|
|
||||||
onDispose {
|
|
||||||
view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyboardState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun hideKeyboard(view: Any?, clearFocus: Boolean) {
|
actual fun hideKeyboard(view: Any?, clearFocus: Boolean) {
|
||||||
|
|||||||
+12
-7
@@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.shape.CircleShape
|
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.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
@@ -48,6 +47,7 @@ import chat.simplex.common.model.ChatController.appPrefs
|
|||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
|
import chat.simplex.common.views.onboarding.OnboardingStage
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import com.google.accompanist.permissions.*
|
import com.google.accompanist.permissions.*
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
@@ -329,11 +329,14 @@ private fun ActiveCallOverlayLayout(
|
|||||||
flipCamera: () -> Unit
|
flipCamera: () -> Unit
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
CloseSheetBar({ chatModel.activeCallViewIsCollapsed.value = true }, true, tintColor = Color(0xFFFFFFD8)) {
|
CallAppBar(
|
||||||
|
title = {
|
||||||
if (call.hasVideo) {
|
if (call.hasVideo) {
|
||||||
Text(call.contact.chatViewName, Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING), color = Color(0xFFFFFFD8), style = MaterialTheme.typography.h2, overflow = TextOverflow.Ellipsis, maxLines = 1)
|
Text(call.contact.chatViewName, Modifier.offset(x = (-4).dp).padding(end = DEFAULT_PADDING), color = Color(0xFFFFFFD8), style = MaterialTheme.typography.h2, overflow = TextOverflow.Ellipsis, maxLines = 1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onBack = { chatModel.activeCallViewIsCollapsed.value = true }
|
||||||
|
)
|
||||||
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectSoundDevice(size: Dp) {
|
fun SelectSoundDevice(size: Dp) {
|
||||||
@@ -411,6 +414,7 @@ private fun ControlButton(icon: Painter, iconText: StringResource, enabled: Bool
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ControlButtonWrap(enabled: Boolean = true, action: () -> Unit, background: Color = controlButtonsBackground(), size: Dp, content: @Composable () -> Unit) {
|
private fun ControlButtonWrap(enabled: Boolean = true, action: () -> Unit, background: Color = controlButtonsBackground(), size: Dp, content: @Composable () -> Unit) {
|
||||||
|
val ripple = remember { ripple(bounded = false, radius = size / 2, color = background.lighter(0.1f)) }
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.background(background, CircleShape)
|
.background(background, CircleShape)
|
||||||
@@ -419,7 +423,7 @@ private fun ControlButtonWrap(enabled: Boolean = true, action: () -> Unit, backg
|
|||||||
onClick = action,
|
onClick = action,
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = rememberRipple(bounded = false, radius = size / 2, color = background.lighter(0.1f)),
|
indication = ripple,
|
||||||
enabled = enabled
|
enabled = enabled
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
@@ -590,8 +594,9 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ModalView(background = Color.Black, showClose = false, close = {}) {
|
ModalView(background = Color.Black, showAppBar = false, close = {}) {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxSize()) {
|
Column {
|
||||||
|
Spacer(Modifier.height(AppBarHeight * fontSizeSqrtMultiplier))
|
||||||
AppBarTitle(stringResource(MR.strings.permissions_required))
|
AppBarTitle(stringResource(MR.strings.permissions_required))
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
val onClick = {
|
val onClick = {
|
||||||
|
|||||||
-9
@@ -8,7 +8,6 @@ import androidx.compose.ui.graphics.painter.BitmapPainter
|
|||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import chat.simplex.common.model.CIFile
|
import chat.simplex.common.model.CIFile
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.CurrentColors
|
import chat.simplex.common.ui.theme.CurrentColors
|
||||||
import chat.simplex.common.views.helpers.ModalManager
|
import chat.simplex.common.views.helpers.ModalManager
|
||||||
@@ -39,14 +38,6 @@ actual fun SimpleAndAnimatedImageView(
|
|||||||
if (getLoadedFilePath(file) != null) {
|
if (getLoadedFilePath(file) != null) {
|
||||||
ModalManager.fullscreen.showCustomModal(animated = false) { close ->
|
ModalManager.fullscreen.showCustomModal(animated = false) { close ->
|
||||||
ImageFullScreenView(imageProvider, close)
|
ImageFullScreenView(imageProvider, close)
|
||||||
if (smallView) {
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
onDispose {
|
|
||||||
val c = CurrentColors.value.colors
|
|
||||||
platform.androidSetStatusAndNavBarColors(c.isLight, c.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-21
@@ -1,32 +1,26 @@
|
|||||||
package chat.simplex.common.views.chatlist
|
package chat.simplex.common.views.chatlist
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
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
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import androidx.compose.ui.platform.*
|
import androidx.compose.ui.platform.*
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.*
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import chat.simplex.common.ANDROID_CALL_TOP_PADDING
|
import chat.simplex.common.ANDROID_CALL_TOP_PADDING
|
||||||
import chat.simplex.common.model.durationText
|
import chat.simplex.common.model.durationText
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.call.*
|
import chat.simplex.common.views.call.*
|
||||||
import chat.simplex.common.views.helpers.*
|
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
|
||||||
private val CALL_INTERACTIVE_AREA_HEIGHT = 74.dp
|
private val CALL_INTERACTIVE_AREA_HEIGHT = 74.dp
|
||||||
@@ -38,11 +32,12 @@ private val CALL_BOTTOM_ICON_HEIGHT = CALL_INTERACTIVE_AREA_HEIGHT + CALL_BOTTOM
|
|||||||
@Composable
|
@Composable
|
||||||
actual fun ActiveCallInteractiveArea(call: Call) {
|
actual fun ActiveCallInteractiveArea(call: Call) {
|
||||||
val onClick = { platform.androidStartCallActivity(false) }
|
val onClick = { platform.androidStartCallActivity(false) }
|
||||||
Box(Modifier.offset(y = CALL_TOP_OFFSET).height(CALL_INTERACTIVE_AREA_HEIGHT)) {
|
val statusBar = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||||
|
Box(Modifier.offset(y = CALL_TOP_OFFSET).height(CALL_INTERACTIVE_AREA_HEIGHT + statusBar)) {
|
||||||
val source = remember { MutableInteractionSource() }
|
val source = remember { MutableInteractionSource() }
|
||||||
val indication = rememberRipple(bounded = true, 3000.dp)
|
val ripple = remember { ripple(bounded = true, 3000.dp) }
|
||||||
Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT).clickable(onClick = onClick, indication = indication, interactionSource = source)) {
|
Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT + statusBar).clickable(onClick = onClick, indication = ripple, interactionSource = source)) {
|
||||||
GreenLine(call)
|
GreenLine(statusBar, call)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
@@ -50,7 +45,7 @@ actual fun ActiveCallInteractiveArea(call: Call) {
|
|||||||
.size(CALL_BOTTOM_ICON_HEIGHT)
|
.size(CALL_BOTTOM_ICON_HEIGHT)
|
||||||
.background(SimplexGreen, CircleShape)
|
.background(SimplexGreen, CircleShape)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.clickable(onClick = onClick, indication = indication, interactionSource = source)
|
.clickable(onClick = onClick, indication = ripple, interactionSource = source)
|
||||||
.align(Alignment.BottomCenter),
|
.align(Alignment.BottomCenter),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
@@ -63,16 +58,13 @@ actual fun ActiveCallInteractiveArea(call: Call) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary solution for storing a color that needs to be applied after call ends
|
|
||||||
var statusBarColorAfterCall = mutableIntStateOf(CurrentColors.value.colors.background.toArgb())
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GreenLine(call: Call) {
|
private fun GreenLine(statusBarHeight: Dp, call: Call) {
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(SimplexGreen)
|
.background(SimplexGreen)
|
||||||
.padding(top = -CALL_TOP_OFFSET)
|
.padding(top = -CALL_TOP_OFFSET + statusBarHeight)
|
||||||
.padding(horizontal = DEFAULT_PADDING),
|
.padding(horizontal = DEFAULT_PADDING),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
@@ -81,12 +73,10 @@ private fun GreenLine(call: Call) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
CallDuration(call)
|
CallDuration(call)
|
||||||
}
|
}
|
||||||
val window = (LocalContext.current as Activity).window
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
statusBarColorAfterCall.intValue = window.statusBarColor
|
platform.androidSetStatusAndNavigationBarAppearance(false, CurrentColors.value.colors.isLight)
|
||||||
window.statusBarColor = SimplexGreen.toArgb()
|
|
||||||
onDispose {
|
onDispose {
|
||||||
window.statusBarColor = statusBarColorAfterCall.intValue
|
platform.androidSetStatusAndNavigationBarAppearance(CurrentColors.value.colors.isLight, CurrentColors.value.colors.isLight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-31
@@ -19,13 +19,11 @@ import androidx.compose.ui.layout.onSizeChanged
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
|
||||||
import chat.simplex.common.model.User
|
import chat.simplex.common.model.User
|
||||||
import chat.simplex.common.model.UserInfo
|
import chat.simplex.common.model.UserInfo
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -36,6 +34,7 @@ private val USER_PICKER_ROW_PADDING = 16.dp
|
|||||||
@Composable
|
@Composable
|
||||||
actual fun UserPickerUsersSection(
|
actual fun UserPickerUsersSection(
|
||||||
users: List<UserInfo>,
|
users: List<UserInfo>,
|
||||||
|
iconColor: Color,
|
||||||
stopped: Boolean,
|
stopped: Boolean,
|
||||||
onUserClicked: (user: User) -> Unit,
|
onUserClicked: (user: User) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -140,41 +139,15 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow<
|
|||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
Box {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.then(clickableModifier)
|
.then(clickableModifier)
|
||||||
.drawBehind {
|
.drawBehind {
|
||||||
val pos = when {
|
val pos = calculatePosition(dismissState)
|
||||||
dismissState.progress.from == DismissValue.Default && dismissState.progress.to == DismissValue.Default -> 1f
|
|
||||||
dismissState.progress.from == DismissValue.DismissedToEnd && dismissState.progress.to == DismissValue.DismissedToEnd -> 0f
|
|
||||||
dismissState.progress.to == DismissValue.Default -> dismissState.progress.fraction
|
|
||||||
else -> 1 - dismissState.progress.fraction
|
|
||||||
}
|
|
||||||
val colors = CurrentColors.value.colors
|
val colors = CurrentColors.value.colors
|
||||||
val resultingColor = if (colors.isLight) colors.onSurface.copy(alpha = ScrimOpacity) else Color.Black.copy(0.64f)
|
val resultingColor = if (colors.isLight) colors.onSurface.copy(alpha = ScrimOpacity) else Color.Black.copy(0.64f)
|
||||||
val adjustedAlpha = resultingColor.alpha * calculateFraction(pos = pos)
|
|
||||||
val shadingColor = resultingColor.copy(alpha = adjustedAlpha)
|
|
||||||
|
|
||||||
if (pickerState.value.isVisible()) {
|
|
||||||
platform.androidSetDrawerStatusAndNavBarColor(
|
|
||||||
isLight = colors.isLight,
|
|
||||||
drawerShadingColor = shadingColor,
|
|
||||||
toolbarOnTop = !appPrefs.oneHandUI.get(),
|
|
||||||
navBarColor = colors.background.mixWith(colors.onBackground, 1 - userPickerAlpha())
|
|
||||||
)
|
|
||||||
} else if (ModalManager.start.modalCount.value == 0) {
|
|
||||||
platform.androidSetDrawerStatusAndNavBarColor(
|
|
||||||
isLight = colors.isLight,
|
|
||||||
drawerShadingColor = shadingColor,
|
|
||||||
toolbarOnTop = !appPrefs.oneHandUI.get(),
|
|
||||||
navBarColor = (if (appPrefs.oneHandUI.get() && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) {
|
|
||||||
colors.background.mixWith(CurrentColors.value.colors.onBackground, 0.97f)
|
|
||||||
} else {
|
|
||||||
colors.background
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
drawRect(
|
drawRect(
|
||||||
if (pos != 0f) resultingColor else Color.Transparent,
|
if (pos != 0f) resultingColor else Color.Transparent,
|
||||||
alpha = calculateFraction(pos = pos)
|
alpha = calculateFraction(pos = pos)
|
||||||
@@ -214,11 +187,23 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow<
|
|||||||
swipeDistance = height.intValue.toFloat(),
|
swipeDistance = height.intValue.toFloat(),
|
||||||
)
|
)
|
||||||
else Modifier
|
else Modifier
|
||||||
Box(draggableModifier.then(modifier)) {
|
Box(draggableModifier.then(modifier).navigationBarsPadding()) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
NavigationBarBackground(
|
||||||
|
modifier = Modifier.graphicsLayer { alpha = if (calculatePosition(dismissState) > 0.1f) 1f else 0f },
|
||||||
|
color = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculatePosition(dismissState: DismissState): Float = when {
|
||||||
|
dismissState.progress.from == DismissValue.Default && dismissState.progress.to == DismissValue.Default -> 1f
|
||||||
|
dismissState.progress.from == DismissValue.DismissedToEnd && dismissState.progress.to == DismissValue.DismissedToEnd -> 0f
|
||||||
|
dismissState.progress.to == DismissValue.Default -> dismissState.progress.fraction
|
||||||
|
else -> 1 - dismissState.progress.fraction
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Modifier.draggableBottomDrawerModifier(
|
private fun Modifier.draggableBottomDrawerModifier(
|
||||||
|
|||||||
+2
@@ -171,6 +171,8 @@ actual fun GetImageBottomSheet(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
|
.imePadding()
|
||||||
|
.navigationBarsPadding()
|
||||||
.onFocusChanged { focusState ->
|
.onFocusChanged { focusState ->
|
||||||
if (!focusState.hasFocus) hideBottomSheet()
|
if (!focusState.hasFocus) hideBottomSheet()
|
||||||
}
|
}
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A workaround for the ANR issue on Compose 1.7.x.
|
||||||
|
* https://issuetracker.google.com/issues/369354336
|
||||||
|
* Code from:
|
||||||
|
* https://issuetracker.google.com/issues/369354336#comment8
|
||||||
|
*/
|
||||||
|
class WorkaroundFocusSearchLayout : FrameLayout {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
context: Context,
|
||||||
|
) : super(context)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet?,
|
||||||
|
) : super(context, attrs)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet?,
|
||||||
|
defStyleAttr: Int,
|
||||||
|
) : super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet?,
|
||||||
|
defStyleAttr: Int,
|
||||||
|
defStyleRes: Int,
|
||||||
|
) : super(context, attrs, defStyleAttr, defStyleRes)
|
||||||
|
|
||||||
|
override fun focusSearch(focused: View?, direction: Int): View? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-7
@@ -2,6 +2,7 @@ package chat.simplex.common.views.usersettings
|
|||||||
|
|
||||||
import SectionBottomSpacer
|
import SectionBottomSpacer
|
||||||
import SectionDividerSpaced
|
import SectionDividerSpaced
|
||||||
|
import SectionSpacer
|
||||||
import SectionView
|
import SectionView
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
@@ -31,6 +32,7 @@ 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.model.ChatController.appPrefs
|
||||||
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
|
||||||
@@ -75,9 +77,7 @@ fun AppearanceScope.AppearanceLayout(
|
|||||||
systemDarkTheme: SharedPreference<String?>,
|
systemDarkTheme: SharedPreference<String?>,
|
||||||
changeIcon: (AppIcon) -> Unit,
|
changeIcon: (AppIcon) -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.appearance_settings))
|
AppBarTitle(stringResource(MR.strings.appearance_settings))
|
||||||
SectionView(stringResource(MR.strings.settings_section_title_interface), contentPadding = PaddingValues()) {
|
SectionView(stringResource(MR.strings.settings_section_title_interface), contentPadding = PaddingValues()) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -106,15 +106,15 @@ fun AppearanceScope.AppearanceLayout(
|
|||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
|
|
||||||
SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) {
|
SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI)
|
||||||
val c = CurrentColors.value.colors
|
|
||||||
platform.androidSetStatusAndNavBarColors(c.isLight, c.background, false, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionDividerSpaced()
|
SectionDividerSpaced()
|
||||||
ThemesSection(systemDarkTheme)
|
ThemesSection(systemDarkTheme)
|
||||||
|
|
||||||
|
SectionDividerSpaced()
|
||||||
|
AppToolbarsSection()
|
||||||
|
|
||||||
SectionDividerSpaced()
|
SectionDividerSpaced()
|
||||||
MessageShapeSection()
|
MessageShapeSection()
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -13,14 +13,13 @@ import dev.icerock.moko.resources.compose.stringResource
|
|||||||
@Composable
|
@Composable
|
||||||
actual fun SettingsSectionApp(
|
actual fun SettingsSectionApp(
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
|
||||||
showVersion: () -> Unit,
|
showVersion: () -> Unit,
|
||||||
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
||||||
) {
|
) {
|
||||||
SectionView(stringResource(MR.strings.settings_section_title_app)) {
|
SectionView(stringResource(MR.strings.settings_section_title_app)) {
|
||||||
SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp)
|
SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp)
|
||||||
SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) })
|
SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) })
|
||||||
SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) })
|
SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(withAuth) })
|
||||||
AppVersionItem(showVersion)
|
AppVersionItem(showVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.*
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.*
|
||||||
|
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView
|
import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
@@ -39,18 +42,43 @@ import dev.icerock.moko.resources.compose.painterResource
|
|||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppScreen() {
|
fun AppScreen() {
|
||||||
AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() }
|
AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() }
|
||||||
SimpleXTheme {
|
SimpleXTheme {
|
||||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
|
||||||
Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
|
Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
|
||||||
|
// This padding applies to landscape view only taking care of navigation bar and holes in screen in status bar area
|
||||||
|
// (because nav bar and holes located on vertical sides of screen in landscape view)
|
||||||
|
val direction = LocalLayoutDirection.current
|
||||||
|
val safePadding = WindowInsets.safeDrawing.asPaddingValues()
|
||||||
|
val cutout = WindowInsets.displayCutout.asPaddingValues()
|
||||||
|
val cutoutStart = cutout.calculateStartPadding(direction)
|
||||||
|
val cutoutEnd = cutout.calculateEndPadding(direction)
|
||||||
|
val cutoutMax = maxOf(cutoutStart, cutoutEnd)
|
||||||
|
val paddingStartUntouched = safePadding.calculateStartPadding(direction)
|
||||||
|
val paddingStart = paddingStartUntouched - cutoutStart
|
||||||
|
val paddingEndUntouched = safePadding.calculateEndPadding(direction)
|
||||||
|
val paddingEnd = paddingEndUntouched - cutoutEnd
|
||||||
|
// Such a strange layout is needed because the main content should be covered by solid color in order to hide overflow
|
||||||
|
// of some elements that may have negative offset (so, can't use Row {}).
|
||||||
|
// To check: go to developer settings of Android, choose Display cutout -> Punch hole, and rotate the phone to landscape, open any chat
|
||||||
|
Box {
|
||||||
|
val fullscreenGallery = remember { chatModel.fullscreenGalleryVisible }
|
||||||
|
Box(Modifier.padding(start = paddingStart + cutoutMax, end = paddingEnd + cutoutMax).consumeWindowInsets(PaddingValues(start = paddingStartUntouched, end = paddingEndUntouched))) {
|
||||||
|
Box(Modifier.drawBehind {
|
||||||
|
if (fullscreenGallery.value) {
|
||||||
|
drawRect(Color.Black, topLeft = Offset(-(paddingStart + cutoutMax).toPx(), 0f), Size(size.width + (paddingStart + cutoutMax).toPx() + (paddingEnd + cutoutMax).toPx(), size.height))
|
||||||
|
}
|
||||||
|
}) {
|
||||||
MainScreen()
|
MainScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen() {
|
fun MainScreen() {
|
||||||
@@ -138,7 +166,9 @@ fun MainScreen() {
|
|||||||
}
|
}
|
||||||
SetupClipboardListener()
|
SetupClipboardListener()
|
||||||
if (appPlatform.isAndroid) {
|
if (appPlatform.isAndroid) {
|
||||||
|
AndroidWrapInCallLayout {
|
||||||
AndroidScreen(userPickerState)
|
AndroidScreen(userPickerState)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
DesktopScreen(userPickerState)
|
DesktopScreen(userPickerState)
|
||||||
}
|
}
|
||||||
@@ -170,7 +200,9 @@ fun MainScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (appPlatform.isAndroid) {
|
if (appPlatform.isAndroid) {
|
||||||
|
AndroidWrapInCallLayout {
|
||||||
ModalManager.fullscreen.showInView()
|
ModalManager.fullscreen.showInView()
|
||||||
|
}
|
||||||
SwitchingUsersView()
|
SwitchingUsersView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,18 +270,38 @@ fun MainScreen() {
|
|||||||
val ANDROID_CALL_TOP_PADDING = 40.dp
|
val ANDROID_CALL_TOP_PADDING = 40.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AndroidScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
fun AndroidWrapInCallLayout(content: @Composable () -> Unit) {
|
||||||
BoxWithConstraints {
|
|
||||||
val call = remember { chatModel.activeCall}.value
|
val call = remember { chatModel.activeCall}.value
|
||||||
val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted
|
val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted
|
||||||
|
Box {
|
||||||
|
Box(Modifier.padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp)) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
if (call != null && showCallArea) {
|
||||||
|
ActiveCallInteractiveArea(call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AndroidScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
||||||
|
BoxWithConstraints {
|
||||||
val currentChatId = remember { mutableStateOf(chatModel.chatId.value) }
|
val currentChatId = remember { mutableStateOf(chatModel.chatId.value) }
|
||||||
val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) }
|
val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) }
|
||||||
|
val cutout = WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||||
|
val direction = LocalLayoutDirection.current
|
||||||
|
val hasCutout = cutout.calculateStartPadding(direction) + cutout.calculateEndPadding(direction) > 0.dp
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
|
// clipping only for devices with cutout currently visible on sides. It prevents showing chat list with open chat view
|
||||||
|
// In order cases it's not needed to use clip
|
||||||
|
.then(if (hasCutout) Modifier.clip(RectangleShape) else Modifier)
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
translationX = -offset.value.dp.toPx()
|
// minOf thing is needed for devices with holes in screen while the user on ChatView rotates his phone from portrait to landscape
|
||||||
|
// because in this case (at least in emulator) maxWidth changes in two steps: big first, smaller on next frame.
|
||||||
|
// But offset is remembered already, so this is a better way than dropping a value of offset
|
||||||
|
translationX = -minOf(offset.value.dp, maxWidth).toPx()
|
||||||
}
|
}
|
||||||
.padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp)
|
|
||||||
) {
|
) {
|
||||||
StartPartOfScreen(userPickerState)
|
StartPartOfScreen(userPickerState)
|
||||||
}
|
}
|
||||||
@@ -271,53 +323,42 @@ fun AndroidScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
|||||||
snapshotFlow { chatModel.chatId.value }
|
snapshotFlow { chatModel.chatId.value }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collect {
|
.collect {
|
||||||
if (it == null) {
|
if (it == null) onComposed(null)
|
||||||
platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get())
|
|
||||||
onComposed(null)
|
|
||||||
}
|
|
||||||
currentChatId.value = it
|
currentChatId.value = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
snapshotFlow { ModalManager.center.modalCount.value > 0 }
|
|
||||||
.filter { chatModel.chatId.value == null }
|
|
||||||
.collect { modalBackground ->
|
|
||||||
if (chatModel.newChatSheetVisible.value) {
|
|
||||||
platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, false, appPrefs.oneHandUI.get())
|
|
||||||
} else if (modalBackground) {
|
|
||||||
platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, false, false)
|
|
||||||
} else {
|
|
||||||
platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Box(Modifier
|
Box(Modifier
|
||||||
.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }
|
.then(if (hasCutout) Modifier.clip(RectangleShape) else Modifier)
|
||||||
.padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp)
|
.graphicsLayer { translationX = maxWidth.toPx() - minOf(offset.value.dp, maxWidth).toPx() }
|
||||||
) Box2@{
|
) Box2@{
|
||||||
currentChatId.value?.let {
|
currentChatId.value?.let {
|
||||||
ChatView(currentChatId, onComposed)
|
ChatView(currentChatId, onComposed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (call != null && showCallArea) {
|
|
||||||
ActiveCallInteractiveArea(call)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StartPartOfScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
fun StartPartOfScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
||||||
if (chatModel.setDeliveryReceipts.value) {
|
if (chatModel.setDeliveryReceipts.value) {
|
||||||
|
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||||
SetDeliveryReceiptsView(chatModel)
|
SetDeliveryReceiptsView(chatModel)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val stopped = chatModel.chatRunning.value == false
|
val stopped = chatModel.chatRunning.value == false
|
||||||
if (chatModel.sharedContent.value == null)
|
if (chatModel.sharedContent.value == null) {
|
||||||
|
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||||
ChatListView(chatModel, userPickerState, AppLock::setPerformLA, stopped)
|
ChatListView(chatModel, userPickerState, AppLock::setPerformLA, stopped)
|
||||||
else
|
}
|
||||||
|
} else {
|
||||||
|
// LALAL initial load of view doesn't show blur. Focusing text field shows it
|
||||||
|
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler(keyboardCoversBar = false)) {
|
||||||
ShareListView(chatModel, stopped)
|
ShareListView(chatModel, stopped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CenterPartOfScreen() {
|
fun CenterPartOfScreen() {
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ object ChatModel {
|
|||||||
// Needed to check for bottom nav bar and to apply or not navigation bar color on Android
|
// Needed to check for bottom nav bar and to apply or not navigation bar color on Android
|
||||||
val newChatSheetVisible = mutableStateOf(false)
|
val newChatSheetVisible = mutableStateOf(false)
|
||||||
|
|
||||||
|
// Needed to apply black color to left/right cutout area on Android
|
||||||
|
val fullscreenGalleryVisible = mutableStateOf(false)
|
||||||
|
|
||||||
// preferences
|
// preferences
|
||||||
val notificationPreviewMode by lazy {
|
val notificationPreviewMode by lazy {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
|
|||||||
+8
-1
@@ -118,6 +118,9 @@ class AppPreferences {
|
|||||||
val privacyEncryptLocalFiles = mkBoolPreference(SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES, true)
|
val privacyEncryptLocalFiles = mkBoolPreference(SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES, true)
|
||||||
val privacyAskToApproveRelays = mkBoolPreference(SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS, true)
|
val privacyAskToApproveRelays = mkBoolPreference(SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS, true)
|
||||||
val privacyMediaBlurRadius = mkIntPreference(SHARED_PREFS_PRIVACY_MEDIA_BLUR_RADIUS, 0)
|
val privacyMediaBlurRadius = mkIntPreference(SHARED_PREFS_PRIVACY_MEDIA_BLUR_RADIUS, 0)
|
||||||
|
// Blur broken on Android 12, see https://github.com/chrisbanes/haze/issues/77. And not available before 12
|
||||||
|
val deviceSupportsBlur = appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 32
|
||||||
|
val appearanceBarsBlurRadius = mkIntPreference(SHARED_PREFS_APPEARANCE_BARS_BLUR_RADIUS, if (deviceSupportsBlur) 50 else 0)
|
||||||
val experimentalCalls = mkBoolPreference(SHARED_PREFS_EXPERIMENTAL_CALLS, false)
|
val experimentalCalls = mkBoolPreference(SHARED_PREFS_EXPERIMENTAL_CALLS, false)
|
||||||
val showUnreadAndFavorites = mkBoolPreference(SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES, false)
|
val showUnreadAndFavorites = mkBoolPreference(SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES, false)
|
||||||
val chatArchiveName = mkStrPreference(SHARED_PREFS_CHAT_ARCHIVE_NAME, null)
|
val chatArchiveName = mkStrPreference(SHARED_PREFS_CHAT_ARCHIVE_NAME, null)
|
||||||
@@ -223,6 +226,8 @@ class AppPreferences {
|
|||||||
val chatItemTail = mkBoolPreference(SHARED_PREFS_CHAT_ITEM_TAIL, true)
|
val chatItemTail = mkBoolPreference(SHARED_PREFS_CHAT_ITEM_TAIL, true)
|
||||||
val fontScale = mkFloatPreference(SHARED_PREFS_FONT_SCALE, 1f)
|
val fontScale = mkFloatPreference(SHARED_PREFS_FONT_SCALE, 1f)
|
||||||
val densityScale = mkFloatPreference(SHARED_PREFS_DENSITY_SCALE, 1f)
|
val densityScale = mkFloatPreference(SHARED_PREFS_DENSITY_SCALE, 1f)
|
||||||
|
val inAppBarsDefaultAlpha = if (deviceSupportsBlur) 0.875f else 0.975f
|
||||||
|
val inAppBarsAlpha = mkFloatPreference(SHARED_PREFS_IN_APP_BARS_ALPHA, inAppBarsDefaultAlpha)
|
||||||
|
|
||||||
val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null)
|
val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null)
|
||||||
val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0)
|
val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0)
|
||||||
@@ -244,7 +249,7 @@ class AppPreferences {
|
|||||||
val iosCallKitEnabled = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_ENABLED, true)
|
val iosCallKitEnabled = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_ENABLED, true)
|
||||||
val iosCallKitCallsInRecents = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS, false)
|
val iosCallKitCallsInRecents = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS, false)
|
||||||
|
|
||||||
val oneHandUI = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI, appPlatform.isAndroid)
|
val oneHandUI = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI, true)
|
||||||
|
|
||||||
val hintPreferences: List<Pair<SharedPreference<Boolean>, Boolean>> = listOf(
|
val hintPreferences: List<Pair<SharedPreference<Boolean>, Boolean>> = listOf(
|
||||||
laNoticeShown to false,
|
laNoticeShown to false,
|
||||||
@@ -362,6 +367,7 @@ class AppPreferences {
|
|||||||
private const val SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES = "PrivacyEncryptLocalFiles"
|
private const val SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES = "PrivacyEncryptLocalFiles"
|
||||||
private const val SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS = "PrivacyAskToApproveRelays"
|
private const val SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS = "PrivacyAskToApproveRelays"
|
||||||
private const val SHARED_PREFS_PRIVACY_MEDIA_BLUR_RADIUS = "PrivacyMediaBlurRadius"
|
private const val SHARED_PREFS_PRIVACY_MEDIA_BLUR_RADIUS = "PrivacyMediaBlurRadius"
|
||||||
|
private const val SHARED_PREFS_APPEARANCE_BARS_BLUR_RADIUS = "AppearanceBarsBlurRadius"
|
||||||
const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup"
|
const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup"
|
||||||
private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls"
|
private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls"
|
||||||
private const val SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES = "ShowUnreadAndFavorites"
|
private const val SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES = "ShowUnreadAndFavorites"
|
||||||
@@ -428,6 +434,7 @@ class AppPreferences {
|
|||||||
private const val SHARED_PREFS_CHAT_ITEM_TAIL = "ChatItemTail"
|
private const val SHARED_PREFS_CHAT_ITEM_TAIL = "ChatItemTail"
|
||||||
private const val SHARED_PREFS_FONT_SCALE = "FontScale"
|
private const val SHARED_PREFS_FONT_SCALE = "FontScale"
|
||||||
private const val SHARED_PREFS_DENSITY_SCALE = "DensityScale"
|
private const val SHARED_PREFS_DENSITY_SCALE = "DensityScale"
|
||||||
|
private const val SHARED_PREFS_IN_APP_BARS_ALPHA = "InAppBarsAlpha"
|
||||||
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"
|
||||||
private const val SHARED_PREFS_CUSTOM_DISAPPEARING_MESSAGE_TIME = "CustomDisappearingMessageTime"
|
private const val SHARED_PREFS_CUSTOM_DISAPPEARING_MESSAGE_TIME = "CustomDisappearingMessageTime"
|
||||||
|
|||||||
+1
-10
@@ -14,20 +14,11 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
expect fun Modifier.navigationBarsWithImePadding(): Modifier
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
expect fun ProvideWindowInsets(
|
|
||||||
consumeWindowInsets: Boolean = true,
|
|
||||||
windowInsetsAnimationsEnabled: Boolean = true,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
expect fun Modifier.desktopOnExternalDrag(
|
expect fun Modifier.desktopOnExternalDrag(
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
onFiles: (List<File>) -> Unit = {},
|
onFiles: (List<File>) -> Unit = {},
|
||||||
onImage: (Painter) -> Unit = {},
|
onImage: (File) -> Unit = {},
|
||||||
onText: (String) -> Unit = {}
|
onText: (String) -> Unit = {}
|
||||||
): Modifier
|
): Modifier
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -8,6 +8,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import chat.simplex.common.model.ChatId
|
import chat.simplex.common.model.ChatId
|
||||||
import chat.simplex.common.model.NotificationsMode
|
import chat.simplex.common.model.NotificationsMode
|
||||||
|
import chat.simplex.common.ui.theme.CurrentColors
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
||||||
interface PlatformInterface {
|
interface PlatformInterface {
|
||||||
@@ -20,12 +21,12 @@ interface PlatformInterface {
|
|||||||
fun androidChatInitializedAndStarted() {}
|
fun androidChatInitializedAndStarted() {}
|
||||||
fun androidIsBackgroundCallAllowed(): Boolean = true
|
fun androidIsBackgroundCallAllowed(): Boolean = true
|
||||||
fun androidSetNightModeIfSupported() {}
|
fun androidSetNightModeIfSupported() {}
|
||||||
fun androidSetStatusAndNavBarColors(isLight: Boolean, backgroundColor: Color, hasTop: Boolean, hasBottom: Boolean) {}
|
fun androidSetStatusAndNavigationBarAppearance(isLightStatusBar: Boolean, isLightNavBar: Boolean, blackNavBar: Boolean = false, themeBackgroundColor: Color = CurrentColors.value.colors.background) {}
|
||||||
fun androidSetDrawerStatusAndNavBarColor(isLight: Boolean, drawerShadingColor: Color, toolbarOnTop: Boolean, navBarColor: Color) {}
|
|
||||||
fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long? = null, chatId: ChatId? = null) {}
|
fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long? = null, chatId: ChatId? = null) {}
|
||||||
fun androidPictureInPictureAllowed(): Boolean = true
|
fun androidPictureInPictureAllowed(): Boolean = true
|
||||||
fun androidCallEnded() {}
|
fun androidCallEnded() {}
|
||||||
fun androidRestartNetworkObserver() {}
|
fun androidRestartNetworkObserver() {}
|
||||||
|
val androidApiLevel: Int? get() = null
|
||||||
@Composable fun androidLockPortraitOrientation() {}
|
@Composable fun androidLockPortraitOrientation() {}
|
||||||
suspend fun androidAskToAllowBackgroundCalls(): Boolean = true
|
suspend fun androidAskToAllowBackgroundCalls(): Boolean = true
|
||||||
@Composable fun desktopShowAppUpdateNotice() {}
|
@Composable fun desktopShowAppUpdateNotice() {}
|
||||||
|
|||||||
+34
@@ -8,6 +8,7 @@ import androidx.compose.foundation.lazy.*
|
|||||||
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
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -21,11 +22,44 @@ expect fun LazyColumnWithScrollBar(
|
|||||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||||
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
||||||
userScrollEnabled: Boolean = true,
|
userScrollEnabled: Boolean = true,
|
||||||
|
additionalBarOffset: State<Dp>? = null,
|
||||||
|
// by default, this function will include .fillMaxSize() without you doing anything. If you don't need it, pass `false` here
|
||||||
|
// maxSize (at least maxHeight) is needed for blur on appBars to work correctly
|
||||||
|
fillMaxSize: Boolean = true,
|
||||||
|
content: LazyListScope.() -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
expect fun LazyColumnWithScrollBarNoAppBar(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
state: LazyListState? = null,
|
||||||
|
contentPadding: PaddingValues = PaddingValues(0.dp),
|
||||||
|
reverseLayout: Boolean = false,
|
||||||
|
verticalArrangement: Arrangement.Vertical =
|
||||||
|
if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
|
||||||
|
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||||
|
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
||||||
|
userScrollEnabled: Boolean = true,
|
||||||
|
additionalBarOffset: State<Dp>? = null,
|
||||||
content: LazyListScope.() -> Unit
|
content: LazyListScope.() -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
expect fun ColumnWithScrollBar(
|
expect fun ColumnWithScrollBar(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
||||||
|
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||||
|
state: ScrollState? = null,
|
||||||
|
// set true when you want to show something in the center with respected .fillMaxSize()
|
||||||
|
maxIntrinsicSize: Boolean = false,
|
||||||
|
// by default, this function will include .fillMaxSize() without you doing anything. If you don't need it, pass `false` here
|
||||||
|
// maxSize (at least maxHeight) is needed for blur on appBars to work correctly
|
||||||
|
fillMaxSize: Boolean = true,
|
||||||
|
content: @Composable ColumnScope.() -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
expect fun ColumnWithScrollBarNoAppBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
||||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||||
|
|||||||
+17
-11
@@ -1,14 +1,14 @@
|
|||||||
package chat.simplex.common.ui.theme
|
package chat.simplex.common.ui.theme
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
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.graphics.layer.GraphicsLayer
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.Density
|
import androidx.compose.ui.unit.*
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import chat.simplex.common.model.ChatController
|
import chat.simplex.common.model.ChatController
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
@@ -587,20 +587,26 @@ data class ThemeModeOverride (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Modifier.themedBackground(baseTheme: DefaultTheme = CurrentColors.value.base, shape: Shape = RectangleShape): Modifier {
|
fun Modifier.themedBackground(baseTheme: DefaultTheme = CurrentColors.value.base, bgLayerSize: MutableState<IntSize>?, bgLayer: GraphicsLayer?/*, shape: Shape = RectangleShape*/): Modifier {
|
||||||
return if (baseTheme == DefaultTheme.SIMPLEX) {
|
return drawBehind {
|
||||||
this.background(brush = Brush.linearGradient(
|
copyBackgroundToAppBar(bgLayerSize, bgLayer) {
|
||||||
|
if (baseTheme == DefaultTheme.SIMPLEX) {
|
||||||
|
drawRect(brush = themedBackgroundBrush())
|
||||||
|
} else {
|
||||||
|
drawRect(CurrentColors.value.colors.background)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun themedBackgroundBrush(): Brush = Brush.linearGradient(
|
||||||
listOf(
|
listOf(
|
||||||
CurrentColors.value.colors.background.darker(0.4f),
|
CurrentColors.value.colors.background.darker(0.4f),
|
||||||
CurrentColors.value.colors.background.lighter(0.4f)
|
CurrentColors.value.colors.background.lighter(0.4f)
|
||||||
),
|
),
|
||||||
Offset(0f, Float.POSITIVE_INFINITY),
|
Offset(0f, Float.POSITIVE_INFINITY),
|
||||||
Offset(Float.POSITIVE_INFINITY, 0f)
|
Offset(Float.POSITIVE_INFINITY, 0f)
|
||||||
), shape = shape)
|
)
|
||||||
} else {
|
|
||||||
this.background(color = CurrentColors.value.colors.background, shape = shape)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val DEFAULT_PADDING = 20.dp
|
val DEFAULT_PADDING = 20.dp
|
||||||
val DEFAULT_SPACE_AFTER_ICON = 4.dp
|
val DEFAULT_SPACE_AFTER_ICON = 4.dp
|
||||||
|
|||||||
+1
-6
@@ -1,7 +1,6 @@
|
|||||||
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.material.MaterialTheme
|
|
||||||
import androidx.compose.runtime.MutableState
|
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
|
||||||
@@ -107,7 +106,7 @@ object ThemeManager {
|
|||||||
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
platform.androidSetNightModeIfSupported()
|
platform.androidSetNightModeIfSupported()
|
||||||
val c = CurrentColors.value.colors
|
val c = CurrentColors.value.colors
|
||||||
platform.androidSetStatusAndNavBarColors(c.isLight, c.background, !ChatController.appPrefs.oneHandUI.get(), ChatController.appPrefs.oneHandUI.get())
|
platform.androidSetStatusAndNavigationBarAppearance(c.isLight, c.isLight)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeDarkTheme(theme: String) {
|
fun changeDarkTheme(theme: String) {
|
||||||
@@ -125,10 +124,6 @@ object ThemeManager {
|
|||||||
themeIds[nonSystemThemeName] = prevValue.themeId
|
themeIds[nonSystemThemeName] = prevValue.themeId
|
||||||
appPrefs.currentThemeIds.set(themeIds)
|
appPrefs.currentThemeIds.set(themeIds)
|
||||||
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||||
if (name == ThemeColor.BACKGROUND) {
|
|
||||||
val c = CurrentColors.value.colors
|
|
||||||
platform.androidSetStatusAndNavBarColors(c.isLight, c.background, false, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyThemeColor(name: ThemeColor, color: Color? = null, pref: MutableState<ThemeModeOverride>) {
|
fun applyThemeColor(name: ThemeColor, color: Color? = null, pref: MutableState<ThemeModeOverride>) {
|
||||||
|
|||||||
+42
-41
@@ -7,40 +7,34 @@ import androidx.compose.foundation.lazy.*
|
|||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.layoutId
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.*
|
||||||
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.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.chat.*
|
import chat.simplex.common.views.chat.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.ChatModel
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import kotlinx.coroutines.flow.collect
|
import chat.simplex.common.views.chat.item.CONSOLE_COMPOSE_LAYOUT_ID
|
||||||
|
import chat.simplex.common.views.chat.item.AdaptingBottomPaddingLayout
|
||||||
|
import chat.simplex.common.views.chatlist.NavigationBarBackground
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TerminalView(floating: Boolean = false, close: () -> Unit) {
|
fun TerminalView(floating: Boolean = false) {
|
||||||
val composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }
|
val composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }
|
||||||
val close = {
|
|
||||||
close()
|
|
||||||
if (appPlatform.isDesktop) {
|
|
||||||
ModalManager.center.closeModals()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BackHandler(onBack = {
|
|
||||||
close()
|
|
||||||
})
|
|
||||||
TerminalLayout(
|
TerminalLayout(
|
||||||
composeState,
|
composeState,
|
||||||
floating,
|
floating,
|
||||||
sendCommand = { sendCommand(chatModel, composeState) },
|
sendCommand = { sendCommand(chatModel, composeState) },
|
||||||
close
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +63,6 @@ fun TerminalLayout(
|
|||||||
composeState: MutableState<ComposeState>,
|
composeState: MutableState<ComposeState>,
|
||||||
floating: Boolean,
|
floating: Boolean,
|
||||||
sendCommand: () -> Unit,
|
sendCommand: () -> Unit,
|
||||||
close: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
|
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
|
||||||
val textStyle = remember { mutableStateOf(smallFont) }
|
val textStyle = remember { mutableStateOf(smallFont) }
|
||||||
@@ -77,12 +70,21 @@ fun TerminalLayout(
|
|||||||
fun onMessageChange(s: String) {
|
fun onMessageChange(s: String) {
|
||||||
composeState.value = composeState.value.copy(message = s)
|
composeState.value = composeState.value.copy(message = s)
|
||||||
}
|
}
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
Scaffold(
|
val composeViewHeight = remember { mutableStateOf(0.dp) }
|
||||||
topBar = { CloseSheetBar(close) },
|
AdaptingBottomPaddingLayout(Modifier, CONSOLE_COMPOSE_LAYOUT_ID, composeViewHeight) {
|
||||||
bottomBar = {
|
TerminalLog(floating, composeViewHeight)
|
||||||
Column {
|
Column(
|
||||||
|
Modifier
|
||||||
|
.layoutId(CONSOLE_COMPOSE_LAYOUT_ID)
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.consumeWindowInsets(PaddingValues(bottom = if (oneHandUI.value) AppBarHeight * fontSizeSqrtMultiplier else 0.dp))
|
||||||
|
.imePadding()
|
||||||
|
.padding(bottom = if (oneHandUI.value) AppBarHeight * fontSizeSqrtMultiplier else 0.dp)
|
||||||
|
.background(MaterialTheme.colors.background)
|
||||||
|
) {
|
||||||
Divider()
|
Divider()
|
||||||
Box(Modifier.padding(horizontal = 8.dp)) {
|
Box(Modifier.padding(horizontal = 8.dp)) {
|
||||||
SendMsgView(
|
SendMsgView(
|
||||||
@@ -110,32 +112,21 @@ fun TerminalLayout(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
contentColor = LocalContentColor.current,
|
|
||||||
modifier = Modifier.navigationBarsWithImePadding()
|
|
||||||
) { contentPadding ->
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(contentPadding)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
color = MaterialTheme.colors.background,
|
|
||||||
contentColor = LocalContentColor.current
|
|
||||||
) {
|
|
||||||
TerminalLog(floating)
|
|
||||||
}
|
}
|
||||||
|
if (!oneHandUI.value) {
|
||||||
|
NavigationBarBackground(true, oneHandUI.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TerminalLog(floating: Boolean) {
|
fun TerminalLog(floating: Boolean, composeViewHeight: State<Dp>) {
|
||||||
val reversedTerminalItems by remember {
|
val reversedTerminalItems by remember {
|
||||||
derivedStateOf { chatModel.terminalItems.value.asReversed() }
|
derivedStateOf { chatModel.terminalItems.value.asReversed() }
|
||||||
}
|
}
|
||||||
val clipboard = LocalClipboardManager.current
|
|
||||||
val listState = LocalAppBarHandler.current?.listState ?: rememberLazyListState()
|
val listState = LocalAppBarHandler.current?.listState ?: rememberLazyListState()
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
var autoScrollToBottom = true
|
var autoScrollToBottom = listState.firstVisibleItemIndex <= 1
|
||||||
launch {
|
launch {
|
||||||
snapshotFlow { listState.layoutInfo.totalItemsCount }
|
snapshotFlow { listState.layoutInfo.totalItemsCount }
|
||||||
.filter { autoScrollToBottom }
|
.filter { autoScrollToBottom }
|
||||||
@@ -150,12 +141,21 @@ fun TerminalLog(floating: Boolean) {
|
|||||||
launch {
|
launch {
|
||||||
snapshotFlow { listState.firstVisibleItemIndex }
|
snapshotFlow { listState.firstVisibleItemIndex }
|
||||||
.collect {
|
.collect {
|
||||||
autoScrollToBottom = listState.firstVisibleItemIndex == 0
|
autoScrollToBottom = it == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LazyColumnWithScrollBar(reverseLayout = true, state = listState) {
|
LazyColumnWithScrollBar (
|
||||||
|
reverseLayout = true,
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
top = topPaddingToContent(),
|
||||||
|
bottom = composeViewHeight.value
|
||||||
|
),
|
||||||
|
state = listState,
|
||||||
|
additionalBarOffset = composeViewHeight
|
||||||
|
) {
|
||||||
items(reversedTerminalItems, key = { item -> item.id to item.createdAtNanos }) { item ->
|
items(reversedTerminalItems, key = { item -> item.id to item.createdAtNanos }) { item ->
|
||||||
|
val clipboard = LocalClipboardManager.current
|
||||||
val rhId = item.remoteHostId
|
val rhId = item.remoteHostId
|
||||||
val rhIdStr = if (rhId == null) "" else "$rhId "
|
val rhIdStr = if (rhId == null) "" else "$rhId "
|
||||||
Text(
|
Text(
|
||||||
@@ -172,7 +172,8 @@ fun TerminalLog(floating: Boolean) {
|
|||||||
ModalManager.start
|
ModalManager.start
|
||||||
}
|
}
|
||||||
modalPlace.showModal(endButtons = { ShareButton { clipboard.shareText(item.details) } }) {
|
modalPlace.showModal(endButtons = { ShareButton { clipboard.shareText(item.details) } }) {
|
||||||
SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
ColumnWithScrollBar {
|
||||||
|
SelectionContainer {
|
||||||
val details = item.details
|
val details = item.details
|
||||||
.let {
|
.let {
|
||||||
if (it.length < 100_000) it
|
if (it.length < 100_000) it
|
||||||
@@ -181,6 +182,7 @@ fun TerminalLog(floating: Boolean) {
|
|||||||
Text(details, modifier = Modifier.heightIn(max = 50_000.dp).padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING))
|
Text(details, modifier = Modifier.heightIn(max = 50_000.dp).padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}.padding(horizontal = 8.dp, vertical = 4.dp)
|
}.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -208,8 +210,7 @@ fun PreviewTerminalLayout() {
|
|||||||
TerminalLayout(
|
TerminalLayout(
|
||||||
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) },
|
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) },
|
||||||
sendCommand = {},
|
sendCommand = {},
|
||||||
floating = false,
|
floating = false
|
||||||
close = {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-29
@@ -40,8 +40,6 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val keyboardState by getKeyboardState()
|
val keyboardState by getKeyboardState()
|
||||||
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
||||||
|
|
||||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -50,11 +48,9 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
val displayName = rememberSaveable { mutableStateOf("") }
|
val displayName = rememberSaveable { mutableStateOf("") }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||||
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING)
|
AppBarTitle(stringResource(MR.strings.create_profile), withPadding = false, bottomPadding = DEFAULT_PADDING)
|
||||||
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(MR.strings.display_name),
|
stringResource(MR.strings.display_name),
|
||||||
@@ -103,7 +99,6 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||||
@@ -111,31 +106,15 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val keyboardState by getKeyboardState()
|
val keyboardState by getKeyboardState()
|
||||||
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
||||||
val handler = remember { AppBarHandler() }
|
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||||
CompositionLocalProvider(
|
ModalView({
|
||||||
LocalAppBarHandler provides handler
|
|
||||||
) {
|
|
||||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.themedBackground(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
CloseSheetBar(close = {
|
|
||||||
if (chatModel.users.none { !it.user.hidden }) {
|
if (chatModel.users.none { !it.user.hidden }) {
|
||||||
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||||
} else {
|
} else {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
})
|
}) {
|
||||||
BackHandler(onBack = {
|
ColumnWithScrollBar {
|
||||||
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
val displayName = rememberSaveable { mutableStateOf("") }
|
val displayName = rememberSaveable { mutableStateOf("") }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) {
|
Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) {
|
||||||
@@ -165,7 +144,6 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
setLastVersionDefault(chatModel)
|
setLastVersionDefault(chatModel)
|
||||||
}
|
}
|
||||||
@@ -255,7 +233,6 @@ fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isVal
|
|||||||
val modifier = Modifier
|
val modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.heightIn(min = 50.dp)
|
.heightIn(min = 50.dp)
|
||||||
.navigationBarsWithImePadding()
|
|
||||||
.onFocusChanged { focused = it.isFocused }
|
.onFocusChanged { focused = it.isFocused }
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
@@ -289,6 +266,7 @@ fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isVal
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
isError = false,
|
isError = false,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ fun IncomingCallAlertLayout(
|
|||||||
acceptCall: () -> Unit
|
acceptCall: () -> Unit
|
||||||
) {
|
) {
|
||||||
val color = if (isInDarkTheme()) MaterialTheme.colors.surface else IncomingCallLight
|
val color = if (isInDarkTheme()) MaterialTheme.colors.surface else IncomingCallLight
|
||||||
Column(Modifier.fillMaxWidth().background(color).padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING, start = DEFAULT_PADDING, end = 8.dp)) {
|
Column(Modifier.fillMaxWidth().background(color).statusBarsPadding().padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING, start = DEFAULT_PADDING, end = 8.dp)) {
|
||||||
IncomingCallInfo(invitation, chatModel)
|
IncomingCallInfo(invitation, chatModel)
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
|||||||
+1
-4
@@ -529,10 +529,7 @@ fun ChatInfoLayout(
|
|||||||
KeyChangeEffect(chat.id) {
|
KeyChangeEffect(chat.id) {
|
||||||
scope.launch { scrollState.scrollTo(0) }
|
scope.launch { scrollState.scrollTo(0) }
|
||||||
}
|
}
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
|
|||||||
+4
-4
@@ -276,7 +276,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryTab() {
|
fun HistoryTab() {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
|
ColumnWithScrollBar {
|
||||||
Details()
|
Details()
|
||||||
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
|
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
|
||||||
val versions = ciInfo.itemVersions
|
val versions = ciInfo.itemVersions
|
||||||
@@ -300,7 +300,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun QuoteTab(qi: CIQuote) {
|
fun QuoteTab(qi: CIQuote) {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
|
ColumnWithScrollBar {
|
||||||
Details()
|
Details()
|
||||||
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
|
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
|
||||||
SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
|
SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
|
||||||
@@ -313,7 +313,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ForwardedFromTab(forwardedFromItem: AChatItem) {
|
fun ForwardedFromTab(forwardedFromItem: AChatItem) {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
|
ColumnWithScrollBar {
|
||||||
Details()
|
Details()
|
||||||
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
|
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
|
||||||
SectionView {
|
SectionView {
|
||||||
@@ -375,7 +375,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeliveryTab(memberDeliveryStatuses: List<MemberDeliveryStatus>) {
|
fun DeliveryTab(memberDeliveryStatuses: List<MemberDeliveryStatus>) {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
|
ColumnWithScrollBar {
|
||||||
Details()
|
Details()
|
||||||
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
|
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
|
||||||
val mss = membersStatuses(chatModel, memberDeliveryStatuses)
|
val mss = membersStatuses(chatModel, memberDeliveryStatuses)
|
||||||
|
|||||||
+171
-175
@@ -12,10 +12,11 @@ import androidx.compose.material.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
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.*
|
||||||
import androidx.compose.ui.draw.drawWithCache
|
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
|
import androidx.compose.ui.graphics.layer.GraphicsLayer
|
||||||
import androidx.compose.ui.layout.layoutId
|
import androidx.compose.ui.layout.layoutId
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
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
|
||||||
@@ -108,6 +109,7 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val clipboard = LocalClipboardManager.current
|
val clipboard = LocalClipboardManager.current
|
||||||
|
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false)) {
|
||||||
when (chatInfo) {
|
when (chatInfo) {
|
||||||
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
|
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
|
||||||
val perChatTheme = remember(chatInfo, CurrentColors.value.base) { if (chatInfo is ChatInfo.Direct) chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null }
|
val perChatTheme = remember(chatInfo, CurrentColors.value.base) { if (chatInfo is ChatInfo.Direct) chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null }
|
||||||
@@ -523,28 +525,10 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
|||||||
showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(),
|
showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(),
|
||||||
showSearch = showSearch
|
showSearch = showSearch
|
||||||
)
|
)
|
||||||
if (appPlatform.isAndroid) {
|
|
||||||
val backgroundColor = MaterialTheme.colors.background
|
|
||||||
val backgroundColorState = rememberUpdatedState(backgroundColor)
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
snapshotFlow { ModalManager.center.modalCount.value > 0 }
|
|
||||||
.collect { modalBackground ->
|
|
||||||
if (modalBackground) {
|
|
||||||
platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, false, false)
|
|
||||||
} else {
|
|
||||||
platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, backgroundColorState.value, true, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ChatInfo.ContactConnection -> {
|
is ChatInfo.ContactConnection -> {
|
||||||
val close = { chatModel.chatId.value = null }
|
val close = { chatModel.chatId.value = null }
|
||||||
val handler = remember { AppBarHandler() }
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalAppBarHandler provides handler
|
|
||||||
) {
|
|
||||||
ModalView(close, showClose = appPlatform.isAndroid, content = {
|
ModalView(close, showClose = appPlatform.isAndroid, content = {
|
||||||
ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close)
|
ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close)
|
||||||
})
|
})
|
||||||
@@ -554,13 +538,8 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
|||||||
chatModel.chatItems.clear()
|
chatModel.chatItems.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
is ChatInfo.InvalidJSON -> {
|
is ChatInfo.InvalidJSON -> {
|
||||||
val close = { chatModel.chatId.value = null }
|
val close = { chatModel.chatId.value = null }
|
||||||
val handler = remember { AppBarHandler() }
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalAppBarHandler provides handler
|
|
||||||
) {
|
|
||||||
ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = {
|
ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = {
|
||||||
InvalidJSONView(chatInfo.json)
|
InvalidJSONView(chatInfo.json)
|
||||||
})
|
})
|
||||||
@@ -570,11 +549,11 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
|||||||
chatModel.chatItems.clear()
|
chatModel.chatItems.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) {
|
fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) {
|
||||||
withBGApi {
|
withBGApi {
|
||||||
@@ -642,24 +621,15 @@ fun ChatLayout(
|
|||||||
.desktopOnExternalDrag(
|
.desktopOnExternalDrag(
|
||||||
enabled = remember(attachmentDisabled.value, chatInfo.value?.userCanSend) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.userCanSend == true) }.value,
|
enabled = remember(attachmentDisabled.value, chatInfo.value?.userCanSend) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.userCanSend == true) }.value,
|
||||||
onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) },
|
onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) },
|
||||||
onImage = {
|
onImage = { file -> CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(file.toURI()), null) } },
|
||||||
// TODO: file is not saved anywhere?!
|
|
||||||
val tmpFile = File.createTempFile("image", ".bmp", tmpDir)
|
|
||||||
tmpFile.deleteOnExit()
|
|
||||||
chatModel.filesToDelete.add(tmpFile)
|
|
||||||
val uri = tmpFile.toURI()
|
|
||||||
CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(uri), null) }
|
|
||||||
},
|
|
||||||
onText = {
|
onText = {
|
||||||
// Need to parse HTML in order to correctly display the content
|
// Need to parse HTML in order to correctly display the content
|
||||||
//composeState.value = composeState.value.copy(message = composeState.value.message + it)
|
//composeState.value = composeState.value.copy(message = composeState.value.message + it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
|
||||||
ModalBottomSheetLayout(
|
ModalBottomSheetLayout(
|
||||||
scrimColor = Color.Black.copy(alpha = 0.12F),
|
scrimColor = Color.Black.copy(alpha = 0.12F),
|
||||||
modifier = Modifier.navigationBarsWithImePadding(),
|
|
||||||
sheetElevation = 0.dp,
|
sheetElevation = 0.dp,
|
||||||
sheetContent = {
|
sheetContent = {
|
||||||
ChooseAttachmentView(
|
ChooseAttachmentView(
|
||||||
@@ -670,53 +640,48 @@ fun ChatLayout(
|
|||||||
sheetState = attachmentBottomSheetState,
|
sheetState = attachmentBottomSheetState,
|
||||||
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
||||||
) {
|
) {
|
||||||
val floatingButton: MutableState<@Composable () -> Unit> = remember { mutableStateOf({}) }
|
val composeViewHeight = remember { mutableStateOf(0.dp) }
|
||||||
val setFloatingButton = { button: @Composable () -> Unit ->
|
Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer)) {
|
||||||
floatingButton.value = button
|
val remoteHostId = remember { remoteHostId }.value
|
||||||
|
val chatInfo = remember { chatInfo }.value
|
||||||
|
AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) {
|
||||||
|
if (chatInfo != null) {
|
||||||
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
ChatItemsList(
|
||||||
|
remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue,
|
||||||
|
useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages,
|
||||||
|
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
|
||||||
|
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
|
||||||
|
setReaction, showItemDetails, markRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Scaffold(
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
topBar = {
|
Box(
|
||||||
|
Modifier
|
||||||
|
.layoutId(CHAT_COMPOSE_LAYOUT_ID)
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.imePadding()
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.then(if (oneHandUI.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier)
|
||||||
|
) {
|
||||||
|
composeView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
StatusBarBackground()
|
||||||
|
} else {
|
||||||
|
NavigationBarBackground(true, oneHandUI.value, noAlpha = true)
|
||||||
|
}
|
||||||
|
Box(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
|
||||||
if (selectedChatItems.value == null) {
|
if (selectedChatItems.value == null) {
|
||||||
val chatInfo = chatInfo.value
|
|
||||||
if (chatInfo != null) {
|
if (chatInfo != null) {
|
||||||
ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
|
ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SelectedItemsTopToolbar(selectedChatItems)
|
SelectedItemsTopToolbar(selectedChatItems)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
bottomBar = composeView,
|
|
||||||
modifier = Modifier.navigationBarsWithImePadding(),
|
|
||||||
floatingActionButton = { floatingButton.value() },
|
|
||||||
contentColor = LocalContentColor.current,
|
|
||||||
backgroundColor = Color.Unspecified
|
|
||||||
) { 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
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(MaterialTheme.colors.background)
|
|
||||||
.then(if (wallpaperImage != null)
|
|
||||||
Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) }
|
|
||||||
else
|
|
||||||
Modifier)
|
|
||||||
.padding(contentPadding)
|
|
||||||
) {
|
|
||||||
val remoteHostId = remember { remoteHostId }.value
|
|
||||||
val chatInfo = remember { chatInfo }.value
|
|
||||||
if (chatInfo != null) {
|
|
||||||
ChatItemsList(
|
|
||||||
remoteHostId, chatInfo, unreadCount, composeState, searchValue,
|
|
||||||
useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages,
|
|
||||||
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
|
|
||||||
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
|
|
||||||
setReaction, showItemDetails, markRead, setFloatingButton, onComposed, developerTools, showViaProxy,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -724,7 +689,7 @@ fun ChatLayout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatInfoToolbar(
|
fun BoxScope.ChatInfoToolbar(
|
||||||
chatInfo: ChatInfo,
|
chatInfo: ChatInfo,
|
||||||
back: () -> Unit,
|
back: () -> Unit,
|
||||||
info: () -> Unit,
|
info: () -> Unit,
|
||||||
@@ -876,24 +841,36 @@ fun ChatInfoToolbar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
DefaultTopAppBar(
|
DefaultAppBar(
|
||||||
navigationButton = { if (appPlatform.isAndroid || showSearch.value) { NavigationButtonBack(onBackClicked) } },
|
navigationButton = { if (appPlatform.isAndroid || showSearch.value) { NavigationButtonBack(onBackClicked) } },
|
||||||
title = { ChatInfoToolbarTitle(chatInfo) },
|
title = { ChatInfoToolbarTitle(chatInfo) },
|
||||||
onTitleClick = if (chatInfo is ChatInfo.Local) null else info,
|
onTitleClick = if (chatInfo is ChatInfo.Local) null else info,
|
||||||
showSearch = showSearch.value,
|
showSearch = showSearch.value,
|
||||||
|
onTop = !oneHandUI.value,
|
||||||
onSearchValueChanged = onSearchValueChanged,
|
onSearchValueChanged = onSearchValueChanged,
|
||||||
buttons = barButtons
|
buttons = { barButtons.forEach { it() } }
|
||||||
)
|
)
|
||||||
|
Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd)) {
|
||||||
Divider(Modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier))
|
val density = LocalDensity.current
|
||||||
|
val width = remember { mutableStateOf(250.dp) }
|
||||||
Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd).offset(y = AppBarHeight * fontSizeSqrtMultiplier)) {
|
val height = remember { mutableStateOf(0.dp) }
|
||||||
DefaultDropdownMenu(showMenu) {
|
DefaultDropdownMenu(
|
||||||
|
showMenu,
|
||||||
|
modifier = Modifier.onSizeChanged { with(density) {
|
||||||
|
width.value = it.width.toDp().coerceAtLeast(250.dp)
|
||||||
|
if (oneHandUI.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) height.value = it.height.toDp()
|
||||||
|
} },
|
||||||
|
offset = DpOffset(-width.value, if (oneHandUI.value) -height.value else AppBarHeight)
|
||||||
|
) {
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
menuItems.asReversed().forEach { it() }
|
||||||
|
} else {
|
||||||
menuItems.forEach { it() }
|
menuItems.forEach { it() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) {
|
fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) {
|
||||||
@@ -934,11 +911,12 @@ private fun ContactVerifiedShield() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BoxWithConstraintsScope.ChatItemsList(
|
fun BoxScope.ChatItemsList(
|
||||||
remoteHostId: Long?,
|
remoteHostId: Long?,
|
||||||
chatInfo: ChatInfo,
|
chatInfo: ChatInfo,
|
||||||
unreadCount: State<Int>,
|
unreadCount: State<Int>,
|
||||||
composeState: MutableState<ComposeState>,
|
composeState: MutableState<ComposeState>,
|
||||||
|
composeViewHeight: State<Dp>,
|
||||||
searchValue: State<String>,
|
searchValue: State<String>,
|
||||||
useLinkPreviews: Boolean,
|
useLinkPreviews: Boolean,
|
||||||
linkMode: SimplexLinkMode,
|
linkMode: SimplexLinkMode,
|
||||||
@@ -963,7 +941,6 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit,
|
setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit,
|
||||||
showItemDetails: (ChatInfo, ChatItem) -> Unit,
|
showItemDetails: (ChatInfo, ChatItem) -> Unit,
|
||||||
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
|
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
|
||||||
setFloatingButton: (@Composable () -> Unit) -> Unit,
|
|
||||||
onComposed: suspend (chatId: String) -> Unit,
|
onComposed: suspend (chatId: String) -> Unit,
|
||||||
developerTools: Boolean,
|
developerTools: Boolean,
|
||||||
showViaProxy: Boolean
|
showViaProxy: Boolean
|
||||||
@@ -987,14 +964,19 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
PreloadItems(chatInfo.id, listState, ChatPagination.UNTIL_PRELOAD_COUNT, loadPrevMessages)
|
PreloadItems(chatInfo.id, listState, ChatPagination.UNTIL_PRELOAD_COUNT, loadPrevMessages)
|
||||||
|
|
||||||
Spacer(Modifier.size(8.dp))
|
Spacer(Modifier.size(8.dp))
|
||||||
val reversedChatItems by remember { derivedStateOf { chatModel.chatItems.asReversed() } }
|
val reversedChatItems = remember { derivedStateOf { chatModel.chatItems.asReversed() } }
|
||||||
val maxHeightRounded = with(LocalDensity.current) { maxHeight.roundToPx() }
|
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() })
|
||||||
val scrollToItem: (Long) -> Unit = { itemId: Long ->
|
val maxHeight = remember { derivedStateOf { listState.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } }
|
||||||
val index = reversedChatItems.indexOfFirst { it.id == itemId }
|
val scrollToItem: State<(Long) -> Unit> = remember {
|
||||||
|
mutableStateOf(
|
||||||
|
{ itemId: Long ->
|
||||||
|
val index = reversedChatItems.value.indexOfFirst { it.id == itemId }
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
scope.launch { listState.animateScrollToItem(kotlin.math.min(reversedChatItems.lastIndex, index + 1), -maxHeightRounded) }
|
scope.launch { listState.animateScrollToItem(kotlin.math.min(reversedChatItems.value.lastIndex, index + 1), -maxHeight.value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
// TODO: Having this block on desktop makes ChatItemsList() to recompose twice on chatModel.chatId update instead of once
|
// TODO: Having this block on desktop makes ChatItemsList() to recompose twice on chatModel.chatId update instead of once
|
||||||
LaunchedEffect(chatInfo.id) {
|
LaunchedEffect(chatInfo.id) {
|
||||||
var stopListening = false
|
var stopListening = false
|
||||||
@@ -1011,8 +993,18 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
VideoPlayerHolder.releaseAll()
|
VideoPlayerHolder.releaseAll()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
LazyColumnWithScrollBar(Modifier.align(Alignment.BottomCenter), state = listState, reverseLayout = true) {
|
LazyColumnWithScrollBar(
|
||||||
itemsIndexed(reversedChatItems, key = { _, item -> item.id to item.meta.createdAt.toEpochMilliseconds() }) { i, cItem ->
|
Modifier.align(Alignment.BottomCenter),
|
||||||
|
state = listState,
|
||||||
|
reverseLayout = true,
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
top = topPaddingToContent(),
|
||||||
|
bottom = composeViewHeight.value
|
||||||
|
),
|
||||||
|
additionalBarOffset = composeViewHeight
|
||||||
|
) {
|
||||||
|
itemsIndexed(reversedChatItems.value, key = { _, item -> item.id to item.meta.createdAt.toEpochMilliseconds() }) { i, cItem ->
|
||||||
|
val itemScope = rememberCoroutineScope()
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
// Makes horizontal and vertical scrolling to coexist nicely.
|
// Makes horizontal and vertical scrolling to coexist nicely.
|
||||||
// With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view
|
// With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view
|
||||||
@@ -1020,10 +1012,10 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
) {
|
) {
|
||||||
val provider = {
|
val provider = {
|
||||||
providerForGallery(i, chatModel.chatItems.value, cItem.id) { indexInReversed ->
|
providerForGallery(i, chatModel.chatItems.value, cItem.id) { indexInReversed ->
|
||||||
scope.launch {
|
itemScope.launch {
|
||||||
listState.scrollToItem(
|
listState.scrollToItem(
|
||||||
kotlin.math.min(reversedChatItems.lastIndex, indexInReversed + 1),
|
kotlin.math.min(reversedChatItems.value.lastIndex, indexInReversed + 1),
|
||||||
-maxHeightRounded
|
-maxHeight.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1036,7 +1028,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
tryOrShowError("${cItem.id}ChatItem", error = {
|
tryOrShowError("${cItem.id}ChatItem", error = {
|
||||||
CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart)
|
CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart)
|
||||||
}) {
|
}) {
|
||||||
ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
|
ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem.value, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1044,7 +1036,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
fun ChatItemView(cItem: ChatItem, range: IntRange?, prevItem: ChatItem?, itemSeparation: ItemSeparation, previousItemSeparation: ItemSeparation?) {
|
fun ChatItemView(cItem: ChatItem, range: IntRange?, prevItem: ChatItem?, itemSeparation: ItemSeparation, previousItemSeparation: ItemSeparation?) {
|
||||||
val dismissState = rememberDismissState(initialValue = DismissValue.Default) {
|
val dismissState = rememberDismissState(initialValue = DismissValue.Default) {
|
||||||
if (it == DismissValue.DismissedToStart) {
|
if (it == DismissValue.DismissedToStart) {
|
||||||
scope.launch {
|
itemScope.launch {
|
||||||
if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local) {
|
if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local) {
|
||||||
if (composeState.value.editing) {
|
if (composeState.value.editing) {
|
||||||
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
|
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
|
||||||
@@ -1241,16 +1233,17 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val range = chatViewItemsRange(currIndex, prevHidden)
|
val range = chatViewItemsRange(currIndex, prevHidden)
|
||||||
|
val reversed = reversedChatItems.value
|
||||||
if (revealed.value && range != null) {
|
if (revealed.value && range != null) {
|
||||||
reversedChatItems.subList(range.first, range.last + 1).forEachIndexed { index, ci ->
|
reversed.subList(range.first, range.last + 1).forEachIndexed { index, ci ->
|
||||||
val prev = if (index + range.first == prevHidden) prevItem else reversedChatItems[index + range.first + 1]
|
val prev = if (index + range.first == prevHidden) prevItem else reversed[index + range.first + 1]
|
||||||
ChatItemView(ci, null, prev, itemSeparation, previousItemSeparation)
|
ChatItemView(ci, null, prev, itemSeparation, previousItemSeparation)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ChatItemView(cItem, range, prevItem, itemSeparation, previousItemSeparation)
|
ChatItemView(cItem, range, prevItem, itemSeparation, previousItemSeparation)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == reversedChatItems.lastIndex) {
|
if (i == reversed.lastIndex) {
|
||||||
DateSeparator(cItem.meta.itemTs)
|
DateSeparator(cItem.meta.itemTs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1258,7 +1251,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
|
|
||||||
if (cItem.isRcvNew && chatInfo.id == ChatModel.chatId.value) {
|
if (cItem.isRcvNew && chatInfo.id == ChatModel.chatId.value) {
|
||||||
LaunchedEffect(cItem.id) {
|
LaunchedEffect(cItem.id) {
|
||||||
scope.launch {
|
itemScope.launch {
|
||||||
delay(600)
|
delay(600)
|
||||||
markRead(CC.ItemRange(cItem.id, cItem.id), null)
|
markRead(CC.ItemRange(cItem.id, cItem.id), null)
|
||||||
}
|
}
|
||||||
@@ -1267,10 +1260,10 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FloatingButtons(chatModel.chatItems, unreadCount, remoteHostId, chatInfo, searchValue, markRead, setFloatingButton, listState)
|
FloatingButtons(chatModel.chatItems, unreadCount, composeViewHeight, remoteHostId, chatInfo, searchValue, markRead, listState)
|
||||||
|
|
||||||
FloatingDate(
|
FloatingDate(
|
||||||
Modifier.padding(top = 10.dp).align(Alignment.TopCenter),
|
Modifier.padding(top = 10.dp + topPaddingToContent()).align(Alignment.TopCenter),
|
||||||
listState,
|
listState,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1325,87 +1318,65 @@ private fun ScrollToBottom(chatId: ChatId, listState: LazyListState, chatItems:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BoxWithConstraintsScope.FloatingButtons(
|
fun BoxScope.FloatingButtons(
|
||||||
chatItems: State<List<ChatItem>>,
|
chatItems: State<List<ChatItem>>,
|
||||||
unreadCount: State<Int>,
|
unreadCount: State<Int>,
|
||||||
|
composeViewHeight: State<Dp>,
|
||||||
remoteHostId: Long?,
|
remoteHostId: Long?,
|
||||||
chatInfo: ChatInfo,
|
chatInfo: ChatInfo,
|
||||||
searchValue: State<String>,
|
searchValue: State<String>,
|
||||||
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
|
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
|
||||||
setFloatingButton: (@Composable () -> Unit) -> Unit,
|
|
||||||
listState: LazyListState
|
listState: LazyListState
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var firstVisibleIndex by remember { mutableStateOf(listState.firstVisibleItemIndex) }
|
val maxHeight = remember { derivedStateOf { listState.layoutInfo.viewportSize.height } }
|
||||||
var lastIndexOfVisibleItems by remember { mutableStateOf(listState.layoutInfo.visibleItemsInfo.lastIndex) }
|
val bottomUnreadCount = remember {
|
||||||
var firstItemIsVisible by remember { mutableStateOf(firstVisibleIndex == 0) }
|
|
||||||
|
|
||||||
LaunchedEffect(listState) {
|
|
||||||
snapshotFlow { listState.firstVisibleItemIndex }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.collect {
|
|
||||||
firstVisibleIndex = it
|
|
||||||
firstItemIsVisible = firstVisibleIndex == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(listState) {
|
|
||||||
// When both snapshotFlows located in one LaunchedEffect second block will never be called because coroutine is paused on first block
|
|
||||||
// so separate them into two LaunchedEffects
|
|
||||||
snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastIndex }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.collect {
|
|
||||||
lastIndexOfVisibleItems = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val bottomUnreadCount by remember {
|
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (unreadCount.value == 0) return@derivedStateOf 0
|
if (unreadCount.value == 0) return@derivedStateOf 0
|
||||||
val items = chatItems.value
|
val items = chatItems.value
|
||||||
val from = items.lastIndex - firstVisibleIndex - lastIndexOfVisibleItems
|
val from = items.lastIndex - listState.firstVisibleItemIndex - listState.layoutInfo.visibleItemsInfo.lastIndex
|
||||||
if (items.size <= from || from < 0) return@derivedStateOf 0
|
if (items.size <= from || from < 0) return@derivedStateOf 0
|
||||||
|
|
||||||
items.subList(from, items.size).count { it.isRcvNew }
|
items.subList(from, items.size).count { it.isRcvNew }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val firstVisibleOffset = (-with(LocalDensity.current) { maxHeight.roundToPx() } * 0.8).toInt()
|
|
||||||
|
|
||||||
LaunchedEffect(bottomUnreadCount, firstItemIsVisible) {
|
val showBottomButtonWithCounter = remember { derivedStateOf { bottomUnreadCount.value > 0 && listState.firstVisibleItemIndex != 0 && searchValue.value.isEmpty() } }
|
||||||
val showButtonWithCounter = bottomUnreadCount > 0 && !firstItemIsVisible && searchValue.value.isEmpty()
|
val showBottomButtonWithArrow = remember { derivedStateOf { !showBottomButtonWithCounter.value && listState.firstVisibleItemIndex != 0 } }
|
||||||
val showButtonWithArrow = !showButtonWithCounter && !firstItemIsVisible
|
BottomEndFloatingButton(
|
||||||
setFloatingButton(
|
|
||||||
bottomEndFloatingButton(
|
|
||||||
bottomUnreadCount,
|
bottomUnreadCount,
|
||||||
showButtonWithCounter,
|
showBottomButtonWithCounter,
|
||||||
showButtonWithArrow,
|
showBottomButtonWithArrow,
|
||||||
|
composeViewHeight,
|
||||||
onClickArrowDown = {
|
onClickArrowDown = {
|
||||||
scope.launch { listState.animateScrollToItem(0) }
|
scope.launch { listState.animateScrollToItem(0) }
|
||||||
},
|
},
|
||||||
onClickCounter = {
|
onClickCounter = {
|
||||||
scope.launch { listState.animateScrollToItem(kotlin.math.max(0, bottomUnreadCount - 1), firstVisibleOffset) }
|
val firstVisibleOffset = (-maxHeight.value * 0.8).toInt()
|
||||||
}
|
scope.launch { listState.animateScrollToItem(kotlin.math.max(0, bottomUnreadCount.value - 1), firstVisibleOffset) }
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
// Don't show top FAB if is in search
|
// Don't show top FAB if is in search
|
||||||
if (searchValue.value.isNotEmpty()) return
|
if (searchValue.value.isNotEmpty()) return
|
||||||
val fabSize = 56.dp
|
val fabSize = 56.dp
|
||||||
val topUnreadCount by remember {
|
val topUnreadCount = remember { derivedStateOf { unreadCount.value - bottomUnreadCount.value } }
|
||||||
derivedStateOf { unreadCount.value - bottomUnreadCount }
|
|
||||||
}
|
|
||||||
val showButtonWithCounter = topUnreadCount > 0
|
|
||||||
val height = with(LocalDensity.current) { maxHeight.toPx() }
|
|
||||||
val showDropDown = remember { mutableStateOf(false) }
|
val showDropDown = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
TopEndFloatingButton(
|
TopEndFloatingButton(
|
||||||
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp).align(Alignment.TopEnd),
|
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent()).align(Alignment.TopEnd),
|
||||||
topUnreadCount,
|
topUnreadCount,
|
||||||
showButtonWithCounter,
|
onClick = { scope.launch { listState.animateScrollBy(maxHeight.value.toFloat()) } },
|
||||||
onClick = { scope.launch { listState.animateScrollBy(height) } },
|
|
||||||
onLongClick = { showDropDown.value = true }
|
onLongClick = { showDropDown.value = true }
|
||||||
)
|
)
|
||||||
|
|
||||||
Box {
|
Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd)) {
|
||||||
DefaultDropdownMenu(showDropDown, offset = DpOffset(this@FloatingButtons.maxWidth - DEFAULT_PADDING, 24.dp + fabSize)) {
|
val density = LocalDensity.current
|
||||||
|
val width = remember { mutableStateOf(250.dp) }
|
||||||
|
DefaultDropdownMenu(
|
||||||
|
showDropDown,
|
||||||
|
modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) } },
|
||||||
|
offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent())
|
||||||
|
) {
|
||||||
ItemAction(
|
ItemAction(
|
||||||
generalGetString(MR.strings.mark_read),
|
generalGetString(MR.strings.mark_read),
|
||||||
painterResource(MR.images.ic_check),
|
painterResource(MR.images.ic_check),
|
||||||
@@ -1413,7 +1384,7 @@ fun BoxWithConstraintsScope.FloatingButtons(
|
|||||||
val minUnreadItemId = chatModel.chats.value.firstOrNull { it.remoteHostId == remoteHostId && it.id == chatInfo.id }?.chatStats?.minUnreadItemId ?: return@ItemAction
|
val minUnreadItemId = chatModel.chats.value.firstOrNull { it.remoteHostId == remoteHostId && it.id == chatInfo.id }?.chatStats?.minUnreadItemId ?: return@ItemAction
|
||||||
markRead(
|
markRead(
|
||||||
CC.ItemRange(minUnreadItemId, chatItems.value[chatItems.value.size - listState.layoutInfo.visibleItemsInfo.lastIndex - 1].id - 1),
|
CC.ItemRange(minUnreadItemId, chatItems.value[chatItems.value.size - listState.layoutInfo.visibleItemsInfo.lastIndex - 1].id - 1),
|
||||||
bottomUnreadCount
|
bottomUnreadCount.value
|
||||||
)
|
)
|
||||||
showDropDown.value = false
|
showDropDown.value = false
|
||||||
})
|
})
|
||||||
@@ -1475,12 +1446,11 @@ fun MemberImage(member: GroupMember) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun TopEndFloatingButton(
|
private fun TopEndFloatingButton(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
unreadCount: Int,
|
unreadCount: State<Int>,
|
||||||
showButtonWithCounter: Boolean,
|
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit
|
onLongClick: () -> Unit
|
||||||
) = when {
|
) = when {
|
||||||
showButtonWithCounter -> {
|
unreadCount.value > 0 -> {
|
||||||
val interactionSource = interactionSourceWithDetection(onClick, onLongClick)
|
val interactionSource = interactionSourceWithDetection(onClick, onLongClick)
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
{}, // no action here
|
{}, // no action here
|
||||||
@@ -1490,7 +1460,7 @@ private fun TopEndFloatingButton(
|
|||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
unreadCountStr(unreadCount),
|
unreadCountStr(unreadCount.value),
|
||||||
color = MaterialTheme.colors.primary,
|
color = MaterialTheme.colors.primary,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
)
|
)
|
||||||
@@ -1500,6 +1470,16 @@ private fun TopEndFloatingButton(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun topPaddingToContent(): Dp {
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
return if (oneHandUI.value) {
|
||||||
|
WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||||
|
} else {
|
||||||
|
AppBarHeight * fontSizeSqrtMultiplier + WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FloatingDate(
|
private fun FloatingDate(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
@@ -1509,8 +1489,9 @@ private fun FloatingDate(
|
|||||||
var isNearBottom by remember { mutableStateOf(true) }
|
var isNearBottom by remember { mutableStateOf(true) }
|
||||||
val lastVisibleItemDate = remember {
|
val lastVisibleItemDate = remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (listState.layoutInfo.visibleItemsInfo.lastIndex >= 0 && listState.firstVisibleItemIndex >= 0) {
|
if (listState.layoutInfo.visibleItemsInfo.lastIndex >= 0) {
|
||||||
val lastVisibleChatItemIndex = chatModel.chatItems.value.lastIndex - listState.firstVisibleItemIndex - listState.layoutInfo.visibleItemsInfo.lastIndex
|
val lastFullyVisibleOffset = listState.layoutInfo.viewportEndOffset
|
||||||
|
val lastVisibleChatItemIndex = chatModel.chatItems.value.lastIndex - (listState.layoutInfo.visibleItemsInfo.lastOrNull { item -> item.offset + item.size <= lastFullyVisibleOffset && item.size > 0 }?.index ?: 0)
|
||||||
val item = chatModel.chatItems.value.getOrNull(lastVisibleChatItemIndex)
|
val item = chatModel.chatItems.value.getOrNull(lastVisibleChatItemIndex)
|
||||||
val timeZone = TimeZone.currentSystemDefault()
|
val timeZone = TimeZone.currentSystemDefault()
|
||||||
item?.meta?.itemTs?.toLocalDateTime(timeZone)?.date?.atStartOfDayIn(timeZone)
|
item?.meta?.itemTs?.toLocalDateTime(timeZone)?.date?.atStartOfDayIn(timeZone)
|
||||||
@@ -1697,35 +1678,34 @@ fun openGroupLink(groupInfo: GroupInfo, rhId: Long?, view: Any? = null, close: (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bottomEndFloatingButton(
|
@Composable
|
||||||
unreadCount: Int,
|
private fun BoxScope.BottomEndFloatingButton(
|
||||||
showButtonWithCounter: Boolean,
|
unreadCount: State<Int>,
|
||||||
showButtonWithArrow: Boolean,
|
showButtonWithCounter: State<Boolean>,
|
||||||
|
showButtonWithArrow: State<Boolean>,
|
||||||
|
composeViewHeight: State<Dp>,
|
||||||
onClickArrowDown: () -> Unit,
|
onClickArrowDown: () -> Unit,
|
||||||
onClickCounter: () -> Unit
|
onClickCounter: () -> Unit
|
||||||
): @Composable () -> Unit = when {
|
) = when {
|
||||||
showButtonWithCounter -> {
|
showButtonWithCounter.value -> {
|
||||||
{
|
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = onClickCounter,
|
onClick = onClickCounter,
|
||||||
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
|
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp),
|
||||||
backgroundColor = MaterialTheme.colors.secondaryVariant,
|
backgroundColor = MaterialTheme.colors.secondaryVariant,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
unreadCountStr(unreadCount),
|
unreadCountStr(unreadCount.value),
|
||||||
color = MaterialTheme.colors.primary,
|
color = MaterialTheme.colors.primary,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
showButtonWithArrow.value -> {
|
||||||
showButtonWithArrow -> {
|
|
||||||
{
|
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = onClickArrowDown,
|
onClick = onClickArrowDown,
|
||||||
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
|
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp),
|
||||||
backgroundColor = MaterialTheme.colors.secondaryVariant,
|
backgroundColor = MaterialTheme.colors.secondaryVariant,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -1735,10 +1715,7 @@ private fun bottomEndFloatingButton(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else -> {}
|
||||||
else -> {
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -1865,6 +1842,25 @@ private fun memberNames(member: GroupMember, prevMember: GroupMember?, memCount:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Modifier.chatViewBackgroundModifier(
|
||||||
|
colors: Colors,
|
||||||
|
wallpaper: AppWallpaper,
|
||||||
|
backgroundGraphicsLayerSize: MutableState<IntSize>?,
|
||||||
|
backgroundGraphicsLayer: GraphicsLayer?
|
||||||
|
): Modifier {
|
||||||
|
val wallpaperImage = wallpaper.type.image
|
||||||
|
val wallpaperType = wallpaper.type
|
||||||
|
val backgroundColor = wallpaper.background ?: wallpaperType.defaultBackgroundColor(CurrentColors.value.base, colors.background)
|
||||||
|
val tintColor = wallpaper.tint ?: wallpaperType.defaultTintColor(CurrentColors.value.base)
|
||||||
|
|
||||||
|
return this
|
||||||
|
.then(if (wallpaperImage != null)
|
||||||
|
Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor, backgroundGraphicsLayerSize, backgroundGraphicsLayer) }
|
||||||
|
else
|
||||||
|
Modifier.drawWithCache { onDrawBehind { copyBackgroundToAppBar(backgroundGraphicsLayerSize, backgroundGraphicsLayer) { drawRect(backgroundColor) } } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun chatViewItemsRange(currIndex: Int?, prevHidden: Int?): IntRange? =
|
fun chatViewItemsRange(currIndex: Int?, prevHidden: Int?): IntRange? =
|
||||||
if (currIndex != null && prevHidden != null && prevHidden > currIndex) {
|
if (currIndex != null && prevHidden != null && prevHidden > currIndex) {
|
||||||
currIndex..prevHidden
|
currIndex..prevHidden
|
||||||
|
|||||||
+4
-2
@@ -13,12 +13,14 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
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.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.model.ChatModel.filesToDelete
|
import chat.simplex.common.model.ChatModel.filesToDelete
|
||||||
import chat.simplex.common.model.ChatModel.withChats
|
import chat.simplex.common.model.ChatModel.withChats
|
||||||
@@ -896,7 +898,7 @@ fun ComposeView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column(Modifier.background(MaterialTheme.colors.background)) {
|
Box(Modifier.background(MaterialTheme.colors.background)) {
|
||||||
Divider()
|
Divider()
|
||||||
Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) {
|
Row(Modifier.padding(end = 8.dp), 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)
|
||||||
@@ -918,7 +920,7 @@ fun ComposeView(
|
|||||||
&& !nextSendGrpInv.value
|
&& !nextSendGrpInv.value
|
||||||
IconButton(
|
IconButton(
|
||||||
attachmentClicked,
|
attachmentClicked,
|
||||||
Modifier.padding(bottom = if (appPlatform.isAndroid) 2.sp.toDp() else 5.sp.toDp() * fontSizeSqrtMultiplier),
|
Modifier.padding(start = 3.dp, end = 1.dp, bottom = if (appPlatform.isAndroid) 2.sp.toDp() else 5.sp.toDp() * fontSizeSqrtMultiplier),
|
||||||
enabled = attachmentEnabled
|
enabled = attachmentEnabled
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
+1
-4
@@ -81,10 +81,7 @@ private fun ContactPreferencesLayout(
|
|||||||
reset: () -> Unit,
|
reset: () -> Unit,
|
||||||
savePrefs: () -> Unit,
|
savePrefs: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.contact_preferences))
|
AppBarTitle(stringResource(MR.strings.contact_preferences))
|
||||||
val timedMessages: MutableState<Boolean> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.timedMessagesAllowed) }
|
val timedMessages: MutableState<Boolean> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.timedMessagesAllowed) }
|
||||||
val onTTLUpdated = { ttl: Int? ->
|
val onTTLUpdated = { ttl: Int? ->
|
||||||
|
|||||||
+4
-3
@@ -1,9 +1,11 @@
|
|||||||
package chat.simplex.common.views.chat
|
package chat.simplex.common.views.chat
|
||||||
|
|
||||||
|
import SectionBottomSpacer
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import chat.simplex.common.platform.ColumnWithScrollBar
|
||||||
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.newchat.QRCodeScanner
|
import chat.simplex.common.views.newchat.QRCodeScanner
|
||||||
@@ -12,9 +14,7 @@ import dev.icerock.moko.resources.compose.stringResource
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) {
|
fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) {
|
||||||
Column(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.scan_code))
|
AppBarTitle(stringResource(MR.strings.scan_code))
|
||||||
QRCodeScanner { text ->
|
QRCodeScanner { text ->
|
||||||
verifyCode(text) {
|
verifyCode(text) {
|
||||||
@@ -28,5 +28,6 @@ fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: ()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(stringResource(MR.strings.scan_code_from_contacts_app), Modifier.padding(horizontal = DEFAULT_PADDING))
|
Text(stringResource(MR.strings.scan_code_from_contacts_app), Modifier.padding(horizontal = DEFAULT_PADDING))
|
||||||
|
SectionBottomSpacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -12,6 +12,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
import chat.simplex.common.platform.BackHandler
|
import chat.simplex.common.platform.BackHandler
|
||||||
import chat.simplex.common.platform.chatModel
|
import chat.simplex.common.platform.chatModel
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
@@ -20,11 +21,12 @@ import chat.simplex.res.MR
|
|||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectedItemsTopToolbar(selectedChatItems: MutableState<Set<Long>?>) {
|
fun BoxScope.SelectedItemsTopToolbar(selectedChatItems: MutableState<Set<Long>?>) {
|
||||||
val onBackClicked = { selectedChatItems.value = null }
|
val onBackClicked = { selectedChatItems.value = null }
|
||||||
BackHandler(onBack = onBackClicked)
|
BackHandler(onBack = onBackClicked)
|
||||||
val count = selectedChatItems.value?.size ?: 0
|
val count = selectedChatItems.value?.size ?: 0
|
||||||
DefaultTopAppBar(
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
DefaultAppBar(
|
||||||
navigationButton = { NavigationButtonClose(onButtonClicked = onBackClicked) },
|
navigationButton = { NavigationButtonClose(onButtonClicked = onBackClicked) },
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
@@ -39,10 +41,9 @@ fun SelectedItemsTopToolbar(selectedChatItems: MutableState<Set<Long>?>) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onTitleClick = null,
|
onTitleClick = null,
|
||||||
showSearch = false,
|
onTop = !oneHandUI.value,
|
||||||
onSearchValueChanged = {},
|
onSearchValueChanged = {},
|
||||||
)
|
)
|
||||||
Divider(Modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -68,6 +69,8 @@ fun SelectedItemsBottomToolbar(
|
|||||||
Modifier
|
Modifier
|
||||||
.matchParentSize()
|
.matchParentSize()
|
||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
|
.padding(horizontal = 2.dp)
|
||||||
|
.height(AppBarHeight * fontSizeSqrtMultiplier)
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectGesture {
|
detectGesture {
|
||||||
true
|
true
|
||||||
@@ -103,6 +106,7 @@ fun SelectedItemsBottomToolbar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Divider(Modifier.align(Alignment.TopStart))
|
||||||
}
|
}
|
||||||
LaunchedEffect(chatInfo, chatItems, selectedChatItems.value) {
|
LaunchedEffect(chatInfo, chatItems, selectedChatItems.value) {
|
||||||
recheckItems(chatInfo, chatItems, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited)
|
recheckItems(chatInfo, chatItems, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited)
|
||||||
|
|||||||
+8
-5
@@ -7,7 +7,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.*
|
import androidx.compose.foundation.shape.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.*
|
import androidx.compose.ui.*
|
||||||
@@ -15,6 +14,7 @@ import androidx.compose.ui.draw.alpha
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
@@ -61,7 +61,8 @@ fun SendMsgView(
|
|||||||
) {
|
) {
|
||||||
val showCustomDisappearingMessageDialog = remember { mutableStateOf(false) }
|
val showCustomDisappearingMessageDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Box(Modifier.padding(vertical = if (appPlatform.isAndroid) 8.dp else 6.dp)) {
|
val padding = if (appPlatform.isAndroid) PaddingValues(vertical = 8.dp) else PaddingValues(top = 3.dp, bottom = 4.dp)
|
||||||
|
Box(Modifier.padding(padding)) {
|
||||||
val cs = composeState.value
|
val cs = composeState.value
|
||||||
var progressByTimeout by rememberSaveable { mutableStateOf(false) }
|
var progressByTimeout by rememberSaveable { mutableStateOf(false) }
|
||||||
LaunchedEffect(composeState.value.inProgress) {
|
LaunchedEffect(composeState.value.inProgress) {
|
||||||
@@ -147,7 +148,7 @@ fun SendMsgView(
|
|||||||
&& (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value)
|
&& (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value)
|
||||||
&& cs.contextItem is ComposeContextItem.NoContextItem
|
&& cs.contextItem is ComposeContextItem.NoContextItem
|
||||||
) {
|
) {
|
||||||
Spacer(Modifier.width(10.dp))
|
Spacer(Modifier.width(12.dp))
|
||||||
StartLiveMessageButton(userCanSend) {
|
StartLiveMessageButton(userCanSend) {
|
||||||
if (composeState.value.preview is ComposePreview.NoPreview) {
|
if (composeState.value.preview is ComposePreview.NoPreview) {
|
||||||
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
|
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
|
||||||
@@ -423,6 +424,7 @@ private fun SendMsgButton(
|
|||||||
onLongClick: (() -> Unit)? = null
|
onLongClick: (() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val ripple = remember { ripple(bounded = false, radius = 24.dp) }
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.requiredSize(36.dp)
|
modifier = Modifier.requiredSize(36.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
@@ -431,7 +433,7 @@ private fun SendMsgButton(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = rememberRipple(bounded = false, radius = 24.dp)
|
indication = ripple
|
||||||
)
|
)
|
||||||
.onRightClick { onLongClick?.invoke() },
|
.onRightClick { onLongClick?.invoke() },
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
@@ -454,6 +456,7 @@ private fun SendMsgButton(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
|
private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val ripple = remember { ripple(bounded = false, radius = 24.dp) }
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.requiredSize(36.dp)
|
modifier = Modifier.requiredSize(36.dp)
|
||||||
.clickable(
|
.clickable(
|
||||||
@@ -461,7 +464,7 @@ private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = rememberRipple(bounded = false, radius = 24.dp)
|
indication = ripple
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
|
|||||||
+1
-5
@@ -56,11 +56,7 @@ private fun VerifyCodeLayout(
|
|||||||
connectionVerified: Boolean,
|
connectionVerified: Boolean,
|
||||||
verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit,
|
verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = DEFAULT_PADDING)
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.security_code), withPadding = false)
|
AppBarTitle(stringResource(MR.strings.security_code), withPadding = false)
|
||||||
val splitCode = splitToParts(connectionCode, 24)
|
val splitCode = splitToParts(connectionCode, 24)
|
||||||
Row(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalArrangement = Arrangement.Center) {
|
Row(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalArrangement = Arrangement.Center) {
|
||||||
|
|||||||
+1
-4
@@ -130,10 +130,7 @@ fun AddGroupMembersLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.button_add_members))
|
AppBarTitle(stringResource(MR.strings.button_add_members))
|
||||||
profileText()
|
profileText()
|
||||||
Spacer(Modifier.size(DEFAULT_PADDING))
|
Spacer(Modifier.size(DEFAULT_PADDING))
|
||||||
|
|||||||
+13
-2
@@ -25,6 +25,7 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
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.withChats
|
import chat.simplex.common.model.ChatModel.withChats
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
@@ -283,9 +284,14 @@ fun ModalData.GroupChatInfoLayout(
|
|||||||
if (s.isEmpty()) members else members.filter { m -> m.anyNameContains(s) }
|
if (s.isEmpty()) members else members.filter { m -> m.anyNameContains(s) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Box {
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
LazyColumnWithScrollBar(
|
LazyColumnWithScrollBar(
|
||||||
Modifier
|
contentPadding = if (oneHandUI.value) {
|
||||||
.fillMaxWidth(),
|
PaddingValues(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + DEFAULT_PADDING + 5.dp, bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding())
|
||||||
|
} else {
|
||||||
|
PaddingValues(top = topPaddingToContent())
|
||||||
|
},
|
||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
@@ -397,6 +403,11 @@ fun ModalData.GroupChatInfoLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SectionBottomSpacer()
|
SectionBottomSpacer()
|
||||||
|
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!oneHandUI.value) {
|
||||||
|
NavigationBarBackground(oneHandUI.value, oneHandUI.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -119,9 +119,7 @@ fun GroupLinkLayout(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier,
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.group_link))
|
AppBarTitle(stringResource(MR.strings.group_link))
|
||||||
Text(
|
Text(
|
||||||
stringResource(MR.strings.you_can_share_group_link_anybody_will_be_able_to_connect),
|
stringResource(MR.strings.you_can_share_group_link_anybody_will_be_able_to_connect),
|
||||||
|
|||||||
+1
-4
@@ -313,10 +313,7 @@ fun GroupMemberInfoLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
|
|||||||
+1
-3
@@ -82,9 +82,7 @@ private fun GroupPreferencesLayout(
|
|||||||
reset: () -> Unit,
|
reset: () -> Unit,
|
||||||
savePrefs: () -> Unit,
|
savePrefs: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.group_preferences))
|
AppBarTitle(stringResource(MR.strings.group_preferences))
|
||||||
val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) }
|
val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) }
|
||||||
val onTTLUpdated = { ttl: Int? ->
|
val onTTLUpdated = { ttl: Int? ->
|
||||||
|
|||||||
+2
-6
@@ -82,10 +82,9 @@ fun GroupProfileLayout(
|
|||||||
}, close)
|
}, close)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
|
||||||
ModalBottomSheetLayout(
|
ModalBottomSheetLayout(
|
||||||
scrimColor = Color.Black.copy(alpha = 0.12F),
|
scrimColor = Color.Black.copy(alpha = 0.12F),
|
||||||
modifier = Modifier.navigationBarsWithImePadding(),
|
modifier = Modifier.imePadding(),
|
||||||
sheetContent = {
|
sheetContent = {
|
||||||
GetImageBottomSheet(
|
GetImageBottomSheet(
|
||||||
chosenImage,
|
chosenImage,
|
||||||
@@ -98,9 +97,7 @@ fun GroupProfileLayout(
|
|||||||
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
||||||
) {
|
) {
|
||||||
ModalView(close = closeWithAlert) {
|
ModalView(close = closeWithAlert) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxWidth()
|
Modifier.fillMaxWidth()
|
||||||
.padding(horizontal = DEFAULT_PADDING)
|
.padding(horizontal = DEFAULT_PADDING)
|
||||||
@@ -178,7 +175,6 @@ fun GroupProfileLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun canUpdateProfile(displayName: String, groupProfile: GroupProfile): Boolean =
|
private fun canUpdateProfile(displayName: String, groupProfile: GroupProfile): Boolean =
|
||||||
displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, groupProfile)
|
displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, groupProfile)
|
||||||
|
|||||||
+1
-3
@@ -95,9 +95,7 @@ private fun GroupWelcomeLayout(
|
|||||||
linkMode: SimplexLinkMode,
|
linkMode: SimplexLinkMode,
|
||||||
save: () -> Unit,
|
save: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
val editMode = remember { mutableStateOf(true) }
|
val editMode = remember { mutableStateOf(true) }
|
||||||
AppBarTitle(stringResource(MR.strings.group_welcome_title))
|
AppBarTitle(stringResource(MR.strings.group_welcome_title))
|
||||||
val wt = rememberSaveable { welcomeText }
|
val wt = rememberSaveable { welcomeText }
|
||||||
|
|||||||
+67
@@ -10,6 +10,7 @@ import androidx.compose.ui.draw.clipToBounds
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.layout.*
|
import androidx.compose.ui.layout.*
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.UriHandler
|
import androidx.compose.ui.platform.UriHandler
|
||||||
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
|
||||||
@@ -320,6 +321,8 @@ fun CIMarkdownText(
|
|||||||
|
|
||||||
const val CHAT_IMAGE_LAYOUT_ID = "chatImage"
|
const val CHAT_IMAGE_LAYOUT_ID = "chatImage"
|
||||||
const val CHAT_BUBBLE_LAYOUT_ID = "chatBubble"
|
const val CHAT_BUBBLE_LAYOUT_ID = "chatBubble"
|
||||||
|
const val CHAT_COMPOSE_LAYOUT_ID = "chatCompose"
|
||||||
|
const val CONSOLE_COMPOSE_LAYOUT_ID = "consoleCompose"
|
||||||
/**
|
/**
|
||||||
* Equal to [androidx.compose.ui.unit.Constraints.MaxFocusMask], which is 0x3FFFF - 1
|
* Equal to [androidx.compose.ui.unit.Constraints.MaxFocusMask], which is 0x3FFFF - 1
|
||||||
* Other values make a crash `java.lang.IllegalArgumentException: Can't represent a width of 123456 and height of 9909 in Constraints`
|
* Other values make a crash `java.lang.IllegalArgumentException: Can't represent a width of 123456 and height of 9909 in Constraints`
|
||||||
@@ -398,6 +401,70 @@ fun DependentLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The purpose of this layout is to make measuring of bottom compose view and adapt top lazy column to its size in the same frame (not on the next frame as you would expect).
|
||||||
|
// So, steps are:
|
||||||
|
// - measuring the layout: measured height of compose view before this step is 0, it's added to content padding of lazy column (so it's == 0)
|
||||||
|
// - measured the layout: measured height of compose view now is correct, but it's not yet applied to lazy column content padding (so it's == 0) and lazy column is placed higher than compose view in view with respect to compose view's height
|
||||||
|
// - on next frame measured height is correct and content padding is the same, lazy column placed to occupy all parent view's size
|
||||||
|
// - every added/removed line in compose view goes through the same process.
|
||||||
|
@Composable
|
||||||
|
fun AdaptingBottomPaddingLayout(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
mainLayoutId: String,
|
||||||
|
expectedHeight: MutableState<Dp>,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val expected = with(LocalDensity.current) { expectedHeight.value.roundToPx() }
|
||||||
|
Layout(
|
||||||
|
content = content,
|
||||||
|
modifier = modifier
|
||||||
|
) { measureable, constraints ->
|
||||||
|
require(measureable.size <= 2) { "Should be exactly one or two elements in this layout, you have ${measureable.size}" }
|
||||||
|
val mainPlaceable = measureable.firstOrNull { it.layoutId == mainLayoutId }!!.measure(constraints)
|
||||||
|
val placeables: List<Placeable> = measureable.map {
|
||||||
|
if (it.layoutId == mainLayoutId)
|
||||||
|
mainPlaceable
|
||||||
|
else
|
||||||
|
it.measure(constraints.copy(maxHeight = if (expected != mainPlaceable.measuredHeight) constraints.maxHeight - mainPlaceable.measuredHeight + expected else constraints.maxHeight)) }
|
||||||
|
expectedHeight.value = mainPlaceable.measuredHeight.toDp()
|
||||||
|
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||||
|
var y = 0
|
||||||
|
placeables.forEach {
|
||||||
|
if (it !== mainPlaceable) {
|
||||||
|
it.place(0, y)
|
||||||
|
y += it.measuredHeight
|
||||||
|
} else {
|
||||||
|
it.place(0, constraints.maxHeight - mainPlaceable.measuredHeight)
|
||||||
|
y += it.measuredHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CenteredRowLayout(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
Layout(
|
||||||
|
content = content,
|
||||||
|
modifier = modifier
|
||||||
|
) { measureable, constraints ->
|
||||||
|
require(measureable.size == 3) { "Should be exactly three elements in this layout, you have ${measureable.size}" }
|
||||||
|
val first = measureable[0].measure(constraints.copy(minWidth = 0, minHeight = 0))
|
||||||
|
val third = measureable[2].measure(constraints.copy(minWidth = first.measuredWidth, minHeight = 0))
|
||||||
|
val second = measureable[1].measure(constraints.copy(minWidth = 0, minHeight = 0, maxWidth = (constraints.maxWidth - first.measuredWidth - third.measuredWidth).coerceAtLeast(0)))
|
||||||
|
// Limit width for every other element to width of important element and height for a sum of all elements.
|
||||||
|
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||||
|
first.place(0, ((constraints.maxHeight - first.measuredHeight) / 2).coerceAtLeast(0))
|
||||||
|
second.place((constraints.maxWidth - second.measuredWidth) / 2, ((constraints.maxHeight - second.measuredHeight) / 2).coerceAtLeast(0))
|
||||||
|
third.place(constraints.maxWidth - third.measuredWidth, ((constraints.maxHeight - third.measuredHeight) / 2).coerceAtLeast(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
class EditedProvider: PreviewParameterProvider<Boolean> {
|
class EditedProvider: PreviewParameterProvider<Boolean> {
|
||||||
|
|||||||
+10
-3
@@ -12,7 +12,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.input.pointer.*
|
import androidx.compose.ui.input.pointer.*
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
|
||||||
import chat.simplex.common.model.CryptoFile
|
import chat.simplex.common.model.CryptoFile
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.CurrentColors
|
import chat.simplex.common.ui.theme.CurrentColors
|
||||||
@@ -58,9 +57,17 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () ->
|
|||||||
val playersToRelease = rememberSaveable { mutableSetOf<URI>() }
|
val playersToRelease = rememberSaveable { mutableSetOf<URI>() }
|
||||||
DisposableEffectOnGone(
|
DisposableEffectOnGone(
|
||||||
always = {
|
always = {
|
||||||
platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, Color.Black, false, false)
|
platform.androidSetStatusAndNavigationBarAppearance(false, false, blackNavBar = true)
|
||||||
|
chatModel.fullscreenGalleryVisible.value = true
|
||||||
},
|
},
|
||||||
whenGone = { playersToRelease.forEach { VideoPlayerHolder.release(it, true, true) } }
|
whenDispose = {
|
||||||
|
val c = CurrentColors.value.colors
|
||||||
|
platform.androidSetStatusAndNavigationBarAppearance(c.isLight, c.isLight)
|
||||||
|
chatModel.fullscreenGalleryVisible.value = false
|
||||||
|
},
|
||||||
|
whenGone = {
|
||||||
|
playersToRelease.forEach { VideoPlayerHolder.release(it, true, true) }
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+156
-91
@@ -10,8 +10,7 @@ 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.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.*
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.*
|
import androidx.compose.ui.focus.*
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
@@ -34,6 +33,7 @@ import chat.simplex.common.views.onboarding.shouldShowWhatsNew
|
|||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.views.call.Call
|
import chat.simplex.common.views.call.Call
|
||||||
import chat.simplex.common.views.chat.item.CIFileViewScope
|
import chat.simplex.common.views.chat.item.CIFileViewScope
|
||||||
|
import chat.simplex.common.views.chat.topPaddingToContent
|
||||||
import chat.simplex.common.views.newchat.*
|
import chat.simplex.common.views.newchat.*
|
||||||
import chat.simplex.common.views.usersettings.*
|
import chat.simplex.common.views.usersettings.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
@@ -41,7 +41,6 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.net.URI
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
private fun showNewChatSheet(oneHandUI: State<Boolean>) {
|
private fun showNewChatSheet(oneHandUI: State<Boolean>) {
|
||||||
@@ -55,7 +54,7 @@ private fun showNewChatSheet(oneHandUI: State<Boolean>) {
|
|||||||
chatModel.newChatSheetVisible.value = false
|
chatModel.newChatSheetVisible.value = false
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
ModalView(close, closeOnTop = !oneHandUI.value) {
|
ModalView(close, showAppBar = !oneHandUI.value) {
|
||||||
if (appPlatform.isAndroid) {
|
if (appPlatform.isAndroid) {
|
||||||
BackHandler {
|
BackHandler {
|
||||||
close()
|
close()
|
||||||
@@ -122,11 +121,7 @@ fun ToggleChatListCard() {
|
|||||||
|
|
||||||
SharedPreferenceToggle(
|
SharedPreferenceToggle(
|
||||||
appPrefs.oneHandUI,
|
appPrefs.oneHandUI,
|
||||||
enabled = true,
|
enabled = true
|
||||||
onChange = {
|
|
||||||
val c = CurrentColors.value.colors
|
|
||||||
platform.androidSetStatusAndNavBarColors(c.isLight, c.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get())
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,74 +149,36 @@ fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow<Animate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
|
val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
|
||||||
Scaffold(
|
val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
|
||||||
topBar = {
|
Box(Modifier.fillMaxSize()) {
|
||||||
if (!oneHandUI.value) {
|
|
||||||
Column {
|
|
||||||
ChatListToolbar(
|
|
||||||
userPickerState,
|
|
||||||
stopped,
|
|
||||||
setPerformLA,
|
|
||||||
)
|
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
bottomBar = {
|
|
||||||
if (oneHandUI.value) {
|
if (oneHandUI.value) {
|
||||||
Column {
|
ChatListWithLoadingScreen(searchText, listState)
|
||||||
Divider()
|
Column(Modifier.align(Alignment.BottomCenter)) {
|
||||||
ChatListToolbar(
|
ChatListToolbar(
|
||||||
userPickerState,
|
userPickerState,
|
||||||
|
listState,
|
||||||
stopped,
|
stopped,
|
||||||
setPerformLA,
|
setPerformLA,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ChatListWithLoadingScreen(searchText, listState)
|
||||||
|
Column {
|
||||||
|
ChatListToolbar(
|
||||||
|
userPickerState,
|
||||||
|
listState,
|
||||||
|
stopped,
|
||||||
|
setPerformLA,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
if (searchText.value.text.isEmpty() && !chatModel.desktopNoUserNoRemote && chatModel.chatRunning.value == true) {
|
||||||
contentColor = LocalContentColor.current,
|
NewChatSheetFloatingButton(oneHandUI, stopped)
|
||||||
floatingActionButton = {
|
|
||||||
if (!oneHandUI.value && searchText.value.text.isEmpty() && !chatModel.desktopNoUserNoRemote && chatModel.chatRunning.value == true) {
|
|
||||||
FloatingActionButton(
|
|
||||||
onClick = {
|
|
||||||
if (!stopped) {
|
|
||||||
showNewChatSheet(oneHandUI)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Modifier
|
|
||||||
.padding(end = DEFAULT_PADDING - 16.dp, bottom = DEFAULT_PADDING - 16.dp)
|
|
||||||
.size(AppBarHeight * fontSizeSqrtMultiplier),
|
|
||||||
elevation = FloatingActionButtonDefaults.elevation(
|
|
||||||
defaultElevation = 0.dp,
|
|
||||||
pressedElevation = 0.dp,
|
|
||||||
hoveredElevation = 0.dp,
|
|
||||||
focusedElevation = 0.dp,
|
|
||||||
),
|
|
||||||
backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
|
|
||||||
contentColor = Color.White
|
|
||||||
) {
|
|
||||||
Icon(painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(22.dp * fontSizeSqrtMultiplier))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Box(Modifier.padding(it)) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
if (!chatModel.desktopNoUserNoRemote) {
|
|
||||||
ChatList(chatModel, searchText = searchText)
|
|
||||||
}
|
|
||||||
if (chatModel.chats.value.isEmpty() && !chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) {
|
|
||||||
Text(stringResource(
|
|
||||||
if (chatModel.chatRunning.value == null) MR.strings.loading_chats else MR.strings.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchText.value.text.isEmpty()) {
|
if (searchText.value.text.isEmpty()) {
|
||||||
if (appPlatform.isDesktop) {
|
if (appPlatform.isDesktop && !oneHandUI.value) {
|
||||||
val call = remember { chatModel.activeCall }.value
|
val call = remember { chatModel.activeCall }.value
|
||||||
if (call != null) {
|
if (call != null) {
|
||||||
ActiveCallInteractiveArea(call)
|
ActiveCallInteractiveArea(call)
|
||||||
@@ -239,6 +196,46 @@ fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow<Animate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState<TextFieldValue>, listState: LazyListState) {
|
||||||
|
if (!chatModel.desktopNoUserNoRemote) {
|
||||||
|
ChatList(searchText = searchText, listState)
|
||||||
|
}
|
||||||
|
if (chatModel.chats.value.isEmpty() && !chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
if (chatModel.chatRunning.value == null) MR.strings.loading_chats else MR.strings.you_have_no_chats
|
||||||
|
), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BoxScope.NewChatSheetFloatingButton(oneHandUI: State<Boolean>, stopped: Boolean) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
if (!stopped) {
|
||||||
|
showNewChatSheet(oneHandUI)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Modifier
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.size(AppBarHeight * fontSizeSqrtMultiplier),
|
||||||
|
elevation = FloatingActionButtonDefaults.elevation(
|
||||||
|
defaultElevation = 0.dp,
|
||||||
|
pressedElevation = 0.dp,
|
||||||
|
hoveredElevation = 0.dp,
|
||||||
|
focusedElevation = 0.dp,
|
||||||
|
),
|
||||||
|
backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
|
||||||
|
contentColor = Color.White
|
||||||
|
) {
|
||||||
|
Icon(painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(22.dp * fontSizeSqrtMultiplier))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ConnectButton(text: String, onClick: () -> Unit) {
|
private fun ConnectButton(text: String, onClick: () -> Unit) {
|
||||||
Button(
|
Button(
|
||||||
@@ -256,7 +253,7 @@ private fun ConnectButton(text: String, onClick: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChatListToolbar(userPickerState: MutableStateFlow<AnimatedViewState>, stopped: Boolean, setPerformLA: (Boolean) -> Unit) {
|
private fun ChatListToolbar(userPickerState: MutableStateFlow<AnimatedViewState>, listState: LazyListState, stopped: Boolean, setPerformLA: (Boolean) -> Unit) {
|
||||||
val serversSummary: MutableState<PresentedServersSummary?> = remember { mutableStateOf(null) }
|
val serversSummary: MutableState<PresentedServersSummary?> = remember { mutableStateOf(null) }
|
||||||
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
|
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
|
||||||
val updatingProgress = remember { chatModel.updatingProgress }.value
|
val updatingProgress = remember { chatModel.updatingProgress }.value
|
||||||
@@ -265,6 +262,18 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow<AnimatedViewState>
|
|||||||
if (oneHandUI.value) {
|
if (oneHandUI.value) {
|
||||||
val sp16 = with(LocalDensity.current) { 16.sp.toDp() }
|
val sp16 = with(LocalDensity.current) { 16.sp.toDp() }
|
||||||
|
|
||||||
|
if (appPlatform.isDesktop && oneHandUI.value) {
|
||||||
|
val call = remember { chatModel.activeCall }
|
||||||
|
if (call.value != null) {
|
||||||
|
barButtons.add {
|
||||||
|
val c = call.value
|
||||||
|
if (c != null) {
|
||||||
|
ActiveCallInteractiveArea(c)
|
||||||
|
Spacer(Modifier.width(5.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
barButtons.add {
|
barButtons.add {
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -323,7 +332,9 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow<AnimatedViewState>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val clipboard = LocalClipboardManager.current
|
val clipboard = LocalClipboardManager.current
|
||||||
DefaultTopAppBar(
|
val scope = rememberCoroutineScope()
|
||||||
|
val canScrollToZero = remember { derivedStateOf { listState.firstVisibleItemIndex != 0 || listState.firstVisibleItemScrollOffset != 0 } }
|
||||||
|
DefaultAppBar(
|
||||||
navigationButton = {
|
navigationButton = {
|
||||||
if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) {
|
if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) {
|
||||||
NavigationButtonMenu {
|
NavigationButtonMenu {
|
||||||
@@ -351,15 +362,14 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow<AnimatedViewState>
|
|||||||
SubscriptionStatusIndicator(
|
SubscriptionStatusIndicator(
|
||||||
click = {
|
click = {
|
||||||
ModalManager.start.closeModals()
|
ModalManager.start.closeModals()
|
||||||
|
val summary = serversSummary.value
|
||||||
ModalManager.start.showModalCloseable(
|
ModalManager.start.showModalCloseable(
|
||||||
endButtons = {
|
endButtons = {
|
||||||
val summary = serversSummary.value
|
|
||||||
if (summary != null) {
|
if (summary != null) {
|
||||||
ShareButton {
|
ShareButton {
|
||||||
val json = Json {
|
val json = Json {
|
||||||
prettyPrint = true
|
prettyPrint = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val text = json.encodeToString(PresentedServersSummary.serializer(), summary)
|
val text = json.encodeToString(PresentedServersSummary.serializer(), summary)
|
||||||
clipboard.shareText(text)
|
clipboard.shareText(text)
|
||||||
}
|
}
|
||||||
@@ -370,10 +380,10 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow<AnimatedViewState>
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTitleClick = null,
|
onTitleClick = if (canScrollToZero.value) { { scrollToBottom(scope, listState) } } else null,
|
||||||
showSearch = false,
|
onTop = !oneHandUI.value,
|
||||||
onSearchValueChanged = {},
|
onSearchValueChanged = {},
|
||||||
buttons = barButtons
|
buttons = { barButtons.forEach { it() } }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,6 +501,7 @@ fun connectIfOpenedViaUri(rhId: Long?, uri: String, chatModel: ChatModel) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState<TextFieldValue>, searchShowingSimplexLink: MutableState<Boolean>, searchChatFilteredBySimplexLink: MutableState<String?>) {
|
private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState<TextFieldValue>, searchShowingSimplexLink: MutableState<Boolean>, searchChatFilteredBySimplexLink: MutableState<String?>) {
|
||||||
|
Box {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
var focused by remember { mutableStateOf(false) }
|
var focused by remember { mutableStateOf(false) }
|
||||||
@@ -560,6 +571,9 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
Divider(Modifier.align(if (oneHandUI.value) Alignment.TopStart else Alignment.BottomStart))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState<String?>, cleanup: (() -> Unit)?) {
|
private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState<String?>, cleanup: (() -> Unit)?) {
|
||||||
@@ -590,8 +604,37 @@ enum class ScrollDirection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldValue>) {
|
fun BoxScope.StatusBarBackground() {
|
||||||
val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
|
if (appPlatform.isAndroid) {
|
||||||
|
val finalColor = MaterialTheme.colors.background.copy(0.88f)
|
||||||
|
Box(Modifier.fillMaxWidth().windowInsetsTopHeight(WindowInsets.statusBars).background(finalColor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BoxScope.NavigationBarBackground(appBarOnBottom: Boolean = false, mixedColor: Boolean, noAlpha: Boolean = false) {
|
||||||
|
if (appPlatform.isAndroid) {
|
||||||
|
val barPadding = WindowInsets.navigationBars.asPaddingValues()
|
||||||
|
val paddingBottom = barPadding.calculateBottomPadding()
|
||||||
|
val color = if (mixedColor) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f) else MaterialTheme.colors.background
|
||||||
|
val finalColor = color.copy(if (noAlpha) 1f else if (appBarOnBottom) remember { appPrefs.inAppBarsAlpha.state }.value else 0.6f)
|
||||||
|
Box(Modifier.align(Alignment.BottomStart).height(paddingBottom).fillMaxWidth().background(finalColor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BoxScope.NavigationBarBackground(modifier: Modifier, color: Color = MaterialTheme.colors.background) {
|
||||||
|
val keyboardState = getKeyboardState()
|
||||||
|
if (appPlatform.isAndroid && keyboardState.value == KeyboardState.Closed) {
|
||||||
|
val barPadding = WindowInsets.navigationBars.asPaddingValues()
|
||||||
|
val paddingBottom = barPadding.calculateBottomPadding()
|
||||||
|
val finalColor = color.copy(0.6f)
|
||||||
|
Box(modifier.align(Alignment.BottomStart).height(paddingBottom).fillMaxWidth().background(finalColor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listState: LazyListState) {
|
||||||
var scrollDirection by remember { mutableStateOf(ScrollDirection.Idle) }
|
var scrollDirection by remember { mutableStateOf(ScrollDirection.Idle) }
|
||||||
var previousIndex by remember { mutableStateOf(0) }
|
var previousIndex by remember { mutableStateOf(0) }
|
||||||
var previousScrollOffset by remember { mutableStateOf(0) }
|
var previousScrollOffset by remember { mutableStateOf(0) }
|
||||||
@@ -628,40 +671,45 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldVal
|
|||||||
val searchShowingSimplexLink = remember { mutableStateOf(false) }
|
val searchShowingSimplexLink = remember { mutableStateOf(false) }
|
||||||
val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) }
|
val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) }
|
||||||
val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.value.toList())
|
val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.value.toList())
|
||||||
|
val topPaddingToContent = topPaddingToContent()
|
||||||
|
val blankSpaceSize = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else topPaddingToContent
|
||||||
LazyColumnWithScrollBar(
|
LazyColumnWithScrollBar(
|
||||||
Modifier.fillMaxSize(),
|
if (!oneHandUI.value) Modifier.imePadding() else Modifier,
|
||||||
listState,
|
listState,
|
||||||
reverseLayout = oneHandUI.value
|
reverseLayout = oneHandUI.value
|
||||||
) {
|
) {
|
||||||
|
item { Spacer(Modifier.height(blankSpaceSize)) }
|
||||||
stickyHeader {
|
stickyHeader {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
|
.zIndex(1f)
|
||||||
.offset {
|
.offset {
|
||||||
val y = if (searchText.value.text.isEmpty()) {
|
|
||||||
val offsetMultiplier = if (oneHandUI.value) 1 else -1
|
val offsetMultiplier = if (oneHandUI.value) 1 else -1
|
||||||
if (
|
val y = if (searchText.value.text.isNotEmpty() || (appPlatform.isAndroid && keyboardState == KeyboardState.Opened) || scrollDirection == ScrollDirection.Up) {
|
||||||
(oneHandUI.value && scrollDirection == ScrollDirection.Up) ||
|
if (listState.firstVisibleItemIndex == 0) -offsetMultiplier * listState.firstVisibleItemScrollOffset
|
||||||
(appPlatform.isAndroid && keyboardState == KeyboardState.Opened)
|
else -offsetMultiplier * blankSpaceSize.roundToPx()
|
||||||
) {
|
|
||||||
0
|
|
||||||
} else if (listState.firstVisibleItemIndex == 0) offsetMultiplier * listState.firstVisibleItemScrollOffset else offsetMultiplier * 1000
|
|
||||||
} else {
|
} else {
|
||||||
0
|
when (listState.firstVisibleItemIndex) {
|
||||||
|
0 -> 0
|
||||||
|
1 -> offsetMultiplier * listState.firstVisibleItemScrollOffset
|
||||||
|
else -> offsetMultiplier * 1000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
IntOffset(0, y)
|
IntOffset(0, y)
|
||||||
}
|
}
|
||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background)
|
||||||
) {
|
) {
|
||||||
if (oneHandUI.value) {
|
if (oneHandUI.value) {
|
||||||
Divider()
|
Column(Modifier.consumeWindowInsets(WindowInsets.navigationBars).consumeWindowInsets(PaddingValues(bottom = AppBarHeight))) {
|
||||||
}
|
ChatListSearchBar(listState, searchText, searchShowingSimplexLink, searchChatFilteredBySimplexLink)
|
||||||
|
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ChatListSearchBar(listState, searchText, searchShowingSimplexLink, searchChatFilteredBySimplexLink)
|
ChatListSearchBar(listState, searchText, searchShowingSimplexLink, searchChatFilteredBySimplexLink)
|
||||||
if (!oneHandUI.value) {
|
|
||||||
Divider()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (appPlatform.isAndroid && !oneHandUICardShown.value && chats.count() > 1) {
|
if (!oneHandUICardShown.value && chats.size > 1) {
|
||||||
item {
|
item {
|
||||||
ToggleChatListCard()
|
ToggleChatListCard()
|
||||||
}
|
}
|
||||||
@@ -672,17 +720,30 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldVal
|
|||||||
} }
|
} }
|
||||||
ChatListNavLinkView(chat, nextChatSelected)
|
ChatListNavLinkView(chat, nextChatSelected)
|
||||||
}
|
}
|
||||||
if (appPlatform.isAndroid && !oneHandUICardShown.value && chats.count() <= 1) {
|
if (!oneHandUICardShown.value && chats.size <= 1) {
|
||||||
item {
|
item {
|
||||||
ToggleChatListCard()
|
ToggleChatListCard()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (appPlatform.isAndroid) {
|
||||||
|
item { Spacer(if (oneHandUI.value) Modifier.windowInsetsTopHeight(WindowInsets.statusBars) else Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (chats.isEmpty() && chatModel.chats.value.isNotEmpty()) {
|
if (chats.isEmpty() && chatModel.chats.value.isNotEmpty()) {
|
||||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(Modifier.fillMaxSize().imePadding(), contentAlignment = Alignment.Center) {
|
||||||
Text(generalGetString(MR.strings.no_filtered_chats), color = MaterialTheme.colors.secondary)
|
Text(generalGetString(MR.strings.no_filtered_chats), color = MaterialTheme.colors.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
StatusBarBackground()
|
||||||
|
} else {
|
||||||
|
NavigationBarBackground(oneHandUI.value, true)
|
||||||
|
}
|
||||||
|
if (!oneHandUICardShown.value) {
|
||||||
|
LaunchedEffect(chats.size) {
|
||||||
|
if (chats.size >= 3) appPrefs.oneHandUICardShown.set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filteredChats(
|
fun filteredChats(
|
||||||
@@ -727,3 +788,7 @@ private fun filtered(chat: Chat): Boolean =
|
|||||||
(chat.chatInfo.chatSettings?.favorite ?: false) ||
|
(chat.chatInfo.chatSettings?.favorite ?: false) ||
|
||||||
chat.chatStats.unreadChat ||
|
chat.chatStats.unreadChat ||
|
||||||
(chat.chatInfo.ntfsEnabled && chat.chatStats.unreadCount > 0)
|
(chat.chatInfo.ntfsEnabled && chat.chatStats.unreadCount > 0)
|
||||||
|
|
||||||
|
fun scrollToBottom(scope: CoroutineScope, listState: LazyListState) {
|
||||||
|
scope.launch { try { listState.animateScrollToItem(0) } catch (e: Exception) { Log.e(TAG, e.stackTraceToString()) } }
|
||||||
|
}
|
||||||
|
|||||||
+5
-15
@@ -620,9 +620,7 @@ fun ModalData.SMPServerSummaryView(
|
|||||||
ModalView(
|
ModalView(
|
||||||
close = close
|
close = close
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
val bottomPadding = DEFAULT_PADDING
|
val bottomPadding = DEFAULT_PADDING
|
||||||
AppBarTitle(
|
AppBarTitle(
|
||||||
stringResource(MR.strings.smp_server),
|
stringResource(MR.strings.smp_server),
|
||||||
@@ -645,9 +643,7 @@ fun ModalData.DetailedXFTPStatsView(
|
|||||||
ModalView(
|
ModalView(
|
||||||
close = close
|
close = close
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
val bottomPadding = DEFAULT_PADDING
|
val bottomPadding = DEFAULT_PADDING
|
||||||
AppBarTitle(
|
AppBarTitle(
|
||||||
@@ -671,9 +667,7 @@ fun ModalData.DetailedSMPStatsView(
|
|||||||
ModalView(
|
ModalView(
|
||||||
close = close
|
close = close
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
val bottomPadding = DEFAULT_PADDING
|
val bottomPadding = DEFAULT_PADDING
|
||||||
AppBarTitle(
|
AppBarTitle(
|
||||||
@@ -697,9 +691,7 @@ fun ModalData.XFTPServerSummaryView(
|
|||||||
ModalView(
|
ModalView(
|
||||||
close = close
|
close = close
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
val bottomPadding = DEFAULT_PADDING
|
val bottomPadding = DEFAULT_PADDING
|
||||||
AppBarTitle(
|
AppBarTitle(
|
||||||
@@ -715,9 +707,7 @@ fun ModalData.XFTPServerSummaryView(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableState<PresentedServersSummary?>) {
|
fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableState<PresentedServersSummary?>) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
var showUserSelection by remember { mutableStateOf(false) }
|
var showUserSelection by remember { mutableStateOf(false) }
|
||||||
val selectedUserCategory =
|
val selectedUserCategory =
|
||||||
remember { stateGetOrPut("selectedUserCategory") { PresentedUserCategory.ALL_USERS } }
|
remember { stateGetOrPut("selectedUserCategory") { PresentedUserCategory.ALL_USERS } }
|
||||||
|
|||||||
+43
-55
@@ -11,10 +11,13 @@ import androidx.compose.ui.graphics.Color
|
|||||||
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.unit.dp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.ui.theme.themedBackground
|
||||||
|
import chat.simplex.common.views.chat.topPaddingToContent
|
||||||
import chat.simplex.common.views.newchat.ActiveProfilePicker
|
import chat.simplex.common.views.newchat.ActiveProfilePicker
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
|
||||||
@@ -22,26 +25,7 @@ import chat.simplex.res.MR
|
|||||||
fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
|
fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
|
||||||
var searchInList by rememberSaveable { mutableStateOf("") }
|
var searchInList by rememberSaveable { mutableStateOf("") }
|
||||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
Box(Modifier.fillMaxSize().themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) {
|
||||||
Scaffold(
|
|
||||||
contentColor = LocalContentColor.current,
|
|
||||||
topBar = {
|
|
||||||
if (!oneHandUI.value) {
|
|
||||||
Column {
|
|
||||||
ShareListToolbar(chatModel, stopped) { searchInList = it.trim() }
|
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
bottomBar = {
|
|
||||||
if (oneHandUI.value) {
|
|
||||||
Column {
|
|
||||||
Divider()
|
|
||||||
ShareListToolbar(chatModel, stopped) { searchInList = it.trim() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
val sharedContent = chatModel.sharedContent.value
|
val sharedContent = chatModel.sharedContent.value
|
||||||
var isMediaOrFileAttachment = false
|
var isMediaOrFileAttachment = false
|
||||||
var isVoice = false
|
var isVoice = false
|
||||||
@@ -69,10 +53,6 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
|
|||||||
}
|
}
|
||||||
null -> {}
|
null -> {}
|
||||||
}
|
}
|
||||||
Box(Modifier.padding(it)) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
if (chatModel.chats.value.isNotEmpty()) {
|
if (chatModel.chats.value.isNotEmpty()) {
|
||||||
ShareList(
|
ShareList(
|
||||||
chatModel,
|
chatModel,
|
||||||
@@ -84,7 +64,13 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
|
|||||||
} else {
|
} else {
|
||||||
EmptyList()
|
EmptyList()
|
||||||
}
|
}
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
StatusBarBackground()
|
||||||
|
} else {
|
||||||
|
NavigationBarBackground(oneHandUI.value, true)
|
||||||
}
|
}
|
||||||
|
Box(Modifier.align(if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart)) {
|
||||||
|
ShareListToolbar(chatModel, stopped) { searchInList = it.trim() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +94,6 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal
|
|||||||
if (showSearch) {
|
if (showSearch) {
|
||||||
BackHandler(onBack = hideSearchOnBack)
|
BackHandler(onBack = hideSearchOnBack)
|
||||||
}
|
}
|
||||||
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
|
|
||||||
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
|
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
|
||||||
val navButton: @Composable RowScope.() -> Unit = {
|
val navButton: @Composable RowScope.() -> Unit = {
|
||||||
when {
|
when {
|
||||||
@@ -118,13 +103,13 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal
|
|||||||
.filter { u -> !u.user.activeUser && !u.user.hidden }
|
.filter { u -> !u.user.activeUser && !u.user.hidden }
|
||||||
.all { u -> u.unreadCount == 0 }
|
.all { u -> u.unreadCount == 0 }
|
||||||
UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) {
|
UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) {
|
||||||
ModalManager.start.showCustomModal { close ->
|
ModalManager.start.showCustomModal(keyboardCoversBar = false) { close ->
|
||||||
val search = rememberSaveable { mutableStateOf("") }
|
val search = rememberSaveable { mutableStateOf("") }
|
||||||
ModalView(
|
ModalView(
|
||||||
{ close() },
|
{ close() },
|
||||||
endButtons = {
|
showSearch = true,
|
||||||
SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it }
|
searchAlwaysVisible = true,
|
||||||
},
|
onSearchValueChanged = { search.value = it },
|
||||||
content = {
|
content = {
|
||||||
ActiveProfilePicker(
|
ActiveProfilePicker(
|
||||||
search = search,
|
search = search,
|
||||||
@@ -148,31 +133,8 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chatModel.chats.value.size >= 8) {
|
|
||||||
barButtons.add {
|
|
||||||
IconButton({ showSearch = true }) {
|
|
||||||
Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stopped) {
|
|
||||||
barButtons.add {
|
|
||||||
IconButton(onClick = {
|
|
||||||
AlertManager.shared.showAlertMsg(
|
|
||||||
generalGetString(MR.strings.chat_is_stopped_indication),
|
|
||||||
generalGetString(MR.strings.you_can_start_chat_via_setting_or_by_restarting_the_app)
|
|
||||||
)
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
painterResource(MR.images.ic_report_filled),
|
|
||||||
generalGetString(MR.strings.chat_is_stopped_indication),
|
|
||||||
tint = Color.Red,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DefaultTopAppBar(
|
DefaultAppBar(
|
||||||
navigationButton = navButton,
|
navigationButton = navButton,
|
||||||
title = {
|
title = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -191,8 +153,29 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal
|
|||||||
},
|
},
|
||||||
onTitleClick = null,
|
onTitleClick = null,
|
||||||
showSearch = showSearch,
|
showSearch = showSearch,
|
||||||
|
onTop = !remember { appPrefs.oneHandUI.state }.value,
|
||||||
onSearchValueChanged = onSearchValueChanged,
|
onSearchValueChanged = onSearchValueChanged,
|
||||||
buttons = barButtons
|
buttons = {
|
||||||
|
if (chatModel.chats.value.size >= 8) {
|
||||||
|
IconButton({ showSearch = true }) {
|
||||||
|
Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stopped) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
AlertManager.shared.showAlertMsg(
|
||||||
|
generalGetString(MR.strings.chat_is_stopped_indication),
|
||||||
|
generalGetString(MR.strings.you_can_start_chat_via_setting_or_by_restarting_the_app)
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
painterResource(MR.images.ic_report_filled),
|
||||||
|
generalGetString(MR.strings.chat_is_stopped_indication),
|
||||||
|
tint = Color.Red,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,8 +194,13 @@ private fun ShareList(
|
|||||||
filteredChats(false, mutableStateOf(false), mutableStateOf(null), search, sorted)
|
filteredChats(false, mutableStateOf(false), mutableStateOf(null), search, sorted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val topPaddingToContent = topPaddingToContent()
|
||||||
LazyColumnWithScrollBar(
|
LazyColumnWithScrollBar(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.then(if (oneHandUI.value) Modifier.consumeWindowInsets(WindowInsets.navigationBars.only(WindowInsetsSides.Vertical)) else Modifier).imePadding(),
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
top = if (oneHandUI.value) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() else topPaddingToContent,
|
||||||
|
bottom = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else 0.dp
|
||||||
|
),
|
||||||
reverseLayout = oneHandUI.value
|
reverseLayout = oneHandUI.value
|
||||||
) {
|
) {
|
||||||
items(chats) { chat ->
|
items(chats) { chat ->
|
||||||
|
|||||||
+16
-6
@@ -23,6 +23,7 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
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.ChatController.stopRemoteHostAndReloadHosts
|
import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts
|
||||||
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.*
|
||||||
@@ -137,12 +138,16 @@ fun UserPicker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
val iconColor = MaterialTheme.colors.secondaryVariant
|
||||||
|
val background = if (appPlatform.isAndroid) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha()) else MaterialTheme.colors.surface
|
||||||
PlatformUserPicker(
|
PlatformUserPicker(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(IntrinsicSize.Min)
|
.height(IntrinsicSize.Min)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true) else Modifier)
|
.then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true, ambientColor = background) else Modifier)
|
||||||
.background(if (appPlatform.isAndroid) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha()) else MaterialTheme.colors.surface)
|
.padding(top = if (appPlatform.isDesktop && oneHandUI.value) 7.dp else 0.dp)
|
||||||
|
.background(background)
|
||||||
.padding(bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
|
.padding(bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
|
||||||
pickerState = userPickerState
|
pickerState = userPickerState
|
||||||
) {
|
) {
|
||||||
@@ -198,12 +203,13 @@ fun UserPicker(
|
|||||||
UserPickerUsersSection(
|
UserPickerUsersSection(
|
||||||
users = users,
|
users = users,
|
||||||
onUserClicked = onUserClicked,
|
onUserClicked = onUserClicked,
|
||||||
|
iconColor = iconColor,
|
||||||
stopped = stopped
|
stopped = stopped
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (currentUser != null) {
|
} else if (currentUser != null) {
|
||||||
SectionItemView({ onUserClicked(currentUser) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) {
|
SectionItemView({ onUserClicked(currentUser) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) {
|
||||||
ProfilePreview(currentUser.profile, stopped = stopped)
|
ProfilePreview(currentUser.profile, iconColor = iconColor, stopped = stopped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,6 +240,7 @@ fun UserPicker(
|
|||||||
Column(modifier = Modifier.padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)) {
|
Column(modifier = Modifier.padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)) {
|
||||||
UserPickerUsersSection(
|
UserPickerUsersSection(
|
||||||
users = inactiveUsers,
|
users = inactiveUsers,
|
||||||
|
iconColor = iconColor,
|
||||||
onUserClicked = onUserClicked,
|
onUserClicked = onUserClicked,
|
||||||
stopped = stopped
|
stopped = stopped
|
||||||
)
|
)
|
||||||
@@ -265,13 +272,15 @@ fun UserPicker(
|
|||||||
generalGetString(MR.strings.auth_open_chat_profiles),
|
generalGetString(MR.strings.auth_open_chat_profiles),
|
||||||
generalGetString(MR.strings.auth_log_in_using_credential)
|
generalGetString(MR.strings.auth_log_in_using_credential)
|
||||||
) {
|
) {
|
||||||
ModalManager.start.showCustomModal { close ->
|
ModalManager.start.showCustomModal(keyboardCoversBar = false) { close ->
|
||||||
val search = rememberSaveable { mutableStateOf("") }
|
val search = rememberSaveable { mutableStateOf("") }
|
||||||
val profileHidden = rememberSaveable { mutableStateOf(false) }
|
val profileHidden = rememberSaveable { mutableStateOf(false) }
|
||||||
ModalView(
|
ModalView(
|
||||||
{ close() },
|
{ close() },
|
||||||
endButtons = {
|
showSearch = true,
|
||||||
SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it }
|
searchAlwaysVisible = true,
|
||||||
|
onSearchValueChanged = {
|
||||||
|
search.value = it
|
||||||
},
|
},
|
||||||
content = { UserProfilesView(chatModel, search, profileHidden) })
|
content = { UserProfilesView(chatModel, search, profileHidden) })
|
||||||
}
|
}
|
||||||
@@ -519,6 +528,7 @@ private fun DevicePickerRow(
|
|||||||
@Composable
|
@Composable
|
||||||
expect fun UserPickerUsersSection(
|
expect fun UserPickerUsersSection(
|
||||||
users: List<UserInfo>,
|
users: List<UserInfo>,
|
||||||
|
iconColor: Color,
|
||||||
stopped: Boolean,
|
stopped: Boolean,
|
||||||
onUserClicked: (user: User) -> Unit,
|
onUserClicked: (user: User) -> Unit,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-3
@@ -46,9 +46,7 @@ fun ChatArchiveLayout(
|
|||||||
saveArchive: () -> Unit,
|
saveArchive: () -> Unit,
|
||||||
deleteArchiveAlert: () -> Unit
|
deleteArchiveAlert: () -> Unit
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(title)
|
AppBarTitle(title)
|
||||||
SectionView(stringResource(MR.strings.chat_archive_section)) {
|
SectionView(stringResource(MR.strings.chat_archive_section)) {
|
||||||
SettingsActionItem(
|
SettingsActionItem(
|
||||||
|
|||||||
+1
-1
@@ -203,7 +203,7 @@ fun DatabaseEncryptionLayout(
|
|||||||
Layout()
|
Layout()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth(), maxIntrinsicSize = true) {
|
ColumnWithScrollBar(maxIntrinsicSize = true) {
|
||||||
Layout()
|
Layout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -77,10 +77,7 @@ fun DatabaseErrorView(
|
|||||||
Text(String.format(generalGetString(MR.strings.database_migrations), ms.joinToString(", ")))
|
Text(String.format(generalGetString(MR.strings.database_migrations), ms.joinToString(", ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBarNoAppBar(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) {
|
||||||
Modifier.fillMaxSize(),
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
val buttonEnabled = validKey(dbKey.value) && !progressIndicator.value
|
val buttonEnabled = validKey(dbKey.value) && !progressIndicator.value
|
||||||
when (val status = chatDbStatus.value) {
|
when (val status = chatDbStatus.value) {
|
||||||
is DBMigrationResult.ErrorNotADatabase ->
|
is DBMigrationResult.ErrorNotADatabase ->
|
||||||
|
|||||||
+1
-3
@@ -156,9 +156,7 @@ fun DatabaseLayout(
|
|||||||
val stopped = !runChat
|
val stopped = !runChat
|
||||||
val operationsDisabled = (!stopped || progressIndicator) && !chatModel.desktopNoUserNoRemote
|
val operationsDisabled = (!stopped || progressIndicator) && !chatModel.desktopNoUserNoRemote
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.your_chat_database))
|
AppBarTitle(stringResource(MR.strings.your_chat_database))
|
||||||
|
|
||||||
if (!chatModel.desktopNoUserNoRemote) {
|
if (!chatModel.desktopNoUserNoRemote) {
|
||||||
|
|||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.graphics.*
|
||||||
|
import androidx.compose.ui.unit.*
|
||||||
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.chatlist.*
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppBarTitle(title: String, hostDevice: Pair<Long?, String>? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp) {
|
||||||
|
val handler = LocalAppBarHandler.current
|
||||||
|
val connection = handler?.connection
|
||||||
|
LaunchedEffect(title) {
|
||||||
|
handler?.title?.value = title
|
||||||
|
}
|
||||||
|
val theme = CurrentColors.collectAsState()
|
||||||
|
val titleColor = MaterialTheme.appColors.title
|
||||||
|
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))
|
||||||
|
else // color is not updated when changing themes if I pass null here
|
||||||
|
Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f))
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
Modifier
|
||||||
|
.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, top = DEFAULT_PADDING_HALF, end = if (withPadding) DEFAULT_PADDING else 0.dp,)
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = bottomTitleAlpha(connection)
|
||||||
|
},
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.h1.copy(brush = brush),
|
||||||
|
color = MaterialTheme.colors.primaryVariant,
|
||||||
|
textAlign = TextAlign.Start
|
||||||
|
)
|
||||||
|
if (hostDevice != null) {
|
||||||
|
Box(Modifier.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp).graphicsLayer {
|
||||||
|
alpha = bottomTitleAlpha(connection)
|
||||||
|
}) {
|
||||||
|
HostDeviceTitle(hostDevice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(bottomPadding))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bottomTitleAlpha(connection: CollapsingAppBarNestedScrollConnection?) =
|
||||||
|
if ((connection?.appBarOffset ?: 0f).absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 1f
|
||||||
|
else ((AppBarHandler.appBarMaxHeightPx) + (connection?.appBarOffset ?: 0f) / 1.5f).coerceAtLeast(0f) / AppBarHandler.appBarMaxHeightPx
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun HostDeviceTitle(hostDevice: Pair<Long?, String>, extraPadding: Boolean = false) {
|
||||||
|
Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) {
|
||||||
|
DevicePill(
|
||||||
|
active = true,
|
||||||
|
onClick = {},
|
||||||
|
actionButtonVisible = false,
|
||||||
|
icon = painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300),
|
||||||
|
text = hostDevice.second
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.*
|
||||||
|
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||||
|
import androidx.compose.ui.graphics.drawscope.translate
|
||||||
|
import androidx.compose.ui.graphics.layer.GraphicsLayer
|
||||||
|
import androidx.compose.ui.graphics.layer.drawLayer
|
||||||
|
import androidx.compose.ui.unit.*
|
||||||
|
import chat.simplex.common.platform.appPlatform
|
||||||
|
import chat.simplex.common.ui.theme.CurrentColors
|
||||||
|
|
||||||
|
fun Modifier.blurredBackgroundModifier(
|
||||||
|
keyboardInset: WindowInsets,
|
||||||
|
handler: AppBarHandler?,
|
||||||
|
blurRadius: State<Int>,
|
||||||
|
prefAlpha: State<Float>,
|
||||||
|
keyboardCoversBar: Boolean,
|
||||||
|
onTop: Boolean,
|
||||||
|
density: Density
|
||||||
|
): Modifier {
|
||||||
|
val graphicsLayer = handler?.graphicsLayer
|
||||||
|
val backgroundGraphicsLayer = handler?.backgroundGraphicsLayer
|
||||||
|
val backgroundGraphicsLayerSize = handler?.backgroundGraphicsLayerSize
|
||||||
|
if (handler == null || graphicsLayer == null || backgroundGraphicsLayer == null || blurRadius.value == 0 || prefAlpha.value == 1f || backgroundGraphicsLayerSize === null)
|
||||||
|
return this
|
||||||
|
|
||||||
|
return if (appPlatform.isAndroid) {
|
||||||
|
this.androidBlurredModifier(keyboardInset, blurRadius.value, keyboardCoversBar, onTop, graphicsLayer, backgroundGraphicsLayer, backgroundGraphicsLayerSize, density)
|
||||||
|
} else {
|
||||||
|
this.desktopBlurredModifier(keyboardInset, blurRadius, keyboardCoversBar, onTop, graphicsLayer, backgroundGraphicsLayer, backgroundGraphicsLayerSize, density)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is more performant version than for Android but can't be used on desktop because on first frame it shows transparent view
|
||||||
|
// which is very noticeable on desktop and unnoticeable on Android
|
||||||
|
private fun Modifier.androidBlurredModifier(
|
||||||
|
keyboardInset: WindowInsets,
|
||||||
|
blurRadius: Int,
|
||||||
|
keyboardCoversBar: Boolean,
|
||||||
|
onTop: Boolean,
|
||||||
|
graphicsLayer: GraphicsLayer,
|
||||||
|
backgroundGraphicsLayer: GraphicsLayer,
|
||||||
|
backgroundGraphicsLayerSize: State<IntSize>,
|
||||||
|
density: Density
|
||||||
|
): Modifier = this
|
||||||
|
.graphicsLayer {
|
||||||
|
renderEffect = if (blurRadius > 0) BlurEffect(blurRadius.dp.toPx(), blurRadius.dp.toPx()) else null
|
||||||
|
clip = blurRadius > 0
|
||||||
|
}
|
||||||
|
.graphicsLayer {
|
||||||
|
if (!onTop) {
|
||||||
|
val bgSize = when {
|
||||||
|
backgroundGraphicsLayerSize.value.height == 0 && backgroundGraphicsLayer.size.height != 0 -> backgroundGraphicsLayer.size.height
|
||||||
|
backgroundGraphicsLayerSize.value.height == 0 -> graphicsLayer.size.height
|
||||||
|
else -> backgroundGraphicsLayerSize.value.height
|
||||||
|
}
|
||||||
|
val keyboardHeightCovered = if (!keyboardCoversBar) keyboardInset.getBottom(density) else 0
|
||||||
|
translationY = -bgSize + size.height + keyboardHeightCovered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drawBehind {
|
||||||
|
drawRect(Color.Black)
|
||||||
|
if (onTop) {
|
||||||
|
clipRect {
|
||||||
|
if (backgroundGraphicsLayer.size != IntSize.Zero) {
|
||||||
|
drawLayer(backgroundGraphicsLayer)
|
||||||
|
} else {
|
||||||
|
drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat()))
|
||||||
|
}
|
||||||
|
drawLayer(graphicsLayer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (backgroundGraphicsLayer.size != IntSize.Zero) {
|
||||||
|
drawLayer(backgroundGraphicsLayer)
|
||||||
|
} else {
|
||||||
|
drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat()))
|
||||||
|
}
|
||||||
|
drawLayer(graphicsLayer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.graphicsLayer {
|
||||||
|
if (!onTop) {
|
||||||
|
val bgSize = when {
|
||||||
|
backgroundGraphicsLayerSize.value.height == 0 && backgroundGraphicsLayer.size.height != 0 -> backgroundGraphicsLayer.size.height
|
||||||
|
backgroundGraphicsLayerSize.value.height == 0 -> graphicsLayer.size.height
|
||||||
|
else -> backgroundGraphicsLayerSize.value.height
|
||||||
|
}
|
||||||
|
val keyboardHeightCovered = if (!keyboardCoversBar) keyboardInset.getBottom(density) else 0
|
||||||
|
translationY -= -bgSize + size.height + keyboardHeightCovered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Modifier.desktopBlurredModifier(
|
||||||
|
keyboardInset: WindowInsets,
|
||||||
|
blurRadius: State<Int>,
|
||||||
|
keyboardCoversBar: Boolean,
|
||||||
|
onTop: Boolean,
|
||||||
|
graphicsLayer: GraphicsLayer,
|
||||||
|
backgroundGraphicsLayer: GraphicsLayer,
|
||||||
|
backgroundGraphicsLayerSize: State<IntSize>,
|
||||||
|
density: Density
|
||||||
|
): Modifier = this
|
||||||
|
.graphicsLayer {
|
||||||
|
renderEffect = if (blurRadius.value > 0) BlurEffect(blurRadius.value.dp.toPx(), blurRadius.value.dp.toPx()) else null
|
||||||
|
clip = blurRadius.value > 0
|
||||||
|
}
|
||||||
|
.drawBehind {
|
||||||
|
drawRect(Color.Black)
|
||||||
|
if (onTop) {
|
||||||
|
clipRect {
|
||||||
|
if (backgroundGraphicsLayer.size != IntSize.Zero) {
|
||||||
|
drawLayer(backgroundGraphicsLayer)
|
||||||
|
} else {
|
||||||
|
drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat()))
|
||||||
|
}
|
||||||
|
drawLayer(graphicsLayer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val bgSize = when {
|
||||||
|
backgroundGraphicsLayerSize.value.height == 0 && backgroundGraphicsLayer.size.height != 0 -> backgroundGraphicsLayer.size.height
|
||||||
|
backgroundGraphicsLayerSize.value.height == 0 -> graphicsLayer.size.height
|
||||||
|
else -> backgroundGraphicsLayerSize.value.height
|
||||||
|
}
|
||||||
|
val keyboardHeightCovered = if (!keyboardCoversBar) keyboardInset.getBottom(density) else 0
|
||||||
|
translate(top = -bgSize + size.height + keyboardHeightCovered) {
|
||||||
|
if (backgroundGraphicsLayer.size != IntSize.Zero) {
|
||||||
|
drawLayer(backgroundGraphicsLayer)
|
||||||
|
} else {
|
||||||
|
drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat()))
|
||||||
|
}
|
||||||
|
drawLayer(graphicsLayer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-1
@@ -1,11 +1,13 @@
|
|||||||
package chat.simplex.common.views.helpers
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.draw.CacheDrawScope
|
import androidx.compose.ui.draw.CacheDrawScope
|
||||||
import androidx.compose.ui.draw.DrawResult
|
import androidx.compose.ui.draw.DrawResult
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.graphics.drawscope.*
|
import androidx.compose.ui.graphics.drawscope.*
|
||||||
|
import androidx.compose.ui.graphics.layer.GraphicsLayer
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
@@ -381,7 +383,14 @@ private fun drawToBitmap(image: ImageBitmap, imageScale: Float, tint: Color, siz
|
|||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color): DrawResult {
|
fun CacheDrawScope.chatViewBackground(
|
||||||
|
image: ImageBitmap,
|
||||||
|
imageType: WallpaperType,
|
||||||
|
background: Color,
|
||||||
|
tint: Color,
|
||||||
|
graphicsLayerSize: MutableState<IntSize>? = null,
|
||||||
|
backgroundGraphicsLayer: GraphicsLayer? = null
|
||||||
|
): DrawResult {
|
||||||
val imageScale = if (imageType is WallpaperType.Preset) {
|
val imageScale = if (imageType is WallpaperType.Preset) {
|
||||||
(imageType.scale ?: 1f) * imageType.predefinedImageScale
|
(imageType.scale ?: 1f) * imageType.predefinedImageScale
|
||||||
} else if (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT) {
|
} else if (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT) {
|
||||||
@@ -396,6 +405,7 @@ fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
return onDrawBehind {
|
return onDrawBehind {
|
||||||
|
copyBackgroundToAppBar(graphicsLayerSize, backgroundGraphicsLayer) {
|
||||||
val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low
|
val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low
|
||||||
drawRect(background)
|
drawRect(background)
|
||||||
when (imageType) {
|
when (imageType) {
|
||||||
@@ -446,3 +456,4 @@ fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperTy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
@@ -19,6 +19,8 @@ fun ChooseAttachmentView(attachmentOption: MutableState<AttachmentOption?>, hide
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.imePadding()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.onFocusChanged { focusState ->
|
.onFocusChanged { focusState ->
|
||||||
if (!focusState.hasFocus) hide()
|
if (!focusState.hasFocus) hide()
|
||||||
|
|||||||
-181
@@ -1,181 +0,0 @@
|
|||||||
package chat.simplex.common.views.helpers
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.material.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.ui.draw.*
|
|
||||||
import androidx.compose.ui.graphics.*
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.*
|
|
||||||
import chat.simplex.common.platform.appPlatform
|
|
||||||
import chat.simplex.common.ui.theme.*
|
|
||||||
import chat.simplex.common.views.chatlist.DevicePill
|
|
||||||
import chat.simplex.res.MR
|
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Color = if (close != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, arrangement: Arrangement.Vertical = Arrangement.Top, closeBarTitle: String? = null, barPaddingValues: PaddingValues = PaddingValues(horizontal = AppBarHorizontalPadding), endButtons: @Composable RowScope.() -> Unit = {}) {
|
|
||||||
var rowModifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(AppBarHeight * fontSizeSqrtMultiplier)
|
|
||||||
val themeBackgroundMix = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f)
|
|
||||||
if (!closeBarTitle.isNullOrEmpty()) {
|
|
||||||
rowModifier = rowModifier.background(themeBackgroundMix)
|
|
||||||
}
|
|
||||||
val handler = LocalAppBarHandler.current
|
|
||||||
val connection = LocalAppBarHandler.current?.connection
|
|
||||||
val title = remember(handler?.title?.value) { handler?.title ?: mutableStateOf("") }
|
|
||||||
|
|
||||||
Column(
|
|
||||||
verticalArrangement = arrangement,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(min = AppBarHeight * fontSizeSqrtMultiplier)
|
|
||||||
.drawWithCache {
|
|
||||||
val backgroundColor = if (appPlatform.isDesktop && connection != null) themeBackgroundMix.copy(alpha = topTitleAlpha(connection)) else Color.Transparent
|
|
||||||
onDrawBehind {
|
|
||||||
if (appPlatform.isDesktop) {
|
|
||||||
drawRect(backgroundColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(barPaddingValues),
|
|
||||||
content = {
|
|
||||||
Row(
|
|
||||||
rowModifier,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
if (showClose) {
|
|
||||||
NavigationButtonBack(tintColor = tintColor, onButtonClicked = close)
|
|
||||||
} else {
|
|
||||||
Spacer(Modifier)
|
|
||||||
}
|
|
||||||
if (!closeBarTitle.isNullOrEmpty()) {
|
|
||||||
Row(
|
|
||||||
Modifier.weight(1f),
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
closeBarTitle,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
maxLines = 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (title.value.isNotEmpty() && connection != null) {
|
|
||||||
Row(
|
|
||||||
Modifier
|
|
||||||
.padding(start = if (showClose) 0.dp else DEFAULT_PADDING_HALF)
|
|
||||||
.weight(1f) // hides the title if something wants full width (eg, search field in chat profiles screen)
|
|
||||||
.graphicsLayer {
|
|
||||||
alpha = topTitleAlpha((connection))
|
|
||||||
}
|
|
||||||
.padding(start = 4.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
title.value,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Spacer(Modifier.weight(1f))
|
|
||||||
}
|
|
||||||
Row {
|
|
||||||
endButtons()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (closeBarTitle.isNullOrEmpty() && title.value.isNotEmpty() && connection != null) {
|
|
||||||
Divider(
|
|
||||||
Modifier
|
|
||||||
.graphicsLayer {
|
|
||||||
alpha = topTitleAlpha(connection)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AppBarTitle(title: String, hostDevice: Pair<Long?, String>? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp) {
|
|
||||||
val handler = LocalAppBarHandler.current
|
|
||||||
val connection = handler?.connection
|
|
||||||
LaunchedEffect(title) {
|
|
||||||
handler?.title?.value = title
|
|
||||||
}
|
|
||||||
val theme = CurrentColors.collectAsState()
|
|
||||||
val titleColor = MaterialTheme.appColors.title
|
|
||||||
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))
|
|
||||||
else // color is not updated when changing themes if I pass null here
|
|
||||||
Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f))
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
Modifier
|
|
||||||
.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, top = DEFAULT_PADDING_HALF, end = if (withPadding) DEFAULT_PADDING else 0.dp,)
|
|
||||||
.graphicsLayer {
|
|
||||||
alpha = bottomTitleAlpha(connection)
|
|
||||||
},
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
style = MaterialTheme.typography.h1.copy(brush = brush),
|
|
||||||
color = MaterialTheme.colors.primaryVariant,
|
|
||||||
textAlign = TextAlign.Start
|
|
||||||
)
|
|
||||||
if (hostDevice != null) {
|
|
||||||
Box(Modifier.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp).graphicsLayer {
|
|
||||||
alpha = bottomTitleAlpha(connection)
|
|
||||||
}) {
|
|
||||||
HostDeviceTitle(hostDevice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(bottomPadding))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun topTitleAlpha(connection: CollapsingAppBarNestedScrollConnection) =
|
|
||||||
if (connection.appBarOffset.absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 0f
|
|
||||||
else ((-connection.appBarOffset * 1.5f) / (AppBarHandler.appBarMaxHeightPx)).coerceIn(0f, 1f)
|
|
||||||
|
|
||||||
private fun bottomTitleAlpha(connection: CollapsingAppBarNestedScrollConnection?) =
|
|
||||||
if ((connection?.appBarOffset ?: 0f).absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 1f
|
|
||||||
else ((AppBarHandler.appBarMaxHeightPx) + (connection?.appBarOffset ?: 0f) / 1.5f).coerceAtLeast(0f) / AppBarHandler.appBarMaxHeightPx
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun HostDeviceTitle(hostDevice: Pair<Long?, String>, extraPadding: Boolean = false) {
|
|
||||||
Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) {
|
|
||||||
DevicePill(
|
|
||||||
active = true,
|
|
||||||
onClick = {},
|
|
||||||
actionButtonVisible = false,
|
|
||||||
icon = painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300),
|
|
||||||
text = hostDevice.second
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview/*(
|
|
||||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
|
||||||
showBackground = true,
|
|
||||||
name = "Dark Mode"
|
|
||||||
)*/
|
|
||||||
@Composable
|
|
||||||
fun PreviewCloseSheetBar() {
|
|
||||||
SimpleXTheme {
|
|
||||||
CloseSheetBar(close = {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+55
-1
@@ -3,15 +3,67 @@ package chat.simplex.common.views.helpers
|
|||||||
import androidx.compose.foundation.ScrollState
|
import androidx.compose.foundation.ScrollState
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.*
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.graphics.layer.GraphicsLayer
|
||||||
|
import androidx.compose.ui.graphics.layer.drawLayer
|
||||||
|
import androidx.compose.ui.graphics.rememberGraphicsLayer
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
import androidx.compose.ui.unit.Velocity
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
|
|
||||||
val LocalAppBarHandler: ProvidableCompositionLocal<AppBarHandler?> = staticCompositionLocalOf { null }
|
val LocalAppBarHandler: ProvidableCompositionLocal<AppBarHandler?> = staticCompositionLocalOf { null }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberAppBarHandler(key1: Any? = null, key2: Any? = null, keyboardCoversBar: Boolean = true): AppBarHandler {
|
||||||
|
val graphicsLayer = rememberGraphicsLayer()
|
||||||
|
val backgroundGraphicsLayer = rememberGraphicsLayer()
|
||||||
|
return remember(key1, key2) { AppBarHandler(graphicsLayer, backgroundGraphicsLayer, keyboardCoversBar) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun adjustAppBarHandler(handler: AppBarHandler): AppBarHandler {
|
||||||
|
val graphicsLayer = rememberGraphicsLayer()
|
||||||
|
val backgroundGraphicsLayer = rememberGraphicsLayer()
|
||||||
|
if (handler.graphicsLayer == null || handler.graphicsLayer?.isReleased == true || handler.backgroundGraphicsLayer?.isReleased == true) {
|
||||||
|
handler.graphicsLayer = graphicsLayer
|
||||||
|
handler.backgroundGraphicsLayer = backgroundGraphicsLayer
|
||||||
|
}
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Modifier.copyViewToAppBar(blurRadius: Int, graphicsLayer: GraphicsLayer?): Modifier {
|
||||||
|
return if (blurRadius > 0 && graphicsLayer != null) {
|
||||||
|
this.drawWithContent {
|
||||||
|
graphicsLayer.record {
|
||||||
|
this@drawWithContent.drawContent()
|
||||||
|
}
|
||||||
|
drawLayer(graphicsLayer)
|
||||||
|
}
|
||||||
|
} else this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DrawScope.copyBackgroundToAppBar(graphicsLayerSize: MutableState<IntSize>?, backgroundGraphicsLayer: GraphicsLayer?, scope: DrawScope.() -> Unit) {
|
||||||
|
val blurRadius = appPrefs.appearanceBarsBlurRadius.get()
|
||||||
|
if (blurRadius > 0 && graphicsLayerSize != null && backgroundGraphicsLayer != null) {
|
||||||
|
graphicsLayerSize.value = backgroundGraphicsLayer.size
|
||||||
|
backgroundGraphicsLayer.record {
|
||||||
|
scope()
|
||||||
|
}
|
||||||
|
drawLayer(backgroundGraphicsLayer)
|
||||||
|
} else {
|
||||||
|
scope()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class AppBarHandler(
|
class AppBarHandler(
|
||||||
|
var graphicsLayer: GraphicsLayer?,
|
||||||
|
var backgroundGraphicsLayer: GraphicsLayer?,
|
||||||
|
val keyboardCoversBar: Boolean = true,
|
||||||
listState: LazyListState = LazyListState(0, 0),
|
listState: LazyListState = LazyListState(0, 0),
|
||||||
scrollState: ScrollState = ScrollState(initial = 0)
|
scrollState: ScrollState = ScrollState(initial = 0)
|
||||||
) {
|
) {
|
||||||
@@ -24,6 +76,8 @@ class AppBarHandler(
|
|||||||
|
|
||||||
val connection = CollapsingAppBarNestedScrollConnection()
|
val connection = CollapsingAppBarNestedScrollConnection()
|
||||||
|
|
||||||
|
val backgroundGraphicsLayerSize: MutableState<IntSize> = mutableStateOf(IntSize.Zero)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var appBarMaxHeightPx: Int = 0
|
var appBarMaxHeightPx: Int = 0
|
||||||
}
|
}
|
||||||
|
|||||||
-3
@@ -3,7 +3,6 @@ package chat.simplex.common.views.helpers
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
import androidx.compose.foundation.text.*
|
import androidx.compose.foundation.text.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
@@ -22,13 +21,11 @@ import androidx.compose.ui.text.input.*
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import chat.simplex.common.views.database.PassphraseStrength
|
import chat.simplex.common.views.database.PassphraseStrength
|
||||||
import chat.simplex.common.views.database.validKey
|
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultBasicTextField(
|
fun DefaultBasicTextField(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
|
|||||||
+2
-1
@@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
@Composable
|
@Composable
|
||||||
fun DefaultDropdownMenu(
|
fun DefaultDropdownMenu(
|
||||||
showMenu: MutableState<Boolean>,
|
showMenu: MutableState<Boolean>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
offset: DpOffset = DpOffset(0.dp, 0.dp),
|
offset: DpOffset = DpOffset(0.dp, 0.dp),
|
||||||
dropdownMenuItems: (@Composable () -> Unit)?
|
dropdownMenuItems: (@Composable () -> Unit)?
|
||||||
) {
|
) {
|
||||||
@@ -23,7 +24,7 @@ fun DefaultDropdownMenu(
|
|||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = showMenu.value,
|
expanded = showMenu.value,
|
||||||
onDismissRequest = { showMenu.value = false },
|
onDismissRequest = { showMenu.value = false },
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.widthIn(min = 250.dp)
|
.widthIn(min = 250.dp)
|
||||||
.background(MaterialTheme.colors.surface)
|
.background(MaterialTheme.colors.surface)
|
||||||
.padding(vertical = 4.dp),
|
.padding(vertical = 4.dp),
|
||||||
|
|||||||
+180
-55
@@ -3,44 +3,120 @@ package chat.simplex.common.views.helpers
|
|||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.*
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.*
|
||||||
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.unit.dp
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.chat.item.CenteredRowLayout
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultTopAppBar(
|
fun DefaultAppBar(
|
||||||
navigationButton: (@Composable RowScope.() -> Unit)? = null,
|
navigationButton: (@Composable RowScope.() -> Unit)? = null,
|
||||||
title: (@Composable () -> Unit)?,
|
title: (@Composable () -> Unit)? = null,
|
||||||
|
fixedTitleText: String? = null,
|
||||||
onTitleClick: (() -> Unit)? = null,
|
onTitleClick: (() -> Unit)? = null,
|
||||||
showSearch: Boolean,
|
onTop: Boolean,
|
||||||
onSearchValueChanged: (String) -> Unit,
|
showSearch: Boolean = false,
|
||||||
buttons: List<@Composable RowScope.() -> Unit> = emptyList(),
|
searchAlwaysVisible: Boolean = false,
|
||||||
|
onSearchValueChanged: (String) -> Unit = {},
|
||||||
|
buttons: @Composable RowScope.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
// If I just disable clickable modifier when don't need it, it will stop passing clicks to search. Replacing the whole modifier
|
// If I just disable clickable modifier when don't need it, it will stop passing clicks to search. Replacing the whole modifier
|
||||||
val modifier = if (!showSearch) {
|
val modifier = if (!showSearch) {
|
||||||
Modifier.clickable(enabled = onTitleClick != null, onClick = onTitleClick ?: { })
|
Modifier.clickable(enabled = onTitleClick != null, onClick = onTitleClick ?: { })
|
||||||
} else Modifier
|
} else Modifier.imePadding()
|
||||||
|
|
||||||
TopAppBar(
|
val themeBackgroundMix = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f)
|
||||||
modifier = modifier,
|
val prefAlpha = remember { appPrefs.inAppBarsAlpha.state }
|
||||||
title = {
|
val handler = LocalAppBarHandler.current
|
||||||
if (!showSearch) {
|
val connection = LocalAppBarHandler.current?.connection
|
||||||
title?.invoke()
|
val titleText = remember(handler?.title?.value, fixedTitleText) {
|
||||||
|
if (fixedTitleText != null) {
|
||||||
|
mutableStateOf(fixedTitleText)
|
||||||
} else {
|
} else {
|
||||||
SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = false, onValueChange = onSearchValueChanged)
|
handler?.title ?: mutableStateOf("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val keyboardInset = WindowInsets.ime
|
||||||
|
Box(modifier) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val blurRadius = remember { appPrefs.appearanceBarsBlurRadius.state }
|
||||||
|
Box(Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.blurredBackgroundModifier(keyboardInset, handler, blurRadius, prefAlpha, handler?.keyboardCoversBar == true, onTop, density)
|
||||||
|
.drawWithCache {
|
||||||
|
// store it as a variable, don't put it inside if without holding it here. Compiler don't see it changes otherwise
|
||||||
|
val alpha = prefAlpha.value
|
||||||
|
val backgroundColor = if (title != null || fixedTitleText != null || connection == null || !onTop) {
|
||||||
|
themeBackgroundMix.copy(alpha)
|
||||||
|
} else {
|
||||||
|
themeBackgroundMix.copy(topTitleAlpha(false, connection))
|
||||||
|
}
|
||||||
|
onDrawBehind {
|
||||||
|
drawRect(backgroundColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.then(if (!onTop) Modifier.navigationBarsPadding() else Modifier)
|
||||||
|
.heightIn(min = AppBarHeight * fontSizeSqrtMultiplier)
|
||||||
|
) {
|
||||||
|
AppBar(
|
||||||
|
title = {
|
||||||
|
if (showSearch) {
|
||||||
|
SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = searchAlwaysVisible, reducedCloseButtonPadding = 12.dp, onValueChange = onSearchValueChanged)
|
||||||
|
} else if (title != null) {
|
||||||
|
title()
|
||||||
|
} else if (titleText.value.isNotEmpty() && connection != null) {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = if (fixedTitleText != null) 1f else topTitleAlpha(true, connection)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
titleText.value,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
backgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f),
|
|
||||||
navigationIcon = navigationButton,
|
navigationIcon = navigationButton,
|
||||||
buttons = if (!showSearch) buttons else emptyList(),
|
buttons = if (!showSearch) buttons else {{}},
|
||||||
centered = !showSearch,
|
centered = !showSearch && (title != null || !onTop),
|
||||||
|
onTop = onTop,
|
||||||
|
)
|
||||||
|
AppBarDivider(onTop, title != null || fixedTitleText != null, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CallAppBar(
|
||||||
|
title: @Composable () -> Unit,
|
||||||
|
onBack: () -> Unit
|
||||||
|
) {
|
||||||
|
AppBar(
|
||||||
|
title,
|
||||||
|
navigationIcon = { NavigationButtonBack(tintColor = Color(0xFFFFFFD8), onButtonClicked = onBack) },
|
||||||
|
centered = false,
|
||||||
|
onTop = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,58 +159,107 @@ fun NavigationButtonMenu(onButtonClicked: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopAppBar(
|
private fun BoxScope.AppBarDivider(onTop: Boolean, fixedAlpha: Boolean, connection: CollapsingAppBarNestedScrollConnection?) {
|
||||||
|
if (connection != null) {
|
||||||
|
Divider(
|
||||||
|
Modifier
|
||||||
|
.align(if (onTop) Alignment.BottomStart else Alignment.TopStart)
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = if (!onTop || fixedAlpha) 1f else topTitleAlpha(false, connection, 1f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Divider(Modifier.align(if (onTop) Alignment.BottomStart else Alignment.TopStart))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AppBar(
|
||||||
title: @Composable () -> Unit,
|
title: @Composable () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
navigationIcon: @Composable (RowScope.() -> Unit)? = null,
|
navigationIcon: @Composable (RowScope.() -> Unit)? = null,
|
||||||
buttons: List<@Composable RowScope.() -> Unit> = emptyList(),
|
buttons: @Composable RowScope.() -> Unit = {},
|
||||||
backgroundColor: Color = MaterialTheme.colors.primarySurface,
|
|
||||||
centered: Boolean,
|
centered: Boolean,
|
||||||
|
onTop: Boolean,
|
||||||
) {
|
) {
|
||||||
Box(
|
val adjustedModifier = modifier
|
||||||
modifier
|
.then(if (onTop) Modifier.statusBarsPadding() else Modifier)
|
||||||
.fillMaxWidth()
|
|
||||||
.height(AppBarHeight * fontSizeSqrtMultiplier)
|
.height(AppBarHeight * fontSizeSqrtMultiplier)
|
||||||
.background(backgroundColor)
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 4.dp),
|
.padding(horizontal = AppBarHorizontalPadding)
|
||||||
contentAlignment = Alignment.CenterStart,
|
if (centered) {
|
||||||
|
AppBarCenterAligned(adjustedModifier, title, navigationIcon, buttons)
|
||||||
|
} else {
|
||||||
|
AppBarStartAligned(adjustedModifier, title, navigationIcon, buttons)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AppBarStartAligned(
|
||||||
|
modifier: Modifier,
|
||||||
|
title: @Composable () -> Unit,
|
||||||
|
navigationIcon: @Composable (RowScope.() -> Unit)? = null,
|
||||||
|
buttons: @Composable RowScope.() -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (navigationIcon != null) {
|
if (navigationIcon != null) {
|
||||||
Row(
|
navigationIcon()
|
||||||
Modifier
|
Spacer(Modifier.width(AppBarHorizontalPadding))
|
||||||
.fillMaxHeight()
|
} else {
|
||||||
.width(TitleInsetWithIcon - AppBarHorizontalPadding),
|
Spacer(Modifier.width(DEFAULT_PADDING))
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
}
|
||||||
content = navigationIcon
|
Row(Modifier
|
||||||
)
|
.weight(1f)
|
||||||
|
.padding(end = DEFAULT_PADDING_HALF)
|
||||||
|
) {
|
||||||
|
title()
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
|
||||||
.fillMaxHeight()
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
buttons.forEach { it() }
|
buttons()
|
||||||
}
|
|
||||||
val startPadding = if (navigationIcon != null) TitleInsetWithIcon else TitleInsetWithoutIcon
|
|
||||||
val endPadding = (buttons.size * 50f).dp
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(
|
|
||||||
start = if (centered) kotlin.math.max(startPadding.value, endPadding.value).dp else startPadding,
|
|
||||||
end = if (centered) kotlin.math.max(startPadding.value, endPadding.value).dp else endPadding
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
title()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AppBarCenterAligned(
|
||||||
|
modifier: Modifier,
|
||||||
|
title: @Composable () -> Unit,
|
||||||
|
navigationIcon: @Composable (RowScope.() -> Unit)? = null,
|
||||||
|
buttons: @Composable RowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
CenteredRowLayout(modifier) {
|
||||||
|
if (navigationIcon != null) {
|
||||||
|
Row(
|
||||||
|
Modifier.padding(end = AppBarHorizontalPadding),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
content = navigationIcon
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Spacer(Modifier)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
Modifier.padding(end = DEFAULT_PADDING_HALF)
|
||||||
|
) {
|
||||||
|
title()
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
buttons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun topTitleAlpha(text: Boolean, connection: CollapsingAppBarNestedScrollConnection, alpha: Float = appPrefs.inAppBarsAlpha.get()) =
|
||||||
|
if (connection.appBarOffset.absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 0f
|
||||||
|
else ((-connection.appBarOffset * 1.5f) / (AppBarHandler.appBarMaxHeightPx)).coerceIn(0f, if (text) 1f else alpha)
|
||||||
|
|
||||||
val AppBarHeight = 56.dp
|
val AppBarHeight = 56.dp
|
||||||
val AppBarHorizontalPadding = 4.dp
|
val AppBarHorizontalPadding = 2.dp
|
||||||
val BottomAppBarHeight = 60.dp
|
|
||||||
private val TitleInsetWithoutIcon = DEFAULT_PADDING - AppBarHorizontalPadding
|
|
||||||
val TitleInsetWithIcon = 72.dp
|
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -107,6 +106,7 @@ fun <T> ExposedDropDownSettingWithIcon(
|
|||||||
expanded.value = !expanded.value && enabled.value
|
expanded.value = !expanded.value && enabled.value
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
val ripple = remember { ripple(bounded = false, radius = boxSize / 2, color = background.lighter(0.1f)) }
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.background(background, CircleShape)
|
.background(background, CircleShape)
|
||||||
@@ -115,7 +115,7 @@ fun <T> ExposedDropDownSettingWithIcon(
|
|||||||
onClick = {},
|
onClick = {},
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = rememberRipple(bounded = false, radius = boxSize / 2, color = background.lighter(0.1f)),
|
indication = ripple,
|
||||||
enabled = enabled.value
|
enabled = enabled.value
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
|
|||||||
+36
-24
@@ -6,12 +6,14 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.ChatModel
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.chatlist.StatusBarBackground
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -21,24 +23,40 @@ import kotlin.math.sqrt
|
|||||||
fun ModalView(
|
fun ModalView(
|
||||||
close: () -> Unit,
|
close: () -> Unit,
|
||||||
showClose: Boolean = true,
|
showClose: Boolean = true,
|
||||||
|
showAppBar: Boolean = true,
|
||||||
enableClose: Boolean = true,
|
enableClose: Boolean = true,
|
||||||
background: Color = MaterialTheme.colors.background,
|
background: Color = Color.Unspecified,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
closeOnTop: Boolean = true,
|
showSearch: Boolean = false,
|
||||||
|
searchAlwaysVisible: Boolean = false,
|
||||||
|
onSearchValueChanged: (String) -> Unit = {},
|
||||||
endButtons: @Composable RowScope.() -> Unit = {},
|
endButtons: @Composable RowScope.() -> Unit = {},
|
||||||
content: @Composable () -> Unit,
|
content: @Composable BoxScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
if (showClose) {
|
if (showClose && showAppBar) {
|
||||||
BackHandler(enabled = enableClose, onBack = close)
|
BackHandler(enabled = enableClose, onBack = close)
|
||||||
}
|
}
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
Surface(Modifier.fillMaxSize(), contentColor = LocalContentColor.current) {
|
Surface(Modifier.fillMaxSize(), contentColor = LocalContentColor.current) {
|
||||||
Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) {
|
Box(if (background != Color.Unspecified) Modifier.background(background) else Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) {
|
||||||
if (closeOnTop) {
|
|
||||||
CloseSheetBar(if (enableClose) close else null, showClose, endButtons = endButtons)
|
|
||||||
}
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
|
if (showAppBar) {
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
StatusBarBackground()
|
||||||
|
}
|
||||||
|
Box(Modifier.align(if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart)) {
|
||||||
|
DefaultAppBar(
|
||||||
|
navigationButton = if (showClose) {{ NavigationButtonBack(onButtonClicked = if (enableClose) close else null) }} else null,
|
||||||
|
onTop = !oneHandUI.value,
|
||||||
|
showSearch = showSearch,
|
||||||
|
searchAlwaysVisible = searchAlwaysVisible,
|
||||||
|
onSearchValueChanged = onSearchValueChanged,
|
||||||
|
buttons = endButtons
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +65,7 @@ enum class ModalPlacement {
|
|||||||
START, CENTER, END, FULLSCREEN
|
START, CENTER, END, FULLSCREEN
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModalData() {
|
class ModalData(val keyboardCoversBar: Boolean = true) {
|
||||||
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>
|
||||||
@@ -55,7 +73,7 @@ class ModalData() {
|
|||||||
fun <T> stateGetOrPutNullable (key: String, default: () -> T?): MutableState<T?> =
|
fun <T> stateGetOrPutNullable (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?>
|
||||||
|
|
||||||
val appBarHandler = AppBarHandler()
|
val appBarHandler = AppBarHandler(null, null, keyboardCoversBar = keyboardCoversBar)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModalManager(private val placement: ModalPlacement? = null) {
|
class ModalManager(private val placement: ModalPlacement? = null) {
|
||||||
@@ -69,23 +87,21 @@ class ModalManager(private val placement: ModalPlacement? = null) {
|
|||||||
private var passcodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null)
|
private var passcodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null)
|
||||||
private var onTimePasscodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null)
|
private var onTimePasscodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null)
|
||||||
|
|
||||||
fun showModal(settings: Boolean = false, showClose: Boolean = true, closeOnTop: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) {
|
fun showModal(settings: Boolean = false, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) {
|
||||||
val data = ModalData()
|
|
||||||
showCustomModal { close ->
|
showCustomModal { close ->
|
||||||
ModalView(close, showClose = showClose, closeOnTop = closeOnTop, endButtons = endButtons, content = { data.content() })
|
ModalView(close, showClose = showClose, endButtons = endButtons, content = { content() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showModalCloseable(settings: Boolean = false, showClose: Boolean = true, closeOnTop: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.(close: () -> Unit) -> Unit) {
|
fun showModalCloseable(settings: Boolean = false, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.(close: () -> Unit) -> Unit) {
|
||||||
val data = ModalData()
|
|
||||||
showCustomModal { close ->
|
showCustomModal { close ->
|
||||||
ModalView(close, showClose = showClose, endButtons = endButtons, closeOnTop = closeOnTop, content = { data.content(close) })
|
ModalView(close, showClose = showClose, endButtons = endButtons, content = { content(close) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showCustomModal(animated: Boolean = true, modal: @Composable ModalData.(close: () -> Unit) -> Unit) {
|
fun showCustomModal(animated: Boolean = true, keyboardCoversBar: Boolean = true, modal: @Composable ModalData.(close: () -> Unit) -> Unit) {
|
||||||
Log.d(TAG, "ModalManager.showCustomModal")
|
Log.d(TAG, "ModalManager.showCustomModal")
|
||||||
val data = ModalData()
|
val data = ModalData(keyboardCoversBar = keyboardCoversBar)
|
||||||
// Means, animation is in progress or not started yet. Do not wait until animation finishes, just remove all from screen.
|
// Means, animation is in progress or not started yet. Do not wait until animation finishes, just remove all from screen.
|
||||||
// This is useful when invoking close() and ShowCustomModal one after another without delay. Otherwise, screen will hold prev view
|
// This is useful when invoking close() and ShowCustomModal one after another without delay. Otherwise, screen will hold prev view
|
||||||
if (toRemove.isNotEmpty()) {
|
if (toRemove.isNotEmpty()) {
|
||||||
@@ -146,9 +162,7 @@ class ModalManager(private val placement: ModalPlacement? = null) {
|
|||||||
// Without animation
|
// Without animation
|
||||||
if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) {
|
if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) {
|
||||||
modalViews.lastOrNull()?.let {
|
modalViews.lastOrNull()?.let {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(LocalAppBarHandler provides adjustAppBarHandler(it.second.appBarHandler)) {
|
||||||
LocalAppBarHandler provides it.second.appBarHandler
|
|
||||||
) {
|
|
||||||
it.third(it.second, ::closeModal)
|
it.third(it.second, ::closeModal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,9 +178,7 @@ class ModalManager(private val placement: ModalPlacement? = null) {
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
modalViews.getOrNull(it - 1)?.let {
|
modalViews.getOrNull(it - 1)?.let {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(LocalAppBarHandler provides adjustAppBarHandler(it.second.appBarHandler)) {
|
||||||
LocalAppBarHandler provides it.second.appBarHandler
|
|
||||||
) {
|
|
||||||
it.third(it.second, ::closeModal)
|
it.third(it.second, ::closeModal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-13
@@ -2,7 +2,7 @@ package chat.simplex.common.views.helpers
|
|||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
import androidx.compose.foundation.text.*
|
import androidx.compose.foundation.text.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
@@ -18,12 +18,9 @@ import androidx.compose.ui.platform.LocalFocusManager
|
|||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
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.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.input.*
|
import androidx.compose.ui.text.input.*
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.*
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -38,6 +35,7 @@ fun SearchTextField(
|
|||||||
placeholder: String = stringResource(MR.strings.search_verb),
|
placeholder: String = stringResource(MR.strings.search_verb),
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
trailingContent: @Composable (() -> Unit)? = null,
|
trailingContent: @Composable (() -> Unit)? = null,
|
||||||
|
reducedCloseButtonPadding: Dp = 0.dp,
|
||||||
onValueChange: (String) -> Unit
|
onValueChange: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
@@ -81,15 +79,20 @@ fun SearchTextField(
|
|||||||
)
|
)
|
||||||
val shape = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize)
|
val shape = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize)
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
|
||||||
|
// sizing is done differently on Android and desktop in order to have the same height of search and compose view on desktop
|
||||||
|
// see PlatformTextField.desktop + SendMsgView
|
||||||
|
val padding = if (appPlatform.isAndroid) PaddingValues() else PaddingValues(top = 3.dp, bottom = 4.dp)
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = searchText.value,
|
value = searchText.value,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(colors.backgroundColor(enabled).value, shape)
|
.background(colors.backgroundColor(enabled).value, shape)
|
||||||
.indicatorLine(enabled, false, interactionSource, colors)
|
.indicatorLine(enabled, false, interactionSource, colors)
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
|
.padding(padding)
|
||||||
.defaultMinSize(
|
.defaultMinSize(
|
||||||
minWidth = TextFieldDefaults.MinWidth,
|
minWidth = TextFieldDefaults.MinWidth,
|
||||||
minHeight = TextFieldDefaults.MinHeight
|
minHeight = if (appPlatform.isAndroid) TextFieldDefaults.MinHeight else 0.dp
|
||||||
),
|
),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
searchText.value = it
|
searchText.value = it
|
||||||
@@ -100,18 +103,14 @@ fun SearchTextField(
|
|||||||
visualTransformation = VisualTransformation.None,
|
visualTransformation = VisualTransformation.None,
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
textStyle = TextStyle(
|
textStyle = textStyle,
|
||||||
color = MaterialTheme.colors.onBackground,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 15.sp
|
|
||||||
),
|
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
decorationBox = @Composable { innerTextField ->
|
decorationBox = @Composable { innerTextField ->
|
||||||
TextFieldDefaults.TextFieldDecorationBox(
|
TextFieldDefaults.TextFieldDecorationBox(
|
||||||
value = searchText.value.text,
|
value = searchText.value.text,
|
||||||
innerTextField = innerTextField,
|
innerTextField = innerTextField,
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(placeholder, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
Text(placeholder, style = textStyle.copy(color = MaterialTheme.colors.secondary), maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
},
|
},
|
||||||
trailingIcon = if (searchText.value.text.isNotEmpty()) {{
|
trailingIcon = if (searchText.value.text.isNotEmpty()) {{
|
||||||
IconButton({
|
IconButton({
|
||||||
@@ -121,7 +120,7 @@ fun SearchTextField(
|
|||||||
}
|
}
|
||||||
searchText.value = TextFieldValue("");
|
searchText.value = TextFieldValue("");
|
||||||
onValueChange("")
|
onValueChange("")
|
||||||
}) {
|
}, Modifier.offset(x = reducedCloseButtonPadding)) {
|
||||||
Icon(painterResource(MR.images.ic_close), stringResource(MR.strings.icon_descr_close_button), tint = MaterialTheme.colors.primary,)
|
Icon(painterResource(MR.images.ic_close), stringResource(MR.strings.icon_descr_close_button), tint = MaterialTheme.colors.primary,)
|
||||||
}
|
}
|
||||||
}} else trailingContent,
|
}} else trailingContent,
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,6 @@ fun TextEditor(
|
|||||||
) {
|
) {
|
||||||
val textFieldModifier = modifier
|
val textFieldModifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.navigationBarsWithImePadding()
|
|
||||||
.onFocusChanged { focused = it.isFocused }
|
.onFocusChanged { focused = it.isFocused }
|
||||||
.padding(10.dp)
|
.padding(10.dp)
|
||||||
|
|
||||||
@@ -87,6 +86,7 @@ fun TextEditor(
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
isError = false,
|
isError = false,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-8
@@ -32,10 +32,7 @@ fun ModalData.UserWallpaperEditor(
|
|||||||
globalThemeUsed: MutableState<Boolean>,
|
globalThemeUsed: MutableState<Boolean>,
|
||||||
save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit
|
save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } }
|
val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } }
|
||||||
var showMore by remember { stateGetOrPut("showMore") { false } }
|
var showMore by remember { stateGetOrPut("showMore") { false } }
|
||||||
val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } }
|
val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } }
|
||||||
@@ -231,10 +228,7 @@ fun ModalData.ChatWallpaperEditor(
|
|||||||
globalThemeUsed: MutableState<Boolean>,
|
globalThemeUsed: MutableState<Boolean>,
|
||||||
save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit
|
save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } }
|
val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } }
|
||||||
var showMore by remember { stateGetOrPut("showMore") { false } }
|
var showMore by remember { stateGetOrPut("showMore") { false } }
|
||||||
val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } }
|
val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } }
|
||||||
|
|||||||
+1
-3
@@ -149,9 +149,7 @@ private fun MigrateFromDeviceLayout(
|
|||||||
) {
|
) {
|
||||||
val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) }
|
val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) }
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(maxIntrinsicSize = true) {
|
||||||
Modifier.fillMaxSize(), maxIntrinsicSize = true
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.migrate_from_device_title))
|
AppBarTitle(stringResource(MR.strings.migrate_from_device_title))
|
||||||
SectionByState(migrationState, tempDatabaseFile.value, chatReceiver)
|
SectionByState(migrationState, tempDatabaseFile.value, chatReceiver)
|
||||||
SectionBottomSpacer()
|
SectionBottomSpacer()
|
||||||
|
|||||||
+1
-3
@@ -162,9 +162,7 @@ private fun ModalData.MigrateToDeviceLayout(
|
|||||||
close: () -> Unit,
|
close: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) }
|
val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) }
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(maxIntrinsicSize = true) {
|
||||||
Modifier.fillMaxSize(), maxIntrinsicSize = true
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.migrate_to_device_title))
|
AppBarTitle(stringResource(MR.strings.migrate_to_device_title))
|
||||||
SectionByState(migrationState, tempDatabaseFile.value, chatReceiver, close)
|
SectionByState(migrationState, tempDatabaseFile.value, chatReceiver, close)
|
||||||
SectionBottomSpacer()
|
SectionBottomSpacer()
|
||||||
|
|||||||
+1
-3
@@ -15,9 +15,7 @@ import chat.simplex.res.MR
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AddContactLearnMore(close: () -> Unit) {
|
fun AddContactLearnMore(close: () -> Unit) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||||
Modifier.padding(horizontal = DEFAULT_PADDING),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.one_time_link), withPadding = false)
|
AppBarTitle(stringResource(MR.strings.one_time_link), withPadding = false)
|
||||||
ReadableText(MR.strings.scan_qr_to_connect_to_contact)
|
ReadableText(MR.strings.scan_qr_to_connect_to_contact)
|
||||||
ReadableText(MR.strings.if_you_cant_meet_in_person)
|
ReadableText(MR.strings.if_you_cant_meet_in_person)
|
||||||
|
|||||||
+5
-9
@@ -84,10 +84,9 @@ fun AddGroupLayout(
|
|||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val incognito = remember { mutableStateOf(incognitoPref.get()) }
|
val incognito = remember { mutableStateOf(incognitoPref.get()) }
|
||||||
|
|
||||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
|
||||||
ModalBottomSheetLayout(
|
ModalBottomSheetLayout(
|
||||||
scrimColor = Color.Black.copy(alpha = 0.12F),
|
scrimColor = Color.Black.copy(alpha = 0.12F),
|
||||||
modifier = Modifier.navigationBarsWithImePadding(),
|
modifier = Modifier.imePadding(),
|
||||||
sheetContent = {
|
sheetContent = {
|
||||||
GetImageBottomSheet(
|
GetImageBottomSheet(
|
||||||
chosenImage,
|
chosenImage,
|
||||||
@@ -100,11 +99,7 @@ fun AddGroupLayout(
|
|||||||
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
||||||
) {
|
) {
|
||||||
ModalView(close = close) {
|
ModalView(close = close) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = DEFAULT_PADDING)
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.create_secret_group_title), hostDevice(rhId))
|
AppBarTitle(stringResource(MR.strings.create_secret_group_title), hostDevice(rhId))
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
@@ -122,7 +117,7 @@ fun AddGroupLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(MR.strings.group_display_name_field),
|
stringResource(MR.strings.group_display_name_field),
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
@@ -134,7 +129,9 @@ fun AddGroupLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Box(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||||
ProfileNameField(displayName, "", { isValidDisplayName(it.trim()) }, focusRequester)
|
ProfileNameField(displayName, "", { isValidDisplayName(it.trim()) }, focusRequester)
|
||||||
|
}
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
|
|
||||||
SettingsActionItem(
|
SettingsActionItem(
|
||||||
@@ -171,7 +168,6 @@ fun AddGroupLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun canCreateProfile(displayName: String): Boolean = displayName.trim().isNotEmpty() && isValidDisplayName(displayName.trim())
|
fun canCreateProfile(displayName: String): Boolean = displayName.trim().isNotEmpty() && isValidDisplayName(displayName.trim())
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -89,7 +89,7 @@ private fun ContactConnectionInfoLayout(
|
|||||||
SettingsActionItemWithContent(
|
SettingsActionItemWithContent(
|
||||||
icon = painterResource(MR.images.ic_theater_comedy_filled),
|
icon = painterResource(MR.images.ic_theater_comedy_filled),
|
||||||
text = null,
|
text = null,
|
||||||
click = { ModalManager.start.showModal { IncognitoView() } },
|
click = { ModalManager.end.showModal { IncognitoView() } },
|
||||||
iconColor = Indigo,
|
iconColor = Indigo,
|
||||||
extraPadding = false
|
extraPadding = false
|
||||||
) {
|
) {
|
||||||
@@ -105,9 +105,7 @@ private fun ContactConnectionInfoLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier,
|
|
||||||
) {
|
|
||||||
AppBarTitle(
|
AppBarTitle(
|
||||||
stringResource(
|
stringResource(
|
||||||
if (contactConnection.initiated) MR.strings.you_invited_a_contact
|
if (contactConnection.initiated) MR.strings.you_invited_a_contact
|
||||||
|
|||||||
+224
-133
@@ -1,9 +1,7 @@
|
|||||||
package chat.simplex.common.views.newchat
|
package chat.simplex.common.views.newchat
|
||||||
|
|
||||||
import SectionDivider
|
|
||||||
import SectionDividerSpaced
|
import SectionDividerSpaced
|
||||||
import SectionItemView
|
import SectionItemView
|
||||||
import SectionSpacer
|
|
||||||
import SectionView
|
import SectionView
|
||||||
import TextIconSpaced
|
import TextIconSpaced
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
@@ -14,8 +12,7 @@ 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.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.*
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.focus.*
|
import androidx.compose.ui.focus.*
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
@@ -32,41 +29,20 @@ import chat.simplex.common.model.*
|
|||||||
import chat.simplex.common.model.ChatController.appPrefs
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.chatlist.ScrollDirection
|
import chat.simplex.common.views.chat.topPaddingToContent
|
||||||
|
import chat.simplex.common.views.chatlist.*
|
||||||
import chat.simplex.common.views.contacts.*
|
import chat.simplex.common.views.contacts.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) {
|
fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) {
|
||||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
val keyboardState by getKeyboardState()
|
|
||||||
val showToolbarInOneHandUI = remember { derivedStateOf { keyboardState == KeyboardState.Closed && oneHandUI.value } }
|
|
||||||
|
|
||||||
Scaffold(
|
Box {
|
||||||
bottomBar = {
|
|
||||||
if (showToolbarInOneHandUI.value) {
|
|
||||||
Column {
|
|
||||||
Divider()
|
|
||||||
CloseSheetBar(
|
|
||||||
close = close,
|
|
||||||
showClose = true,
|
|
||||||
endButtons = { Spacer(Modifier.minimumInteractiveComponentSize()) },
|
|
||||||
arrangement = Arrangement.Bottom,
|
|
||||||
closeBarTitle = generalGetString(MR.strings.new_message),
|
|
||||||
barPaddingValues = PaddingValues(horizontal = 0.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxSize().padding(it)
|
|
||||||
) {
|
|
||||||
val closeAll = { ModalManager.start.closeModals() }
|
val closeAll = { ModalManager.start.closeModals() }
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
@@ -84,6 +60,14 @@ fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) {
|
|||||||
close = close
|
close = close
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
Column(Modifier.align(Alignment.BottomCenter)) {
|
||||||
|
DefaultAppBar(
|
||||||
|
navigationButton = { NavigationButtonBack(onButtonClicked = close) },
|
||||||
|
fixedTitleText = generalGetString(MR.strings.new_message),
|
||||||
|
onTop = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,69 +171,6 @@ private fun ModalData.NewChatSheetLayout(
|
|||||||
derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) }
|
derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) }
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumnWithScrollBar(
|
|
||||||
Modifier.fillMaxSize(),
|
|
||||||
listState,
|
|
||||||
reverseLayout = oneHandUI.value
|
|
||||||
) {
|
|
||||||
if (!oneHandUI.value) {
|
|
||||||
item {
|
|
||||||
Box(contentAlignment = Alignment.Center) {
|
|
||||||
val bottomPadding = DEFAULT_PADDING
|
|
||||||
AppBarTitle(
|
|
||||||
stringResource(MR.strings.new_message),
|
|
||||||
hostDevice(rh?.remoteHostId),
|
|
||||||
bottomPadding = bottomPadding
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stickyHeader {
|
|
||||||
Column(
|
|
||||||
Modifier
|
|
||||||
.offset {
|
|
||||||
val y = if (searchText.value.text.isEmpty()) {
|
|
||||||
val offsetMultiplier = if (oneHandUI.value) 1 else -1
|
|
||||||
|
|
||||||
if (
|
|
||||||
(oneHandUI.value && scrollDirection == ScrollDirection.Up) ||
|
|
||||||
(appPlatform.isAndroid && keyboardState == KeyboardState.Opened)
|
|
||||||
) {
|
|
||||||
0
|
|
||||||
} else if (oneHandUI.value && listState.firstVisibleItemIndex == 0) {
|
|
||||||
listState.firstVisibleItemScrollOffset
|
|
||||||
} else if (!oneHandUI.value && listState.firstVisibleItemIndex == 0) {
|
|
||||||
0
|
|
||||||
} else if (!oneHandUI.value && listState.firstVisibleItemIndex == 1) {
|
|
||||||
-listState.firstVisibleItemScrollOffset
|
|
||||||
} else {
|
|
||||||
offsetMultiplier * 1000
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
IntOffset(0, y)
|
|
||||||
}
|
|
||||||
.background(MaterialTheme.colors.background)
|
|
||||||
) {
|
|
||||||
Divider()
|
|
||||||
ContactsSearchBar(
|
|
||||||
listState = listState,
|
|
||||||
searchText = searchText,
|
|
||||||
searchShowingSimplexLink = searchShowingSimplexLink,
|
|
||||||
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
|
|
||||||
close = close,
|
|
||||||
)
|
|
||||||
if (!oneHandUI.value) {
|
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
if (searchText.value.text.isEmpty()) {
|
|
||||||
Spacer(Modifier.padding(bottom = 27.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
val actionButtonsOriginal = listOf(
|
val actionButtonsOriginal = listOf(
|
||||||
Triple(
|
Triple(
|
||||||
painterResource(MR.images.ic_add_link),
|
painterResource(MR.images.ic_add_link),
|
||||||
@@ -268,10 +189,10 @@ private fun ModalData.NewChatSheetLayout(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val actionButtons by remember(oneHandUI.value) {
|
@Composable
|
||||||
derivedStateOf {
|
fun DeletedChatsItem(actionButtons: List<Triple<Painter, String, () -> Unit>>) {
|
||||||
if (oneHandUI.value) actionButtonsOriginal.asReversed() else actionButtonsOriginal
|
if (searchText.value.text.isEmpty()) {
|
||||||
}
|
Spacer(Modifier.padding(bottom = 27.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchText.value.text.isEmpty()) {
|
if (searchText.value.text.isEmpty()) {
|
||||||
@@ -294,8 +215,11 @@ private fun ModalData.NewChatSheetLayout(
|
|||||||
ModalManager.start.showCustomModal { closeDeletedChats ->
|
ModalManager.start.showCustomModal { closeDeletedChats ->
|
||||||
ModalView(
|
ModalView(
|
||||||
close = closeDeletedChats,
|
close = closeDeletedChats,
|
||||||
closeOnTop = !oneHandUI.value,
|
showAppBar = !oneHandUI.value,
|
||||||
) {
|
) {
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
BackHandler(onBack = closeDeletedChats)
|
||||||
|
}
|
||||||
DeletedContactsView(rh = rh, closeDeletedChats = closeDeletedChats, close = {
|
DeletedContactsView(rh = rh, closeDeletedChats = closeDeletedChats, close = {
|
||||||
ModalManager.start.closeModals()
|
ModalManager.start.closeModals()
|
||||||
})
|
})
|
||||||
@@ -316,20 +240,8 @@ private fun ModalData.NewChatSheetLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
@Composable
|
||||||
if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) {
|
fun NoFilteredContactsItem() {
|
||||||
if (!oneHandUI.value) {
|
|
||||||
SectionDividerSpaced()
|
|
||||||
SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {}
|
|
||||||
} else {
|
|
||||||
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false)
|
|
||||||
SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {}
|
|
||||||
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) {
|
if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) {
|
||||||
Column(sectionModifier.fillMaxSize().padding(DEFAULT_PADDING)) {
|
Column(sectionModifier.fillMaxSize().padding(DEFAULT_PADDING)) {
|
||||||
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||||
@@ -342,6 +254,68 @@ private fun ModalData.NewChatSheetLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OneHandLazyColumn() {
|
||||||
|
val blankSpaceSize = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier
|
||||||
|
LazyColumnWithScrollBar(
|
||||||
|
state = listState,
|
||||||
|
reverseLayout = oneHandUI.value
|
||||||
|
) {
|
||||||
|
item { Spacer(Modifier.height(blankSpaceSize)) }
|
||||||
|
stickyHeader {
|
||||||
|
val scrolledSomething by remember { derivedStateOf { listState.firstVisibleItemScrollOffset > 0 || listState.firstVisibleItemIndex > 0 } }
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.zIndex(1f)
|
||||||
|
.offset {
|
||||||
|
val y = if (searchText.value.text.isNotEmpty() || (appPlatform.isAndroid && keyboardState == KeyboardState.Opened)) {
|
||||||
|
if (listState.firstVisibleItemIndex == 0) -minOf(listState.firstVisibleItemScrollOffset, blankSpaceSize.roundToPx())
|
||||||
|
else -blankSpaceSize.roundToPx()
|
||||||
|
} else {
|
||||||
|
when (listState.firstVisibleItemIndex) {
|
||||||
|
0 -> 0
|
||||||
|
1 -> listState.firstVisibleItemScrollOffset
|
||||||
|
else -> 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IntOffset(0, y)
|
||||||
|
}
|
||||||
|
// show background when something is scrolled because otherwise the bar is transparent.
|
||||||
|
// not using background always because of gradient in SimpleX theme
|
||||||
|
.background(
|
||||||
|
if (scrolledSomething && (keyboardState == KeyboardState.Opened || searchText.value.text.isNotEmpty())) {
|
||||||
|
MaterialTheme.colors.background
|
||||||
|
} else {
|
||||||
|
Color.Unspecified
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Divider()
|
||||||
|
Column(Modifier.consumeWindowInsets(WindowInsets.navigationBars).consumeWindowInsets(PaddingValues(bottom = AppBarHeight * fontSizeSqrtMultiplier))) {
|
||||||
|
ContactsSearchBar(
|
||||||
|
listState = listState,
|
||||||
|
searchText = searchText,
|
||||||
|
searchShowingSimplexLink = searchShowingSimplexLink,
|
||||||
|
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
|
||||||
|
close = close,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
DeletedChatsItem(actionButtonsOriginal.asReversed())
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) {
|
||||||
|
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false)
|
||||||
|
SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {}
|
||||||
|
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
NoFilteredContactsItem()
|
||||||
|
}
|
||||||
itemsIndexed(filteredContactChats) { index, chat ->
|
itemsIndexed(filteredContactChats) { index, chat ->
|
||||||
val nextChatSelected = remember(chat.id, filteredContactChats) {
|
val nextChatSelected = remember(chat.id, filteredContactChats) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -350,6 +324,106 @@ private fun ModalData.NewChatSheetLayout(
|
|||||||
}
|
}
|
||||||
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true)
|
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true)
|
||||||
}
|
}
|
||||||
|
if (appPlatform.isAndroid) {
|
||||||
|
item {
|
||||||
|
Spacer(Modifier.windowInsetsTopHeight(WindowInsets.statusBars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NonOneHandLazyColumn() {
|
||||||
|
val blankSpaceSize = topPaddingToContent()
|
||||||
|
LazyColumnWithScrollBar(
|
||||||
|
Modifier.imePadding(),
|
||||||
|
state = listState,
|
||||||
|
reverseLayout = false
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Box(Modifier.padding(top = blankSpaceSize)) {
|
||||||
|
AppBarTitle(
|
||||||
|
stringResource(MR.strings.new_message),
|
||||||
|
hostDevice(rh?.remoteHostId),
|
||||||
|
bottomPadding = DEFAULT_PADDING
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stickyHeader {
|
||||||
|
val scrolledSomething by remember { derivedStateOf { listState.firstVisibleItemScrollOffset > 0 || listState.firstVisibleItemIndex > 0 } }
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.zIndex(1f)
|
||||||
|
.offset {
|
||||||
|
val y = if (searchText.value.text.isNotEmpty() || (appPlatform.isAndroid && keyboardState == KeyboardState.Opened)) {
|
||||||
|
if (listState.firstVisibleItemIndex == 0) (listState.firstVisibleItemScrollOffset - (listState.layoutInfo.visibleItemsInfo[0].size - blankSpaceSize.roundToPx())).coerceAtLeast(0)
|
||||||
|
else blankSpaceSize.roundToPx()
|
||||||
|
} else {
|
||||||
|
when (listState.firstVisibleItemIndex) {
|
||||||
|
0 -> 0
|
||||||
|
1 -> -listState.firstVisibleItemScrollOffset
|
||||||
|
else -> -1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IntOffset(0, y)
|
||||||
|
}
|
||||||
|
// show background when something is scrolled because otherwise the bar is transparent.
|
||||||
|
// not using background always because of gradient in SimpleX theme
|
||||||
|
.background(
|
||||||
|
if (scrolledSomething && (keyboardState == KeyboardState.Opened || searchText.value.text.isNotEmpty())) {
|
||||||
|
MaterialTheme.colors.background
|
||||||
|
} else {
|
||||||
|
Color.Unspecified
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Divider()
|
||||||
|
ContactsSearchBar(
|
||||||
|
listState = listState,
|
||||||
|
searchText = searchText,
|
||||||
|
searchShowingSimplexLink = searchShowingSimplexLink,
|
||||||
|
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
|
||||||
|
close = close,
|
||||||
|
)
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
DeletedChatsItem(actionButtonsOriginal)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) {
|
||||||
|
SectionDividerSpaced()
|
||||||
|
SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
NoFilteredContactsItem()
|
||||||
|
}
|
||||||
|
itemsIndexed(filteredContactChats) { index, chat ->
|
||||||
|
val nextChatSelected = remember(chat.id, filteredContactChats) {
|
||||||
|
derivedStateOf {
|
||||||
|
chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true)
|
||||||
|
}
|
||||||
|
if (appPlatform.isAndroid) {
|
||||||
|
item {
|
||||||
|
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box {
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
OneHandLazyColumn()
|
||||||
|
StatusBarBackground()
|
||||||
|
} else {
|
||||||
|
NonOneHandLazyColumn()
|
||||||
|
NavigationBarBackground(oneHandUI.value, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,26 +628,7 @@ private fun contactTypesSearchTargets(baseContactTypes: List<ContactType>, searc
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats: () -> Unit, close: () -> Unit) {
|
private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats: () -> Unit, close: () -> Unit) {
|
||||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
val keyboardState by getKeyboardState()
|
Box {
|
||||||
val showToolbarInOneHandUI = remember { derivedStateOf { keyboardState == KeyboardState.Closed && oneHandUI.value } }
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
bottomBar = {
|
|
||||||
if (showToolbarInOneHandUI.value) {
|
|
||||||
Column {
|
|
||||||
Divider()
|
|
||||||
CloseSheetBar(
|
|
||||||
close = closeDeletedChats,
|
|
||||||
showClose = true,
|
|
||||||
endButtons = { Spacer(Modifier.minimumInteractiveComponentSize()) },
|
|
||||||
arrangement = Arrangement.Bottom,
|
|
||||||
closeBarTitle = generalGetString(MR.strings.deleted_chats),
|
|
||||||
barPaddingValues = PaddingValues(horizontal = 0.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) { contentPadding ->
|
|
||||||
val listState = remember { appBarHandler.listState }
|
val listState = remember { appBarHandler.listState }
|
||||||
val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
|
val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
|
||||||
val searchShowingSimplexLink = remember { mutableStateOf(false) }
|
val searchShowingSimplexLink = remember { mutableStateOf(false) }
|
||||||
@@ -590,9 +645,14 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats
|
|||||||
contactChats = allChats
|
contactChats = allChats
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Box {
|
||||||
|
val topPaddingToContent = topPaddingToContent()
|
||||||
LazyColumnWithScrollBar(
|
LazyColumnWithScrollBar(
|
||||||
Modifier.fillMaxSize(),
|
if (!oneHandUI.value) Modifier.imePadding() else Modifier,
|
||||||
contentPadding = contentPadding,
|
contentPadding = PaddingValues(
|
||||||
|
top = if (!oneHandUI.value) topPaddingToContent else 0.dp,
|
||||||
|
bottom = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else 0.dp
|
||||||
|
),
|
||||||
reverseLayout = oneHandUI.value,
|
reverseLayout = oneHandUI.value,
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
@@ -610,7 +670,6 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats
|
|||||||
item {
|
item {
|
||||||
if (!oneHandUI.value) {
|
if (!oneHandUI.value) {
|
||||||
Divider()
|
Divider()
|
||||||
}
|
|
||||||
ContactsSearchBar(
|
ContactsSearchBar(
|
||||||
listState = listState,
|
listState = listState,
|
||||||
searchText = searchText,
|
searchText = searchText,
|
||||||
@@ -618,6 +677,18 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats
|
|||||||
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
|
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
|
||||||
close = close,
|
close = close,
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
Column(Modifier.consumeWindowInsets(WindowInsets.navigationBars).consumeWindowInsets(PaddingValues(bottom = AppBarHeight))) {
|
||||||
|
ContactsSearchBar(
|
||||||
|
listState = listState,
|
||||||
|
searchText = searchText,
|
||||||
|
searchShowingSimplexLink = searchShowingSimplexLink,
|
||||||
|
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
|
||||||
|
close = close,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
|
||||||
|
}
|
||||||
|
}
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -642,6 +713,26 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats
|
|||||||
}
|
}
|
||||||
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = false)
|
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = false)
|
||||||
}
|
}
|
||||||
|
if (appPlatform.isAndroid) {
|
||||||
|
item {
|
||||||
|
Spacer(if (oneHandUI.value) Modifier.windowInsetsTopHeight(WindowInsets.statusBars) else Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
StatusBarBackground()
|
||||||
|
} else {
|
||||||
|
NavigationBarBackground(oneHandUI.value, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
Column(Modifier.align(Alignment.BottomCenter)) {
|
||||||
|
DefaultAppBar(
|
||||||
|
navigationButton = { NavigationButtonBack(onButtonClicked = closeDeletedChats) },
|
||||||
|
fixedTitleText = generalGetString(MR.strings.deleted_chats),
|
||||||
|
onTop = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-5
@@ -29,10 +29,12 @@ 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.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.model.ChatModel.withChats
|
import chat.simplex.common.model.ChatModel.withChats
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.chat.topPaddingToContent
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.usersettings.*
|
import chat.simplex.common.views.usersettings.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
@@ -398,8 +400,12 @@ fun ActiveProfilePicker(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.alpha(if (progressByTimeout) 0.6f else 1f)
|
.alpha(if (progressByTimeout) 0.6f else 1f)
|
||||||
) {
|
) {
|
||||||
LazyColumnWithScrollBar(userScrollEnabled = !switchingProfile.value) {
|
LazyColumnWithScrollBar(Modifier.padding(top = topPaddingToContent()), userScrollEnabled = !switchingProfile.value) {
|
||||||
item {
|
item {
|
||||||
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
if (oneHandUI.value) {
|
||||||
|
Spacer(Modifier.padding(top = DEFAULT_PADDING + 5.dp))
|
||||||
|
}
|
||||||
AppBarTitle(stringResource(MR.strings.select_chat_profile), hostDevice(rhId), bottomPadding = DEFAULT_PADDING)
|
AppBarTitle(stringResource(MR.strings.select_chat_profile), hostDevice(rhId), bottomPadding = DEFAULT_PADDING)
|
||||||
}
|
}
|
||||||
val activeProfile = filteredProfiles.firstOrNull { it.activeUser }
|
val activeProfile = filteredProfiles.firstOrNull { it.activeUser }
|
||||||
@@ -434,6 +440,9 @@ fun ActiveProfilePicker(
|
|||||||
ProfilePickerUserOption(p)
|
ProfilePickerUserOption(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
item {
|
||||||
|
Spacer(Modifier.imePadding().padding(bottom = DEFAULT_BOTTOM_PADDING))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (progressByTimeout) {
|
if (progressByTimeout) {
|
||||||
@@ -472,13 +481,13 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection
|
|||||||
end = 16.dp
|
end = 16.dp
|
||||||
),
|
),
|
||||||
click = {
|
click = {
|
||||||
ModalManager.start.showCustomModal { close ->
|
ModalManager.start.showCustomModal(keyboardCoversBar = false) { close ->
|
||||||
val search = rememberSaveable { mutableStateOf("") }
|
val search = rememberSaveable { mutableStateOf("") }
|
||||||
ModalView(
|
ModalView(
|
||||||
{ close() },
|
{ close() },
|
||||||
endButtons = {
|
showSearch = true,
|
||||||
SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it }
|
searchAlwaysVisible = true,
|
||||||
},
|
onSearchValueChanged = { search.value = it },
|
||||||
content = {
|
content = {
|
||||||
ActiveProfilePicker(
|
ActiveProfilePicker(
|
||||||
search = search,
|
search = search,
|
||||||
@@ -616,6 +625,7 @@ fun LinkTextView(link: String, share: Boolean) {
|
|||||||
enabled = false,
|
enabled = false,
|
||||||
isError = false,
|
isError = false,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-7
@@ -76,15 +76,9 @@ private fun CreateSimpleXAddressLayout(
|
|||||||
createAddress: () -> Unit,
|
createAddress: () -> Unit,
|
||||||
nextStep: () -> Unit,
|
nextStep: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val handler = remember { AppBarHandler() }
|
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalAppBarHandler provides handler
|
|
||||||
) {
|
|
||||||
ModalView({}, showClose = false) {
|
ModalView({}, showClose = false) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.themedBackground(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
AppBarTitle(stringResource(MR.strings.simplex_address))
|
AppBarTitle(stringResource(MR.strings.simplex_address))
|
||||||
|
|||||||
+1
-5
@@ -23,11 +23,7 @@ import dev.icerock.moko.resources.StringResource
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>? = null) {
|
fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>? = null) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(Modifier.padding(DEFAULT_PADDING)) {
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(DEFAULT_PADDING),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false)
|
AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false)
|
||||||
ReadableText(MR.strings.many_people_asked_how_can_it_deliver)
|
ReadableText(MR.strings.many_people_asked_how_can_it_deliver)
|
||||||
ReadableText(MR.strings.to_protect_privacy_simplex_has_ids_for_queues)
|
ReadableText(MR.strings.to_protect_privacy_simplex_has_ids_for_queues)
|
||||||
|
|||||||
+5
-12
@@ -7,21 +7,16 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.ChatModel
|
||||||
import chat.simplex.common.platform.BackHandler
|
|
||||||
import chat.simplex.common.platform.chatModel
|
import chat.simplex.common.platform.chatModel
|
||||||
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||||
import chat.simplex.common.ui.theme.themedBackground
|
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.remote.AddingMobileDevice
|
import chat.simplex.common.views.remote.AddingMobileDevice
|
||||||
import chat.simplex.common.views.remote.DeviceNameField
|
import chat.simplex.common.views.remote.DeviceNameField
|
||||||
import chat.simplex.common.views.usersettings.PreferenceToggle
|
import chat.simplex.common.views.usersettings.PreferenceToggle
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -59,14 +54,11 @@ private fun LinkAMobileLayout(
|
|||||||
staleQrCode: MutableState<Boolean>,
|
staleQrCode: MutableState<Boolean>,
|
||||||
updateDeviceName: (String) -> Unit,
|
updateDeviceName: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(Modifier.themedBackground()) {
|
ModalView({ appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) }) {
|
||||||
CloseSheetBar(close = {
|
Column(Modifier.fillMaxSize().padding(top = AppBarHeight * fontSizeSqrtMultiplier)) {
|
||||||
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
Box(Modifier.align(Alignment.CenterHorizontally)) {
|
||||||
})
|
|
||||||
BackHandler(onBack = {
|
|
||||||
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
|
||||||
})
|
|
||||||
AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles))
|
AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles))
|
||||||
|
}
|
||||||
Row(Modifier.weight(1f).padding(horizontal = DEFAULT_PADDING * 2), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.weight(1f).padding(horizontal = DEFAULT_PADDING * 2), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Column(
|
Column(
|
||||||
Modifier.weight(0.3f),
|
Modifier.weight(0.3f),
|
||||||
@@ -93,3 +85,4 @@ private fun LinkAMobileLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-9
@@ -25,16 +25,9 @@ import chat.simplex.res.MR
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SetNotificationsMode(m: ChatModel) {
|
fun SetNotificationsMode(m: ChatModel) {
|
||||||
val handler = remember { AppBarHandler() }
|
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalAppBarHandler provides handler
|
|
||||||
) {
|
|
||||||
ModalView({}, showClose = false) {
|
ModalView({}, showClose = false) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) {
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.themedBackground()
|
|
||||||
) {
|
|
||||||
Box(Modifier.align(Alignment.CenterHorizontally)) {
|
Box(Modifier.align(Alignment.CenterHorizontally)) {
|
||||||
AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title))
|
AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title))
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-5
@@ -104,13 +104,10 @@ private fun SetupDatabasePassphraseLayout(
|
|||||||
onConfirmEncrypt: () -> Unit,
|
onConfirmEncrypt: () -> Unit,
|
||||||
nextStep: () -> Unit,
|
nextStep: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val handler = remember { AppBarHandler() }
|
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalAppBarHandler provides handler
|
|
||||||
) {
|
|
||||||
ModalView({}, showClose = false) {
|
ModalView({}, showClose = false) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(
|
||||||
Modifier.fillMaxSize().themedBackground().padding(bottom = DEFAULT_PADDING * 2),
|
Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer).padding(bottom = DEFAULT_PADDING * 2),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
AppBarTitle(stringResource(MR.strings.setup_database_passphrase))
|
AppBarTitle(stringResource(MR.strings.setup_database_passphrase))
|
||||||
|
|||||||
+2
-1
@@ -31,6 +31,7 @@ import dev.icerock.moko.resources.StringResource
|
|||||||
@Composable
|
@Composable
|
||||||
fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) {
|
fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) {
|
||||||
if (onboarding) {
|
if (onboarding) {
|
||||||
|
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||||
ModalView({}, showClose = false, endButtons = {
|
ModalView({}, showClose = false, endButtons = {
|
||||||
IconButton({ ModalManager.fullscreen.showModal { HowItWorks(chatModel.currentUser.value, null) } }) {
|
IconButton({ ModalManager.fullscreen.showModal { HowItWorks(chatModel.currentUser.value, null) } }) {
|
||||||
Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary)
|
Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary)
|
||||||
@@ -41,6 +42,7 @@ fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) {
|
|||||||
onboardingStage = chatModel.controller.appPrefs.onboardingStage
|
onboardingStage = chatModel.controller.appPrefs.onboardingStage
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
SimpleXInfoLayout(
|
SimpleXInfoLayout(
|
||||||
user = chatModel.currentUser.value,
|
user = chatModel.currentUser.value,
|
||||||
@@ -56,7 +58,6 @@ fun SimpleXInfoLayout(
|
|||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = DEFAULT_PADDING),
|
.padding(horizontal = DEFAULT_PADDING),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
|||||||
+1
-2
@@ -119,11 +119,10 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) {
|
|||||||
ModalView(close = close) {
|
ModalView(close = close) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = DEFAULT_PADDING),
|
.padding(horizontal = DEFAULT_PADDING),
|
||||||
verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING.times(0.75f))
|
verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING.times(0.75f))
|
||||||
) {
|
) {
|
||||||
AppBarTitle(String.format(generalGetString(MR.strings.new_in_version), v.version), bottomPadding = DEFAULT_PADDING)
|
AppBarTitle(String.format(generalGetString(MR.strings.new_in_version), v.version), withPadding = false, bottomPadding = DEFAULT_PADDING)
|
||||||
|
|
||||||
v.features.forEach { feature ->
|
v.features.forEach { feature ->
|
||||||
if (feature.show) {
|
if (feature.show) {
|
||||||
|
|||||||
+2
-6
@@ -74,9 +74,7 @@ private fun ConnectDesktopLayout(deviceName: String, close: () -> Unit) {
|
|||||||
val sessionAddress = remember { mutableStateOf("") }
|
val sessionAddress = remember { mutableStateOf("") }
|
||||||
val remoteCtrls = remember { mutableStateListOf<RemoteCtrlInfo>() }
|
val remoteCtrls = remember { mutableStateListOf<RemoteCtrlInfo>() }
|
||||||
val session = remember { chatModel.remoteCtrlSession }.value
|
val session = remember { chatModel.remoteCtrlSession }.value
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
val discovery = if (session == null) null else session.sessionState is UIRemoteCtrlSessionState.Searching
|
val discovery = if (session == null) null else session.sessionState is UIRemoteCtrlSessionState.Searching
|
||||||
if (discovery == true || (discovery == null && !showConnectScreen.value)) {
|
if (discovery == true || (discovery == null && !showConnectScreen.value)) {
|
||||||
SearchingDesktop(deviceName, remoteCtrls)
|
SearchingDesktop(deviceName, remoteCtrls)
|
||||||
@@ -408,9 +406,7 @@ private fun DesktopAddressView(sessionAddress: MutableState<String>) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LinkedDesktopsView(remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
|
private fun LinkedDesktopsView(remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.linked_desktops))
|
AppBarTitle(stringResource(MR.strings.linked_desktops))
|
||||||
SectionView(stringResource(MR.strings.desktop_devices).uppercase()) {
|
SectionView(stringResource(MR.strings.desktop_devices).uppercase()) {
|
||||||
remoteCtrls.forEach { rc ->
|
remoteCtrls.forEach { rc ->
|
||||||
|
|||||||
+10
-2
@@ -89,7 +89,7 @@ fun ConnectMobileLayout(
|
|||||||
connectDesktop: () -> Unit,
|
connectDesktop: () -> Unit,
|
||||||
deleteHost: (RemoteHostInfo) -> Unit,
|
deleteHost: (RemoteHostInfo) -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
|
ColumnWithScrollBar {
|
||||||
AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles))
|
AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles))
|
||||||
SectionView(generalGetString(MR.strings.this_device_name).uppercase()) {
|
SectionView(generalGetString(MR.strings.this_device_name).uppercase()) {
|
||||||
DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) }
|
DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) }
|
||||||
@@ -176,7 +176,15 @@ private fun ConnectMobileViewLayout(
|
|||||||
refreshQrCode: () -> Unit = {},
|
refreshQrCode: () -> Unit = {},
|
||||||
UnderQrLayout: @Composable () -> Unit = {},
|
UnderQrLayout: @Composable () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
|
@Composable
|
||||||
|
fun ScrollableLayout(content: @Composable ColumnScope.() -> Unit) {
|
||||||
|
if (LocalAppBarHandler.current != null) {
|
||||||
|
ColumnWithScrollBar(content = content)
|
||||||
|
} else {
|
||||||
|
ColumnWithScrollBarNoAppBar(content = content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScrollableLayout {
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
AppBarTitle(title)
|
AppBarTitle(title)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-10
@@ -202,10 +202,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
|
|||||||
) {
|
) {
|
||||||
val secondsLabel = stringResource(MR.strings.network_option_seconds_label)
|
val secondsLabel = stringResource(MR.strings.network_option_seconds_label)
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.network_settings_title))
|
AppBarTitle(stringResource(MR.strings.network_settings_title))
|
||||||
|
|
||||||
if (currentRemoteHost == null) {
|
if (currentRemoteHost == null) {
|
||||||
@@ -328,9 +325,7 @@ private fun SMPProxyModePicker(
|
|||||||
icon = painterResource(MR.images.ic_settings_ethernet),
|
icon = painterResource(MR.images.ic_settings_ethernet),
|
||||||
onSelected = {
|
onSelected = {
|
||||||
showModal {
|
showModal {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.network_smp_proxy_mode_private_routing))
|
AppBarTitle(stringResource(MR.strings.network_smp_proxy_mode_private_routing))
|
||||||
SectionViewSelectableCards(null, smpProxyMode, values, updateSMPProxyMode)
|
SectionViewSelectableCards(null, smpProxyMode, values, updateSMPProxyMode)
|
||||||
}
|
}
|
||||||
@@ -365,9 +360,7 @@ private fun SMPProxyFallbackPicker(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onSelected = {
|
onSelected = {
|
||||||
showModal {
|
showModal {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.network_smp_proxy_fallback_allow_downgrade))
|
AppBarTitle(stringResource(MR.strings.network_smp_proxy_fallback_allow_downgrade))
|
||||||
SectionViewSelectableCards(null, smpProxyFallback, values, updateSMPProxyFallback)
|
SectionViewSelectableCards(null, smpProxyFallback, values, updateSMPProxyFallback)
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-15
@@ -4,9 +4,11 @@ import SectionBottomSpacer
|
|||||||
import SectionDividerSpaced
|
import SectionDividerSpaced
|
||||||
import SectionItemView
|
import SectionItemView
|
||||||
import SectionItemViewSpaceBetween
|
import SectionItemViewSpaceBetween
|
||||||
|
import SectionItemViewWithoutMinPadding
|
||||||
import SectionSpacer
|
import SectionSpacer
|
||||||
import SectionView
|
import SectionView
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.grid.*
|
import androidx.compose.foundation.lazy.grid.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
@@ -39,6 +41,7 @@ import chat.simplex.common.views.chat.item.msgTailWidthDp
|
|||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import com.godaddy.android.colorpicker.ClassicColorPicker
|
import com.godaddy.android.colorpicker.ClassicColorPicker
|
||||||
import com.godaddy.android.colorpicker.HsvColor
|
import com.godaddy.android.colorpicker.HsvColor
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
@@ -85,21 +88,107 @@ object AppearanceScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppToolbarsSection() {
|
||||||
|
BoxWithConstraints {
|
||||||
|
SectionView(stringResource(MR.strings.appearance_app_toolbars).uppercase()) {
|
||||||
|
SectionItemViewWithoutMinPadding {
|
||||||
|
Box(Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
stringResource(MR.strings.appearance_in_app_bars_alpha),
|
||||||
|
Modifier.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null
|
||||||
|
) {
|
||||||
|
appPrefs.inAppBarsAlpha.set(appPrefs.inAppBarsDefaultAlpha)
|
||||||
|
},
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.padding(end = 10.dp))
|
||||||
|
Slider(
|
||||||
|
(1 - remember { appPrefs.inAppBarsAlpha.state }.value).coerceIn(0f, 0.5f),
|
||||||
|
onValueChange = {
|
||||||
|
val diff = it % 0.025f
|
||||||
|
appPrefs.inAppBarsAlpha.set(1f - (String.format(Locale.US, "%.3f", it + (if (diff >= 0.0125f) -diff + 0.025f else -diff)).toFloatOrNull() ?: 1f))
|
||||||
|
},
|
||||||
|
Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - DEFAULT_PADDING * 2) * 0.618f),
|
||||||
|
valueRange = 0f..0.5f,
|
||||||
|
steps = 21,
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
activeTickColor = Color.Transparent,
|
||||||
|
inactiveTickColor = Color.Transparent,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// In Android in OneHandUI there is a problem with setting initial value of blur if it was 0 before entering the screen.
|
||||||
|
// So doing in two steps works ok
|
||||||
|
fun saveBlur(value: Int) {
|
||||||
|
val oneHandUI = appPrefs.oneHandUI.get()
|
||||||
|
val pref = appPrefs.appearanceBarsBlurRadius
|
||||||
|
if (appPlatform.isAndroid && oneHandUI && pref.get() == 0) {
|
||||||
|
pref.set(if (value > 2) value - 1 else value + 1)
|
||||||
|
withApi {
|
||||||
|
delay(50)
|
||||||
|
pref.set(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pref.set(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val blur = remember { appPrefs.appearanceBarsBlurRadius.state }
|
||||||
|
if (appPrefs.deviceSupportsBlur || blur.value > 0) {
|
||||||
|
SectionItemViewWithoutMinPadding {
|
||||||
|
Box(Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
stringResource(MR.strings.appearance_bars_blur_radius),
|
||||||
|
Modifier.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null
|
||||||
|
) {
|
||||||
|
saveBlur(50)
|
||||||
|
},
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.padding(end = 10.dp))
|
||||||
|
Slider(
|
||||||
|
blur.value.toFloat() / 100f,
|
||||||
|
onValueChange = {
|
||||||
|
val diff = it % 0.05f
|
||||||
|
saveBlur(((String.format(Locale.US, "%.2f", it + (if (diff >= 0.025f) -diff + 0.05f else -diff)).toFloatOrNull() ?: 1f) * 100).toInt())
|
||||||
|
},
|
||||||
|
Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - DEFAULT_PADDING * 2) * 0.618f),
|
||||||
|
valueRange = 0f..1f,
|
||||||
|
steps = 21,
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
activeTickColor = Color.Transparent,
|
||||||
|
inactiveTickColor = Color.Transparent,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageShapeSection() {
|
fun MessageShapeSection() {
|
||||||
SectionView(stringResource(MR.strings.settings_section_title_message_shape).uppercase(), contentPadding = PaddingValues()) {
|
BoxWithConstraints {
|
||||||
Row(modifier = Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING + 4.dp ) ,verticalAlignment = Alignment.CenterVertically) {
|
SectionView(stringResource(MR.strings.settings_section_title_message_shape).uppercase()) {
|
||||||
Text(stringResource(MR.strings.settings_message_shape_corner), color = colors.onBackground)
|
SectionItemViewWithoutMinPadding {
|
||||||
|
Text(stringResource(MR.strings.settings_message_shape_corner), Modifier.weight(1f))
|
||||||
Spacer(Modifier.width(10.dp))
|
Spacer(Modifier.width(10.dp))
|
||||||
Slider(
|
Slider(
|
||||||
remember { appPreferences.chatItemRoundness.state }.value,
|
remember { appPreferences.chatItemRoundness.state }.value,
|
||||||
valueRange = 0f..1f,
|
|
||||||
steps = 20,
|
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
val diff = it % 0.05f
|
val diff = it % 0.05f
|
||||||
appPreferences.chatItemRoundness.set(it + (if (diff >= 0.025f) -diff + 0.05f else -diff))
|
appPreferences.chatItemRoundness.set(it + (if (diff >= 0.025f) -diff + 0.05f else -diff))
|
||||||
saveThemeToDatabase(null)
|
saveThemeToDatabase(null)
|
||||||
},
|
},
|
||||||
|
Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - DEFAULT_PADDING * 2) * 0.618f),
|
||||||
|
valueRange = 0f..1f,
|
||||||
|
steps = 20,
|
||||||
colors = SliderDefaults.colors(
|
colors = SliderDefaults.colors(
|
||||||
activeTickColor = Color.Transparent,
|
activeTickColor = Color.Transparent,
|
||||||
inactiveTickColor = Color.Transparent,
|
inactiveTickColor = Color.Transparent,
|
||||||
@@ -109,13 +198,14 @@ object AppearanceScope {
|
|||||||
SettingsPreferenceItem(icon = null, stringResource(MR.strings.settings_message_shape_tail), appPreferences.chatItemTail)
|
SettingsPreferenceItem(icon = null, stringResource(MR.strings.settings_message_shape_tail), appPreferences.chatItemTail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FontScaleSection() {
|
fun FontScaleSection() {
|
||||||
val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) }
|
val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) }
|
||||||
SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
|
SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
|
||||||
Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Box(Modifier.size(60.dp)
|
Box(Modifier.size(50.dp)
|
||||||
.background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22))
|
.background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22))
|
||||||
.clip(RoundedCornerShape(percent = 22))
|
.clip(RoundedCornerShape(percent = 22))
|
||||||
.clickable {
|
.clickable {
|
||||||
@@ -129,7 +219,7 @@ object AppearanceScope {
|
|||||||
Text("Aa", color = if (localFontScale.value == 1f) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground)
|
Text("Aa", color = if (localFontScale.value == 1f) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.width(10.dp))
|
Spacer(Modifier.width(15.dp))
|
||||||
// Text("${(localFontScale.value * 100).roundToInt()}%", Modifier.width(70.dp), textAlign = TextAlign.Center, fontSize = 12.sp)
|
// Text("${(localFontScale.value * 100).roundToInt()}%", Modifier.width(70.dp), textAlign = TextAlign.Center, fontSize = 12.sp)
|
||||||
if (appPlatform.isAndroid) {
|
if (appPlatform.isAndroid) {
|
||||||
Slider(
|
Slider(
|
||||||
@@ -185,7 +275,7 @@ object AppearanceScope {
|
|||||||
Column(Modifier
|
Column(Modifier
|
||||||
.drawWithCache {
|
.drawWithCache {
|
||||||
if (wallpaperImage != null && wallpaperType != null && backgroundColor != null && tintColor != null) {
|
if (wallpaperImage != null && wallpaperType != null && backgroundColor != null && tintColor != null) {
|
||||||
chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor)
|
chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor, null, null)
|
||||||
} else {
|
} else {
|
||||||
onDrawBehind {
|
onDrawBehind {
|
||||||
drawRect(themeBackgroundColor)
|
drawRect(themeBackgroundColor)
|
||||||
@@ -514,9 +604,7 @@ object AppearanceScope {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomizeThemeView(onChooseType: (WallpaperType?) -> Unit) {
|
fun CustomizeThemeView(onChooseType: (WallpaperType?) -> Unit) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
val currentTheme by CurrentColors.collectAsState()
|
val currentTheme by CurrentColors.collectAsState()
|
||||||
|
|
||||||
AppBarTitle(stringResource(MR.strings.customize_theme_title))
|
AppBarTitle(stringResource(MR.strings.customize_theme_title))
|
||||||
@@ -909,10 +997,7 @@ object AppearanceScope {
|
|||||||
currentColors: () -> ThemeManager.ActiveTheme,
|
currentColors: () -> ThemeManager.ActiveTheme,
|
||||||
onColorChange: (Color?) -> Unit,
|
onColorChange: (Color?) -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(Modifier.imePadding()) {
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
AppBarTitle(name.text)
|
AppBarTitle(name.text)
|
||||||
|
|
||||||
val supportedLiveChange = name in listOf(ThemeColor.SECONDARY, ThemeColor.BACKGROUND, ThemeColor.SURFACE, ThemeColor.RECEIVED_MESSAGE, ThemeColor.SENT_MESSAGE, ThemeColor.SENT_QUOTE, ThemeColor.WALLPAPER_BACKGROUND, ThemeColor.WALLPAPER_TINT)
|
val supportedLiveChange = name in listOf(ThemeColor.SECONDARY, ThemeColor.BACKGROUND, ThemeColor.SURFACE, ThemeColor.RECEIVED_MESSAGE, ThemeColor.SENT_MESSAGE, ThemeColor.SENT_QUOTE, ThemeColor.WALLPAPER_BACKGROUND, ThemeColor.WALLPAPER_TINT)
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ fun CallSettingsLayout(
|
|||||||
callOnLockScreen: SharedPreference<CallOnLockScreen>,
|
callOnLockScreen: SharedPreference<CallOnLockScreen>,
|
||||||
editIceServers: () -> Unit,
|
editIceServers: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
|
ColumnWithScrollBar {
|
||||||
AppBarTitle(stringResource(MR.strings.your_calls))
|
AppBarTitle(stringResource(MR.strings.your_calls))
|
||||||
val lockCallState = remember { mutableStateOf(callOnLockScreen.get()) }
|
val lockCallState = remember { mutableStateOf(callOnLockScreen.get()) }
|
||||||
SectionView(stringResource(MR.strings.settings_section_title_settings)) {
|
SectionView(stringResource(MR.strings.settings_section_title_settings)) {
|
||||||
|
|||||||
+4
-6
@@ -22,12 +22,10 @@ import chat.simplex.common.views.helpers.*
|
|||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeveloperView(
|
fun DeveloperView(withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
||||||
m: ChatModel,
|
|
||||||
showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
|
||||||
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
|
val m = chatModel
|
||||||
|
ColumnWithScrollBar {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
AppBarTitle(stringResource(MR.strings.settings_developer_tools))
|
AppBarTitle(stringResource(MR.strings.settings_developer_tools))
|
||||||
val developerTools = m.controller.appPrefs.developerTools
|
val developerTools = m.controller.appPrefs.developerTools
|
||||||
@@ -35,7 +33,7 @@ fun DeveloperView(
|
|||||||
val unchangedHints = mutableStateOf(unchangedHintPreferences())
|
val unchangedHints = mutableStateOf(unchangedHintPreferences())
|
||||||
SectionView {
|
SectionView {
|
||||||
InstallTerminalAppItem(uriHandler)
|
InstallTerminalAppItem(uriHandler)
|
||||||
ChatConsoleItem { withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential), showCustomModal { it, close -> TerminalView(false, close) }) }
|
ChatConsoleItem { withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.start.showModalCloseable { TerminalView(false) } } }
|
||||||
ResetHintsItem(unchangedHints)
|
ResetHintsItem(unchangedHints)
|
||||||
SettingsPreferenceItem(painterResource(MR.images.ic_code), stringResource(MR.strings.show_developer_options), developerTools)
|
SettingsPreferenceItem(painterResource(MR.images.ic_code), stringResource(MR.strings.show_developer_options), developerTools)
|
||||||
SectionTextFooter(
|
SectionTextFooter(
|
||||||
|
|||||||
+1
-5
@@ -21,11 +21,7 @@ fun HelpView(userDisplayName: String) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HelpLayout(userDisplayName: String) {
|
fun HelpLayout(userDisplayName: String) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)){
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = DEFAULT_PADDING),
|
|
||||||
){
|
|
||||||
AppBarTitle(String.format(stringResource(MR.strings.personal_welcome), userDisplayName), withPadding = false)
|
AppBarTitle(String.format(stringResource(MR.strings.personal_welcome), userDisplayName), withPadding = false)
|
||||||
ChatHelpView()
|
ChatHelpView()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -56,10 +56,7 @@ private fun HiddenProfileLayout(
|
|||||||
user: User,
|
user: User,
|
||||||
saveProfilePassword: (String) -> Unit
|
saveProfilePassword: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.hide_profile))
|
AppBarTitle(stringResource(MR.strings.hide_profile))
|
||||||
SectionView(contentPadding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) {
|
SectionView(contentPadding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) {
|
||||||
UserProfileRow(user)
|
UserProfileRow(user)
|
||||||
|
|||||||
+3
-8
@@ -109,7 +109,7 @@ fun NetworkAndServersView() {
|
|||||||
toggleSocksProxy: (Boolean) -> Unit,
|
toggleSocksProxy: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val m = chatModel
|
val m = chatModel
|
||||||
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
|
ColumnWithScrollBar {
|
||||||
val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.start.showModal(content = it) }
|
val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.start.showModal(content = it) }
|
||||||
val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.start.showCustomModal { close -> it(close) }}
|
val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.start.showCustomModal { close -> it(close) }}
|
||||||
|
|
||||||
@@ -304,10 +304,7 @@ fun SocksProxySettings(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings))
|
AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings))
|
||||||
SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) {
|
SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) {
|
||||||
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||||
@@ -479,9 +476,7 @@ fun SessionModePicker(
|
|||||||
icon = painterResource(MR.images.ic_safety_divider),
|
icon = painterResource(MR.images.ic_safety_divider),
|
||||||
onSelected = {
|
onSelected = {
|
||||||
showModal {
|
showModal {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.network_session_mode_transport_isolation))
|
AppBarTitle(stringResource(MR.strings.network_session_mode_transport_isolation))
|
||||||
SectionViewSelectable(null, sessionMode, values, updateSessionMode)
|
SectionViewSelectable(null, sessionMode, values, updateSessionMode)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-9
@@ -56,9 +56,7 @@ fun NotificationsSettingsLayout(
|
|||||||
val modes = remember { notificationModes() }
|
val modes = remember { notificationModes() }
|
||||||
val previewModes = remember { notificationPreviewModes() }
|
val previewModes = remember { notificationPreviewModes() }
|
||||||
|
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.notifications))
|
AppBarTitle(stringResource(MR.strings.notifications))
|
||||||
SectionView(null) {
|
SectionView(null) {
|
||||||
if (appPlatform == AppPlatform.ANDROID) {
|
if (appPlatform == AppPlatform.ANDROID) {
|
||||||
@@ -90,9 +88,7 @@ fun NotificationsModeView(
|
|||||||
onNotificationsModeSelected: (NotificationsMode) -> Unit,
|
onNotificationsModeSelected: (NotificationsMode) -> Unit,
|
||||||
) {
|
) {
|
||||||
val modes = remember { notificationModes() }
|
val modes = remember { notificationModes() }
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.settings_notifications_mode_title).lowercase().capitalize(Locale.current))
|
AppBarTitle(stringResource(MR.strings.settings_notifications_mode_title).lowercase().capitalize(Locale.current))
|
||||||
SectionViewSelectable(null, notificationsMode, modes, onNotificationsModeSelected)
|
SectionViewSelectable(null, notificationsMode, modes, onNotificationsModeSelected)
|
||||||
}
|
}
|
||||||
@@ -104,9 +100,7 @@ fun NotificationPreviewView(
|
|||||||
onNotificationPreviewModeSelected: (NotificationPreviewMode) -> Unit,
|
onNotificationPreviewModeSelected: (NotificationPreviewMode) -> Unit,
|
||||||
) {
|
) {
|
||||||
val previewModes = remember { notificationPreviewModes() }
|
val previewModes = remember { notificationPreviewModes() }
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.settings_notification_preview_title))
|
AppBarTitle(stringResource(MR.strings.settings_notification_preview_title))
|
||||||
SectionViewSelectable(null, notificationPreviewMode, previewModes, onNotificationPreviewModeSelected)
|
SectionViewSelectable(null, notificationPreviewMode, previewModes, onNotificationPreviewModeSelected)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -66,9 +66,7 @@ private fun PreferencesLayout(
|
|||||||
reset: () -> Unit,
|
reset: () -> Unit,
|
||||||
savePrefs: () -> Unit,
|
savePrefs: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar(
|
ColumnWithScrollBar {
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
AppBarTitle(stringResource(MR.strings.your_preferences))
|
AppBarTitle(stringResource(MR.strings.your_preferences))
|
||||||
val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.allow) }
|
val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.allow) }
|
||||||
TimedMessagesFeatureSection(timedMessages) {
|
TimedMessagesFeatureSection(timedMessages) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user