Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin
2024-08-21 19:52:24 +01:00
300 changed files with 1392 additions and 1022 deletions
-19
View File
@@ -15,31 +15,12 @@ class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
logger.debug("AppDelegate: didFinishLaunchingWithOptions")
application.registerForRemoteNotifications()
if #available(iOS 17.0, *) { trackKeyboard() }
NotificationCenter.default.addObserver(self, selector: #selector(pasteboardChanged), name: UIPasteboard.changedNotification, object: nil)
removePasscodesIfReinstalled()
prepareForLaunch()
return true
}
@available(iOS 17.0, *)
private func trackKeyboard() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
@available(iOS 17.0, *)
@objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
ChatModel.shared.keyboardHeight = keyboardFrame.cgRectValue.height
}
}
@available(iOS 17.0, *)
@objc func keyboardWillHide(_ notification: Notification) {
ChatModel.shared.keyboardHeight = 0
}
@objc func pasteboardChanged() {
ChatModel.shared.pasteboardHasStrings = UIPasteboard.general.hasStrings
}
+4 -4
View File
@@ -36,7 +36,7 @@ struct ContentView: View {
@State private var waitingForOrPassedAuth = true
@State private var chatListActionSheet: ChatListActionSheet? = nil
private let callTopPadding: CGFloat = 50
private let callTopPadding: CGFloat = 40
private enum ChatListActionSheet: Identifiable {
case planAndConnectSheet(sheet: PlanAndConnectActionSheet)
@@ -151,12 +151,12 @@ struct ContentView: View {
}
}
.onAppear {
reactOnDarkThemeChanges()
reactOnDarkThemeChanges(systemInDarkThemeCurrently)
}
.onChange(of: colorScheme) { scheme in
// It's needed to update UI colors when iOS wants to make screenshot after going to background,
// so when a user changes his global theme from dark to light or back, the app will adapt to it
reactOnDarkThemeChanges()
reactOnDarkThemeChanges(scheme == .dark)
}
.onChange(of: theme.name) { _ in
ThemeManager.adjustWindowStyle()
@@ -207,7 +207,7 @@ struct ContentView: View {
CallDuration(call: call)
}
.padding(.horizontal)
.frame(height: callTopPadding - 10)
.frame(height: callTopPadding)
.background(Color(uiColor: UIColor(red: 47/255, green: 208/255, blue: 88/255, alpha: 1)))
.onTapGesture {
chatModel.activeCallViewIsCollapsed = false
+3 -22
View File
@@ -143,7 +143,7 @@ final class ChatModel: ObservableObject {
@Published var contentViewAccessAuthenticated: Bool = false
@Published var laRequest: LocalAuthRequest?
// list of chat "previews"
@Published var chats: [Chat] = []
@Published private(set) var chats: [Chat] = []
@Published var deletedChats: Set<String> = []
// current chat
@Published var chatId: String?
@@ -183,8 +183,6 @@ final class ChatModel: ObservableObject {
@Published var stopPreviousRecPlay: URL? = nil // coordinates currently playing source
@Published var draft: ComposeState?
@Published var draftChatId: String?
// tracks keyboard height via subscription in AppDelegate
@Published var keyboardHeight: CGFloat = 0
@Published var pasteboardHasStrings: Bool = UIPasteboard.general.hasStrings
@Published var networkInfo = UserNetworkInfo(networkType: .other, online: true)
@@ -359,25 +357,8 @@ final class ChatModel: ObservableObject {
}
}
func updateChats(with newChats: [ChatData]) {
for i in 0..<newChats.count {
let c = newChats[i]
if let j = getChatIndex(c.id) {
let chat = chats[j]
chat.chatInfo = c.chatInfo
chat.chatItems = c.chatItems
chat.chatStats = c.chatStats
if i != j {
if chatId != c.chatInfo.id {
popChat_(j, to: i)
} else if i == 0 {
chatToTop = c.chatInfo.id
}
}
} else {
addChat_(Chat(c), at: i)
}
}
func updateChats(_ newChats: [ChatData]) {
chats = newChats.map { Chat($0) }
NtfManager.shared.setNtfBadgeCount(totalUnreadCountForAllUsers())
popChatCollector.clear()
}
+35 -25
View File
@@ -112,9 +112,9 @@ func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? =
return resp
}
func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil) async -> ChatResponse {
func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil, log: Bool = true) async -> ChatResponse {
await withCheckedContinuation { cont in
cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl))
cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl, log: log))
}
}
@@ -1218,12 +1218,18 @@ func apiEndCall(_ contact: Contact) async throws {
try await sendCommandOkResp(.apiEndCall(contact: contact))
}
func apiGetCallInvitations() throws -> [RcvCallInvitation] {
func apiGetCallInvitationsSync() throws -> [RcvCallInvitation] {
let r = chatSendCmdSync(.apiGetCallInvitations)
if case let .callInvitations(invs) = r { return invs }
throw r
}
func apiGetCallInvitations() async throws -> [RcvCallInvitation] {
let r = await chatSendCmd(.apiGetCallInvitations)
if case let .callInvitations(invs) = r { return invs }
throw r
}
func apiCallStatus(_ contact: Contact, _ status: String) async throws {
if let callStatus = WebRTCCallStatus.init(rawValue: status) {
try await sendCommandOkResp(.apiCallStatus(contact: contact, callStatus: callStatus))
@@ -1420,9 +1426,9 @@ func apiGetVersion() throws -> CoreVersionInfo {
throw r
}
func getAgentSubsTotal() throws -> (SMPServerSubs, Bool) {
func getAgentSubsTotal() async throws -> (SMPServerSubs, Bool) {
let userId = try currentUserId("getAgentSubsTotal")
let r = chatSendCmdSync(.getAgentSubsTotal(userId: userId), log: false)
let r = await chatSendCmd(.getAgentSubsTotal(userId: userId), log: false)
if case let .agentSubsTotal(_, subsTotal, hasSession) = r { return (subsTotal, hasSession) }
logger.error("getAgentSubsTotal error: \(String(describing: r))")
throw r
@@ -1517,7 +1523,7 @@ func startChat(refreshInvitations: Bool = true) throws {
try getUserChatData()
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers())
if (refreshInvitations) {
try refreshCallInvitations()
Task { try await refreshCallInvitations() }
}
(m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken()
_ = try apiStartChat()
@@ -1591,8 +1597,7 @@ func getUserChatData() throws {
m.userAddress = try apiGetUserAddress()
m.chatItemTTL = try getChatItemTTL()
let chats = try apiGetChats()
m.chats = chats.map { Chat.init($0) }
m.popChatCollector.clear()
m.updateChats(chats)
}
private func getUserChatDataAsync() async throws {
@@ -1604,14 +1609,12 @@ private func getUserChatDataAsync() async throws {
await MainActor.run {
m.userAddress = userAddress
m.chatItemTTL = chatItemTTL
m.chats = chats.map { Chat.init($0) }
m.popChatCollector.clear()
m.updateChats(chats)
}
} else {
await MainActor.run {
m.userAddress = nil
m.chats = []
m.popChatCollector.clear()
m.updateChats([])
}
}
}
@@ -2164,23 +2167,30 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async {
}
}
func refreshCallInvitations() throws {
func refreshCallInvitations() async throws {
let m = ChatModel.shared
let callInvitations = try justRefreshCallInvitations()
if let (chatId, ntfAction) = m.ntfCallInvitationAction,
let invitation = m.callInvitations.removeValue(forKey: chatId) {
m.ntfCallInvitationAction = nil
CallController.shared.callAction(invitation: invitation, action: ntfAction)
} else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) {
activateCall(invitation)
let callInvitations = try await apiGetCallInvitations()
await MainActor.run {
m.callInvitations = callsByChat(callInvitations)
if let (chatId, ntfAction) = m.ntfCallInvitationAction,
let invitation = m.callInvitations.removeValue(forKey: chatId) {
m.ntfCallInvitationAction = nil
CallController.shared.callAction(invitation: invitation, action: ntfAction)
} else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) {
activateCall(invitation)
}
}
}
func justRefreshCallInvitations() throws -> [RcvCallInvitation] {
let m = ChatModel.shared
let callInvitations = try apiGetCallInvitations()
m.callInvitations = callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv }
return callInvitations
func justRefreshCallInvitations() throws {
let callInvitations = try apiGetCallInvitationsSync()
ChatModel.shared.callInvitations = callsByChat(callInvitations)
}
private func callsByChat(_ callInvitations: [RcvCallInvitation]) -> [ChatId: RcvCallInvitation] {
callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) {
result, inv in result[inv.contact.id] = inv
}
}
func activateCall(_ callInvitation: RcvCallInvitation) {
+11 -9
View File
@@ -83,9 +83,11 @@ struct SimpleXApp: App {
if appState != .stopped {
startChatAndActivate {
if appState.inactive && chatModel.chatRunning == true {
updateChats()
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
updateCallInvitations()
Task {
await updateChats()
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
await updateCallInvitations()
}
}
}
}
@@ -130,16 +132,16 @@ struct SimpleXApp: App {
}
}
private func updateChats() {
private func updateChats() async {
do {
let chats = try apiGetChats()
chatModel.updateChats(with: chats)
let chats = try await apiGetChatsAsync()
await MainActor.run { chatModel.updateChats(chats) }
if let id = chatModel.chatId,
let chat = chatModel.getChat(id) {
Task { await loadChat(chat: chat, clearItems: false) }
}
if let ncr = chatModel.ntfContactRequest {
chatModel.ntfContactRequest = nil
await MainActor.run { chatModel.ntfContactRequest = nil }
if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo {
Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) }
}
@@ -149,9 +151,9 @@ struct SimpleXApp: App {
}
}
private func updateCallInvitations() {
private func updateCallInvitations() async {
do {
try refreshCallInvitations()
try await refreshCallInvitations()
} catch let error {
logger.error("apiGetCallInvitations: cannot update call invitations \(responseError(error))")
}
+2 -2
View File
@@ -91,8 +91,8 @@ var systemInDarkThemeCurrently: Bool {
return UITraitCollection.current.userInterfaceStyle == .dark
}
func reactOnDarkThemeChanges() {
if currentThemeDefault.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.colors.isLight == systemInDarkThemeCurrently {
func reactOnDarkThemeChanges(_ inDarkNow: Bool) {
if currentThemeDefault.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.colors.isLight == inDarkNow {
// Change active colors from light to dark and back based on system theme
ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME)
}
@@ -186,7 +186,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
logger.debug("CallController: started chat")
self.shouldSuspendChat = true
// There are no invitations in the model, as it was processed by NSE
_ = try? justRefreshCallInvitations()
try? justRefreshCallInvitations()
logger.debug("CallController: updated call invitations chat")
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
// Extract the call information from the push notification payload
@@ -411,6 +411,15 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
func endCall() {
if #available(iOS 16.0, *) {
_endCall()
} else {
// Fixes `connection.close()` getting locked up in iOS15
DispatchQueue.global(qos: .utility).async { self._endCall() }
}
}
private func _endCall() {
guard let call = activeCall.wrappedValue else { return }
logger.debug("WebRTCClient: ending the call")
activeCall.wrappedValue = nil
@@ -671,7 +671,14 @@ private struct CallButton: View {
InfoViewButton(image: image, title: title, disabledLook: !canCall, width: width) {
if canCall {
CallController.shared.startCall(contact, mediaType)
if CallController.useCallKit() {
CallController.shared.startCall(contact, mediaType)
} else {
// When CallKit is not used, colorscheme will be changed and it will be visible if not hiding sheets first
dismissAllSheets(animated: true) {
CallController.shared.startCall(contact, mediaType)
}
}
} else if contact.nextSendGrpInv {
showAlert(SomeAlert(
alert: mkAlert(
@@ -294,7 +294,6 @@ struct FramedItemView: View {
.padding(.horizontal, 12)
.overlay(DetermineWidth())
.frame(minWidth: 0, alignment: .leading)
.textSelection(.enabled)
if let mediaWidth = maxMediaWidth(), mediaWidth < maxWidth {
v.frame(maxWidth: mediaWidth, alignment: .leading)
@@ -53,7 +53,6 @@ struct ChatView: View {
if #available(iOS 16.0, *) {
viewBody
.scrollDismissesKeyboard(.immediately)
.keyboardPadding()
.toolbarBackground(.hidden, for: .navigationBar)
} else {
viewBody
@@ -382,7 +382,11 @@ struct ComposeView: View {
}
}
}
.background(ToolbarMaterial.material(toolbarMaterial))
.background {
Color.clear
.overlay(ToolbarMaterial.material(toolbarMaterial))
.ignoresSafeArea(.all, edges: .bottom)
}
.onChange(of: composeState.message) { msg in
if composeState.linkPreviewAllowed {
if msg.count > 0 {
@@ -185,7 +185,6 @@ struct GroupChatInfoView: View {
logger.error("GroupChatInfoView apiGetGroupLink: \(responseError(error))")
}
}
.keyboardPadding()
}
private func groupInfoHeader() -> some View {
@@ -180,6 +180,13 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
snapshot,
animatingDifferences: itemCount != 0 && abs(items.count - itemCount) == 1
)
// Sets content offset on initial load
if itemCount == 0 {
tableView.setContentOffset(
CGPoint(x: 0, y: -InvertedTableView.inset),
animated: false
)
}
itemCount = items.count
}
}
@@ -355,6 +355,7 @@ struct ChatListNavLink: View {
.tint(.red)
}
.frame(height: dynamicRowHeight)
.contentShape(Rectangle())
.onTapGesture { showContactRequestDialog = true }
.confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } }
@@ -392,6 +393,7 @@ struct ChatListNavLink: View {
}
}
}
.contentShape(Rectangle())
.onTapGesture {
showContactConnectionInfo = true
}
@@ -217,14 +217,31 @@ struct ChatListView: View {
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
}
ForEach(cs, id: \.viewId) { chat in
ChatListNavLink(chat: chat)
if #available(iOS 16.0, *) {
ForEach(cs, id: \.viewId) { chat in
ChatListNavLink(chat: chat)
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
.padding(.trailing, -16)
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
.listRowBackground(Color.clear)
}
.offset(x: -8)
} else {
ForEach(cs, id: \.viewId) { chat in
VStack(spacing: .zero) {
Divider()
.padding(.leading, 16)
ChatListNavLink(chat: chat)
.padding(.horizontal, 8)
.padding(.vertical, 6)
}
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
.padding(.trailing, -16)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets())
.background { theme.colors.background } // Hides default list selection colour
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
.listRowBackground(Color.clear)
}
}
.offset(x: -8)
}
.listStyle(.plain)
.onChange(of: chatModel.chatId) { currentChatId in
@@ -324,7 +341,7 @@ struct ChatListView: View {
struct SubsStatusIndicator: View {
@State private var subs: SMPServerSubs = SMPServerSubs.newSMPServerSubs
@State private var hasSess: Bool = false
@State private var timer: Timer? = nil
@State private var task: Task<Void, Never>?
@State private var showServersSummary = false
@AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false
@@ -343,10 +360,10 @@ struct SubsStatusIndicator: View {
}
.disabled(ChatModel.shared.chatRunning != true)
.onAppear {
startTimer()
startTask()
}
.onDisappear {
stopTimer()
stopTask()
}
.appSheet(isPresented: $showServersSummary) {
ServersSummaryView()
@@ -354,25 +371,28 @@ struct SubsStatusIndicator: View {
}
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
if AppChatState.shared.value == .active {
getSubsTotal()
private func startTask() {
task = Task {
while !Task.isCancelled {
if AppChatState.shared.value == .active {
do {
let (subs, hasSess) = try await getAgentSubsTotal()
await MainActor.run {
self.subs = subs
self.hasSess = hasSess
}
} catch let error {
logger.error("getSubsTotal error: \(responseError(error))")
}
}
try? await Task.sleep(nanoseconds: 1_000_000_000) // Sleep for 1 second
}
}
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
private func getSubsTotal() {
do {
(subs, hasSess) = try getAgentSubsTotal()
} catch let error {
logger.error("getSubsTotal error: \(responseError(error))")
}
func stopTask() {
task?.cancel()
task = nil
}
}
@@ -498,21 +518,21 @@ func chatStoppedIcon() -> some View {
struct ChatListView_Previews: PreviewProvider {
static var previews: some View {
let chatModel = ChatModel()
chatModel.chats = [
Chat(
chatModel.updateChats([
ChatData(
chatInfo: ChatInfo.sampleData.direct,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")]
),
Chat(
ChatData(
chatInfo: ChatInfo.sampleData.group,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")]
),
Chat(
ChatData(
chatInfo: ChatInfo.sampleData.contactRequest,
chatItems: []
)
]
])
return Group {
ChatListView(showSettings: Binding.constant(false))
.environmentObject(chatModel)
@@ -16,7 +16,6 @@ struct ContactConnectionView: View {
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
@State private var localAlias = ""
@FocusState private var aliasTextFieldFocused: Bool
@State private var showContactConnectionInfo = false
var body: some View {
if case let .contactConnection(conn) = chat.chatInfo {
@@ -32,7 +31,6 @@ struct ContactConnectionView: View {
.scaledToFill()
.frame(width: 48, height: 48)
.foregroundColor(Color(uiColor: .tertiarySystemGroupedBackground).asAnotherColorFromSecondaryVariant(theme))
.onTapGesture { showContactConnectionInfo = true }
}
.frame(width: 63, height: 63)
.padding(.leading, 4)
@@ -72,9 +70,6 @@ struct ContactConnectionView: View {
Spacer()
}
.frame(maxHeight: .infinity)
.appSheet(isPresented: $showContactConnectionInfo) {
ContactConnectionInfo(contactConnection: contactConnection)
}
}
}
}
@@ -85,15 +85,18 @@ struct UserPicker: View {
.padding(8)
.opacity(userPickerVisible ? 1.0 : 0.0)
.onAppear {
do {
// This check prevents the call of listUsers after the app is suspended, and the database is closed.
if case .active = scenePhase {
m.users = try listUsers()
}
} catch let error {
logger.error("Error loading users \(responseError(error))")
}
}
// This check prevents the call of listUsers after the app is suspended, and the database is closed.
if case .active = scenePhase {
Task {
do {
let users = try await listUsersAsync()
await MainActor.run { m.users = users }
} catch {
logger.error("Error loading users \(responseError(error))")
}
}
}
}
}
private func userView(_ u: UserInfo) -> some View {
@@ -491,7 +491,7 @@ struct DatabaseView: View {
appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
do {
let chats = try apiGetChats()
m.updateChats(with: chats)
m.updateChats(chats)
} catch let error {
logger.error("apiGetChats: cannot update chats \(responseError(error))")
}
@@ -1,21 +0,0 @@
//
// KeyboardPadding.swift
// SimpleX (iOS)
//
// Created by Evgeny on 10/07/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import SwiftUI
extension View {
@ViewBuilder func keyboardPadding() -> some View {
if #available(iOS 17.0, *) {
GeometryReader { g in
self.padding(.bottom, max(0, ChatModel.shared.keyboardHeight - g.safeAreaInsets.bottom))
}
} else {
self
}
}
}
@@ -65,8 +65,7 @@ struct LocalAuthView: View {
// Clear sensitive data on screen just in case app fails to hide its views while new database is created
m.chatId = nil
ItemsModel.shared.reversedChatItems = []
m.chats = []
m.popChatCollector.clear()
m.updateChats([])
m.users = []
_ = kcAppPassword.set(password)
_ = kcSelfDestructPassword.remove()
@@ -59,7 +59,7 @@ struct AddGroupView: View {
.navigationBarTitle("Group link")
}
} else {
createGroupView().keyboardPadding()
createGroupView()
}
}
@@ -77,7 +77,6 @@ struct CreateProfile: View {
focusDisplayName = true
}
}
.keyboardPadding()
}
}
@@ -128,7 +127,6 @@ struct CreateFirstProfile: View {
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.keyboardPadding()
}
func onboardingButtons() -> some View {
+21 -25
View File
@@ -135,7 +135,6 @@
5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; };
5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCE227DE9246000BD591 /* ComposeView.swift */; };
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; };
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; };
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
@@ -220,15 +219,15 @@
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; };
E58C91472C72458500EADB92 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91422C72458500EADB92 /* libffi.a */; };
E58C91482C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91432C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a */; };
E58C91492C72458500EADB92 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91442C72458500EADB92 /* libgmpxx.a */; };
E58C914A2C72458500EADB92 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91452C72458500EADB92 /* libgmp.a */; };
E58C914B2C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91462C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a */; };
E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; };
E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; };
E5E218532C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */; };
E5E218542C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */; };
E5E218552C6D4C0F0013B4C6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218502C6D4C0F0013B4C6 /* libgmp.a */; };
E5E218562C6D4C0F0013B4C6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218512C6D4C0F0013B4C6 /* libffi.a */; };
E5E218572C6D4C0F0013B4C6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -479,7 +478,6 @@
5CE6C7B42AAB1527007F345C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
5CEACCE227DE9246000BD591 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = "<group>"; };
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = "<group>"; };
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = "<group>"; };
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; };
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; };
@@ -562,6 +560,11 @@
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = "<group>"; };
E58C91422C72458500EADB92 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
E58C91432C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a"; sourceTree = "<group>"; };
E58C91442C72458500EADB92 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
E58C91452C72458500EADB92 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
E58C91462C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a"; sourceTree = "<group>"; };
E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
@@ -614,11 +617,6 @@
E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a"; sourceTree = "<group>"; };
E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a"; sourceTree = "<group>"; };
E5E218502C6D4C0F0013B4C6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
E5E218512C6D4C0F0013B4C6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -657,14 +655,14 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
E58C91492C72458500EADB92 /* libgmpxx.a in Frameworks */,
E58C914B2C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a in Frameworks */,
E58C914A2C72458500EADB92 /* libgmp.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
E5E218542C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
E58C91482C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
E5E218572C6D4C0F0013B4C6 /* libgmpxx.a in Frameworks */,
E5E218562C6D4C0F0013B4C6 /* libffi.a in Frameworks */,
E5E218552C6D4C0F0013B4C6 /* libgmp.a in Frameworks */,
E5E218532C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a in Frameworks */,
E58C91472C72458500EADB92 /* libffi.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -741,11 +739,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
E5E218512C6D4C0F0013B4C6 /* libffi.a */,
E5E218502C6D4C0F0013B4C6 /* libgmp.a */,
E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */,
E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */,
E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */,
E58C91422C72458500EADB92 /* libffi.a */,
E58C91452C72458500EADB92 /* libgmp.a */,
E58C91442C72458500EADB92 /* libgmpxx.a */,
E58C91432C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a */,
E58C91462C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a */,
);
path = Libraries;
sourceTree = "<group>";
@@ -796,7 +794,6 @@
18415DAAAD1ADBEDB0EDA852 /* VideoPlayerView.swift */,
64466DCB29FFE3E800E3D48D /* MailView.swift */,
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */,
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */,
8C7F8F0D2C19C0C100D16888 /* ViewModifiers.swift */,
8C74C3ED2C1B942300039E77 /* ChatWallpaper.swift */,
8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */,
@@ -1445,7 +1442,6 @@
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */,
8C74C3EA2C1B90AF00039E77 /* ThemeManager.swift in Sources */,
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */,
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */,
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */,
5CB634B129E5EFEA0066AD6B /* PasscodeView.swift in Sources */,
8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */,
@@ -2330,7 +2326,7 @@
repositoryURL = "https://github.com/twostraws/CodeScanner";
requirement = {
kind = exactVersion;
version = 2.1.1;
version = 2.5.0;
};
};
8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */ = {
@@ -6,8 +6,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/twostraws/CodeScanner",
"state" : {
"revision" : "c27a66149b7483fe42e2ec6aad61d5c3fffe522d",
"version" : "2.1.1"
"revision" : "34da57fb63b47add20de8a85da58191523ccce57",
"version" : "2.5.0"
}
},
{
@@ -23,7 +23,6 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/kirualex/SwiftyGif",
"state" : {
"branch" : "master",
"revision" : "5e8619335d394901379c9add5c4c1c2f420b3800"
}
},
+6
View File
@@ -1500,6 +1500,12 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike {
public var id: ChatId { get { chatInfo.id } }
public init(chatInfo: ChatInfo, chatItems: [ChatItem], chatStats: ChatStats = ChatStats()) {
self.chatInfo = chatInfo
self.chatItems = chatItems
self.chatStats = chatStats
}
public static func invalidJSON(_ json: String) -> ChatData {
ChatData(
chatInfo: .invalidJSON(json: json),
@@ -52,6 +52,9 @@ actual fun windowOrientation(): WindowOrientation = when (mainActivity.get()?.re
@Composable
actual fun windowWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp
@Composable
actual fun windowHeight(): Dp = LocalConfiguration.current.screenHeightDp.dp
actual fun desktopExpandWindowToWidth(width: Dp) {}
actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text)
@@ -4,14 +4,21 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import chat.simplex.common.views.helpers.*
import kotlinx.coroutines.flow.filter
import kotlin.math.absoluteValue
@Composable
actual fun LazyColumnWithScrollBar(
modifier: Modifier,
state: LazyListState,
state: LazyListState?,
contentPadding: PaddingValues,
reverseLayout: Boolean,
verticalArrangement: Arrangement.Vertical,
@@ -20,7 +27,24 @@ actual fun LazyColumnWithScrollBar(
userScrollEnabled: Boolean,
content: LazyListScope.() -> Unit
) {
LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
val state = state ?: LocalAppBarHandler.current?.listState ?: rememberLazyListState()
val connection = LocalAppBarHandler.current?.connection
LaunchedEffect(Unit) {
snapshotFlow { state.firstVisibleItemScrollOffset }
.filter { state.firstVisibleItemIndex == 0 }
.collect { scrollPosition ->
val offset = connection?.appBarOffset
if (offset != null && (offset + scrollPosition).absoluteValue > 1) {
connection.appBarOffset = -scrollPosition.toFloat()
// 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)
} else {
LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
}
}
@Composable
@@ -28,8 +52,34 @@ actual fun ColumnWithScrollBar(
modifier: Modifier,
verticalArrangement: Arrangement.Vertical,
horizontalAlignment: Alignment.Horizontal,
state: ScrollState,
content: @Composable ColumnScope.() -> Unit
state: ScrollState?,
maxIntrinsicSize: Boolean,
content: @Composable() (ColumnScope.() -> Unit)
) {
Column(modifier.verticalScroll(rememberScrollState()), verticalArrangement, horizontalAlignment, content)
val state = state ?: LocalAppBarHandler.current?.scrollState ?: rememberScrollState()
val connection = LocalAppBarHandler.current?.connection
LaunchedEffect(Unit) {
snapshotFlow { state.value }
.collect { scrollPosition ->
val offset = connection?.appBarOffset
if (offset != null && (offset + scrollPosition).absoluteValue > 1) {
connection.appBarOffset = -scrollPosition.toFloat()
// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}")
}
}
}
if (connection != null) {
Column(
if (maxIntrinsicSize) {
modifier.nestedScroll(connection).verticalScroll(state).height(IntrinsicSize.Max)
} else {
modifier.nestedScroll(connection).verticalScroll(state)
}, verticalArrangement, horizontalAlignment, content)
} else {
Column(if (maxIntrinsicSize) {
modifier.verticalScroll(state).height(IntrinsicSize.Max)
} else {
modifier.verticalScroll(state)
}, verticalArrangement, horizontalAlignment, content)
}
}
@@ -457,7 +457,7 @@ private fun DisabledBackgroundCallsButton() {
) {
Text(stringResource(MR.strings.system_restricted_background_in_call_title), color = WarningOrange)
Spacer(Modifier.width(8.dp))
IconButton(onClick = { show = false }, Modifier.size(24.dp)) {
IconButton(onClick = { show = false }, Modifier.size(22.dp)) {
Icon(painterResource(MR.images.ic_close), null, tint = WarningOrange)
}
}
@@ -539,7 +539,7 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni
Icon(
painterResource(MR.images.ic_call_500),
stringResource(MR.strings.permissions_record_audio),
Modifier.size(24.dp),
Modifier.size(22.dp),
tint = Color(0xFFFFFFD8)
)
}
@@ -547,7 +547,7 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni
Icon(
painterResource(MR.images.ic_videocam),
stringResource(MR.strings.permissions_camera),
Modifier.size(24.dp),
Modifier.size(22.dp),
tint = Color(0xFFFFFFD8)
)
}
@@ -13,12 +13,15 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.MaterialTheme.colors
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import dev.icerock.moko.resources.compose.stringResource
@@ -112,13 +115,13 @@ fun AppearanceScope.AppearanceLayout(
}
}
SectionDividerSpaced(maxTopPadding = true)
SectionDividerSpaced()
ThemesSection(systemDarkTheme)
SectionDividerSpaced(maxTopPadding = true)
SectionDividerSpaced()
ProfileImageSection()
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) {
LazyRow {
@@ -129,7 +132,8 @@ fun AppearanceScope.AppearanceLayout(
contentDescription = "",
contentScale = ContentScale.Fit,
modifier = Modifier
.shadow(if (item == icon.value) 1.dp else 0.dp, ambientColor = colors.secondaryVariant)
.border(1.dp, color = if (item == icon.value) colors.secondaryVariant else Color.Transparent, RoundedCornerShape(percent = 22))
.clip(RoundedCornerShape(percent = 22))
.size(70.dp)
.clickable { changeIcon(item) }
.padding(10.dp)
@@ -143,7 +147,7 @@ fun AppearanceScope.AppearanceLayout(
}
}
SectionDividerSpaced(maxBottomPadding = true)
SectionDividerSpaced(maxTopPadding = true)
FontScaleSection()
SectionBottomSpacer()
@@ -19,9 +19,9 @@ actual fun SettingsSectionApp(
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
) {
SectionView(stringResource(MR.strings.settings_section_title_app)) {
SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp, extraPadding = true)
SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) }, extraPadding = true)
SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }, extraPadding = true)
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_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) })
AppVersionItem(showVersion)
}
}
@@ -17,6 +17,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView
import chat.simplex.common.model.*
@@ -50,6 +51,7 @@ data class SettingsViewState(
@Composable
fun AppScreen() {
AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() }
SimpleXTheme {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
@@ -28,9 +28,6 @@ interface PlatformInterface {
fun androidRestartNetworkObserver() {}
@Composable fun androidLockPortraitOrientation() {}
suspend fun androidAskToAllowBackgroundCalls(): Boolean = true
@Composable fun desktopScrollBarComponents(): Triple<Animatable<Float, AnimationVector1D>, Modifier, MutableState<Job>> = remember { Triple(Animatable(0f), Modifier, mutableStateOf(Job())) }
@Composable fun desktopScrollBar(state: LazyListState, modifier: Modifier, scrollBarAlpha: Animatable<Float, AnimationVector1D>, scrollJob: MutableState<Job>, reversed: Boolean) {}
@Composable fun desktopScrollBar(state: ScrollState, modifier: Modifier, scrollBarAlpha: Animatable<Float, AnimationVector1D>, scrollJob: MutableState<Job>, reversed: Boolean) {}
@Composable fun desktopShowAppUpdateNotice() {}
}
/**
@@ -30,6 +30,9 @@ expect fun windowOrientation(): WindowOrientation
@Composable
expect fun windowWidth(): Dp
@Composable
expect fun windowHeight(): Dp
expect fun desktopExpandWindowToWidth(width: Dp)
expect fun isRtl(text: CharSequence): Boolean
@@ -13,7 +13,7 @@ import androidx.compose.ui.unit.dp
@Composable
expect fun LazyColumnWithScrollBar(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
state: LazyListState? = null,
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
verticalArrangement: Arrangement.Vertical =
@@ -29,6 +29,8 @@ expect fun ColumnWithScrollBar(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
state: ScrollState = rememberScrollState(),
state: ScrollState? = null,
// set true when you want to show something in the center with respected .fillMaxSize()
maxIntrinsicSize: Boolean = false,
content: @Composable ColumnScope.() -> Unit
)
@@ -607,6 +607,8 @@ val DEFAULT_SPACE_AFTER_ICON = 4.dp
val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2
val DEFAULT_BOTTOM_PADDING = 48.dp
val DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp
val DEFAULT_MIN_SECTION_ITEM_HEIGHT = 50.dp
val DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL = 15.dp
val DEFAULT_START_MODAL_WIDTH = 388.dp
val DEFAULT_MIN_CENTER_MODAL_WIDTH = 590.dp
@@ -125,19 +125,13 @@ fun TerminalLayout(
}
}
private var lazyListState = 0 to 0
@Composable
fun TerminalLog() {
val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
DisposableEffect(Unit) {
onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
}
val reversedTerminalItems by remember {
derivedStateOf { chatModel.terminalItems.value.asReversed() }
}
val clipboard = LocalClipboardManager.current
LazyColumnWithScrollBar(state = listState, reverseLayout = true) {
LazyColumnWithScrollBar(reverseLayout = true) {
items(reversedTerminalItems) { item ->
val rhId = item.remoteHostId
val rhIdStr = if (rhId == null) "" else "$rhId "
@@ -599,7 +599,7 @@ fun ChatInfoLayout(
}
}
}
SectionDividerSpaced()
SectionDividerSpaced(maxBottomPadding = false)
val conn = contact.activeConn
if (conn != null) {
@@ -616,7 +616,7 @@ fun ChatInfoLayout(
ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) }
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName))
}
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
}
if (contact.ready && contact.active) {
@@ -650,7 +650,7 @@ fun ChatInfoLayout(
}
}
}
SectionDividerSpaced()
SectionDividerSpaced(maxBottomPadding = false)
}
SectionView {
@@ -970,7 +970,7 @@ fun InfoViewActionButton(
Icon(
icon,
contentDescription = null,
Modifier.size(24.dp * fontSizeSqrtMultiplier),
Modifier.size(22.dp * fontSizeSqrtMultiplier),
tint = if (disabledLook) MaterialTheme.colors.secondary else MaterialTheme.colors.onPrimary
)
}
@@ -224,7 +224,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
Row(
Modifier
.fillMaxWidth()
.sizeIn(minHeight = 46.dp)
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.padding(PaddingValues(horizontal = DEFAULT_PADDING))
.clickable { expanded.value = !expanded.value },
horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -277,7 +277,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
@Composable
fun HistoryTab() {
// LALAL SCROLLBAR DOESN'T WORK
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
Details()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
@@ -302,7 +301,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
@Composable
fun QuoteTab(qi: CIQuote) {
// LALAL SCROLLBAR DOESN'T WORK
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
Details()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
@@ -316,7 +314,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
@Composable
fun ForwardedFromTab(forwardedFromItem: AChatItem) {
// LALAL SCROLLBAR DOESN'T WORK
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
Details()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
@@ -379,7 +376,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
@Composable
fun DeliveryTab(memberDeliveryStatuses: List<MemberDeliveryStatus>) {
// LALAL SCROLLBAR DOESN'T WORK
ColumnWithScrollBar(Modifier.fillMaxWidth()) {
Details()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
@@ -504,24 +504,34 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
}
is ChatInfo.ContactConnection -> {
val close = { chatModel.chatId.value = null }
ModalView(close, showClose = appPlatform.isAndroid, content = {
ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close)
})
LaunchedEffect(chatInfo.id) {
onComposed(chatInfo.id)
ModalManager.end.closeModals()
chatModel.chatItems.clear()
val handler = remember { AppBarHandler() }
CompositionLocalProvider(
LocalAppBarHandler provides handler
) {
ModalView(close, showClose = appPlatform.isAndroid, content = {
ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close)
})
LaunchedEffect(chatInfo.id) {
onComposed(chatInfo.id)
ModalManager.end.closeModals()
chatModel.chatItems.clear()
}
}
}
is ChatInfo.InvalidJSON -> {
val close = { chatModel.chatId.value = null }
ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = {
InvalidJSONView(chatInfo.json)
})
LaunchedEffect(chatInfo.id) {
onComposed(chatInfo.id)
ModalManager.end.closeModals()
chatModel.chatItems.clear()
val handler = remember { AppBarHandler() }
CompositionLocalProvider(
LocalAppBarHandler provides handler
) {
ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = {
InvalidJSONView(chatInfo.json)
})
LaunchedEffect(chatInfo.id) {
onComposed(chatInfo.id)
ModalManager.end.closeModals()
chatModel.chatItems.clear()
}
}
}
else -> {}
@@ -93,22 +93,22 @@ private fun ContactPreferencesLayout(
TimedMessagesFeatureSection(featuresAllowed, contact.mergedPreferences.timedMessages, timedMessages, onTTLUpdated) { allowed, ttl ->
applyPrefs(featuresAllowed.copy(timedMessagesAllowed = allowed, timedMessagesTTL = ttl ?: currentFeaturesAllowed.timedMessagesTTL))
}
SectionDividerSpaced(true, maxBottomPadding = false)
SectionDividerSpaced(true)
val allowFullDeletion: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.fullDelete) }
FeatureSection(ChatFeature.FullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, allowFullDeletion) {
applyPrefs(featuresAllowed.copy(fullDelete = it))
}
SectionDividerSpaced(true, maxBottomPadding = false)
SectionDividerSpaced(true)
val allowReactions: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.reactions) }
FeatureSection(ChatFeature.Reactions, user.fullPreferences.reactions.allow, contact.mergedPreferences.reactions, allowReactions) {
applyPrefs(featuresAllowed.copy(reactions = it))
}
SectionDividerSpaced(true, maxBottomPadding = false)
SectionDividerSpaced(true)
val allowVoice: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.voice) }
FeatureSection(ChatFeature.Voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, allowVoice) {
applyPrefs(featuresAllowed.copy(voice = it))
}
SectionDividerSpaced(true, maxBottomPadding = false)
SectionDividerSpaced(true)
val allowCalls: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.calls) }
FeatureSection(ChatFeature.Calls, user.fullPreferences.calls.allow, contact.mergedPreferences.calls, allowCalls) {
applyPrefs(featuresAllowed.copy(calls = it))
@@ -66,7 +66,7 @@ fun SelectedItemsBottomToolbar(
Icon(
painterResource(MR.images.ic_delete),
null,
Modifier.size(24.dp),
Modifier.size(22.dp),
tint = if (!deleteEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error
)
}
@@ -75,7 +75,7 @@ fun SelectedItemsBottomToolbar(
Icon(
painterResource(MR.images.ic_flag),
null,
Modifier.size(24.dp),
Modifier.size(22.dp),
tint = if (!moderateEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error
)
}
@@ -84,7 +84,7 @@ fun SelectedItemsBottomToolbar(
Icon(
painterResource(MR.images.ic_share),
null,
Modifier.size(24.dp),
Modifier.size(22.dp),
tint = if (allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
)
}
@@ -4,6 +4,7 @@ import SectionBottomSpacer
import SectionCustomFooter
import SectionDividerSpaced
import SectionItemView
import SectionItemViewWithoutMinPadding
import SectionSpacer
import SectionView
import androidx.compose.foundation.*
@@ -177,7 +178,7 @@ fun AddGroupMembersLayout(
InviteSectionFooter(selectedContactsCount = selectedContacts.size, allowModifyMembers, clearSelection)
}
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.select_contacts)) {
SectionView(stringResource(MR.strings.select_contacts).uppercase()) {
SectionItemView(padding = PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF)) {
SearchRowView(searchText)
}
@@ -255,7 +256,8 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec
Text(
String.format(generalGetString(MR.strings.num_contacts_selected), selectedContactsCount),
color = MaterialTheme.colors.secondary,
fontSize = 12.sp
lineHeight = 18.sp,
fontSize = 14.sp
)
Box(
Modifier.clickable { if (enabled) clearSelection() }
@@ -263,14 +265,16 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec
Text(
stringResource(MR.strings.clear_contacts_selection_button),
color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
fontSize = 12.sp
lineHeight = 18.sp,
fontSize = 14.sp,
)
}
} else {
Text(
stringResource(MR.strings.no_contacts_selected),
color = MaterialTheme.colors.secondary,
fontSize = 12.sp
lineHeight = 18.sp,
fontSize = 14.sp,
)
}
}
@@ -318,7 +322,7 @@ fun ContactCheckRow(
icon = painterResource(MR.images.ic_circle)
iconColor = MaterialTheme.colors.secondary
}
SectionItemView(
SectionItemViewWithoutMinPadding(
click = if (enabled) {
{
if (prohibitedToInviteIncognito) {
@@ -284,7 +284,6 @@ fun GroupChatInfoLayout(
if (s.isEmpty()) members else members.filter { m -> m.anyNameContains(s) }
}
}
// LALAL strange scrolling
LazyColumnWithScrollBar(
Modifier
.fillMaxWidth(),
@@ -366,7 +365,7 @@ fun GroupChatInfoLayout(
SearchRowView(searchText)
}
}
SectionItemView(minHeight = 54.dp) {
SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
MemberRow(groupInfo.membership, user = true)
}
}
@@ -374,7 +373,7 @@ fun GroupChatInfoLayout(
items(filteredMembers.value) { member ->
Divider()
val showMenu = remember { mutableStateOf(false) }
SectionItemViewLongClickable({ showMemberInfo(member) }, { showMenu.value = true }, minHeight = 54.dp) {
SectionItemViewLongClickable({ showMemberInfo(member) }, { showMenu.value = true }, minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
DropDownMenuForMember(chat.remoteHostId, member, groupInfo, showMenu)
MemberRow(member, onClick = { showMemberInfo(member) })
}
@@ -514,7 +513,7 @@ private fun MemberRow(member: GroupMember, user: Boolean = false, onClick: (() -
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
MemberProfileImage(size = 46.dp, member)
MemberProfileImage(size = DEFAULT_MIN_SECTION_ITEM_HEIGHT, member)
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -133,13 +133,7 @@ private fun GroupWelcomeLayout(
val clipboard = LocalClipboardManager.current
CopyTextButton { clipboard.setText(AnnotatedString(wt.value)) }
Divider(
Modifier.padding(
start = DEFAULT_PADDING_HALF,
top = 8.dp,
end = DEFAULT_PADDING_HALF,
bottom = 8.dp)
)
SectionDividerSpaced(maxBottomPadding = false)
SaveButton(
save = save,
@@ -185,7 +185,14 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
scaffoldState = scaffoldState,
drawerContent = {
tryOrShowError("Settings", error = { ErrorSettingsView() }) {
SettingsView(chatModel, setPerformLA, scaffoldState.drawerState)
val handler = remember { AppBarHandler() }
CompositionLocalProvider(
LocalAppBarHandler provides handler
) {
ModalView(showClose = appPlatform.isDesktop, close = { scope.launch { scaffoldState.drawerState.close() } }) {
SettingsView(chatModel, setPerformLA, scaffoldState.drawerState)
}
}
}
},
contentColor = LocalContentColor.current,
@@ -212,7 +219,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
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(24.dp * fontSizeSqrtMultiplier))
Icon(painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(22.dp * fontSizeSqrtMultiplier))
}
}
}
@@ -513,7 +520,7 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState
Icon(
painterResource(MR.images.ic_search),
contentDescription = null,
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(24.dp * fontSizeSqrtMultiplier),
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(22.dp * fontSizeSqrtMultiplier),
tint = MaterialTheme.colors.secondary
)
SearchTextField(
@@ -546,15 +546,19 @@ fun XFTPServerSummaryLayout(summary: XFTPServerSummary, statsStartedAt: Instant,
}) {
Text(generalGetString(MR.strings.open_server_settings_button))
}
if (summary.stats != null || summary.sessions != null) {
SectionDividerSpaced()
}
}
if (summary.stats != null) {
SectionDividerSpaced()
XFTPStatsView(stats = summary.stats, rh = rh, statsStartedAt = statsStartedAt)
if (summary.sessions != null) {
SectionDividerSpaced(maxTopPadding = true)
}
}
if (summary.sessions != null) {
SectionDividerSpaced()
ServerSessionsView(summary.sessions)
}
}
@@ -581,20 +585,24 @@ fun SMPServerSummaryLayout(summary: SMPServerSummary, statsStartedAt: Instant, r
}) {
Text(generalGetString(MR.strings.open_server_settings_button))
}
SectionDividerSpaced()
}
if (summary.stats != null) {
SectionDividerSpaced()
SMPStatsView(stats = summary.stats, remoteHostInfo = rh, statsStartedAt = statsStartedAt)
if (summary.subs != null || summary.sessions != null) {
SectionDividerSpaced(maxTopPadding = true)
}
}
if (summary.subs != null) {
SectionDividerSpaced()
SMPSubscriptionsSection(subs = summary.subs, summary = summary, rh = rh)
if (summary.sessions != null) {
SectionDividerSpaced()
}
}
if (summary.sessions != null) {
SectionDividerSpaced()
ServerSessionsView(summary.sessions)
}
}
@@ -615,14 +623,12 @@ fun ModalData.SMPServerSummaryView(
ColumnWithScrollBar(
Modifier.fillMaxSize(),
) {
Box(contentAlignment = Alignment.Center) {
val bottomPadding = DEFAULT_PADDING
AppBarTitle(
stringResource(MR.strings.smp_server),
hostDevice(rh?.remoteHostId),
bottomPadding = bottomPadding
)
}
val bottomPadding = DEFAULT_PADDING
AppBarTitle(
stringResource(MR.strings.smp_server),
hostDevice(rh?.remoteHostId),
bottomPadding = bottomPadding
)
SMPServerSummaryLayout(summary, statsStartedAt, rh)
}
}
@@ -709,7 +715,7 @@ fun ModalData.XFTPServerSummaryView(
@Composable
fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableState<PresentedServersSummary?>) {
Column(
ColumnWithScrollBar(
Modifier.fillMaxSize(),
) {
var showUserSelection by remember { mutableStateOf(false) }
@@ -760,14 +766,12 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
Column(
Modifier.fillMaxSize(),
) {
Box(contentAlignment = Alignment.Center) {
val bottomPadding = DEFAULT_PADDING
AppBarTitle(
stringResource(MR.strings.servers_info),
hostDevice(rh?.remoteHostId),
bottomPadding = bottomPadding
)
}
val bottomPadding = DEFAULT_PADDING
AppBarTitle(
stringResource(MR.strings.servers_info),
hostDevice(rh?.remoteHostId),
bottomPadding = bottomPadding
)
if (serversSummary.value == null) {
Box(
modifier = Modifier
@@ -827,7 +831,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
verticalAlignment = Alignment.Top,
userScrollEnabled = appPlatform.isAndroid
) { index ->
ColumnWithScrollBar(
Column(
Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Top
@@ -858,7 +862,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
val statsStartedAt = it.statsStartedAt
SMPStatsView(totals.stats, statsStartedAt, rh)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
SMPSubscriptionsSection(totals)
SectionDividerSpaced()
@@ -890,7 +894,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
footer = generalGetString(MR.strings.servers_info_proxied_servers_section_footer),
rh = rh
)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
}
ServerSessionsView(totals.sessions)
@@ -907,7 +911,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
val previouslyUsedXFTPServers = xftpSummary.previouslyUsedXFTPServers
XFTPStatsView(totals.stats, statsStartedAt, rh)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
if (currentlyUsedXFTPServers.isNotEmpty()) {
XFTPServersListView(
@@ -934,7 +938,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
}
}
SectionDividerSpaced()
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
ReconnectAllServersButton(rh)
@@ -262,7 +262,7 @@ fun UserProfilePickerItem(
Row(
Modifier
.fillMaxWidth()
.sizeIn(minHeight = 46.dp)
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.combinedClickable(
enabled = enabled,
onClick = if (u.activeUser) openSettings else onClick,
@@ -330,7 +330,7 @@ fun RemoteHostPickerItem(h: RemoteHostInfo, onLongClick: () -> Unit = {}, action
Modifier
.fillMaxWidth()
.background(color = if (h.activeHost) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified)
.sizeIn(minHeight = 46.dp)
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick
@@ -373,7 +373,7 @@ fun LocalDevicePickerItem(active: Boolean, onLongClick: () -> Unit = {}, onClick
Modifier
.fillMaxWidth()
.background(color = if (active) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified)
.sizeIn(minHeight = 46.dp)
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.combinedClickable(
onClick = if (active) {{}} else onClick,
onLongClick = onLongClick,
@@ -107,101 +107,104 @@ fun DatabaseEncryptionLayout(
migration: Boolean,
onConfirmEncrypt: () -> Unit,
) {
val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents()
val scrollState = rememberScrollState()
Column(
if (!migration) Modifier.fillMaxWidth().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier) else Modifier.fillMaxWidth(),
) {
if (!migration) {
AppBarTitle(stringResource(MR.strings.database_passphrase))
} else {
ChatStoppedView()
SectionSpacer()
}
SectionView(if (migration) generalGetString(MR.strings.database_passphrase).uppercase() else null) {
SavePassphraseSetting(
useKeychain.value,
initialRandomDBPassphrase.value,
storedKey.value,
enabled = (!initialRandomDBPassphrase.value && !progressIndicator.value) || migration
) { checked ->
if (checked) {
setUseKeychain(true, useKeychain, migration)
} else if (storedKey.value && !migration) {
// Don't show in migration process since it will remove the key after successful encryption
removePassphraseAlert {
removePassphraseFromKeyChain(useKeychain, storedKey, false)
}
} else {
setUseKeychain(false, useKeychain, migration)
}
@Composable
fun Layout() {
Column {
if (!migration) {
AppBarTitle(stringResource(MR.strings.database_passphrase))
} else {
ChatStoppedView()
SectionSpacer()
}
SectionView(if (migration) generalGetString(MR.strings.database_passphrase).uppercase() else null) {
SavePassphraseSetting(
useKeychain.value,
initialRandomDBPassphrase.value,
storedKey.value,
enabled = (!initialRandomDBPassphrase.value && !progressIndicator.value) || migration
) { checked ->
if (checked) {
setUseKeychain(true, useKeychain, migration)
} else if (storedKey.value && !migration) {
// Don't show in migration process since it will remove the key after successful encryption
removePassphraseAlert {
removePassphraseFromKeyChain(useKeychain, storedKey, false)
}
} else {
setUseKeychain(false, useKeychain, migration)
}
}
if (!initialRandomDBPassphrase.value && chatDbEncrypted == true) {
PassphraseField(
currentKey,
generalGetString(MR.strings.current_passphrase),
modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
isValid = ::validKey,
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
)
}
if (!initialRandomDBPassphrase.value && chatDbEncrypted == true) {
PassphraseField(
currentKey,
generalGetString(MR.strings.current_passphrase),
newKey,
generalGetString(MR.strings.new_passphrase),
modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
showStrength = true,
isValid = ::validKey,
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
)
}
PassphraseField(
newKey,
generalGetString(MR.strings.new_passphrase),
modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
showStrength = true,
isValid = ::validKey,
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
)
val onClickUpdate = {
// Don't do things concurrently. Shouldn't be here concurrently, just in case
if (!progressIndicator.value) {
if (currentKey.value == "") {
if (useKeychain.value)
encryptDatabaseSavedAlert(onConfirmEncrypt)
else
encryptDatabaseAlert(onConfirmEncrypt)
} else {
if (useKeychain.value)
changeDatabaseKeySavedAlert(onConfirmEncrypt)
else
changeDatabaseKeyAlert(onConfirmEncrypt)
val onClickUpdate = {
// Don't do things concurrently. Shouldn't be here concurrently, just in case
if (!progressIndicator.value) {
if (currentKey.value == "") {
if (useKeychain.value)
encryptDatabaseSavedAlert(onConfirmEncrypt)
else
encryptDatabaseAlert(onConfirmEncrypt)
} else {
if (useKeychain.value)
changeDatabaseKeySavedAlert(onConfirmEncrypt)
else
changeDatabaseKeyAlert(onConfirmEncrypt)
}
}
}
val disabled = currentKey.value == newKey.value ||
newKey.value != confirmNewKey.value ||
newKey.value.isEmpty() ||
!validKey(currentKey.value) ||
!validKey(newKey.value) ||
progressIndicator.value
PassphraseField(
confirmNewKey,
generalGetString(MR.strings.confirm_new_passphrase),
modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value },
keyboardActions = KeyboardActions(onDone = {
if (!disabled) onClickUpdate()
defaultKeyboardAction(ImeAction.Done)
}),
)
SectionItemViewSpaceBetween(onClickUpdate, disabled = disabled, minHeight = TextFieldDefaults.MinHeight) {
Text(generalGetString(if (migration) MR.strings.set_passphrase else MR.strings.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
}
val disabled = currentKey.value == newKey.value ||
newKey.value != confirmNewKey.value ||
newKey.value.isEmpty() ||
!validKey(currentKey.value) ||
!validKey(newKey.value) ||
progressIndicator.value
PassphraseField(
confirmNewKey,
generalGetString(MR.strings.confirm_new_passphrase),
modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value },
keyboardActions = KeyboardActions(onDone = {
if (!disabled) onClickUpdate()
defaultKeyboardAction(ImeAction.Done)
}),
)
SectionItemViewSpaceBetween(onClickUpdate, disabled = disabled, minHeight = TextFieldDefaults.MinHeight) {
Text(generalGetString(if (migration) MR.strings.set_passphrase else MR.strings.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
Column {
DatabaseEncryptionFooter(useKeychain, chatDbEncrypted, storedKey, initialRandomDBPassphrase, migration)
}
SectionBottomSpacer()
}
Column {
DatabaseEncryptionFooter(useKeychain, chatDbEncrypted, storedKey, initialRandomDBPassphrase, migration)
}
SectionBottomSpacer()
}
if (appPlatform.isDesktop && !migration) {
Box(Modifier.fillMaxSize()) {
platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false)
if (migration) {
Column(Modifier.fillMaxWidth()) {
Layout()
}
} else {
ColumnWithScrollBar(Modifier.fillMaxWidth(), maxIntrinsicSize = true) {
Layout()
}
}
}
@@ -195,7 +195,7 @@ fun DatabaseLayout(
stringResource(MR.strings.stop_chat_to_enable_database_actions)
}
)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
}
SectionView(stringResource(MR.strings.chat_database_section)) {
@@ -264,7 +264,7 @@ fun DatabaseLayout(
disabled = operationsDisabled
)
}
SectionDividerSpaced(maxTopPadding = true)
SectionDividerSpaced()
SectionView(stringResource(MR.strings.files_and_media_section).uppercase()) {
val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0
@@ -6,59 +6,90 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
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.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.*
import chat.simplex.common.platform.appPlatform
import chat.simplex.common.ui.theme.*
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(MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f))
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,
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
if (showClose) {
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,
color = MaterialTheme.colors.onBackground,
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
)
}
} else {
Spacer(Modifier.weight(1f))
}
Row {
endButtons()
@@ -66,11 +97,24 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co
}
}
)
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)
@@ -81,23 +125,37 @@ fun AppBarTitle(title: String, hostDevice: Pair<Long?, String>? = null, withPad
Text(
title,
Modifier
.fillMaxWidth()
.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp,),
.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.Center
textAlign = TextAlign.Start
)
if (hostDevice != null) {
HostDeviceTitle(hostDevice)
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.Center) {
Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) {
Icon(painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), null, Modifier.size(15.dp), tint = MaterialTheme.colors.secondary)
Spacer(Modifier.width(10.dp))
Text(hostDevice.second, color = MaterialTheme.colors.secondary)
@@ -0,0 +1,44 @@
package chat.simplex.common.views.helpers
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.*
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
val LocalAppBarHandler: ProvidableCompositionLocal<AppBarHandler?> = staticCompositionLocalOf { null }
@Stable
class AppBarHandler(
listState: LazyListState = LazyListState(0, 0),
scrollState: ScrollState = ScrollState(initial = 0)
) {
val title = mutableStateOf("")
var listState by mutableStateOf(listState, structuralEqualityPolicy())
internal set
var scrollState by mutableStateOf(scrollState, structuralEqualityPolicy())
internal set
val connection = CollapsingAppBarNestedScrollConnection()
companion object {
var appBarMaxHeightPx: Int = 0
}
}
class CollapsingAppBarNestedScrollConnection(): NestedScrollConnection {
var appBarOffset: Float by mutableFloatStateOf(0f)
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
appBarOffset += available.y
return Offset(0f, 0f)
}
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
appBarOffset -= available.y
return Offset(x = 0f, 0f)
}
}
@@ -25,9 +25,9 @@ fun DefaultSwitch(
)
) {
val color = if (checked) MaterialTheme.colors.primary.copy(alpha = 0.3f) else MaterialTheme.colors.secondary.copy(alpha = 0.3f)
val size = with(LocalDensity.current) { Size(46.dp.toPx(), 28.dp.toPx()) }
val offset = with(LocalDensity.current) { Offset(1.dp.toPx(), 10.dp.toPx()) }
val radius = with(LocalDensity.current) { 28.dp.toPx() }
val size = with(LocalDensity.current) { Size(40.dp.toPx(), 26.dp.toPx()) }
val offset = with(LocalDensity.current) { Offset(4.dp.toPx(), 11.dp.toPx()) }
val radius = with(LocalDensity.current) { 13.dp.toPx() }
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
@@ -50,7 +50,7 @@ fun <T> ExposedDropDownSetting(
)
Spacer(Modifier.size(12.dp))
Icon(
if (!expanded.value) painterResource(MR.images.ic_expand_more) else painterResource(MR.images.ic_expand_less),
if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up),
generalGetString(MR.strings.icon_descr_more_button),
tint = MaterialTheme.colors.secondary
)
@@ -2,11 +2,12 @@ package chat.simplex.common.views.helpers
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import chat.simplex.common.model.ChatController.appPrefs
@@ -48,13 +49,15 @@ enum class ModalPlacement {
START, CENTER, END, FULLSCREEN
}
class ModalData {
class ModalData() {
private val state = mutableMapOf<String, MutableState<Any?>>()
fun <T> stateGetOrPut (key: String, default: () -> T): MutableState<T> =
state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState<T>
fun <T> stateGetOrPutNullable (key: String, default: () -> T?): MutableState<T?> =
state.getOrPut(key) { mutableStateOf(default() as Any?) } as MutableState<T?>
val appBarHandler = AppBarHandler()
}
class ModalManager(private val placement: ModalPlacement? = null) {
@@ -139,7 +142,13 @@ class ModalManager(private val placement: ModalPlacement? = null) {
fun showInView() {
// Without animation
if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) {
modalViews.lastOrNull()?.let { it.third(it.second, ::closeModal) }
modalViews.lastOrNull()?.let {
CompositionLocalProvider(
LocalAppBarHandler provides it.second.appBarHandler
) {
it.third(it.second, ::closeModal)
}
}
return
}
AnimatedContent(targetState = modalCount.value,
@@ -151,7 +160,13 @@ class ModalManager(private val placement: ModalPlacement? = null) {
}.using(SizeTransform(clip = false))
}
) {
modalViews.getOrNull(it - 1)?.let { it.third(it.second, ::closeModal) }
modalViews.getOrNull(it - 1)?.let {
CompositionLocalProvider(
LocalAppBarHandler provides it.second.appBarHandler
) {
it.third(it.second, ::closeModal)
}
}
// This is needed because if we delete from modalViews immediately on request, animation will be bad
if (toRemove.isNotEmpty() && it == modalCount.value && transition.currentState == EnterExitState.Visible && !transition.isRunning) {
runAtomically { toRemove.removeIf { elem -> modalViews.removeAt(elem); true } }
@@ -12,7 +12,6 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import chat.simplex.common.model.NotificationsMode
import chat.simplex.common.platform.onRightClick
import chat.simplex.common.platform.windowWidth
import chat.simplex.common.ui.theme.*
@@ -20,7 +19,6 @@ import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.onboarding.SelectableCard
import chat.simplex.common.views.usersettings.SettingsActionItemWithContent
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.stringResource
@Composable
fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), content: (@Composable ColumnScope.() -> Unit)) {
@@ -28,7 +26,7 @@ fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(),
if (title != null) {
Text(
title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2,
modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), fontSize = 12.sp
modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = DEFAULT_PADDING), fontSize = 12.sp
)
}
Column(Modifier.padding(padding).fillMaxWidth()) { content() }
@@ -101,13 +99,13 @@ fun <T> SectionViewSelectableCards(
@Composable
fun SectionItemView(
click: (() -> Unit)? = null,
minHeight: Dp = 46.dp,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
disabled: Boolean = false,
extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)
else
PaddingValues(horizontal = DEFAULT_PADDING),
PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
@@ -122,10 +120,9 @@ fun SectionItemView(
}
@Composable
fun SectionItemViewLongClickable(
click: () -> Unit,
longClick: () -> Unit,
minHeight: Dp = 46.dp,
fun SectionItemViewWithoutMinPadding(
click: (() -> Unit)? = null,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
disabled: Boolean = false,
extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding)
@@ -133,6 +130,22 @@ fun SectionItemViewLongClickable(
else
PaddingValues(horizontal = DEFAULT_PADDING),
content: (@Composable RowScope.() -> Unit)
) {
SectionItemView(click, minHeight, disabled, extraPadding, padding, content)
}
@Composable
fun SectionItemViewLongClickable(
click: () -> Unit,
longClick: () -> Unit,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
disabled: Boolean = false,
extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)
else
PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
.fillMaxWidth()
@@ -149,30 +162,11 @@ fun SectionItemViewLongClickable(
}
}
@Composable
fun SectionItemViewWithIcon(
click: (() -> Unit)? = null,
minHeight: Dp = 46.dp,
disabled: Boolean = false,
padding: PaddingValues = PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING),
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
.fillMaxWidth()
.sizeIn(minHeight = minHeight)
Row(
if (click == null || disabled) modifier.padding(padding) else modifier.clickable(onClick = click).padding(padding),
verticalAlignment = Alignment.CenterVertically
) {
content()
}
}
@Composable
fun SectionItemViewSpaceBetween(
click: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
minHeight: Dp = 46.dp,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING),
disabled: Boolean = false,
content: (@Composable RowScope.() -> Unit)
@@ -181,7 +175,7 @@ fun SectionItemViewSpaceBetween(
.fillMaxWidth()
.sizeIn(minHeight = minHeight)
Row(
if (click == null || disabled) modifier.padding(padding) else modifier
if (click == null || disabled) modifier.padding(padding).padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL) else modifier
.combinedClickable(onClick = click, onLongClick = onLongClick).padding(padding)
.onRightClick { onLongClick?.invoke() },
horizontalArrangement = Arrangement.SpaceBetween,
@@ -254,9 +248,9 @@ fun SectionDividerSpaced(maxTopPadding: Boolean = false, maxBottomPadding: Boole
Divider(
Modifier.padding(
start = DEFAULT_PADDING_HALF,
top = if (maxTopPadding) 37.dp else 27.dp,
top = if (maxTopPadding) DEFAULT_PADDING + 18.dp else DEFAULT_PADDING + 2.dp,
end = DEFAULT_PADDING_HALF,
bottom = if (maxBottomPadding) 37.dp else 27.dp)
bottom = if (maxBottomPadding) DEFAULT_PADDING + 18.dp else DEFAULT_PADDING + 2.dp)
)
}
@@ -1,6 +1,7 @@
package chat.simplex.common.views.helpers
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionSpacer
import SectionView
@@ -203,11 +204,12 @@ fun ModalData.UserWallpaperEditor(
}
)
SectionSpacer()
SectionDividerSpaced()
AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor)
SectionSpacer()
SectionDividerSpaced(maxBottomPadding = false)
ImportExportThemeSection(null, remember { chatModel.currentUser }.value?.uiThemes) {
withBGApi {
themeModeOverride.value = it
@@ -440,11 +442,11 @@ fun ModalData.ChatWallpaperEditor(
}
)
SectionSpacer()
SectionDividerSpaced()
AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor)
SectionSpacer()
SectionDividerSpaced(maxBottomPadding = false)
ImportExportThemeSection(themeModeOverride.value, remember { chatModel.currentUser }.value?.uiThemes) {
withBGApi {
themeModeOverride.value = it
@@ -4,7 +4,7 @@ import SectionBottomSpacer
import SectionSpacer
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
@@ -17,7 +17,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatController.getNetCfg
import chat.simplex.common.model.ChatController.startChat
import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase
@@ -147,20 +146,13 @@ private fun MigrateFromDeviceLayout(
) {
val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) }
val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents()
val scrollState = rememberScrollState()
Column(
Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).height(IntrinsicSize.Max),
ColumnWithScrollBar(
Modifier.fillMaxSize(), maxIntrinsicSize = true
) {
AppBarTitle(stringResource(MR.strings.migrate_from_device_title))
SectionByState(migrationState, tempDatabaseFile.value, chatReceiver)
SectionBottomSpacer()
}
if (appPlatform.isDesktop) {
Box(Modifier.fillMaxSize()) {
platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false)
}
}
platform.androidLockPortraitOrientation()
}
@@ -155,20 +155,13 @@ private fun ModalData.MigrateToDeviceLayout(
close: () -> Unit,
) {
val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) }
val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents()
val scrollState = rememberScrollState()
Column(
Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).height(IntrinsicSize.Max),
ColumnWithScrollBar(
Modifier.fillMaxSize(), maxIntrinsicSize = true
) {
AppBarTitle(stringResource(MR.strings.migrate_to_device_title))
SectionByState(migrationState, tempDatabaseFile.value, chatReceiver, close)
SectionBottomSpacer()
}
if (appPlatform.isDesktop) {
Box(Modifier.fillMaxSize()) {
platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false)
}
}
platform.androidLockPortraitOrientation()
}
@@ -142,7 +142,7 @@ private fun ContactConnectionInfoLayout(
}
SectionTextFooter(sharedProfileInfo(chatModel, contactConnection.incognito))
SectionDividerSpaced(maxBottomPadding = false)
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
DeleteButton(deleteConnection)
@@ -68,10 +68,10 @@ fun NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) {
Column(modifier = Modifier.fillMaxSize()) {
NewChatSheetLayout(
addContact = {
ModalManager.start.showModalCloseable { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll ) }
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll ) }
},
scanPaste = {
ModalManager.start.showModalCloseable { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll) }
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll) }
},
createGroup = {
ModalManager.start.showCustomModal { close -> AddGroupView(chatModel, chatModel.currentRemoteHost.value, close, closeAll) }
@@ -194,7 +194,15 @@ private fun NewChatSheetLayout(
(appPlatform.isAndroid && keyboardState == KeyboardState.Opened)
) {
0
} else if (listState.firstVisibleItemIndex == 0) offsetMultiplier * listState.firstVisibleItemScrollOffset else offsetMultiplier * 1000
} 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
}
@@ -254,13 +262,13 @@ private fun NewChatSheetLayout(
}
}
}
SectionDividerSpaced(maxBottomPadding = false)
val deletedContactTypes = listOf(ContactType.CHAT_DELETED)
val deletedChats by remember(chatModel.chats.value, deletedContactTypes) {
derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) }
}
if (deletedChats.isNotEmpty()) {
SectionDividerSpaced(maxBottomPadding = false)
Row(modifier = sectionModifier) {
SectionView {
SectionItemView(
@@ -287,16 +295,20 @@ private fun NewChatSheetLayout(
}
}
}
SectionDividerSpaced(maxBottomPadding = false)
}
}
}
item {
if (filteredContactChats.isNotEmpty() && !oneHandUI.value) {
if (searchText.value.text.isNotEmpty()) {
Spacer(Modifier.height(DEFAULT_PADDING))
} else {
SectionDividerSpaced()
}
Text(
stringResource(MR.strings.contact_list_header_title).uppercase(), color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2,
modifier = sectionModifier.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF, bottom = DEFAULT_PADDING_HALF), fontSize = 12.sp
modifier = sectionModifier.padding(start = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF), fontSize = 12.sp
)
}
}
@@ -356,7 +368,7 @@ private fun ContactsSearchBar(
Icon(
painterResource(MR.images.ic_search),
contentDescription = null,
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(24.dp * fontSizeSqrtMultiplier),
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(22.dp * fontSizeSqrtMultiplier),
tint = MaterialTheme.colors.secondary
)
SearchTextField(
@@ -5,6 +5,7 @@ import SectionItemView
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.HorizontalPager
@@ -96,66 +97,61 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC
}
}
Column(
Modifier.fillMaxSize(),
) {
Box(contentAlignment = Alignment.Center) {
val bottomPadding = DEFAULT_PADDING
AppBarTitle(stringResource(MR.strings.new_chat), hostDevice(rh?.remoteHostId), bottomPadding = bottomPadding)
Column(Modifier.align(Alignment.CenterEnd).padding(bottom = bottomPadding, end = DEFAULT_PADDING)) {
AddContactLearnMoreButton()
BoxWithConstraints {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.new_chat), hostDevice(rh?.remoteHostId), bottomPadding = DEFAULT_PADDING)
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(
initialPage = selection.value.ordinal,
initialPageOffsetFraction = 0f
) { NewChatOption.values().size }
KeyChangeEffect(pagerState.currentPage) {
selection.value = NewChatOption.values()[pagerState.currentPage]
}
}
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(
initialPage = selection.value.ordinal,
initialPageOffsetFraction = 0f
) { NewChatOption.values().size }
KeyChangeEffect(pagerState.currentPage) {
selection.value = NewChatOption.values()[pagerState.currentPage]
}
TabRow(
selectedTabIndex = pagerState.currentPage,
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.primary,
) {
tabTitles.forEachIndexed { index, it ->
LeadingIconTab(
selected = pagerState.currentPage == index,
onClick = {
scope.launch {
pagerState.animateScrollToPage(index)
}
},
text = { Text(it, fontSize = 13.sp) },
icon = {
Icon(
if (NewChatOption.INVITE.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_qr_code),
it
)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = MaterialTheme.colors.secondary,
)
}
}
HorizontalPager(state = pagerState, Modifier.fillMaxSize(), verticalAlignment = Alignment.Top, userScrollEnabled = appPlatform.isAndroid) { index ->
// LALAL SCROLLBAR DOESN'T WORK
ColumnWithScrollBar(
Modifier
.fillMaxSize(),
verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top) {
Spacer(Modifier.height(DEFAULT_PADDING))
when (index) {
NewChatOption.INVITE.ordinal -> {
PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq)
}
NewChatOption.CONNECT.ordinal -> {
ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close)
}
TabRow(
selectedTabIndex = pagerState.currentPage,
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.primary,
) {
tabTitles.forEachIndexed { index, it ->
LeadingIconTab(
selected = pagerState.currentPage == index,
onClick = {
scope.launch {
pagerState.animateScrollToPage(index)
}
},
text = { Text(it, fontSize = 13.sp) },
icon = {
Icon(
if (NewChatOption.INVITE.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_qr_code),
it
)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = MaterialTheme.colors.secondary,
)
}
}
HorizontalPager(state = pagerState, Modifier, pageNestedScrollConnection = LocalAppBarHandler.current!!.connection, verticalAlignment = Alignment.Top, userScrollEnabled = appPlatform.isAndroid) { index ->
Column(
Modifier
.fillMaxWidth()
.heightIn(min = this@BoxWithConstraints.maxHeight - 150.dp),
verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top
) {
Spacer(Modifier.height(DEFAULT_PADDING))
when (index) {
NewChatOption.INVITE.ordinal -> {
PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq)
}
NewChatOption.CONNECT.ordinal -> {
ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close)
}
}
SectionBottomSpacer()
}
SectionBottomSpacer()
}
}
}
@@ -228,18 +224,18 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection
}
@Composable
private fun AddContactLearnMoreButton() {
fun AddContactLearnMoreButton() {
IconButton(
{
ModalManager.start.showModalCloseable { close ->
AddContactLearnMore(close)
}
},
Modifier.size(18.dp * fontSizeSqrtMultiplier)
}
) {
Icon(
painterResource(MR.images.ic_info),
stringResource(MR.strings.learn_more),
tint = MaterialTheme.colors.primary
)
}
}
@@ -297,7 +293,7 @@ private fun PasteLinkView(rhId: Long?, pastedLink: MutableState<String>, showQRC
@Composable
fun LinkTextView(link: String, share: Boolean) {
val clipboard = LocalClipboardManager.current
Row(Modifier.fillMaxWidth().heightIn(min = 46.dp).padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) {
Row(Modifier.fillMaxWidth().heightIn(min = DEFAULT_MIN_SECTION_ITEM_HEIGHT).padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.weight(1f).clickable {
chatModel.markShowingInvitationUsed()
clipboard.shareText(link)
@@ -23,9 +23,10 @@ import dev.icerock.moko.resources.StringResource
@Composable
fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>? = null) {
ColumnWithScrollBar(Modifier
.fillMaxWidth()
.padding(DEFAULT_PADDING),
ColumnWithScrollBar(
Modifier
.fillMaxWidth()
.padding(DEFAULT_PADDING),
) {
AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false)
ReadableText(MR.strings.many_people_asked_how_can_it_deliver)
@@ -446,7 +446,7 @@ fun IntSettingRow(title: String, selection: MutableState<Int>, values: List<Int>
)
Spacer(Modifier.size(4.dp))
Icon(
if (!expanded.value) painterResource(MR.images.ic_expand_more) else painterResource(MR.images.ic_expand_less),
if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up),
generalGetString(MR.strings.invite_to_group_button),
modifier = Modifier.padding(start = 8.dp),
tint = MaterialTheme.colors.secondary
@@ -506,7 +506,7 @@ fun TimeoutSettingRow(title: String, selection: MutableState<Long>, values: List
)
Spacer(Modifier.size(4.dp))
Icon(
if (!expanded.value) painterResource(MR.images.ic_expand_more) else painterResource(MR.images.ic_expand_less),
if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up),
generalGetString(MR.strings.invite_to_group_button),
modifier = Modifier.padding(start = 8.dp),
tint = MaterialTheme.colors.secondary
@@ -549,13 +549,13 @@ object AppearanceScope {
},
)
}
SectionDividerSpaced(maxTopPadding = true)
SectionDividerSpaced()
CustomizeThemeColorsSection(currentTheme) { name ->
editColor(name)
}
SectionSpacer()
SectionDividerSpaced(maxBottomPadding = false)
val currentOverrides = remember(currentTheme) { ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get()) }
val canResetColors = currentTheme.base.hasChangedAnyColor(currentOverrides)
@@ -889,7 +889,7 @@ object AppearanceScope {
val hexTrimmed = currentColor.toReadableHex().replaceFirst("#ff", "#")
val savedColor by remember(wallpaperType) { mutableStateOf(initialColor) }
Row(Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).height(46.dp)) {
Row(Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).height(DEFAULT_MIN_SECTION_ITEM_HEIGHT)) {
Box(Modifier.weight(1f).fillMaxHeight().background(savedColor).clickable {
currentColor = savedColor
onColorChange(currentColor)
@@ -1,6 +1,7 @@
package chat.simplex.common.views.usersettings
import SectionBottomSpacer
import SectionDividerSpaced
import SectionSpacer
import SectionTextFooter
import SectionView
@@ -43,7 +44,7 @@ fun DeveloperView(
)
}
if (devTools.value) {
SectionSpacer()
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.developer_options_section).uppercase()) {
SettingsPreferenceItem(painterResource(MR.images.ic_drive_folder_upload), stringResource(MR.strings.confirm_database_upgrades), m.controller.appPrefs.confirmDBUpgrades)
if (appPlatform.isDesktop) {
@@ -3,6 +3,7 @@ package chat.simplex.common.views.usersettings
import SectionBottomSpacer
import SectionItemView
import SectionItemViewSpaceBetween
import SectionItemViewWithoutMinPadding
import SectionSpacer
import SectionTextFooter
import SectionView
@@ -74,10 +75,10 @@ private fun HiddenProfileLayout(
val confirmValid by remember { derivedStateOf { confirmHidePassword.value == "" || hidePassword.value == confirmHidePassword.value } }
val saveDisabled by remember { derivedStateOf { hidePassword.value == "" || !passwordValid || confirmHidePassword.value == "" || !confirmValid } }
SectionView(stringResource(MR.strings.hidden_profile_password).uppercase()) {
SectionItemView {
SectionItemViewWithoutMinPadding {
PassphraseField(hidePassword, generalGetString(MR.strings.password_to_show), isValid = { passwordValid }, showStrength = true)
}
SectionItemView {
SectionItemViewWithoutMinPadding {
PassphraseField(confirmHidePassword, stringResource(MR.strings.confirm_password), isValid = { confirmValid }, dependsOn = hidePassword)
}
SectionItemViewSpaceBetween({ saveProfilePassword(hidePassword.value) }, disabled = saveDisabled, minHeight = TextFieldDefaults.MinHeight) {
@@ -264,30 +264,26 @@ fun SocksProxySettings(
.fillMaxWidth()
) {
AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings))
SectionView {
SectionItemView {
DefaultConfigurableTextField(
hostUnsaved,
stringResource(MR.strings.host_verb),
modifier = Modifier.fillMaxWidth(),
isValid = ::validHost,
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
keyboardType = KeyboardType.Text,
)
}
SectionItemView {
DefaultConfigurableTextField(
portUnsaved,
stringResource(MR.strings.port_verb),
modifier = Modifier.fillMaxWidth(),
isValid = ::validPort,
keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }),
keyboardType = KeyboardType.Number,
)
}
SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
DefaultConfigurableTextField(
hostUnsaved,
stringResource(MR.strings.host_verb),
modifier = Modifier.fillMaxWidth(),
isValid = ::validHost,
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
keyboardType = KeyboardType.Text,
)
DefaultConfigurableTextField(
portUnsaved,
stringResource(MR.strings.port_verb),
modifier = Modifier.fillMaxWidth(),
isValid = ::validPort,
keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }),
keyboardType = KeyboardType.Number,
)
}
Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 27.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp))
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionItemView({
@@ -104,13 +104,13 @@ fun PrivacySettingsView(
}
SettingsPreferenceItem(painterResource(MR.images.ic_security), stringResource(MR.strings.protect_ip_address), chatModel.controller.appPrefs.privacyAskToApproveRelays)
}
SectionCustomFooter {
SectionTextFooter(
if (chatModel.controller.appPrefs.privacyAskToApproveRelays.state.value) {
Text(stringResource(MR.strings.app_will_ask_to_confirm_unknown_file_servers))
stringResource(MR.strings.app_will_ask_to_confirm_unknown_file_servers)
} else {
Text(stringResource(MR.strings.without_tor_or_vpn_ip_address_will_be_visible_to_file_servers))
stringResource(MR.strings.without_tor_or_vpn_ip_address_will_be_visible_to_file_servers)
}
}
)
val currentUser = chatModel.currentUser.value
if (currentUser != null) {
@@ -110,7 +110,7 @@ private fun PresetServer(
)
}
}
SectionDividerSpaced(maxTopPadding = true)
SectionDividerSpaced()
UseServerSection(true, testing, server, testServer, onUpdate, onDelete)
}
@@ -150,7 +150,7 @@ private fun CustomServer(
}
}
}
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
UseServerSection(valid.value, testing, server, testServer, onUpdate, onDelete)
if (valid.value) {
@@ -73,33 +73,30 @@ private fun SetDeliveryReceiptsLayout(
skip: () -> Unit,
userCount: Int,
) {
// This view located in the left panel which means it has to have a padding from right side in order
// to see scroll bar. And this padding should be applied to upper element, not scrollable column modifier
val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp
val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents()
val scrollState = rememberScrollState()
Column(
Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).padding(top = DEFAULT_PADDING, end = endPadding),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarTitle(stringResource(MR.strings.delivery_receipts_title))
Box(Modifier.padding(top = DEFAULT_PADDING, end = endPadding)) {
ColumnWithScrollBar(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarTitle(stringResource(MR.strings.delivery_receipts_title))
Spacer(Modifier.weight(1f))
Spacer(Modifier.weight(1f))
EnableReceiptsButton(enableReceipts)
if (userCount > 1) {
TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled_all_profiles))
} else {
TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled))
}
EnableReceiptsButton(enableReceipts)
if (userCount > 1) {
TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled_all_profiles))
} else {
TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled))
}
Spacer(Modifier.weight(1f))
Spacer(Modifier.weight(1f))
SkipButton(skip)
SkipButton(skip)
SectionBottomSpacer()
}
if (appPlatform.isDesktop) {
Box(Modifier.fillMaxSize().padding(end = endPadding)) {
platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false)
SectionBottomSpacer()
}
}
}
@@ -3,7 +3,6 @@ package chat.simplex.common.views.usersettings
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionItemViewWithIcon
import SectionView
import TextIconSpaced
import androidx.compose.desktop.ui.tooling.preview.Preview
@@ -111,85 +110,73 @@ fun SettingsLayout(
}
val theme = CurrentColors.collectAsState()
val uriHandler = LocalUriHandler.current
Box(Modifier.fillMaxSize()) {
ColumnWithScrollBar(
Modifier
.fillMaxSize()
.themedBackground(theme.value.base)
.padding(top = if (appPlatform.isAndroid) DEFAULT_PADDING else DEFAULT_PADDING * 2.8f)
) {
AppBarTitle(stringResource(MR.strings.your_settings))
ColumnWithScrollBar(
Modifier
.fillMaxSize()
.themedBackground(theme.value.base)
) {
AppBarTitle(stringResource(MR.strings.your_settings))
SectionView(stringResource(MR.strings.settings_section_title_you)) {
val profileHidden = rememberSaveable { mutableStateOf(false) }
if (profile != null) {
SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) {
ProfilePreview(profile, stopped = stopped)
}
SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden, drawerState) } } }, disabled = stopped, extraPadding = true)
SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true)
ChatPreferencesItem(showCustomModal, stopped = stopped)
} else if (chatModel.localUserCreated.value == false) {
SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.create_chat_profile), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.center.showModalCloseable { close ->
LaunchedEffect(Unit) {
closeSettings()
SectionView(stringResource(MR.strings.settings_section_title_you)) {
val profileHidden = rememberSaveable { mutableStateOf(false) }
if (profile != null) {
SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) {
ProfilePreview(profile, stopped = stopped)
}
SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden, drawerState) } } }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped)
ChatPreferencesItem(showCustomModal, stopped = stopped)
} else if (chatModel.localUserCreated.value == false) {
SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.create_chat_profile), {
withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) {
ModalManager.center.showModalCloseable { close ->
LaunchedEffect(Unit) {
closeSettings()
}
CreateProfile(chatModel, close)
}
CreateProfile(chatModel, close)
} } }, disabled = stopped, extraPadding = true)
}
if (appPlatform.isDesktop) {
SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped, extraPadding = true)
} else {
SettingsActionItem(painterResource(MR.images.ic_desktop), stringResource(MR.strings.settings_section_title_use_from_desktop), showCustomModal{ it, close -> ConnectDesktopView(close) }, disabled = stopped, extraPadding = true)
}
SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } }}, disabled = stopped, extraPadding = true)
}
}, disabled = stopped)
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_settings)) {
SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped, extraPadding = true)
SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped, extraPadding = true)
SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true)
SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true)
SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }, extraPadding = true)
DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
if (appPlatform.isDesktop) {
SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped)
} else {
SettingsActionItem(painterResource(MR.images.ic_desktop), stringResource(MR.strings.settings_section_title_use_from_desktop), showCustomModal { it, close -> ConnectDesktopView(close) }, disabled = stopped)
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_help)) {
SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped, extraPadding = true)
SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped, extraPadding = true)
SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }, extraPadding = true)
if (!chatModel.desktopNoUserNoRemote) {
SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true)
}
SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary, extraPadding = true)
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_support)) {
ContributeItem(uriHandler)
RateAppItem(uriHandler)
StarOnGithubItem(uriHandler)
}
SectionDividerSpaced()
SettingsSectionApp(showSettingsModal, showCustomModal, showVersion, withAuth)
SectionBottomSpacer()
SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped)
}
if (appPlatform.isDesktop) {
Box(
Modifier
.fillMaxWidth()
.height(AppBarHeight * fontSizeSqrtMultiplier)
.background(MaterialTheme.colors.background)
.background(if (isInDarkTheme()) ToolbarDark else ToolbarLight)
.padding(start = 4.dp),
contentAlignment = Alignment.CenterStart
) {
NavigationButtonBack(closeSettings, height = 24.sp.toDp())
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_settings)) {
SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) })
DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_help)) {
SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) })
if (!chatModel.desktopNoUserNoRemote) {
SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped)
}
SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary)
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_support)) {
ContributeItem(uriHandler)
RateAppItem(uriHandler)
StarOnGithubItem(uriHandler)
}
SectionDividerSpaced()
SettingsSectionApp(showSettingsModal, showCustomModal, showVersion, withAuth)
SectionBottomSpacer()
}
}
@@ -202,18 +189,19 @@ expect fun SettingsSectionApp(
)
@Composable private fun DatabaseItem(encrypted: Boolean, saved: Boolean, openDatabaseView: () -> Unit, stopped: Boolean) {
SectionItemViewWithIcon(openDatabaseView) {
SectionItemView(openDatabaseView) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(Modifier.weight(1f)) {
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
Icon(
painterResource(MR.images.ic_database),
contentDescription = stringResource(MR.strings.database_passphrase_and_export),
tint = if (encrypted && (appPlatform.isAndroid || !saved)) MaterialTheme.colors.secondary else WarningOrange,
)
TextIconSpaced(true)
TextIconSpaced(false)
Text(stringResource(MR.strings.database_passphrase_and_export))
}
if (stopped) {
@@ -237,8 +225,7 @@ expect fun SettingsSectionApp(
PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close)
}()
}),
disabled = stopped,
extraPadding = true
disabled = stopped
)
}
@@ -253,27 +240,26 @@ fun ChatLockItem(
click = showSettingsModal { SimplexLockView(ChatModel, currentLAMode, setPerformLA) },
icon = if (performLA.value) painterResource(MR.images.ic_lock_filled) else painterResource(MR.images.ic_lock),
text = stringResource(MR.strings.chat_lock),
iconColor = if (performLA.value) SimplexGreen else MaterialTheme.colors.secondary,
extraPadding = false,
iconColor = if (performLA.value) SimplexGreen else MaterialTheme.colors.secondary
) {
Text(if (performLA.value) remember { currentLAMode.state }.value.text else generalGetString(MR.strings.la_mode_off), color = MaterialTheme.colors.secondary)
}
}
@Composable private fun ContributeItem(uriHandler: UriHandler) {
SectionItemViewWithIcon({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) {
SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) {
Icon(
painterResource(MR.images.ic_keyboard),
contentDescription = "GitHub",
tint = MaterialTheme.colors.secondary,
)
TextIconSpaced(extraPadding = true)
TextIconSpaced()
Text(generalGetString(MR.strings.contribute), color = MaterialTheme.colors.primary)
}
}
@Composable private fun RateAppItem(uriHandler: UriHandler) {
SectionItemViewWithIcon({
SectionItemView({
runCatching { uriHandler.openUriCatching("market://details?id=chat.simplex.app") }
.onFailure { uriHandler.openUriCatching("https://play.google.com/store/apps/details?id=chat.simplex.app") }
}
@@ -283,19 +269,19 @@ fun ChatLockItem(
contentDescription = "Google Play",
tint = MaterialTheme.colors.secondary,
)
TextIconSpaced(extraPadding = true)
TextIconSpaced()
Text(generalGetString(MR.strings.rate_the_app), color = MaterialTheme.colors.primary)
}
}
@Composable private fun StarOnGithubItem(uriHandler: UriHandler) {
SectionItemViewWithIcon({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) {
SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) {
Icon(
painter = painterResource(MR.images.ic_github),
contentDescription = "GitHub",
tint = MaterialTheme.colors.secondary,
)
TextIconSpaced(extraPadding = true)
TextIconSpaced()
Text(generalGetString(MR.strings.star_on_github), color = MaterialTheme.colors.primary)
}
}
@@ -313,7 +299,7 @@ fun ChatLockItem(
}
@Composable fun TerminalAlwaysVisibleItem(pref: SharedPreference<Boolean>, onChange: (Boolean) -> Unit) {
SettingsActionItemWithContent(painterResource(MR.images.ic_engineering), stringResource(MR.strings.terminal_always_visible), extraPadding = false) {
SettingsActionItemWithContent(painterResource(MR.images.ic_engineering), stringResource(MR.strings.terminal_always_visible)) {
DefaultSwitch(
checked = remember { pref.state }.value,
onCheckedChange = onChange,
@@ -360,7 +346,7 @@ fun unchangedHintPreferences(): Boolean = appPreferences.hintPreferences.all { (
@Composable
fun AppVersionItem(showVersion: () -> Unit) {
SectionItemViewWithIcon(showVersion) { AppVersionText() }
SectionItemView(showVersion) { AppVersionText() }
}
@Composable fun AppVersionText() {
@@ -451,7 +437,7 @@ fun PreferenceToggle(
checked: Boolean,
onChange: (Boolean) -> Unit = {},
) {
SettingsActionItemWithContent(null, text, disabled = disabled, extraPadding = true,) {
SettingsActionItemWithContent(null, text, disabled = disabled) {
DefaultSwitch(
checked = checked,
onCheckedChange = onChange,
@@ -13,11 +13,12 @@ import chat.simplex.res.MR
@Composable
fun UserAddressLearnMore() {
ColumnWithScrollBar(Modifier
.fillMaxHeight()
.padding(horizontal = DEFAULT_PADDING)
ColumnWithScrollBar(
Modifier
.fillMaxHeight()
.padding(horizontal = DEFAULT_PADDING)
) {
AppBarTitle(stringResource(MR.strings.simplex_address))
AppBarTitle(stringResource(MR.strings.simplex_address), withPadding = false)
ReadableText(MR.strings.you_can_share_your_address)
ReadableText(MR.strings.you_wont_lose_your_contacts_if_delete_address)
ReadableText(MR.strings.you_can_accept_or_reject_connection)
@@ -174,7 +174,7 @@ private fun UserAddressLayout(
saveAas: (AutoAcceptState, MutableState<AutoAcceptState>) -> Unit,
) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId), withPadding = false)
AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId))
Column(
Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -185,7 +185,7 @@ private fun UserAddressLayout(
CreateAddressButton(createAddress)
SectionTextFooter(stringResource(MR.strings.create_address_and_let_people_connect))
}
SectionDividerSpaced(maxBottomPadding = false)
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
SectionView {
LearnMoreButton(learnMore)
}
@@ -4,6 +4,7 @@ import SectionBottomSpacer
import SectionDivider
import SectionItemView
import SectionItemViewSpaceBetween
import SectionItemViewWithoutMinPadding
import SectionSpacer
import SectionTextFooter
import SectionView
@@ -277,7 +278,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: (
@Composable fun PasswordAndAction(label: StringResource, color: Color = MaterialTheme.colors.primary) {
SectionView() {
SectionItemView {
SectionItemViewWithoutMinPadding {
PassphraseField(actionPassword, generalGetString(MR.strings.profile_password), isValid = { passwordValid }, showStrength = true)
}
SectionItemViewSpaceBetween({ doAction(actionPassword.value) }, disabled = !actionEnabled, minHeight = TextFieldDefaults.MinHeight) {
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M182-218q59.315-55.57 134.804-89.785Q392.293-342 479.896-342q87.604 0 163.197 34.215Q718.685-273.57 778-218v-560H182v560Zm300.232-200.5q57.268 0 96.518-39.482Q618-497.465 618-554.732q0-57.268-39.482-96.518-39.483-39.25-96.75-39.25-57.268 0-96.518 39.482Q346-611.535 346-554.268q0 57.268 39.482 96.518 39.483 39.25 96.75 39.25ZM182-124.5q-22.969 0-40.234-17.266Q124.5-159.031 124.5-182v-596q0-22.969 17.266-40.234Q159.031-835.5 182-835.5h596q22.969 0 40.234 17.266Q835.5-800.969 835.5-778v596q0 22.969-17.266 40.234Q800.969-124.5 778-124.5H182Zm52.5-57.5h491v-9.111Q671.5-237.5 609.161-261 546.823-284.5 480-284.5q-67.177 0-129.339 23.5Q288.5-237.5 234.5-191.111V-182Zm247.441-294q-32.733 0-55.587-22.913-22.854-22.913-22.854-55.646 0-32.733 22.913-55.587Q449.326-633 482.059-633q32.733 0 55.587 22.913 22.854 22.913 22.854 55.646 0 32.733-22.913 55.587Q514.674-476 481.941-476ZM480-498.5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22"><path d="M182-218q59.315-55.57 134.804-89.785Q392.293-342 479.896-342q87.604 0 163.197 34.215Q718.685-273.57 778-218v-560H182v560Zm300.232-200.5q57.268 0 96.518-39.482Q618-497.465 618-554.732q0-57.268-39.482-96.518-39.483-39.25-96.75-39.25-57.268 0-96.518 39.482Q346-611.535 346-554.268q0 57.268 39.482 96.518 39.483 39.25 96.75 39.25ZM182-124.5q-22.969 0-40.234-17.266Q124.5-159.031 124.5-182v-596q0-22.969 17.266-40.234Q159.031-835.5 182-835.5h596q22.969 0 40.234 17.266Q835.5-800.969 835.5-778v596q0 22.969-17.266 40.234Q800.969-124.5 778-124.5H182Zm52.5-57.5h491v-9.111Q671.5-237.5 609.161-261 546.823-284.5 480-284.5q-67.177 0-129.339 23.5Q288.5-237.5 234.5-191.111V-182Zm247.441-294q-32.733 0-55.587-22.913-22.854-22.913-22.854-55.646 0-32.733 22.913-55.587Q449.326-633 482.059-633q32.733 0 55.587 22.913 22.854 22.913 22.854 55.646 0 32.733-22.913 55.587Q514.674-476 481.941-476ZM480-498.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 994 B

After

Width:  |  Height:  |  Size: 994 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M224.89 800.5Q288 761 348.25 741T480 721q71.5 0 132 20t124.5 59.5Q781 746 799.25 691.351q18.25-54.648 18.25-115.25 0-144.101-96.75-240.851T480 238.5q-144 0-240.75 96.75T142.5 576.101q0 60.602 18.75 115.25Q180 746 224.89 800.5ZM479.869 605q-57.369 0-96.619-39.381-39.25-39.38-39.25-96.75 0-57.369 39.381-96.619 39.38-39.25 96.75-39.25 57.369 0 96.619 39.381 39.25 39.38 39.25 96.75 0 57.369-39.381 96.619-39.38 39.25-96.75 39.25Zm-.274 366q-81.553 0-154.09-31.263-72.538-31.263-125.772-85Q146.5 801 115.75 729.136 85 657.272 85 575.564q0-81.789 31.263-153.789 31.263-71.999 85-125.387Q255 243 326.864 212q71.864-31 153.572-31 81.789 0 153.795 31.132 72.005 31.131 125.387 84.5Q813 350 844 422.023q31 72.023 31 153.647 0 81.705-31.013 153.629-31.013 71.925-84.5 125.563Q706 908.5 633.827 939.75 561.655 971 479.595 971Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M224.89 800.5Q288 761 348.25 741T480 721q71.5 0 132 20t124.5 59.5Q781 746 799.25 691.351q18.25-54.648 18.25-115.25 0-144.101-96.75-240.851T480 238.5q-144 0-240.75 96.75T142.5 576.101q0 60.602 18.75 115.25Q180 746 224.89 800.5ZM479.869 605q-57.369 0-96.619-39.381-39.25-39.38-39.25-96.75 0-57.369 39.381-96.619 39.38-39.25 96.75-39.25 57.369 0 96.619 39.381 39.25 39.38 39.25 96.75 0 57.369-39.381 96.619-39.38 39.25-96.75 39.25Zm-.274 366q-81.553 0-154.09-31.263-72.538-31.263-125.772-85Q146.5 801 115.75 729.136 85 657.272 85 575.564q0-81.789 31.263-153.789 31.263-71.999 85-125.387Q255 243 326.864 212q71.864-31 153.572-31 81.789 0 153.795 31.132 72.005 31.131 125.387 84.5Q813 350 844 422.023q31 72.023 31 153.647 0 81.705-31.013 153.629-31.013 71.925-84.5 125.563Q706 908.5 633.827 939.75 561.655 971 479.595 971Z"/></svg>

Before

Width:  |  Height:  |  Size: 921 B

After

Width:  |  Height:  |  Size: 921 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M479.825 852q-12.325 0-20.325-8.375t-8-20.625V604.5h-219q-12.25 0-20.375-8.535T204 575.575q0-11.856 8.125-20.216Q220.25 547 232.5 547h219V328q0-11.675 8.175-20.088 8.176-8.412 20.5-8.412 12.325 0 20.575 8.412Q509 316.325 509 328v219h218.5q12.25 0 20.625 8.463t8.375 20.212q0 12.325-8.375 20.575T727.5 604.5H509V823q0 12.25-8.425 20.625-8.426 8.375-20.75 8.375Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M479.825 852q-12.325 0-20.325-8.375t-8-20.625V604.5h-219q-12.25 0-20.375-8.535T204 575.575q0-11.856 8.125-20.216Q220.25 547 232.5 547h219V328q0-11.675 8.175-20.088 8.176-8.412 20.5-8.412 12.325 0 20.575 8.412Q509 316.325 509 328v219h218.5q12.25 0 20.625 8.463t8.375 20.212q0 12.325-8.375 20.575T727.5 604.5H509V823q0 12.25-8.425 20.625-8.426 8.375-20.75 8.375Z"/></svg>

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 464 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M281 773.5q-84 0-140.25-56.534-56.25-56.533-56.25-140Q84.5 493.5 140.75 437 197 380.5 281 380.5h139q12.25 0 20.625 8.463T449 409.175q0 12.325-8.375 20.575T420 438H281q-60 0-99.5 39.5T142 577q0 60 39.5 99.5T281 716h139q12.25 0 20.625 8.463T449 744.675q0 12.325-8.375 20.575T420 773.5H281Zm73.5-168q-12.25 0-20.375-8.535T326 576.575q0-11.856 8.125-20.216Q342.25 548 354.5 548h248q12.25 0 20.625 8.463t8.375 20.212q0 12.325-8.375 20.575T602.5 605.5h-248Zm520.5-29h-57.5q0-60-39.792-99.5-39.791-39.5-99.208-39.5H539q-12.25 0-20.375-8.535t-8.125-20.39q0-11.856 8.125-20.216Q526.75 380 539 380h139.5q83.453 0 139.976 56.524Q875 493.047 875 576.5ZM726.825 893q-12.325 0-20.325-8.375t-8-20.625v-90H608q-12.25 0-20.375-8.535t-8.125-20.39q0-11.856 8.125-20.216Q595.75 716.5 608 716.5h90.5V626q0-11.675 8.175-20.088 8.176-8.412 20.5-8.412 12.325 0 20.575 8.412Q756 614.325 756 626v90.5h90q12.25 0 20.625 8.463T875 745.175q0 12.325-8.375 20.575T846 774h-90v90q0 12.25-8.425 20.625-8.426 8.375-20.75 8.375Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M281 773.5q-84 0-140.25-56.534-56.25-56.533-56.25-140Q84.5 493.5 140.75 437 197 380.5 281 380.5h139q12.25 0 20.625 8.463T449 409.175q0 12.325-8.375 20.575T420 438H281q-60 0-99.5 39.5T142 577q0 60 39.5 99.5T281 716h139q12.25 0 20.625 8.463T449 744.675q0 12.325-8.375 20.575T420 773.5H281Zm73.5-168q-12.25 0-20.375-8.535T326 576.575q0-11.856 8.125-20.216Q342.25 548 354.5 548h248q12.25 0 20.625 8.463t8.375 20.212q0 12.325-8.375 20.575T602.5 605.5h-248Zm520.5-29h-57.5q0-60-39.792-99.5-39.791-39.5-99.208-39.5H539q-12.25 0-20.375-8.535t-8.125-20.39q0-11.856 8.125-20.216Q526.75 380 539 380h139.5q83.453 0 139.976 56.524Q875 493.047 875 576.5ZM726.825 893q-12.325 0-20.325-8.375t-8-20.625v-90H608q-12.25 0-20.375-8.535t-8.125-20.39q0-11.856 8.125-20.216Q595.75 716.5 608 716.5h90.5V626q0-11.675 8.175-20.088 8.176-8.412 20.5-8.412 12.325 0 20.575 8.412Q756 614.325 756 626v90.5h90q12.25 0 20.625 8.463T875 745.175q0 12.325-8.375 20.575T846 774h-90v90q0 12.25-8.425 20.625-8.426 8.375-20.75 8.375Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M181.5 931q-22.969 0-40.234-17.266Q124 896.469 124 873.5V278q0-22.969 17.266-40.234Q158.531 220.5 181.5 220.5H561q12.25 0 20.625 8.463T590 249.175q0 12.325-8.375 20.575T561 278H181.5v595.5H777V495q0-12.25 8.425-20.625 8.426-8.375 20.5-8.375 12.075 0 20.325 8.375T834.5 495v378.5q0 22.969-17.266 40.234Q799.969 931 777 931H181.5Zm546.325-493.5q-12.325 0-20.575-8.375T699 408.5V357h-51.5q-12.25 0-20.625-8.425-8.375-8.426-8.375-20.5 0-12.075 8.375-20.325t20.625-8.25H699v-52q0-11.675 8.425-20.088 8.426-8.412 20.5-8.412 12.075 0 20.325 8.412 8.25 8.413 8.25 20.088v52h52q11.675 0 20.088 8.463Q837 316.426 837 328.175q0 12.325-8.412 20.575Q820.175 357 808.5 357h-52v51.5q0 12.25-8.463 20.625t-20.212 8.375ZM273 772.5h413.175q9.825 0 13.825-7.75T698 749L585.578 599.603q-4.703-6.103-11.463-6.103-6.759 0-11.615 6L448 749.5l-81.462-106.388q-4.692-5.612-11.5-5.612-6.807 0-11.719 5.584L261.574 749.02q-5.074 7.98-.949 15.73T273 772.5ZM181.5 495v378.5V278v217Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M181.5 931q-22.969 0-40.234-17.266Q124 896.469 124 873.5V278q0-22.969 17.266-40.234Q158.531 220.5 181.5 220.5H561q12.25 0 20.625 8.463T590 249.175q0 12.325-8.375 20.575T561 278H181.5v595.5H777V495q0-12.25 8.425-20.625 8.426-8.375 20.5-8.375 12.075 0 20.325 8.375T834.5 495v378.5q0 22.969-17.266 40.234Q799.969 931 777 931H181.5Zm546.325-493.5q-12.325 0-20.575-8.375T699 408.5V357h-51.5q-12.25 0-20.625-8.425-8.375-8.426-8.375-20.5 0-12.075 8.375-20.325t20.625-8.25H699v-52q0-11.675 8.425-20.088 8.426-8.412 20.5-8.412 12.075 0 20.325 8.412 8.25 8.413 8.25 20.088v52h52q11.675 0 20.088 8.463Q837 316.426 837 328.175q0 12.325-8.412 20.575Q820.175 357 808.5 357h-52v51.5q0 12.25-8.463 20.625t-20.212 8.375ZM273 772.5h413.175q9.825 0 13.825-7.75T698 749L585.578 599.603q-4.703-6.103-11.463-6.103-6.759 0-11.615 6L448 749.5l-81.462-106.388q-4.692-5.612-11.5-5.612-6.807 0-11.719 5.584L261.574 749.02q-5.074 7.98-.949 15.73T273 772.5ZM181.5 495v378.5V278v217Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480.33-85q-81.704 0-153.629-31.263t-125.563-85Q147.5-255 116.25-326.789 85-398.579 85-480.202q0-81.705 31.363-153.863 31.362-72.159 84.769-125.547Q254.539-813 326.79-844q72.25-31 153.135-31 46.834 0 90.763 10.514Q614.617-853.973 654-835q-3.5 8.5-4.75 17.083Q648-809.333 648-800q0 7.897.75 14.949Q649.5-778 652-771q-38-22.5-81.071-34.5t-91.031-12q-140.21 0-238.804 98.25T142.5-480.486q0 140.515 98.736 239.25 98.735 98.736 239.25 98.736 140.514 0 238.764-98.594T817.5-480q0-38.526-8.25-75.013T786-624q10.963 7.759 24.931 11.88Q824.9-608 840-608h6.75q3.393 0 6.75-.5 10.5 30.5 16 62.433t5.5 65.888q0 81.086-31.013 153.475t-84.5 125.697Q706-147.699 633.977-116.349 561.954-85 480.33-85Zm144.123-448.5q22.947 0 37.997-15.003 15.05-15.004 15.05-37.95 0-22.947-15.003-37.997-15.004-15.05-37.95-15.05-22.947 0-37.997 15.003-15.05 15.004-15.05 37.95 0 22.947 15.003 37.997 15.004 15.05 37.95 15.05Zm-289 0q22.947 0 37.997-15.003 15.05-15.004 15.05-37.95 0-22.947-15.003-37.997-15.004-15.05-37.95-15.05-22.947 0-37.997 15.003-15.05 15.004-15.05 37.95 0 22.947 15.003 37.997 15.004 15.05 37.95 15.05ZM480-264q64.5 0 119.25-34.5t79.75-95H281q26 60.5 80.25 95T480-264Zm0-216Zm331.5-291.5H760q-12.25 0-20.375-8.175-8.125-8.176-8.125-20.5 0-12.325 8.125-20.575T760-829h51.5v-51q0-12.25 8.175-20.625 8.176-8.375 20.5-8.375 12.325 0 20.575 8.375T869-880v51h51q12.25 0 20.625 8.425 8.375 8.426 8.375 20.75 0 12.325-8.375 20.325t-20.625 8h-51v51.5q0 12.25-8.425 20.375-8.426 8.125-20.75 8.125-12.325 0-20.325-8.125t-8-20.375v-51.5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22"><path d="M480.33-85q-81.704 0-153.629-31.263t-125.563-85Q147.5-255 116.25-326.789 85-398.579 85-480.202q0-81.705 31.363-153.863 31.362-72.159 84.769-125.547Q254.539-813 326.79-844q72.25-31 153.135-31 46.834 0 90.763 10.514Q614.617-853.973 654-835q-3.5 8.5-4.75 17.083Q648-809.333 648-800q0 7.897.75 14.949Q649.5-778 652-771q-38-22.5-81.071-34.5t-91.031-12q-140.21 0-238.804 98.25T142.5-480.486q0 140.515 98.736 239.25 98.735 98.736 239.25 98.736 140.514 0 238.764-98.594T817.5-480q0-38.526-8.25-75.013T786-624q10.963 7.759 24.931 11.88Q824.9-608 840-608h6.75q3.393 0 6.75-.5 10.5 30.5 16 62.433t5.5 65.888q0 81.086-31.013 153.475t-84.5 125.697Q706-147.699 633.977-116.349 561.954-85 480.33-85Zm144.123-448.5q22.947 0 37.997-15.003 15.05-15.004 15.05-37.95 0-22.947-15.003-37.997-15.004-15.05-37.95-15.05-22.947 0-37.997 15.003-15.05 15.004-15.05 37.95 0 22.947 15.003 37.997 15.004 15.05 37.95 15.05Zm-289 0q22.947 0 37.997-15.003 15.05-15.004 15.05-37.95 0-22.947-15.003-37.997-15.004-15.05-37.95-15.05-22.947 0-37.997 15.003-15.05 15.004-15.05 37.95 0 22.947 15.003 37.997 15.004 15.05 37.95 15.05ZM480-264q64.5 0 119.25-34.5t79.75-95H281q26 60.5 80.25 95T480-264Zm0-216Zm331.5-291.5H760q-12.25 0-20.375-8.175-8.125-8.176-8.125-20.5 0-12.325 8.125-20.575T760-829h51.5v-51q0-12.25 8.175-20.625 8.176-8.375 20.5-8.375 12.325 0 20.575 8.375T869-880v51h51q12.25 0 20.625 8.425 8.375 8.426 8.375 20.75 0 12.325-8.375 20.325t-20.625 8h-51v51.5q0 12.25-8.425 20.375-8.426 8.125-20.75 8.125-12.325 0-20.325-8.125t-8-20.375v-51.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M811.5-771.5H760q-11.5 0-20-8.175-8.5-8.176-8.5-20.5 0-12.325 8.125-20.575T760-829h51.5v-51q0-12.25 8.175-20.625 8.176-8.375 20.5-8.375 12.325 0 20.575 8.375T869-880v51h51q12.25 0 20.625 8.425 8.375 8.426 8.375 20.75 0 12.325-8.375 20.325t-20.625 8h-51v51.5q0 11.5-8.425 20-8.426 8.5-20.75 8.5-12.325 0-20.325-8.125t-8-20.375v-51.5ZM480.33-85q-81.704 0-153.629-31.263t-125.563-85Q147.5-255 116.25-326.789 85-398.579 85-480.202q0-81.705 31.363-153.863 31.362-72.159 84.769-125.547Q254.539-813 326.79-844q72.25-31 153.135-31 46.834 0 90.763 10.514Q614.617-853.973 654-835q-3.5 8.5-4.75 17.083-1.25 8.584-1.25 17.486 0 38.431 23.441 68.894 23.441 30.464 61.059 39.037 9.073 37.671 39.525 61.085Q802.477-608 840.275-608h6.475q3.393 0 6.75-.5 10.5 30.5 16 62.433t5.5 65.888q0 81.086-31.013 153.475t-84.5 125.697Q706-147.699 633.977-116.349 561.954-85 480.33-85Zm144.123-448.5q22.947 0 37.997-15.003 15.05-15.004 15.05-37.95 0-22.947-15.003-37.997-15.004-15.05-37.95-15.05-22.947 0-37.997 15.003-15.05 15.004-15.05 37.95 0 22.947 15.003 37.997 15.004 15.05 37.95 15.05Zm-289 0q22.947 0 37.997-15.003 15.05-15.004 15.05-37.95 0-22.947-15.003-37.997-15.004-15.05-37.95-15.05-22.947 0-37.997 15.003-15.05 15.004-15.05 37.95 0 22.947 15.003 37.997 15.004 15.05 37.95 15.05ZM480-264q64.5 0 119.25-34.5t79.75-95H281q26 60.5 80.25 95T480-264Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22"><path d="M811.5-771.5H760q-11.5 0-20-8.175-8.5-8.176-8.5-20.5 0-12.325 8.125-20.575T760-829h51.5v-51q0-12.25 8.175-20.625 8.176-8.375 20.5-8.375 12.325 0 20.575 8.375T869-880v51h51q12.25 0 20.625 8.425 8.375 8.426 8.375 20.75 0 12.325-8.375 20.325t-20.625 8h-51v51.5q0 11.5-8.425 20-8.426 8.5-20.75 8.5-12.325 0-20.325-8.125t-8-20.375v-51.5ZM480.33-85q-81.704 0-153.629-31.263t-125.563-85Q147.5-255 116.25-326.789 85-398.579 85-480.202q0-81.705 31.363-153.863 31.362-72.159 84.769-125.547Q254.539-813 326.79-844q72.25-31 153.135-31 46.834 0 90.763 10.514Q614.617-853.973 654-835q-3.5 8.5-4.75 17.083-1.25 8.584-1.25 17.486 0 38.431 23.441 68.894 23.441 30.464 61.059 39.037 9.073 37.671 39.525 61.085Q802.477-608 840.275-608h6.475q3.393 0 6.75-.5 10.5 30.5 16 62.433t5.5 65.888q0 81.086-31.013 153.475t-84.5 125.697Q706-147.699 633.977-116.349 561.954-85 480.33-85Zm144.123-448.5q22.947 0 37.997-15.003 15.05-15.004 15.05-37.95 0-22.947-15.003-37.997-15.004-15.05-37.95-15.05-22.947 0-37.997 15.003-15.05 15.004-15.05 37.95 0 22.947 15.003 37.997 15.004 15.05 37.95 15.05Zm-289 0q22.947 0 37.997-15.003 15.05-15.004 15.05-37.95 0-22.947-15.003-37.997-15.004-15.05-37.95-15.05-22.947 0-37.997 15.003-15.05 15.004-15.05 37.95 0 22.947 15.003 37.997 15.004 15.05 37.95 15.05ZM480-264q64.5 0 119.25-34.5t79.75-95H281q26 60.5 80.25 95T480-264Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M214.099 793Q125.5 793 65.25 728.425 5 663.85 5 574.179q0-89.088 60.754-152.384Q126.51 358.5 214.07 358.5q36.078 0 69.254 11.63T342.5 407l94.5 90.5-40 40.5-90.5-88q-18.5-18.5-42.516-26.25T214 416q-64.207 0-107.854 46.5Q62.5 509 62.5 574.146q0 65.733 43.247 113.543Q148.994 735.5 213.998 735.5q25.002 0 48.752-8 23.75-8 42.75-25l313.441-295.434Q644.5 382 677.608 370.25q33.108-11.75 67.681-11.75 88.939 0 149.575 63.255Q955.5 485.009 955.5 573.532q0 89.941-60.755 154.705Q833.991 793 745.43 793q-35.078 0-68.754-11.13T618 745.5l-92-91 40-40 88 88q17.5 17.5 41.75 25.25t49.818 7.75q65.179 0 108.805-48Q898 639.5 898 573.341q0-64.745-44.348-111.043Q809.303 416 745.518 416q-24.919 0-48.719 8.75Q673 433.5 655 451L341.559 746.434Q315.5 770.5 282.018 781.75 248.536 793 214.099 793Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M214.099 793Q125.5 793 65.25 728.425 5 663.85 5 574.179q0-89.088 60.754-152.384Q126.51 358.5 214.07 358.5q36.078 0 69.254 11.63T342.5 407l94.5 90.5-40 40.5-90.5-88q-18.5-18.5-42.516-26.25T214 416q-64.207 0-107.854 46.5Q62.5 509 62.5 574.146q0 65.733 43.247 113.543Q148.994 735.5 213.998 735.5q25.002 0 48.752-8 23.75-8 42.75-25l313.441-295.434Q644.5 382 677.608 370.25q33.108-11.75 67.681-11.75 88.939 0 149.575 63.255Q955.5 485.009 955.5 573.532q0 89.941-60.755 154.705Q833.991 793 745.43 793q-35.078 0-68.754-11.13T618 745.5l-92-91 40-40 88 88q17.5 17.5 41.75 25.25t49.818 7.75q65.179 0 108.805-48Q898 639.5 898 573.341q0-64.745-44.348-111.043Q809.303 416 745.518 416q-24.919 0-48.719 8.75Q673 433.5 655 451L341.559 746.434Q315.5 770.5 282.018 781.75 248.536 793 214.099 793Z"/></svg>

Before

Width:  |  Height:  |  Size: 881 B

After

Width:  |  Height:  |  Size: 881 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M626 944.5 277.5 596q-4.5-4.794-6.5-9.554-2-4.76-2-10.486 0-5.726 2-10.486 2-4.76 6.5-9.474l348.9-348.9q10.6-10.6 26.6-10.6t27 10.5q11 11.545 11 27.682 0 16.136-11.073 27.391L366 576l313.955 313.955Q692.5 902.5 691.5 917.75q-1 15.25-11.5 25.75-11.5 11.5-27.409 11.75-15.909.25-26.591-10.75Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M626 944.5 277.5 596q-4.5-4.794-6.5-9.554-2-4.76-2-10.486 0-5.726 2-10.486 2-4.76 6.5-9.474l348.9-348.9q10.6-10.6 26.6-10.6t27 10.5q11 11.545 11 27.682 0 16.136-11.073 27.391L366 576l313.955 313.955Q692.5 902.5 691.5 917.75q-1 15.25-11.5 25.75-11.5 11.5-27.409 11.75-15.909.25-26.591-10.75Z"/></svg>

Before

Width:  |  Height:  |  Size: 394 B

After

Width:  |  Height:  |  Size: 394 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M479.96 881q-4.804 0-10.232-2.045Q464.3 876.909 460 872L184 596q-8.5-8.4-8.5-19.95 0-11.55 8.5-20.05t20.341-8.5q11.841 0 20.188 8.5L451.5 782.5v-494q0-12.013 8.463-20.506 8.463-8.494 20.212-8.494 12.325 0 20.575 8.375T509 288.5v494l227-227q8.182-8 19.841-8T776 555.842q8.5 8.342 8.5 20t-8.587 20.245L500 872q-4.58 5-9.499 7-4.919 2-10.541 2Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M479.96 881q-4.804 0-10.232-2.045Q464.3 876.909 460 872L184 596q-8.5-8.4-8.5-19.95 0-11.55 8.5-20.05t20.341-8.5q11.841 0 20.188 8.5L451.5 782.5v-494q0-12.013 8.463-20.506 8.463-8.494 20.212-8.494 12.325 0 20.575 8.375T509 288.5v494l227-227q8.182-8 19.841-8T776 555.842q8.5 8.342 8.5 20t-8.587 20.245L500 872q-4.58 5-9.499 7-4.919 2-10.541 2Z"/></svg>

Before

Width:  |  Height:  |  Size: 445 B

After

Width:  |  Height:  |  Size: 445 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="22px" viewBox="0 -960 960 960" width="22px" fill="#5f6368"><path d="M480-362 284-557.5h392L480-362Z"/></svg>

After

Width:  |  Height:  |  Size: 156 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="22px" viewBox="0 -960 960 960" width="22px" fill="#5f6368"><path d="m284-402 196-197 196 197H284Z"/></svg>

After

Width:  |  Height:  |  Size: 154 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#5f6368"><path d="M629-446.5H235.48q-13.79 0-23.64-9.79-9.84-9.79-9.84-23.5t9.84-23.71q9.85-10 23.64-10H629L455.79-686.71Q445.5-697 445.25-710.5t10.25-24.48q10.5-10.52 24-10.27t23.81 10.57L734.1-503.59q4.9 4.91 7.65 10.97 2.75 6.06 2.75 12.78 0 6.71-2.75 12.78Q739-461 734.5-456.5l-231 231q-11 11-23.75 10.5t-23.25-11.02Q446-237 446-250.42q0-13.41 10.5-23.58L629-446.5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22" fill="#5f6368"><path d="M629-446.5H235.48q-13.79 0-23.64-9.79-9.84-9.79-9.84-23.5t9.84-23.71q9.85-10 23.64-10H629L455.79-686.71Q445.5-697 445.25-710.5t10.25-24.48q10.5-10.52 24-10.27t23.81 10.57L734.1-503.59q4.9 4.91 7.65 10.97 2.75 6.06 2.75 12.78 0 6.71-2.75 12.78Q739-461 734.5-456.5l-231 231q-11 11-23.75 10.5t-23.25-11.02Q446-237 446-250.42q0-13.41 10.5-23.58L629-446.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 472 B

After

Width:  |  Height:  |  Size: 472 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M278.5 941.451q-11-11.05-11.25-26.5Q267 899.5 278.5 888l314-314-314-314q-11-11-11.25-26.5t11.222-27q10.472-11.5 26.25-12t27.278 11L681 554q4.5 4.794 6.5 9.554 2 4.76 2 10.486 0 5.726-2 10.486-2 4.76-6.5 9.474L332 942.5q-11.009 11-26.755 10.75Q289.5 953 278.5 941.451Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M278.5 941.451q-11-11.05-11.25-26.5Q267 899.5 278.5 888l314-314-314-314q-11-11-11.25-26.5t11.222-27q10.472-11.5 26.25-12t27.278 11L681 554q4.5 4.794 6.5 9.554 2 4.76 2 10.486 0 5.726-2 10.486-2 4.76-6.5 9.474L332 942.5q-11.009 11-26.755 10.75Q289.5 953 278.5 941.451Z"/></svg>

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 371 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M479.825 892q-12.325 0-20.325-8.125t-8-20.375V369l-227 227q-8.833 9-20.417 9-11.583 0-20.083-8.853-8.5-8.853-8.5-20.414 0-11.562 8.5-20.233l275.956-275.956q4.427-4.68 9.891-6.612Q475.311 271 480.575 271q5.264 0 10.094 2 4.831 2 9.331 6.5l276 276q8.5 8.671 8.5 20.233 0 11.561-8.342 20.414-8.342 8.853-20 8.853T736 596L509 369v494.5q0 12.25-8.425 20.375-8.426 8.125-20.75 8.125Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M479.825 892q-12.325 0-20.325-8.125t-8-20.375V369l-227 227q-8.833 9-20.417 9-11.583 0-20.083-8.853-8.5-8.853-8.5-20.414 0-11.562 8.5-20.233l275.956-275.956q4.427-4.68 9.891-6.612Q475.311 271 480.575 271q5.264 0 10.094 2 4.831 2 9.331 6.5l276 276q8.5 8.671 8.5 20.233 0 11.561-8.342 20.414-8.342 8.853-20 8.853T736 596L509 369v494.5q0 12.25-8.425 20.375-8.426 8.125-20.75 8.125Z"/></svg>

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 481 B

@@ -1,4 +1,4 @@
<svg height="24" viewBox="0 -960 960 960" width="24" fill="#000000" xmlns="http://www.w3.org/2000/svg">
<svg height="22" viewBox="0 -960 960 960" width="22" fill="#000000" xmlns="http://www.w3.org/2000/svg">
<path d="M 831.5 -480 L 657 -656.5 C 651.667 -662.167 648.917 -668.833 648.75 -676.5 C 648.583 -684.167 651.333 -690.833 657 -696.5 C 662.667 -702.5 669.333 -705.5 677 -705.5 C 684.667 -705.5 691.5 -702.667 697.5 -697 L 894 -500.5 C 897 -497.167 899.25 -493.917 900.75 -490.75 C 902.25 -487.583 903 -484 903 -480 C 903 -476 902.25 -472.417 900.75 -469.25 C 899.25 -466.083 897 -463 894 -460 L 697.5 -263.5 C 691.5 -257.5 684.667 -254.583 677 -254.75 C 669.333 -254.917 662.667 -257.833 657 -263.5 C 651 -269.5 648.167 -276.25 648.5 -283.75 C 648.833 -291.25 651.667 -297.833 657 -303.5 L 831.5 -480 Z M 128.5 -480 L 303 -303.5 C 308.333 -297.833 311.083 -291.167 311.25 -283.5 C 311.417 -275.833 308.667 -269.167 303 -263.5 C 297.333 -257.5 290.667 -254.5 283 -254.5 C 275.333 -254.5 268.667 -257.5 263 -263.5 L 66.5 -460 C 63.167 -463 60.833 -466.083 59.5 -469.25 C 58.167 -472.417 57.5 -476 57.5 -480 C 57.5 -484 58.167 -487.583 59.5 -490.75 C 60.833 -493.917 63.167 -497.167 66.5 -500.5 L 263 -697 C 268.667 -702.667 275.333 -705.417 283 -705.25 C 290.667 -705.083 297.5 -702.167 303.5 -696.5 C 309.167 -690.5 311.833 -683.75 311.5 -676.25 C 311.167 -668.75 308.333 -662.167 303 -656.5 L 128.5 -480 Z" transform="matrix(0.9999999999999999, 0, 0, 0.9999999999999999, 0, 0)"/>
<rect x="123" y="-514" width="711.266" height="68" style="stroke: rgb(0, 0, 0);" transform="matrix(0.9999999999999999, 0, 0, 0.9999999999999999, 0, 0)"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M308.5 774H522q11.675 0 20.088-8.425 8.412-8.426 8.412-20.5 0-12.075-8.412-20.325-8.413-8.25-20.088-8.25H308.5q-12.25 0-20.625 8.463t-8.375 20.212q0 12.325 8.375 20.575T308.5 774Zm0-169.5H652q11.675 0 20.088-8.425 8.412-8.426 8.412-20.5 0-12.075-8.412-20.325Q663.675 547 652 547H308.5q-12.25 0-20.625 8.463t-8.375 20.212q0 12.325 8.375 20.575t20.625 8.25Zm0-169.5H652q11.675 0 20.088-8.425 8.412-8.426 8.412-20.5 0-12.075-8.412-20.325-8.413-8.25-20.088-8.25H308.5q-12.25 0-20.625 8.463t-8.375 20.212q0 12.325 8.375 20.575T308.5 435ZM182 931.5q-22.969 0-40.234-17.266Q124.5 896.969 124.5 874V278q0-22.969 17.266-40.234Q159.031 220.5 182 220.5h596q22.969 0 40.234 17.266Q835.5 255.031 835.5 278v596q0 22.969-17.266 40.234Q800.969 931.5 778 931.5H182Zm0-57.5h596V278H182v596Zm0 0V278v596Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M308.5 774H522q11.675 0 20.088-8.425 8.412-8.426 8.412-20.5 0-12.075-8.412-20.325-8.413-8.25-20.088-8.25H308.5q-12.25 0-20.625 8.463t-8.375 20.212q0 12.325 8.375 20.575T308.5 774Zm0-169.5H652q11.675 0 20.088-8.425 8.412-8.426 8.412-20.5 0-12.075-8.412-20.325Q663.675 547 652 547H308.5q-12.25 0-20.625 8.463t-8.375 20.212q0 12.325 8.375 20.575t20.625 8.25Zm0-169.5H652q11.675 0 20.088-8.425 8.412-8.426 8.412-20.5 0-12.075-8.412-20.325-8.413-8.25-20.088-8.25H308.5q-12.25 0-20.625 8.463t-8.375 20.212q0 12.325 8.375 20.575T308.5 435ZM182 931.5q-22.969 0-40.234-17.266Q124.5 896.969 124.5 874V278q0-22.969 17.266-40.234Q159.031 220.5 182 220.5h596q22.969 0 40.234 17.266Q835.5 255.031 835.5 278v596q0 22.969-17.266 40.234Q800.969 931.5 778 931.5H182Zm0-57.5h596V278H182v596Zm0 0V278v596Z"/></svg>

Before

Width:  |  Height:  |  Size: 889 B

After

Width:  |  Height:  |  Size: 889 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M460.058 975.5q-90.558 0-154.808-62.418Q241 850.664 241 760.739v-431.91Q241 265 286.147 220.5 331.294 176 395.25 176q64.75 0 109.5 44.75t44.75 109.115v395.221q0 37.494-25.898 63.954t-63.75 26.46q-37.852 0-63.602-28.21T370.5 720V346q0-7.5 5.75-13.25t13.5-5.75q7.75 0 13.5 5.75T409 346v377.069q0 22.431 14.75 38.181Q438.5 777 460 777t36.25-15.25Q511 746.5 511 725.039V329.103q0-48.103-33.796-81.353-33.796-33.25-81.75-33.25T313.5 247.624q-34 33.123-34 81.303v433.791q0 73.282 53.312 123.782t127.25 50.5Q535 937 587.75 885.706t52.75-125.344V346q0-7.5 5.75-13.25t13.5-5.75q7.75 0 13.5 5.75T679 346v413.5q0 90.164-64.192 153.082-64.193 62.918-154.75 62.918Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M460.058 975.5q-90.558 0-154.808-62.418Q241 850.664 241 760.739v-431.91Q241 265 286.147 220.5 331.294 176 395.25 176q64.75 0 109.5 44.75t44.75 109.115v395.221q0 37.494-25.898 63.954t-63.75 26.46q-37.852 0-63.602-28.21T370.5 720V346q0-7.5 5.75-13.25t13.5-5.75q7.75 0 13.5 5.75T409 346v377.069q0 22.431 14.75 38.181Q438.5 777 460 777t36.25-15.25Q511 746.5 511 725.039V329.103q0-48.103-33.796-81.353-33.796-33.25-81.75-33.25T313.5 247.624q-34 33.123-34 81.303v433.791q0 73.282 53.312 123.782t127.25 50.5Q535 937 587.75 885.706t52.75-125.344V346q0-7.5 5.75-13.25t13.5-5.75q7.75 0 13.5 5.75T679 346v413.5q0 90.164-64.192 153.082-64.193 62.918-154.75 62.918Z"/></svg>

Before

Width:  |  Height:  |  Size: 756 B

After

Width:  |  Height:  |  Size: 756 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M460.058-80.5q-90.558 0-154.808-62.418Q241-205.336 241-295.261v-431.91Q241-791 286.147-835.5 331.294-880 395.25-880q64.75 0 109.5 44.75t44.75 109.115v395.221q0 37.494-25.898 63.954t-63.75 26.46q-37.852 0-63.602-28.21T370.5-336v-374q0-7.5 5.75-13.25t13.5-5.75q7.75 0 13.5 5.75T409-710v377.069q0 22.431 14.75 38.181Q438.5-279 460-279t36.25-15.25Q511-309.5 511-330.961v-395.936q0-48.103-33.796-81.353-33.796-33.25-81.75-33.25T313.5-808.376q-34 33.123-34 81.303v433.791q0 73.282 53.312 123.782t127.25 50.5Q535-119 587.75-170.294q52.75-51.293 52.75-125.344V-710q0-7.5 5.75-13.25t13.5-5.75q7.75 0 13.5 5.75T679-710v413.5q0 90.164-64.192 153.082Q550.615-80.5 460.058-80.5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22"><path d="M460.058-80.5q-90.558 0-154.808-62.418Q241-205.336 241-295.261v-431.91Q241-791 286.147-835.5 331.294-880 395.25-880q64.75 0 109.5 44.75t44.75 109.115v395.221q0 37.494-25.898 63.954t-63.75 26.46q-37.852 0-63.602-28.21T370.5-336v-374q0-7.5 5.75-13.25t13.5-5.75q7.75 0 13.5 5.75T409-710v377.069q0 22.431 14.75 38.181Q438.5-279 460-279t36.25-15.25Q511-309.5 511-330.961v-395.936q0-48.103-33.796-81.353-33.796-33.25-81.75-33.25T313.5-808.376q-34 33.123-34 81.303v433.791q0 73.282 53.312 123.782t127.25 50.5Q535-119 587.75-170.294q52.75-51.293 52.75-125.344V-710q0-7.5 5.75-13.25t13.5-5.75q7.75 0 13.5 5.75T679-710v413.5q0 90.164-64.192 153.082Q550.615-80.5 460.058-80.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 771 B

After

Width:  |  Height:  |  Size: 771 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M609.824-771.5q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm0 660q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm160-520q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm0 380q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm60-190q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25ZM480-81.5q-82.481 0-155.275-31.304-72.794-31.305-126.706-85.219-53.913-53.915-85.216-126.711Q81.5-397.531 81.5-480.016q0-82.484 31.303-155.273 31.303-72.79 85.216-126.699 53.912-53.909 126.706-85.461Q397.519-879 480-879v60q-141.5 0-240 98.562Q141.5-621.875 141.5-480t98.312 240.188Q338.125-141.5 480-141.5v60Zm-.111-330q-28.389 0-48.389-20.078-20-20.078-20-48.422 0-5.938.75-12.441.75-6.503 3.25-11.808L336-584l40-40.5 81.023 79.5q4.477-2 22.977-4 28.344 0 48.672 20.361Q549-508.279 549-479.889q0 28.389-20.361 48.389-20.36 20-48.75 20Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22"><path d="M609.824-771.5q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm0 660q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm160-520q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm0 380q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm60-190q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25ZM480-81.5q-82.481 0-155.275-31.304-72.794-31.305-126.706-85.219-53.913-53.915-85.216-126.711Q81.5-397.531 81.5-480.016q0-82.484 31.303-155.273 31.303-72.79 85.216-126.699 53.912-53.909 126.706-85.461Q397.519-879 480-879v60q-141.5 0-240 98.562Q141.5-621.875 141.5-480t98.312 240.188Q338.125-141.5 480-141.5v60Zm-.111-330q-28.389 0-48.389-20.078-20-20.078-20-48.422 0-5.938.75-12.441.75-6.503 3.25-11.808L336-584l40-40.5 81.023 79.5q4.477-2 22.977-4 28.344 0 48.672 20.361Q549-508.279 549-479.889q0 28.389-20.361 48.389-20.36 20-48.75 20Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M504.5-45q-92 0-169.75-46T210-216.5l-147.5-247 19-19.5q15-15 36-16.5T156-489l129 93v-410.5q0-10.925 8.154-19.713 8.153-8.787 20.75-8.787 11.096 0 19.846 8.787 8.75 8.788 8.75 19.713v522l-185-134L258-250q37.5 68.5 103.318 108 65.817 39.5 143.182 39.5 112.792 0 192.896-78.104Q777.5-258.708 777.5-371v-395.688q0-10.812 8.154-19.562 8.153-8.75 20.75-8.75 11.096 0 19.846 8.787Q835-777.425 835-766.5V-371q0 136-96.832 231Q641.335-45 504.5-45Zm-55-446.5v-395q0-10.925 8.654-19.713 8.653-8.787 20.25-8.787 12.096 0 20.346 8.787Q507-897.425 507-886.5v395h-57.5Zm164.5 0v-355q0-10.925 8.154-19.713 8.153-8.787 20.75-8.787 11.096 0 19.846 8.787 8.75 8.788 8.75 19.713v355H614ZM468-297Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22"><path d="M504.5-45q-92 0-169.75-46T210-216.5l-147.5-247 19-19.5q15-15 36-16.5T156-489l129 93v-410.5q0-10.925 8.154-19.713 8.153-8.787 20.75-8.787 11.096 0 19.846 8.787 8.75 8.788 8.75 19.713v522l-185-134L258-250q37.5 68.5 103.318 108 65.817 39.5 143.182 39.5 112.792 0 192.896-78.104Q777.5-258.708 777.5-371v-395.688q0-10.812 8.154-19.562 8.153-8.75 20.75-8.75 11.096 0 19.846 8.787Q835-777.425 835-766.5V-371q0 136-96.832 231Q641.335-45 504.5-45Zm-55-446.5v-395q0-10.925 8.654-19.713 8.653-8.787 20.25-8.787 12.096 0 20.346 8.787Q507-897.425 507-886.5v395h-57.5Zm164.5 0v-355q0-10.925 8.154-19.713 8.153-8.787 20.75-8.787 11.096 0 19.846 8.787 8.75 8.788 8.75 19.713v355H614ZM468-297Z"/></svg>

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 782 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M428 706.25q9 8.75 20.75 8.75t20.718-8.937L559.25 616.5l90.782 90.563Q659 716 670.25 716t20.25-8.75q9-8.75 9-20.75t-8.937-20.937L600 576l89.563-90.063Q698.5 477.5 698.5 466q0-11.5-9-20.25T668.75 437q-11.75 0-20.687 8.437L559.5 535.5l-91.063-91.063Q459.5 436 448.25 436.5 437 437 428 445t-9 20.25q0 12.25 8.937 20.687L519 576l-91 89.563q-8 8.937-8 20.904 0 11.966 8 19.783ZM362.19 852q-22.19 0-40.31-11.5Q303.761 829 291.5 811L148.135 609.333Q137 595.5 137 576.634T148 542l143.5-201q12.333-18 30.386-29.5Q339.939 300 362 300h416q23.719 0 40.609 16.891Q835.5 333.781 835.5 357.5v437q0 23.719-16.891 40.609Q801.719 852 778 852H362.19ZM197 576l152.952 218.5H778v-437H350L197 576Zm581 0V357.5v437V576Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M428 706.25q9 8.75 20.75 8.75t20.718-8.937L559.25 616.5l90.782 90.563Q659 716 670.25 716t20.25-8.75q9-8.75 9-20.75t-8.937-20.937L600 576l89.563-90.063Q698.5 477.5 698.5 466q0-11.5-9-20.25T668.75 437q-11.75 0-20.687 8.437L559.5 535.5l-91.063-91.063Q459.5 436 448.25 436.5 437 437 428 445t-9 20.25q0 12.25 8.937 20.687L519 576l-91 89.563q-8 8.937-8 20.904 0 11.966 8 19.783ZM362.19 852q-22.19 0-40.31-11.5Q303.761 829 291.5 811L148.135 609.333Q137 595.5 137 576.634T148 542l143.5-201q12.333-18 30.386-29.5Q339.939 300 362 300h416q23.719 0 40.609 16.891Q835.5 333.781 835.5 357.5v437q0 23.719-16.891 40.609Q801.719 852 778 852H362.19ZM197 576l152.952 218.5H778v-437H350L197 576Zm581 0V357.5v437V576Z"/></svg>

Before

Width:  |  Height:  |  Size: 800 B

After

Width:  |  Height:  |  Size: 800 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M251.5 891.5q-84.552 0-145.526-60.935Q45 769.63 45 684.253 45 608 94.25 549.5q49.25-58.5 126.25-70 18.824-95.893 92.113-156.947Q385.901 261.5 482 261.5q111.657 0 186.829 80.414Q744 422.329 744 534v26q71.5-3 121.25 44.75T915 725.392q0 67.795-49.125 116.951Q816.75 891.5 749.5 891.5H509q-22.969 0-40.234-17.266Q451.5 856.969 451.5 834V575.5l-63.5 63q-9 9-20.25 8.5T348 637.5q-9-8.5-9-20.25t9-20.75l111.973-112.473q4.607-4.527 9.6-6.777 4.994-2.25 10.7-2.25 5.707 0 10.467 2.25 4.76 2.25 9.285 6.775l113.43 113.43Q622 606 622 617.5q0 11.5-8.5 20-9.111 9-20.806 9-11.694 0-20.194-9l-63.5-62V834h240.5q43.87 0 75.935-31.645Q857.5 770.71 857.5 725.75q0-44.75-31.819-76.42-31.819-31.671-76.556-31.671H686.5V534.5q0-88.378-60.036-151.939Q566.428 319 478.087 319q-88.342 0-148.892 63.561-60.549 63.561-60.549 151.939h-19.018q-61.628 0-104.378 43T102.5 684q0 61.5 43.679 105.75Q189.857 834 251.5 834H365q12.25 0 20.625 8.463T394 862.675q0 12.325-8.375 20.575T365 891.5H251.5Zm228.5-287Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M251.5 891.5q-84.552 0-145.526-60.935Q45 769.63 45 684.253 45 608 94.25 549.5q49.25-58.5 126.25-70 18.824-95.893 92.113-156.947Q385.901 261.5 482 261.5q111.657 0 186.829 80.414Q744 422.329 744 534v26q71.5-3 121.25 44.75T915 725.392q0 67.795-49.125 116.951Q816.75 891.5 749.5 891.5H509q-22.969 0-40.234-17.266Q451.5 856.969 451.5 834V575.5l-63.5 63q-9 9-20.25 8.5T348 637.5q-9-8.5-9-20.25t9-20.75l111.973-112.473q4.607-4.527 9.6-6.777 4.994-2.25 10.7-2.25 5.707 0 10.467 2.25 4.76 2.25 9.285 6.775l113.43 113.43Q622 606 622 617.5q0 11.5-8.5 20-9.111 9-20.806 9-11.694 0-20.194-9l-63.5-62V834h240.5q43.87 0 75.935-31.645Q857.5 770.71 857.5 725.75q0-44.75-31.819-76.42-31.819-31.671-76.556-31.671H686.5V534.5q0-88.378-60.036-151.939Q566.428 319 478.087 319q-88.342 0-148.892 63.561-60.549 63.561-60.549 151.939h-19.018q-61.628 0-104.378 43T102.5 684q0 61.5 43.679 105.75Q189.857 834 251.5 834H365q12.25 0 20.625 8.463T394 862.675q0 12.325-8.375 20.575T365 891.5H251.5Zm228.5-287Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M313.5 964.5q-12.475 0-20.487-8.013Q285 948.475 285 936V279.5q0-12.475 8.013-20.487Q301.025 251 313.5 251H402v-35q0-12.475 8.013-20.487 8.012-8.013 20.487-8.013H530q11.975 0 20.237 8.013Q558.5 203.525 558.5 216v35H647q11.975 0 20.237 8.013 8.263 8.012 8.263 20.487V936q0 12.475-8.263 20.487Q658.975 964.5 647 964.5H313.5Zm29-228.5H618V308.5H342.5V736Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M313.5 964.5q-12.475 0-20.487-8.013Q285 948.475 285 936V279.5q0-12.475 8.013-20.487Q301.025 251 313.5 251H402v-35q0-12.475 8.013-20.487 8.012-8.013 20.487-8.013H530q11.975 0 20.237 8.013Q558.5 203.525 558.5 216v35H647q11.975 0 20.237 8.013 8.263 8.012 8.263 20.487V936q0 12.475-8.263 20.487Q658.975 964.5 647 964.5H313.5Zm29-228.5H618V308.5H342.5V736Z"/></svg>

Before

Width:  |  Height:  |  Size: 455 B

After

Width:  |  Height:  |  Size: 455 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M313.5 964.5q-12.475 0-20.487-8.013Q285 948.475 285 936V279.5q0-12.475 8.013-20.487Q301.025 251 313.5 251H402v-35q0-12.475 8.013-20.487 8.012-8.013 20.487-8.013H530q11.975 0 20.237 8.013Q558.5 203.525 558.5 216v35H647q11.975 0 20.237 8.013 8.263 8.012 8.263 20.487V936q0 12.475-8.263 20.487Q658.975 964.5 647 964.5H313.5Zm29-314H618v-342H342.5v342Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 96 960 960" width="22"><path d="M313.5 964.5q-12.475 0-20.487-8.013Q285 948.475 285 936V279.5q0-12.475 8.013-20.487Q301.025 251 313.5 251H402v-35q0-12.475 8.013-20.487 8.012-8.013 20.487-8.013H530q11.975 0 20.237 8.013Q558.5 203.525 558.5 216v35H647q11.975 0 20.237 8.013 8.263 8.012 8.263 20.487V936q0 12.475-8.263 20.487Q658.975 964.5 647 964.5H313.5Zm29-314H618v-342H342.5v342Z"/></svg>

Before

Width:  |  Height:  |  Size: 452 B

After

Width:  |  Height:  |  Size: 452 B

Some files were not shown because too many files have changed in this diff Show More