ios: menu to switch active user profile (#1758)
* ios: User chooser UI * Change * Changes * update view * fix layout/refactor * fix preview * wider menu, update label * hide Your profiles button * Clickable background that hides userChooser * No click listener * Better animation * Disabled scrolling for small number of items * Separated scrollview and buttons * No transition * Re-indent * Limiting width by the longest label * UserManagerView * Adapted API * Hide user chooser after selection * Top counter, users refactor * Padding * use VStack to fix layout bug * eol * rename: rename to getUserChatData * update layout * s/semibold/medium * remove SettingsButton view * rename Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a668bd5736
commit
153f80fe64
@@ -16,6 +16,7 @@ final class ChatModel: ObservableObject {
|
||||
@Published var onboardingStage: OnboardingStage?
|
||||
@Published var v3DBMigration: V3DBMigrationState = v3DBMigrationDefault.get()
|
||||
@Published var currentUser: User?
|
||||
@Published var users: [UserInfo] = []
|
||||
@Published var chatInitialized = false
|
||||
@Published var chatRunning: Bool?
|
||||
@Published var chatDbChanged = false
|
||||
|
||||
@@ -131,6 +131,24 @@ func apiCreateActiveUser(_ p: Profile) throws -> User {
|
||||
throw r
|
||||
}
|
||||
|
||||
func listUsers() -> [UserInfo] {
|
||||
let r = chatSendCmdSync(.listUsers)
|
||||
if case let .usersList(users) = r { return users }
|
||||
return []
|
||||
}
|
||||
|
||||
func apiSetActiveUser(_ userId: Int64) throws -> User {
|
||||
let r = chatSendCmdSync(.apiSetActiveUser(userId: userId))
|
||||
if case let .activeUser(user) = r { return user }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiDeleteUser(_ userId: Int64) throws {
|
||||
let r = chatSendCmdSync(.apiDeleteUser(userId: userId))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiStartChat() throws -> Bool {
|
||||
let r = chatSendCmdSync(.startChat(subscribe: true, expire: true))
|
||||
switch r {
|
||||
@@ -900,11 +918,8 @@ func startChat() throws {
|
||||
try setNetworkConfig(getNetCfg())
|
||||
let justStarted = try apiStartChat()
|
||||
if justStarted {
|
||||
m.userAddress = try apiGetUserAddress()
|
||||
(m.userSMPServers, m.presetSMPServers) = try getUserSMPServers()
|
||||
m.chatItemTTL = try getChatItemTTL()
|
||||
let chats = try apiGetChats()
|
||||
m.chats = chats.map { Chat.init($0) }
|
||||
try getUserChatData(m)
|
||||
m.users = listUsers()
|
||||
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCount())
|
||||
try refreshCallInvitations()
|
||||
(m.savedToken, m.tokenStatus, m.notificationMode) = apiGetNtfToken()
|
||||
@@ -922,6 +937,14 @@ func startChat() throws {
|
||||
chatLastStartGroupDefault.set(Date.now)
|
||||
}
|
||||
|
||||
func getUserChatData(_ m: ChatModel) throws {
|
||||
m.userAddress = try apiGetUserAddress()
|
||||
(m.userSMPServers, m.presetSMPServers) = try getUserSMPServers()
|
||||
m.chatItemTTL = try getChatItemTTL()
|
||||
let chats = try apiGetChats()
|
||||
m.chats = chats.map { Chat.init($0) }
|
||||
}
|
||||
|
||||
class ChatReceiver {
|
||||
private var receiveLoop: Task<Void, Never>?
|
||||
private var receiveMessages = true
|
||||
|
||||
@@ -11,25 +11,46 @@ import SimpleXChat
|
||||
|
||||
struct ChatListView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
// not really used in this view
|
||||
@State private var showSettings = false
|
||||
@State private var searchText = ""
|
||||
@State private var showAddChat = false
|
||||
@State var userPickerVisible = false
|
||||
@State var selectedView: Int? = nil
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
if chatModel.chats.isEmpty {
|
||||
onboardingButtons()
|
||||
}
|
||||
if chatModel.chats.count > 8 {
|
||||
chatList.searchable(text: $searchText)
|
||||
} else {
|
||||
chatList
|
||||
ZStack(alignment: .topLeading) {
|
||||
NavigationView {
|
||||
VStack {
|
||||
if chatModel.chats.isEmpty {
|
||||
onboardingButtons()
|
||||
}
|
||||
if chatModel.chats.count > 8 {
|
||||
chatList.searchable(text: $searchText)
|
||||
} else {
|
||||
chatList
|
||||
}
|
||||
NavigationLink(destination: UserProfilesView().navigationTitle("Profiles"), tag: 0, selection: $selectedView) {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
if userPickerVisible {
|
||||
Rectangle().fill(.white.opacity(0.001)).onTapGesture {
|
||||
withAnimation {
|
||||
userPickerVisible.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
UserPicker(
|
||||
showSettings: $showSettings,
|
||||
userPickerVisible: $userPickerVisible,
|
||||
manageUsers: {
|
||||
selectedView = 0
|
||||
userPickerVisible = false
|
||||
}
|
||||
)
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
}
|
||||
|
||||
var chatList: some View {
|
||||
@@ -48,13 +69,35 @@ struct ChatListView: View {
|
||||
}
|
||||
.onChange(of: chatModel.appOpenUrl) { _ in connectViaUrl() }
|
||||
.onAppear() { connectViaUrl() }
|
||||
.onDisappear() { withAnimation { userPickerVisible = false } }
|
||||
.offset(x: -8)
|
||||
.listStyle(.plain)
|
||||
.navigationTitle("Your chats")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
SettingsButton()
|
||||
Button {
|
||||
withAnimation {
|
||||
userPickerVisible.toggle()
|
||||
}
|
||||
} label: {
|
||||
let user = chatModel.currentUser ?? User.sampleData
|
||||
let color = Color(uiColor: .tertiarySystemGroupedBackground)
|
||||
HStack(spacing: 0) {
|
||||
ProfileImage(imageStr: user.image, color: color)
|
||||
.frame(width: 32, height: 32)
|
||||
.padding(.trailing, 6)
|
||||
.padding(.vertical, 6)
|
||||
let unread = chatModel.users
|
||||
.filter { !$0.user.activeUser }
|
||||
.reduce(0, {cnt, u in cnt + u.unreadCount})
|
||||
if unread > 0 {
|
||||
unreadCounter(unread)
|
||||
.padding(.leading, -12)
|
||||
.padding(.top, -16)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .principal) {
|
||||
if (chatModel.incognito) {
|
||||
@@ -75,6 +118,9 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showSettings) {
|
||||
SettingsView(showSettings: $showSettings)
|
||||
}
|
||||
.background(
|
||||
NavigationLink(
|
||||
destination: chatView(),
|
||||
|
||||
181
apps/ios/Shared/Views/ChatList/UserPicker.swift
Normal file
181
apps/ios/Shared/Views/ChatList/UserPicker.swift
Normal file
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// Created by Avently on 16.01.2023.
|
||||
// Copyright (c) 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
private let fillColorDark = Color(uiColor: UIColor(red: 0.11, green: 0.11, blue: 0.11, alpha: 255))
|
||||
private let fillColorLight = Color(uiColor: UIColor(red: 0.99, green: 0.99, blue: 0.99, alpha: 255))
|
||||
|
||||
struct UserPicker: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Binding var showSettings: Bool
|
||||
@Binding var userPickerVisible: Bool
|
||||
var manageUsers: () -> Void = {}
|
||||
@State var scrollViewContentSize: CGSize = .zero
|
||||
@State var disableScrolling: Bool = true
|
||||
private let menuButtonHeight: CGFloat = 68
|
||||
@State var chatViewNameWidth: CGFloat = 0
|
||||
|
||||
var fillColor: Color {
|
||||
colorScheme == .dark ? fillColorDark : fillColorLight
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Spacer().frame(height: 1)
|
||||
VStack(spacing: 0) {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
ForEach(Array(chatModel.users.enumerated()), id: \.0) { i, userInfo in
|
||||
Button(action: {
|
||||
if !userInfo.user.activeUser {
|
||||
changeActiveUser(toUser: userInfo)
|
||||
}
|
||||
}, label: {
|
||||
HStack(spacing: 0) {
|
||||
ProfileImage(imageStr: userInfo.user.image)
|
||||
.frame(width: 44, height: 44)
|
||||
.padding(.trailing, 12)
|
||||
Text(userInfo.user.chatViewName)
|
||||
.fontWeight(i == 0 ? .medium : .regular)
|
||||
.foregroundColor(.primary)
|
||||
.overlay(DetermineWidth())
|
||||
Spacer()
|
||||
if i == 0 {
|
||||
Image(systemName: "checkmark")
|
||||
} else if userInfo.unreadCount > 0 {
|
||||
unreadCounter(userInfo.unreadCount)
|
||||
}
|
||||
}
|
||||
.padding(.trailing)
|
||||
.padding([.leading, .vertical], 12)
|
||||
})
|
||||
.buttonStyle(PressedButtonStyle(defaultColor: fillColor, pressedColor: Color(uiColor: .secondarySystemFill)))
|
||||
if i < chatModel.users.count - 1 {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlay {
|
||||
GeometryReader { geo -> Color in
|
||||
DispatchQueue.main.async {
|
||||
scrollViewContentSize = geo.size
|
||||
let layoutFrame = UIApplication.shared.windows[0].safeAreaLayoutGuide.layoutFrame
|
||||
disableScrolling = scrollViewContentSize.height + menuButtonHeight * 2 + 10 < layoutFrame.height
|
||||
}
|
||||
return Color.clear
|
||||
}
|
||||
}
|
||||
}
|
||||
.simultaneousGesture(DragGesture(minimumDistance: disableScrolling ? 0 : 10000000))
|
||||
.frame(maxHeight: scrollViewContentSize.height)
|
||||
|
||||
Divider()
|
||||
menuButton("Your user profiles", icon: "pencil") {
|
||||
manageUsers()
|
||||
}
|
||||
Divider()
|
||||
menuButton("Settings", icon: "gearshape") {
|
||||
showSettings = true
|
||||
withAnimation {
|
||||
userPickerVisible.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||
.background(
|
||||
Rectangle()
|
||||
.fill(fillColor)
|
||||
.cornerRadius(16)
|
||||
.shadow(color: .black.opacity(0.12), radius: 24, x: 0, y: 0)
|
||||
)
|
||||
.onPreferenceChange(DetermineWidth.Key.self) { chatViewNameWidth = $0 }
|
||||
.frame(maxWidth: chatViewNameWidth > 0 ? min(300, chatViewNameWidth + 130) : 300)
|
||||
.padding(8)
|
||||
.onChange(of: [chatModel.currentUser?.chatViewName, chatModel.currentUser?.image] ) { _ in
|
||||
reloadCurrentUser()
|
||||
}
|
||||
.opacity(userPickerVisible ? 1.0 : 0.0)
|
||||
.onAppear {
|
||||
reloadUsers()
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadCurrentUser() {
|
||||
if let updatedUser = chatModel.currentUser, let index = chatModel.users.firstIndex(where: { $0.user.userId == updatedUser.userId }) {
|
||||
let removed = chatModel.users.remove(at: index)
|
||||
chatModel.users.insert(UserInfo(user: updatedUser, unreadCount: removed.unreadCount), at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func changeActiveUser(toUser: UserInfo) {
|
||||
Task {
|
||||
do {
|
||||
let activeUser = try apiSetActiveUser(toUser.user.userId)
|
||||
let oldActiveIndex = chatModel.users.firstIndex(where: { $0.user.userId == chatModel.currentUser?.userId })!
|
||||
var oldActive = chatModel.users[oldActiveIndex]
|
||||
oldActive.user.activeUser = false
|
||||
chatModel.users[oldActiveIndex] = oldActive
|
||||
|
||||
chatModel.currentUser = activeUser
|
||||
let currentActiveIndex = chatModel.users.firstIndex(where: { $0.user.userId == activeUser.userId })!
|
||||
let removed = chatModel.users.remove(at: currentActiveIndex)
|
||||
chatModel.users.insert(UserInfo(user: activeUser, unreadCount: removed.unreadCount), at: 0)
|
||||
chatModel.users = chatModel.users.map { $0 }
|
||||
try getUserChatData(chatModel)
|
||||
userPickerVisible = false
|
||||
} catch {
|
||||
logger.error("Unable to set active user: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadUsers() {
|
||||
Task {
|
||||
chatModel.users = listUsers().sorted { one, two -> Bool in one.user.activeUser }
|
||||
}
|
||||
}
|
||||
|
||||
private func menuButton(_ title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 0) {
|
||||
Text(title)
|
||||
.overlay(DetermineWidth())
|
||||
Spacer()
|
||||
Image(systemName: icon)
|
||||
// .frame(width: 24, alignment: .center)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 22)
|
||||
.frame(height: menuButtonHeight)
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle(defaultColor: fillColor, pressedColor: Color(uiColor: .secondarySystemFill)))
|
||||
}
|
||||
}
|
||||
|
||||
func unreadCounter(_ unread: Int64) -> some View {
|
||||
unreadCountText(Int(truncatingIfNeeded: unread))
|
||||
.font(.caption)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 4)
|
||||
.frame(minWidth: 18, minHeight: 18)
|
||||
.background(Color.accentColor)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
|
||||
struct UserPicker_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let m = ChatModel()
|
||||
m.users = [UserInfo.sampleData, UserInfo.sampleData]
|
||||
return UserPicker(
|
||||
showSettings: Binding.constant(false),
|
||||
userPickerVisible: Binding.constant(true)
|
||||
)
|
||||
.environmentObject(m)
|
||||
}
|
||||
}
|
||||
15
apps/ios/Shared/Views/Helpers/PressedButtonStyle.swift
Normal file
15
apps/ios/Shared/Views/Helpers/PressedButtonStyle.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Created by Avently on 16.01.2023.
|
||||
// Copyright (c) 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PressedButtonStyle: ButtonStyle {
|
||||
var defaultColor: Color
|
||||
var pressedColor: Color
|
||||
func makeBody(configuration: Self.Configuration) -> some View {
|
||||
configuration.label
|
||||
.background(configuration.isPressed ? pressedColor : defaultColor)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// SettingsButton.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 31/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsButton: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@State private var showSettings = false
|
||||
|
||||
var body: some View {
|
||||
Button { showSettings = true } label: {
|
||||
Image(systemName: "gearshape")
|
||||
}
|
||||
.sheet(isPresented: $showSettings, content: {
|
||||
SettingsView(showSettings: $showSettings)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsButton()
|
||||
}
|
||||
}
|
||||
56
apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
Normal file
56
apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Created by Avently on 17.01.2023.
|
||||
// Copyright (c) 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct UserProfilesView: View {
|
||||
@EnvironmentObject private var m: ChatModel
|
||||
@Environment(\.editMode) private var editMode
|
||||
@State private var selectedUser: Int? = nil
|
||||
@State private var showAddUser: Bool? = false
|
||||
@State private var showScanSMPServer = false
|
||||
@State private var testing = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section("Your profiles") {
|
||||
ForEach(Array($m.users.enumerated()), id: \.0) { i, userInfo in
|
||||
userProfileView(userInfo, index: i)
|
||||
.deleteDisabled(userInfo.wrappedValue.user.activeUser)
|
||||
}
|
||||
.onDelete { indexSet in
|
||||
do {
|
||||
try apiDeleteUser(m.users[indexSet.first!].user.userId)
|
||||
m.users.remove(atOffsets: indexSet)
|
||||
} catch {
|
||||
fatalError("Failed to delete user: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: CreateProfile(), tag: true, selection: $showAddUser) {
|
||||
Text("Add profile…")
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbar { EditButton() }
|
||||
}
|
||||
|
||||
private func userProfileView(_ userBinding: Binding<UserInfo>, index: Int) -> some View {
|
||||
let user = userBinding.wrappedValue.user
|
||||
return NavigationLink(tag: index, selection: $selectedUser) {
|
||||
// UserPrefs(user: userBinding, index: index)
|
||||
// .navigationBarTitle(user.chatViewName)
|
||||
// .navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
Text(user.chatViewName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UserProfilesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UserProfilesView()
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,10 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1841538E296606C74533367C /* UserPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18415835CBD939A9ABDC108A /* UserPicker.swift */; };
|
||||
1841560FD1CD447955474C1D /* UserProfilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18415845648CA4F5A8BCA272 /* UserProfilesView.swift */; };
|
||||
1841594C978674A7B42EF0C0 /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1841511920742C6E152E469F /* AnimatedImageView.swift */; };
|
||||
18415B0585EB5A9A0A7CA8CD /* PressedButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18415A7F0F189D87DEFEABCA /* PressedButtonStyle.swift */; };
|
||||
3C714777281C081000CB4D4B /* WebRTCView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C714776281C081000CB4D4B /* WebRTCView.swift */; };
|
||||
3C71477A281C0F6800CB4D4B /* www in Resources */ = {isa = PBXBuildFile; fileRef = 3C714779281C0F6800CB4D4B /* www */; };
|
||||
3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */; };
|
||||
@@ -95,7 +98,6 @@
|
||||
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */; };
|
||||
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E62868D76D001FD2EF /* NotificationsView.swift */; };
|
||||
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */; };
|
||||
5CB924D427A853F100ACCCDD /* SettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D327A853F100ACCCDD /* SettingsButton.swift */; };
|
||||
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D627A8563F00ACCCDD /* SettingsView.swift */; };
|
||||
5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* UserProfile.swift */; };
|
||||
5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; };
|
||||
@@ -222,6 +224,9 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1841511920742C6E152E469F /* AnimatedImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatedImageView.swift; sourceTree = "<group>"; };
|
||||
18415835CBD939A9ABDC108A /* UserPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPicker.swift; sourceTree = "<group>"; };
|
||||
18415845648CA4F5A8BCA272 /* UserProfilesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfilesView.swift; sourceTree = "<group>"; };
|
||||
18415A7F0F189D87DEFEABCA /* PressedButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PressedButtonStyle.swift; sourceTree = "<group>"; };
|
||||
3C714776281C081000CB4D4B /* WebRTCView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCView.swift; sourceTree = "<group>"; };
|
||||
3C714779281C0F6800CB4D4B /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; name = www; path = ../android/app/src/main/assets/www; sourceTree = "<group>"; };
|
||||
3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteToConnectView.swift; sourceTree = "<group>"; };
|
||||
@@ -321,7 +326,6 @@
|
||||
5CB346E42868AA7F001FD2EF /* SuspendChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuspendChat.swift; sourceTree = "<group>"; };
|
||||
5CB346E62868D76D001FD2EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||
5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushEnvironment.swift; sourceTree = "<group>"; };
|
||||
5CB924D327A853F100ACCCDD /* SettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButton.swift; sourceTree = "<group>"; };
|
||||
5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
5CB924E027A867BA00ACCCDD /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = "<group>"; };
|
||||
5CB924E327A8683A00ACCCDD /* UserAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddress.swift; sourceTree = "<group>"; };
|
||||
@@ -536,6 +540,7 @@
|
||||
5C6BA666289BD954009B8ECC /* DismissSheets.swift */,
|
||||
5C00164328A26FBC0094D739 /* ContextMenu.swift */,
|
||||
5CA7DFC229302AF000F7FDDE /* AppSheet.swift */,
|
||||
18415A7F0F189D87DEFEABCA /* PressedButtonStyle.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
@@ -623,7 +628,6 @@
|
||||
5CB924DF27A8678B00ACCCDD /* UserSettings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CB924D327A853F100ACCCDD /* SettingsButton.swift */,
|
||||
5CB924D627A8563F00ACCCDD /* SettingsView.swift */,
|
||||
5CB346E62868D76D001FD2EF /* NotificationsView.swift */,
|
||||
5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */,
|
||||
@@ -642,6 +646,7 @@
|
||||
5CB2084E28DA4B4800D024EC /* RTCServers.swift */,
|
||||
5C3F1D592844B4DE00EC8A82 /* ExperimentalFeaturesView.swift */,
|
||||
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */,
|
||||
18415845648CA4F5A8BCA272 /* UserProfilesView.swift */,
|
||||
);
|
||||
path = UserSettings;
|
||||
sourceTree = "<group>";
|
||||
@@ -656,6 +661,7 @@
|
||||
5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */,
|
||||
5C13730A28156D2700F43030 /* ContactConnectionView.swift */,
|
||||
5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */,
|
||||
18415835CBD939A9ABDC108A /* UserPicker.swift */,
|
||||
);
|
||||
path = ChatList;
|
||||
sourceTree = "<group>";
|
||||
@@ -1053,7 +1059,6 @@
|
||||
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */,
|
||||
5C5E5D3B2824468B00B0488A /* ActiveCallView.swift in Sources */,
|
||||
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */,
|
||||
5CB924D427A853F100ACCCDD /* SettingsButton.swift in Sources */,
|
||||
3C714777281C081000CB4D4B /* WebRTCView.swift in Sources */,
|
||||
6440CA00288857A10062C672 /* CIEventView.swift in Sources */,
|
||||
5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */,
|
||||
@@ -1073,6 +1078,9 @@
|
||||
644EFFE42937BE9700525D5B /* MarkedDeletedItemView.swift in Sources */,
|
||||
1841594C978674A7B42EF0C0 /* AnimatedImageView.swift in Sources */,
|
||||
5C7031162953C97F00150A12 /* CIFeaturePreferenceView.swift in Sources */,
|
||||
1841538E296606C74533367C /* UserPicker.swift in Sources */,
|
||||
18415B0585EB5A9A0A7CA8CD /* PressedButtonStyle.swift in Sources */,
|
||||
1841560FD1CD447955474C1D /* UserProfilesView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableAddressSanitizer = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -15,6 +15,9 @@ let jsonEncoder = getJSONEncoder()
|
||||
public enum ChatCommand {
|
||||
case showActiveUser
|
||||
case createActiveUser(profile: Profile)
|
||||
case listUsers
|
||||
case apiSetActiveUser(userId: Int64)
|
||||
case apiDeleteUser(userId: Int64)
|
||||
case startChat(subscribe: Bool, expire: Bool)
|
||||
case apiStopChat
|
||||
case apiActivateChat
|
||||
@@ -96,6 +99,9 @@ public enum ChatCommand {
|
||||
switch self {
|
||||
case .showActiveUser: return "/u"
|
||||
case let .createActiveUser(profile): return "/create user \(profile.displayName) \(profile.fullName)"
|
||||
case .listUsers: return "/users"
|
||||
case let .apiSetActiveUser(userId): return "/_user \(userId)"
|
||||
case let .apiDeleteUser(userId): return "/_delete user \(userId)"
|
||||
case let .startChat(subscribe, expire): return "/_start subscribe=\(onOff(subscribe)) expire=\(onOff(expire))"
|
||||
case .apiStopChat: return "/_stop"
|
||||
case .apiActivateChat: return "/_app activate"
|
||||
@@ -184,6 +190,9 @@ public enum ChatCommand {
|
||||
switch self {
|
||||
case .showActiveUser: return "showActiveUser"
|
||||
case .createActiveUser: return "createActiveUser"
|
||||
case .listUsers: return "listUsers"
|
||||
case .apiSetActiveUser: return "apiSetActiveUser"
|
||||
case .apiDeleteUser: return "apiDeleteUser"
|
||||
case .startChat: return "startChat"
|
||||
case .apiStopChat: return "apiStopChat"
|
||||
case .apiActivateChat: return "apiActivateChat"
|
||||
@@ -302,6 +311,7 @@ struct APIResponse: Decodable {
|
||||
public enum ChatResponse: Decodable, Error {
|
||||
case response(type: String, json: String)
|
||||
case activeUser(user: User)
|
||||
case usersList(users: [UserInfo])
|
||||
case chatStarted
|
||||
case chatRunning
|
||||
case chatStopped
|
||||
@@ -408,6 +418,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
switch self {
|
||||
case let .response(type, _): return "* \(type)"
|
||||
case .activeUser: return "activeUser"
|
||||
case .usersList: return "usersList"
|
||||
case .chatStarted: return "chatStarted"
|
||||
case .chatRunning: return "chatRunning"
|
||||
case .chatStopped: return "chatStopped"
|
||||
@@ -514,6 +525,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
switch self {
|
||||
case let .response(_, json): return json
|
||||
case let .activeUser(user): return String(describing: user)
|
||||
case let .usersList(users): return String(describing: users)
|
||||
case .chatStarted: return noDetails
|
||||
case .chatRunning: return noDetails
|
||||
case .chatStopped: return noDetails
|
||||
|
||||
@@ -9,19 +9,21 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public struct User: Decodable, NamedChat {
|
||||
public struct User: Decodable, NamedChat, Identifiable {
|
||||
public var userId: Int64
|
||||
var userContactId: Int64
|
||||
var localDisplayName: ContactName
|
||||
public var profile: LocalProfile
|
||||
public var fullPreferences: FullPreferences
|
||||
var activeUser: Bool
|
||||
public var activeUser: Bool
|
||||
|
||||
public var displayName: String { get { profile.displayName } }
|
||||
public var fullName: String { get { profile.fullName } }
|
||||
public var image: String? { get { profile.image } }
|
||||
public var localAlias: String { get { "" } }
|
||||
|
||||
public var id: Int64 { userId }
|
||||
|
||||
public static let sampleData = User(
|
||||
userId: 1,
|
||||
userContactId: 1,
|
||||
@@ -32,6 +34,21 @@ public struct User: Decodable, NamedChat {
|
||||
)
|
||||
}
|
||||
|
||||
public struct UserInfo: Decodable {
|
||||
public var user: User
|
||||
public var unreadCount: Int64
|
||||
|
||||
public init(user: User, unreadCount: Int64) {
|
||||
self.user = user
|
||||
self.unreadCount = unreadCount
|
||||
}
|
||||
|
||||
public static let sampleData = UserInfo(
|
||||
user: User.sampleData,
|
||||
unreadCount: 1
|
||||
)
|
||||
}
|
||||
|
||||
public typealias ContactName = String
|
||||
|
||||
public typealias GroupName = String
|
||||
|
||||
Reference in New Issue
Block a user