Merge branch 'master' into dc/android-desktop-time-msg-grouping

This commit is contained in:
Diogo
2024-09-21 21:24:30 +01:00
17 changed files with 216 additions and 178 deletions
@@ -19,7 +19,6 @@ struct ChatItemForwardingView: View {
@Binding var composeState: ComposeState
@State private var searchText: String = ""
@FocusState private var searchFocused
@State private var alert: SomeAlert?
private let chatsToForwardTo = filterChatsToForwardTo(chats: ChatModel.shared.chats)
@@ -46,8 +45,6 @@ struct ChatItemForwardingView: View {
VStack(alignment: .leading) {
if !chatsToForwardTo.isEmpty {
List {
searchFieldView(text: $searchText, focussed: $searchFocused, theme.colors.onBackground, theme.colors.secondary)
.padding(.leading, 2)
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { foundChat($0, s) }
ForEach(chats) { chat in
@@ -55,6 +52,7 @@ struct ChatItemForwardingView: View {
.disabled(chatModel.deletedChats.contains(chat.chatInfo.id))
}
}
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
.modifier(ThemedBackground(grouped: true))
} else {
ZStack {
+92 -135
View File
@@ -14,11 +14,20 @@ struct UserPicker: View {
@Environment(\.colorScheme) private var colorScheme: ColorScheme
@Environment(\.dismiss) private var dismiss: DismissAction
@Binding var activeSheet: UserPickerSheet?
@State private var currentUser: Int64?
@State private var switchingProfile = false
@State private var frameWidth: CGFloat = 0
// Inset grouped list dimensions
private let imageSize: CGFloat = 44
private let rowPadding: CGFloat = 16
private let sectionSpacing: CGFloat = 35
private var sectionHorizontalPadding: CGFloat { frameWidth > 375 ? 20 : 16 }
private let sectionShape = RoundedRectangle(cornerRadius: 10, style: .continuous)
var body: some View {
if #available(iOS 16.0, *) {
let v = viewBody.presentationDetents([.height(420)])
let v = viewBody.presentationDetents([.height(400)])
if #available(iOS 16.4, *) {
v.scrollBounceBehavior(.basedOnSize)
} else {
@@ -28,88 +37,80 @@ struct UserPicker: View {
viewBody
}
}
@ViewBuilder
private var viewBody: some View {
let otherUsers = m.users.filter { u in !u.user.hidden && u.user.userId != m.currentUser?.userId }
return List {
Section(header: Text("You").foregroundColor(theme.colors.secondary)) {
if let user = m.currentUser {
openSheetOnTap(label: {
ZStack {
let v = ProfilePreview(profileOf: user)
.foregroundColor(.primary)
.padding(.leading, -8)
if #available(iOS 16.0, *) {
v
let otherUsers: [UserInfo] = m.users
.filter { u in !u.user.hidden && u.user.userId != m.currentUser?.userId }
.sorted(using: KeyPathComparator<UserInfo>(\.user.activeOrder, order: .reverse))
let sectionWidth = max(frameWidth - sectionHorizontalPadding * 2, 0)
let currentUserWidth = max(frameWidth - sectionHorizontalPadding - rowPadding * 2 - 14 - imageSize, 0)
VStack(spacing: 0) {
if let user = m.currentUser {
StickyScrollView {
HStack(spacing: rowPadding) {
HStack {
ProfileImage(imageStr: user.image, size: imageSize, color: Color(uiColor: .tertiarySystemGroupedBackground))
.padding(.trailing, 6)
profileName(user).lineLimit(1)
}
.padding(rowPadding)
.frame(width: otherUsers.isEmpty ? sectionWidth : currentUserWidth, alignment: .leading)
.background(Color(.secondarySystemGroupedBackground))
.clipShape(sectionShape)
.onTapGesture { activeSheet = .currentProfile }
ForEach(otherUsers) { u in
userView(u, size: imageSize)
.frame(maxWidth: sectionWidth * 0.618)
.fixedSize()
}
}
.padding(.horizontal, sectionHorizontalPadding)
}
.frame(height: 2 * rowPadding + imageSize)
.padding(.top, sectionSpacing)
.overlay(DetermineWidth())
.onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 }
}
List {
Section {
openSheetOnTap("qrcode", title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", sheet: .address)
openSheetOnTap("switch.2", title: "Chat preferences", sheet: .chatPreferences)
openSheetOnTap("person.crop.rectangle.stack", title: "Your chat profiles", sheet: .chatProfiles)
openSheetOnTap("desktopcomputer", title: "Use from desktop", sheet: .useFromDesktop)
ZStack(alignment: .trailing) {
openSheetOnTap("gearshape", title: "Settings", sheet: .settings)
Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill")
.resizable()
.symbolRenderingMode(.monochrome)
.foregroundColor(theme.colors.secondary)
.frame(maxWidth: 20, maxHeight: 20)
.onTapGesture {
if (colorScheme == .light) {
ThemeManager.applyTheme(systemDarkThemeDefault.get())
} else {
v.padding(.vertical, 4)
ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName)
}
}
}) {
activeSheet = .currentProfile
}
openSheetOnTap(title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", icon: "qrcode") {
activeSheet = .address
}
openSheetOnTap(title: "Chat preferences", icon: "switch.2") {
activeSheet = .chatPreferences
}
}
}
Section {
if otherUsers.isEmpty {
openSheetOnTap(title: "Your chat profiles", icon: "person.crop.rectangle.stack") {
activeSheet = .chatProfiles
}
} else {
let v = userPickerRow(otherUsers, size: 44)
.padding(.leading, -11)
if #available(iOS 16.0, *) {
v
} else {
v.padding(.vertical, 4)
}
}
openSheetOnTap(title: "Use from desktop", icon: "desktopcomputer") {
activeSheet = .useFromDesktop
}
ZStack(alignment: .trailing) {
openSheetOnTap(title: "Settings", icon: "gearshape") {
activeSheet = .settings
}
Label {} icon: {
Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill")
.resizable()
.symbolRenderingMode(.monochrome)
.foregroundColor(theme.colors.secondary)
.frame(maxWidth: 20, maxHeight: 20)
}
.onTapGesture {
if (colorScheme == .light) {
ThemeManager.applyTheme(systemDarkThemeDefault.get())
} else {
ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName)
.onLongPressGesture {
ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME)
}
}
.onLongPressGesture {
ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME)
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.onAppear {
// This check prevents the call of listUsers after the app is suspended, and the database is closed.
if case .active = scenePhase {
currentUser = m.currentUser?.userId
Task {
do {
let users = try await listUsersAsync()
await MainActor.run { m.users = users }
await MainActor.run {
m.users = users
currentUser = m.currentUser?.userId
}
} catch {
logger.error("Error loading users \(responseError(error))")
}
@@ -119,71 +120,34 @@ struct UserPicker: View {
.modifier(ThemedBackground(grouped: true))
.disabled(switchingProfile)
}
private func userPickerRow(_ users: [UserInfo], size: CGFloat) -> some View {
HStack(spacing: 6) {
let s = ScrollView(.horizontal) {
HStack(spacing: 27) {
ForEach(users) { u in
if !u.user.hidden && u.user.userId != m.currentUser?.userId {
userView(u, size: size)
}
}
}
.padding(.leading, 4)
.padding(.trailing, 22)
}
ZStack(alignment: .trailing) {
if #available(iOS 16.0, *) {
s.scrollIndicators(.hidden)
} else {
s
}
LinearGradient(
colors: [.clear, .black],
startPoint: .leading,
endPoint: .trailing
)
.frame(width: size, height: size + 3)
.blendMode(.destinationOut)
.allowsHitTesting(false)
}
.compositingGroup()
.padding(.top, -3) // to fit unread badge
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(theme.colors.secondary)
.padding(.trailing, 4)
.onTapGesture {
activeSheet = .chatProfiles
}
}
}
private func userView(_ u: UserInfo, size: CGFloat) -> some View {
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground))
.padding([.top, .trailing], 3)
if (u.unreadCount > 0) {
unreadBadge(u)
HStack {
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground))
if (u.unreadCount > 0) {
unreadBadge(u).offset(x: 4, y: -4)
}
}
.padding(.trailing, 6)
Text(u.user.displayName).font(.title2).lineLimit(1)
}
.frame(width: size)
.padding(rowPadding)
.background(Color(.secondarySystemGroupedBackground))
.clipShape(sectionShape)
.onTapGesture {
switchingProfile = true
dismiss()
Task {
do {
try await changeActiveUserAsync_(u.user.userId, viewPwd: nil)
await MainActor.run {
switchingProfile = false
dismiss()
}
await MainActor.run { switchingProfile = false }
} catch {
await MainActor.run {
switchingProfile = false
AlertManager.shared.showAlertMsg(
title: "Error switching profile!",
message: "Error: \(responseError(error))"
showAlert(
NSLocalizedString("Error switching profile!", comment: "alertTitle"),
message: String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "alert message"), responseError(error))
)
}
}
@@ -191,21 +155,14 @@ struct UserPicker: View {
}
}
private func openSheetOnTap(title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View {
openSheetOnTap(label: {
ZStack(alignment: .leading) {
Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center)
.symbolRenderingMode(.monochrome)
.foregroundColor(theme.colors.secondary)
Text(title)
.foregroundColor(.primary)
.padding(.leading, 36)
private func openSheetOnTap(_ icon: String, title: LocalizedStringKey, sheet: UserPickerSheet) -> some View {
Button {
activeSheet = sheet
} label: {
settingsRow(icon, color: theme.colors.secondary) {
Text(title).foregroundColor(.primary)
}
}, action: action)
}
private func openSheetOnTap<V: View>(label: () -> V, action: @escaping () -> Void) -> some View {
Button(action: action, label: label)
}
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
}
@@ -0,0 +1,52 @@
//
// StickyScrollView.swift
// SimpleX (iOS)
//
// Created by user on 20/09/2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct StickyScrollView<Content: View>: UIViewRepresentable {
@ViewBuilder let content: () -> Content
func makeUIView(context: Context) -> UIScrollView {
let hc = context.coordinator.hostingController
hc.view.backgroundColor = .clear
let sv = UIScrollView()
sv.showsHorizontalScrollIndicator = false
sv.addSubview(hc.view)
sv.delegate = context.coordinator
return sv
}
func updateUIView(_ scrollView: UIScrollView, context: Context) {
let hc = context.coordinator.hostingController
hc.rootView = content()
hc.view.frame.size = hc.view.intrinsicContentSize
scrollView.contentSize = hc.view.intrinsicContentSize
}
func makeCoordinator() -> Coordinator {
Coordinator(content: content())
}
class Coordinator: NSObject, UIScrollViewDelegate {
let hostingController: UIHostingController<Content>
init(content: Content) {
self.hostingController = UIHostingController(rootView: content)
}
func scrollViewWillEndDragging(
_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>
) {
if targetContentOffset.pointee.x < 64 {
targetContentOffset.pointee.x = 0
}
}
}
}
@@ -507,18 +507,18 @@ struct ProfilePreview: View {
HStack {
ProfileImage(imageStr: profileOf.image, size: 44, color: color)
.padding(.trailing, 6)
profileName().lineLimit(1)
profileName(profileOf).lineLimit(1)
}
}
private func profileName() -> Text {
var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2)
if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName {
t = t + Text(" (" + profileOf.fullName + ")")
}
func profileName(_ profileOf: NamedChat) -> Text {
var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2)
if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName {
t = t + Text(" (" + profileOf.fullName + ")")
// .font(.callout)
}
return t
}
}
return t
}
struct SettingsView_Previews: PreviewProvider {
@@ -204,6 +204,7 @@
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; };
CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; };
CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; };
CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */; };
CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; };
CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; };
CEE723B12C3BD3D70009AE93 /* SimpleX SE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -543,6 +544,7 @@
CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = "<group>"; };
CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = "<group>"; };
CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = "<group>"; };
CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyScrollView.swift; sourceTree = "<group>"; };
CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = "<group>"; };
CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
@@ -796,6 +798,7 @@
CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */,
CE7548092C622630009579B7 /* SwipeLabel.swift */,
CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */,
CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */,
);
path = Helpers;
sourceTree = "<group>";
@@ -1496,6 +1499,7 @@
5C93293F2928E0FD0090FFF9 /* AudioRecPlay.swift in Sources */,
5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */,
5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */,
CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */,
5C9CC7A928C532AB00BEF955 /* DatabaseErrorView.swift in Sources */,
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */,
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */,
+2
View File
@@ -17,6 +17,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable {
public var profile: LocalProfile
public var fullPreferences: FullPreferences
public var activeUser: Bool
public var activeOrder: Int64
public var displayName: String { get { profile.displayName } }
public var fullName: String { get { profile.fullName } }
@@ -49,6 +50,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable {
profile: LocalProfile.sampleData,
fullPreferences: FullPreferences.sampleData,
activeUser: true,
activeOrder: 0,
showNtfs: true,
sendRcptsContacts: true,
sendRcptsSmallGroups: false
@@ -806,6 +806,7 @@ data class User(
val profile: LocalProfile,
val fullPreferences: FullChatPreferences,
override val activeUser: Boolean,
val activeOrder: Long,
override val showNtfs: Boolean,
val sendRcptsContacts: Boolean,
val sendRcptsSmallGroups: Boolean,
@@ -833,6 +834,7 @@ data class User(
profile = LocalProfile.sampleData,
fullPreferences = FullChatPreferences.sampleData,
activeUser = true,
activeOrder = 0,
showNtfs = true,
sendRcptsContacts = true,
sendRcptsSmallGroups = false,
+1
View File
@@ -147,6 +147,7 @@ library
Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays
Simplex.Chat.Migrations.M20240528_quota_err_counter
Simplex.Chat.Migrations.M20240827_calls_uuid
Simplex.Chat.Migrations.M20240920_user_order
Simplex.Chat.Mobile
Simplex.Chat.Mobile.File
Simplex.Chat.Mobile.Shared
+1 -2
View File
@@ -593,8 +593,7 @@ processChatCommand' vr = \case
user_ <- chatReadVar currentUser
user' <- privateGetUser userId'
validateUserPassword_ user_ user' viewPwd_
withFastStore' (`setActiveUser` userId')
let user'' = user' {activeUser = True}
user'' <- withFastStore' (`setActiveUser` user')
chatWriteVar currentUser $ Just user''
pure $ CRActiveUser user''
SetActiveUser uName viewPwd_ -> do
+4 -7
View File
@@ -74,9 +74,7 @@ getSelectActiveUser st = do
selectUser :: [User] -> IO (Maybe User)
selectUser = \case
[] -> pure Nothing
[user@User {userId}] -> do
withTransaction st (`setActiveUser` userId)
pure $ Just user
[user] -> Just <$> withTransaction st (`setActiveUser` user)
users -> do
putStrLn "Select user profile:"
forM_ (zip [1 :: Int ..] users) $ \(n, user) -> putStrLn $ show n <> ": " <> userStr user
@@ -88,10 +86,9 @@ getSelectActiveUser st = do
Nothing -> putStrLn "not a number" >> loop
Just n
| n <= 0 || n > length users -> putStrLn "invalid user number" >> loop
| otherwise -> do
let user@User {userId} = users !! (n - 1)
withTransaction st (`setActiveUser` userId)
pure $ Just user
| otherwise ->
let user = users !! (n - 1)
in Just <$> withTransaction st (`setActiveUser` user)
createActiveUser :: ChatController -> IO User
createActiveUser cc = do
@@ -0,0 +1,18 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20240920_user_order where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20240920_user_order :: Query
m20240920_user_order =
[sql|
ALTER TABLE users ADD COLUMN active_order INTEGER NOT NULL DEFAULT 0;
|]
down_m20240920_user_order :: Query
down_m20240920_user_order =
[sql|
ALTER TABLE users DROP COLUMN active_order;
|]
+2 -1
View File
@@ -35,7 +35,8 @@ CREATE TABLE users(
send_rcpts_contacts INTEGER NOT NULL DEFAULT 0,
send_rcpts_small_groups INTEGER NOT NULL DEFAULT 0,
user_member_profile_updated_at TEXT,
ui_themes TEXT, -- 1 for active user
ui_themes TEXT,
active_order INTEGER NOT NULL DEFAULT 0, -- 1 for active user
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE RESTRICT
+3 -1
View File
@@ -111,6 +111,7 @@ import Simplex.Chat.Migrations.M20240510_chat_items_via_proxy
import Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays
import Simplex.Chat.Migrations.M20240528_quota_err_counter
import Simplex.Chat.Migrations.M20240827_calls_uuid
import Simplex.Chat.Migrations.M20240920_user_order
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -221,7 +222,8 @@ schemaMigrations =
("20240510_chat_items_via_proxy", m20240510_chat_items_via_proxy, Just down_m20240510_chat_items_via_proxy),
("20240515_rcv_files_user_approved_relays", m20240515_rcv_files_user_approved_relays, Just down_m20240515_rcv_files_user_approved_relays),
("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter),
("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid)
("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid),
("20240920_user_order", m20240920_user_order, Just down_m20240920_user_order)
]
-- | The list of migrations in ascending order by date
+15 -11
View File
@@ -19,7 +19,6 @@ module Simplex.Chat.Store.Profiles
getUsersInfo,
getUsers,
setActiveUser,
getSetActiveUser,
getUser,
getUserIdByName,
getUserByAConnId,
@@ -106,10 +105,11 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image,
let showNtfs = True
sendRcptsContacts = True
sendRcptsSmallGroups = True
order <- getNextActiveOrder db
DB.execute
db
"INSERT INTO users (agent_user_id, local_display_name, active_user, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,0,?,?,?,?,?)"
(auId, displayName, activeUser, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, currentTs, currentTs)
"INSERT INTO users (agent_user_id, local_display_name, active_user, active_order, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,?,0,?,?,?,?,?)"
(auId, displayName, activeUser, order, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, currentTs, currentTs)
userId <- insertedRowId db
DB.execute
db
@@ -126,7 +126,7 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image,
(profileId, displayName, userId, True, currentTs, currentTs, currentTs)
contactId <- insertedRowId db
DB.execute db "UPDATE users SET contact_id = ? WHERE user_id = ?" (contactId, userId)
pure $ toUser $ (userId, auId, contactId, profileId, activeUser, displayName, fullName, image, Nothing, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing)
pure $ toUser $ (userId, auId, contactId, profileId, activeUser, order, displayName, fullName, image, Nothing, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing)
getUsersInfo :: DB.Connection -> IO [UserInfo]
getUsersInfo db = getUsers db >>= mapM getUserInfo
@@ -161,15 +161,19 @@ getUsers :: DB.Connection -> IO [User]
getUsers db =
map toUser <$> DB.query_ db userQuery
setActiveUser :: DB.Connection -> UserId -> IO ()
setActiveUser db userId = do
setActiveUser :: DB.Connection -> User -> IO User
setActiveUser db user@User {userId} = do
DB.execute_ db "UPDATE users SET active_user = 0"
DB.execute db "UPDATE users SET active_user = 1 WHERE user_id = ?" (Only userId)
activeOrder <- getNextActiveOrder db
DB.execute db "UPDATE users SET active_user = 1, active_order = ? WHERE user_id = ?" (activeOrder, userId)
pure user {activeUser = True, activeOrder}
getSetActiveUser :: DB.Connection -> UserId -> ExceptT StoreError IO User
getSetActiveUser db userId = do
liftIO $ setActiveUser db userId
getUser db userId
getNextActiveOrder :: DB.Connection -> IO Int64
getNextActiveOrder db = do
order <- fromMaybe 0 . join <$> maybeFirstRow fromOnly (DB.query_ db "SELECT max(active_order) FROM users")
if order == maxBound
then 0 <$ DB.execute db "UPDATE users SET active_order = active_order - ?" (Only (maxBound :: Int64))
else pure $ order + 1
getUser :: DB.Connection -> UserId -> ExceptT StoreError IO User
getUser db userId =
+4 -4
View File
@@ -422,16 +422,16 @@ toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userCo
userQuery :: Query
userQuery =
[sql|
SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences,
SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences,
u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes
FROM users u
JOIN contacts uct ON uct.contact_id = u.contact_id
JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id
|]
toUser :: (UserId, UserId, ContactId, ProfileId, Bool, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (Bool, Bool, Bool, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User
toUser ((userId, auId, userContactId, profileId, activeUser, displayName, fullName, image, contactLink, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) =
User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes}
toUser :: (UserId, UserId, ContactId, ProfileId, Bool, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (Bool, Bool, Bool, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User
toUser ((userId, auId, userContactId, profileId, activeUser, activeOrder, displayName, fullName, image, contactLink, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) =
User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, activeOrder, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes}
where
profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences = userPreferences, localAlias = ""}
fullPreferences = mergePreferences Nothing userPreferences
+1
View File
@@ -113,6 +113,7 @@ data User = User
profile :: LocalProfile,
fullPreferences :: FullPreferences,
activeUser :: Bool,
activeOrder :: Int64,
viewPwdHash :: Maybe UserPwdHash,
showNtfs :: Bool,
sendRcptsContacts :: Bool,
+5 -5
View File
@@ -94,10 +94,10 @@ activeUserExists =
#endif
activeUserExistsSwift :: LB.ByteString
activeUserExistsSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}}}"
activeUserExistsSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}}}"
activeUserExistsTagged :: LB.ByteString
activeUserExistsTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}"
activeUserExistsTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}"
activeUser :: LB.ByteString
activeUser =
@@ -108,10 +108,10 @@ activeUser =
#endif
activeUserSwift :: LB.ByteString
activeUserSwift = "{\"resp\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}"
activeUserSwift = "{\"resp\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}"
activeUserTagged :: LB.ByteString
activeUserTagged = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}"
activeUserTagged = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}"
chatStarted :: LB.ByteString
chatStarted =
@@ -184,7 +184,7 @@ pendingSubSummaryTagged :: LB.ByteString
pendingSubSummaryTagged = "{\"resp\":{\"type\":\"pendingSubSummary\",\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}"
userJSON :: LB.ByteString
userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}"
userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}"
parsedMarkdown :: LB.ByteString
parsedMarkdown =