forked from mirrors/linphone-iphone
Compare commits
50 commits
feature/6.
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0179c12b07 | ||
|
|
03bd42d750 | ||
|
|
512ca5afe8 | ||
|
|
f2615b89e0 | ||
|
|
5cadd63d1e | ||
|
|
6b9857b11b | ||
|
|
a760a98366 | ||
|
|
b7ef60692d | ||
|
|
5c14429eda | ||
|
|
2f95e7b4d3 | ||
|
|
273c77edbe | ||
|
|
0b81887728 | ||
|
|
63a1886ff5 | ||
|
|
2a6abbe183 | ||
|
|
ed6f3215b9 | ||
|
|
ad893ab16e | ||
|
|
7ae256b566 | ||
|
|
acdd201bbe | ||
|
|
ce24ddc919 | ||
|
|
ad48ff8bca | ||
|
|
095705ad37 | ||
|
|
1510a1b045 | ||
|
|
abf294625a | ||
|
|
0c573e6e5c | ||
|
|
ac4ea27a78 | ||
|
|
a524d3c362 | ||
|
|
f1fcb9129c | ||
|
|
b376328f33 | ||
|
|
413e1b4082 | ||
|
|
56f732144d | ||
|
|
db24bd842f | ||
|
|
be53335b67 | ||
|
|
0bc9aa977c | ||
|
|
ad54e09253 | ||
|
|
4641ef680c | ||
|
|
46f5f21216 | ||
|
|
00bbf41f9a | ||
|
|
cf109f9787 | ||
|
|
a3c20e3ae7 | ||
|
|
a9854bc378 | ||
|
|
20da49167c | ||
|
|
b71267eed5 | ||
|
|
d353a9416e | ||
|
|
4d10b44da5 | ||
|
|
7e1e3adf8d | ||
|
|
e74df76301 | ||
|
|
34ff5bcf16 | ||
|
|
e3dd3f5a86 | ||
|
|
3ca2bca27c | ||
|
|
9a208e36b2 |
66 changed files with 3226 additions and 2471 deletions
|
|
@ -43,6 +43,8 @@
|
|||
66FBFC4B2B83BD7B00BC6AB1 /* CoreExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491FA2B24D32600CEA16D /* CoreExtension.swift */; };
|
||||
66FDB7812C7C689A00561566 /* EventEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FDB7802C7C689A00561566 /* EventEditViewController.swift */; };
|
||||
C60E8F192C0F649200A06DB8 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60E8F182C0F649200A06DB8 /* UIApplicationExtension.swift */; };
|
||||
C618BF562D75CA03005A00E0 /* linphonesw in Frameworks */ = {isa = PBXBuildFile; productRef = C618BF552D75CA03005A00E0 /* linphonesw */; };
|
||||
C618BF582D75CA0D005A00E0 /* linphonesw in Frameworks */ = {isa = PBXBuildFile; productRef = C618BF572D75CA0D005A00E0 /* linphonesw */; };
|
||||
C62817282C1B389700DBA646 /* SideMenuAccountRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C62817272C1B389700DBA646 /* SideMenuAccountRow.swift */; };
|
||||
C628172E2C1C3A3600DBA646 /* AccountExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C628172D2C1C3A3600DBA646 /* AccountExtension.swift */; };
|
||||
C62817302C1C3DCC00DBA646 /* AccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C628172F2C1C3DCC00DBA646 /* AccountModel.swift */; };
|
||||
|
|
@ -405,6 +407,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C618BF582D75CA0D005A00E0 /* linphonesw in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -412,6 +415,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C618BF562D75CA03005A00E0 /* linphonesw in Frameworks */,
|
||||
4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -1066,7 +1070,7 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = D719ABAE2ABC67BF00B41C10 /* Build configuration list for PBXProject "Linphone" */;
|
||||
buildConfigurationList = D719ABAE2ABC67BF00B41C10 /* Build configuration list for PBXProject "linphone" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
|
|
@ -1075,6 +1079,9 @@
|
|||
Base,
|
||||
);
|
||||
mainGroup = D719ABAA2ABC67BF00B41C10;
|
||||
packageReferences = (
|
||||
C618BF542D75CA03005A00E0 /* XCRemoteSwiftPackageReference "linphone-sdk-swift-ios" */,
|
||||
);
|
||||
productRefGroup = D719ABB42ABC67BF00B41C10 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
|
|
@ -1388,13 +1395,14 @@
|
|||
CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 65;
|
||||
CURRENT_PROJECT_VERSION = 73;
|
||||
DEVELOPMENT_TEAM = Z2V957B3D6;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"DEBUG=1",
|
||||
"USE_CRASHLYTICS=1",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = msgNotificationService/Info.plist;
|
||||
|
|
@ -1408,7 +1416,7 @@
|
|||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 6.0.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1430,11 +1438,14 @@
|
|||
CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 65;
|
||||
CURRENT_PROJECT_VERSION = 73;
|
||||
DEVELOPMENT_TEAM = Z2V957B3D6;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"USE_CRASHLYTICS=1",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = msgNotificationService/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = msgNotificationService;
|
||||
|
|
@ -1447,7 +1458,7 @@
|
|||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 6.0.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1584,7 +1595,7 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 65;
|
||||
CURRENT_PROJECT_VERSION = 73;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\"";
|
||||
|
|
@ -1594,6 +1605,7 @@
|
|||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"DEBUG=1",
|
||||
"USE_CRASHLYTICS=1",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Linphone/Info.plist;
|
||||
|
|
@ -1623,7 +1635,7 @@
|
|||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.3;
|
||||
MARKETING_VERSION = 6.0.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -1643,13 +1655,16 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 65;
|
||||
CURRENT_PROJECT_VERSION = 73;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = Z2V957B3D6;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"USE_CRASHLYTICS=1",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Linphone/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Linphone;
|
||||
|
|
@ -1678,7 +1693,7 @@
|
|||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.3;
|
||||
MARKETING_VERSION = 6.0.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -1701,7 +1716,7 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D719ABAE2ABC67BF00B41C10 /* Build configuration list for PBXProject "Linphone" */ = {
|
||||
D719ABAE2ABC67BF00B41C10 /* Build configuration list for PBXProject "linphone" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D719ABC02ABC67BF00B41C10 /* Debug */,
|
||||
|
|
@ -1720,6 +1735,30 @@
|
|||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
C618BF542D75CA03005A00E0 /* XCRemoteSwiftPackageReference "linphone-sdk-swift-ios" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git";
|
||||
requirement = {
|
||||
branch = stable;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
C618BF552D75CA03005A00E0 /* linphonesw */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C618BF542D75CA03005A00E0 /* XCRemoteSwiftPackageReference "linphone-sdk-swift-ios" */;
|
||||
productName = linphonesw;
|
||||
};
|
||||
C618BF572D75CA0D005A00E0 /* linphonesw */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C618BF542D75CA03005A00E0 /* XCRemoteSwiftPackageReference "linphone-sdk-swift-ios" */;
|
||||
productName = linphonesw;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = D719ABAB2ABC67BF00B41C10 /* Project object */;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
BlueprintIdentifier = "D719ABB22ABC67BF00B41C10"
|
||||
BuildableName = "Linphone.app"
|
||||
BlueprintName = "Linphone"
|
||||
ReferencedContainer = "container:Linphone.xcodeproj">
|
||||
ReferencedContainer = "container:linphone.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
BlueprintIdentifier = "D719ABB22ABC67BF00B41C10"
|
||||
BuildableName = "Linphone.app"
|
||||
BlueprintName = "Linphone"
|
||||
ReferencedContainer = "container:Linphone.xcodeproj">
|
||||
ReferencedContainer = "container:linphone.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
BlueprintIdentifier = "D719ABB22ABC67BF00B41C10"
|
||||
BuildableName = "Linphone.app"
|
||||
BlueprintName = "Linphone"
|
||||
ReferencedContainer = "container:Linphone.xcodeproj">
|
||||
ReferencedContainer = "container:linphone.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
|
|
|
|||
21
Linphone/Assets.xcassets/mountain2.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/mountain2.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "mountain2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Linphone/Assets.xcassets/mountain2.imageset/mountain2.png
vendored
Normal file
BIN
Linphone/Assets.xcassets/mountain2.imageset/mountain2.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
|
|
@ -88,8 +88,9 @@ final class ContactsManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
MagicSearchSingleton.shared.searchForContactsWithoutCoreThread(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
let store = CNContactStore()
|
||||
|
||||
store.requestAccess(for: .contacts) { (granted, error) in
|
||||
if let error = error {
|
||||
print("\(#function) - failed to request access", error)
|
||||
|
|
@ -105,69 +106,127 @@ final class ContactsManager: ObservableObject {
|
|||
do {
|
||||
var contactCounter = 0
|
||||
try store.enumerateContacts(with: request, usingBlock: { (contact, _) in
|
||||
DispatchQueue.main.async {
|
||||
let newContact = Contact(
|
||||
identifier: contact.identifier,
|
||||
firstName: contact.givenName,
|
||||
lastName: contact.familyName,
|
||||
organizationName: contact.organizationName,
|
||||
jobTitle: "",
|
||||
displayName: contact.nickname,
|
||||
sipAddresses: contact.instantMessageAddresses.map { $0.value.service.lowercased() == "SIP".lowercased() ? $0.value.username : "" },
|
||||
phoneNumbers: contact.phoneNumbers.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.value.stringValue)},
|
||||
imageData: ""
|
||||
)
|
||||
|
||||
let imageThumbnail = UIImage(data: contact.thumbnailImageData ?? Data())
|
||||
self.saveImage(
|
||||
image: imageThumbnail
|
||||
?? self.textToImage(
|
||||
firstName: contact.givenName.isEmpty
|
||||
&& contact.familyName.isEmpty
|
||||
&& contact.phoneNumbers.first?.value.stringValue != nil
|
||||
? contact.phoneNumbers.first!.value.stringValue
|
||||
: contact.givenName, lastName: contact.familyName),
|
||||
name: contact.givenName + contact.familyName,
|
||||
prefix: ((imageThumbnail == nil) ? "-default" : ""),
|
||||
contact: newContact, linphoneFriend: false, existingFriend: nil) {
|
||||
if (self.friendList?.friends.count ?? 0) + (self.linphoneFriendList?.friends.count ?? 0) == contactCounter {
|
||||
// Every contact properly added, proceed
|
||||
self.linphoneFriendList?.updateSubscriptions()
|
||||
self.friendList?.updateSubscriptions()
|
||||
|
||||
if let friendListDelegate = self.friendListDelegate {
|
||||
self.friendList?.removeDelegate(delegate: friendListDelegate)
|
||||
}
|
||||
self.friendListDelegate = FriendListDelegateStub(onNewSipAddressDiscovered: { (_: FriendList, linphoneFriend: Friend, sipUri: String) in
|
||||
var addedAvatarListModel: [ContactAvatarModel] = []
|
||||
linphoneFriend.phoneNumbers.forEach { phone in
|
||||
let address = try? Factory.Instance.createAddress(addr: sipUri)
|
||||
let presence = linphoneFriend.getPresenceModelForUriOrTel(uriOrTel: address?.asStringUriOnly() ?? "")
|
||||
if address != nil {
|
||||
linphoneFriend.edit()
|
||||
linphoneFriend.addAddress(address: address!)
|
||||
linphoneFriend.done()
|
||||
|
||||
addedAvatarListModel.append(
|
||||
ContactAvatarModel(
|
||||
friend: linphoneFriend,
|
||||
name: linphoneFriend.name ?? "",
|
||||
address: linphoneFriend.address?.clone()?.asStringUriOnly() ?? "",
|
||||
withPresence: true
|
||||
let newContact = Contact(
|
||||
identifier: contact.identifier,
|
||||
firstName: contact.givenName,
|
||||
lastName: contact.familyName,
|
||||
organizationName: contact.organizationName,
|
||||
jobTitle: "",
|
||||
displayName: contact.nickname,
|
||||
sipAddresses: contact.instantMessageAddresses.map { $0.value.service.lowercased() == "SIP".lowercased() ? $0.value.username : "" },
|
||||
phoneNumbers: contact.phoneNumbers.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.value.stringValue)},
|
||||
imageData: ""
|
||||
)
|
||||
|
||||
let imageThumbnail = UIImage(data: contact.thumbnailImageData ?? Data())
|
||||
if let image = imageThumbnail {
|
||||
DispatchQueue.main.async {
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: contact.givenName + contact.familyName,
|
||||
prefix: "",
|
||||
contact: newContact, linphoneFriend: false, existingFriend: nil) {
|
||||
if (self.friendList?.friends.count ?? 0) == contactCounter {
|
||||
// Every contact properly added, proceed
|
||||
self.linphoneFriendList?.updateSubscriptions()
|
||||
self.friendList?.updateSubscriptions()
|
||||
|
||||
if let friendListDelegate = self.friendListDelegate {
|
||||
self.friendList?.removeDelegate(delegate: friendListDelegate)
|
||||
}
|
||||
|
||||
self.friendListDelegate = FriendListDelegateStub(onNewSipAddressDiscovered: { (_: FriendList, linphoneFriend: Friend, sipUri: String) in
|
||||
var addedAvatarListModel: [ContactAvatarModel] = []
|
||||
linphoneFriend.phoneNumbers.forEach { _ in
|
||||
let address = try? Factory.Instance.createAddress(addr: sipUri)
|
||||
if address != nil {
|
||||
linphoneFriend.edit()
|
||||
linphoneFriend.addAddress(address: address!)
|
||||
linphoneFriend.done()
|
||||
|
||||
let addressTmp = linphoneFriend.address?.clone()?.asStringUriOnly() ?? ""
|
||||
addedAvatarListModel.append(
|
||||
ContactAvatarModel(
|
||||
friend: linphoneFriend,
|
||||
name: linphoneFriend.name ?? "",
|
||||
address: addressTmp,
|
||||
withPresence: true
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(
|
||||
name: NSNotification.Name("ContactAdded"),
|
||||
object: nil,
|
||||
userInfo: ["address": addressTmp]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarListModel += addedAvatarListModel
|
||||
}
|
||||
|
||||
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
})
|
||||
self.friendList?.addDelegate(delegate: self.friendListDelegate!)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarListModel += addedAvatarListModel
|
||||
self.avatarListModel = self.avatarListModel.sorted { $0.name < $1.name }
|
||||
}
|
||||
})
|
||||
self.friendList?.addDelegate(delegate: self.friendListDelegate!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.textToImageInMainThread(firstName: contact.givenName, lastName: contact.familyName) { image in
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: contact.givenName + contact.familyName,
|
||||
prefix: "-default",
|
||||
contact: newContact, linphoneFriend: false, existingFriend: nil) {
|
||||
if (self.friendList?.friends.count ?? 0) == contactCounter {
|
||||
// Every contact properly added, proceed
|
||||
self.linphoneFriendList?.updateSubscriptions()
|
||||
self.friendList?.updateSubscriptions()
|
||||
|
||||
if let friendListDelegate = self.friendListDelegate {
|
||||
self.friendList?.removeDelegate(delegate: friendListDelegate)
|
||||
}
|
||||
|
||||
self.friendListDelegate = FriendListDelegateStub(onNewSipAddressDiscovered: { (_: FriendList, linphoneFriend: Friend, sipUri: String) in
|
||||
var addedAvatarListModel: [ContactAvatarModel] = []
|
||||
linphoneFriend.phoneNumbers.forEach { _ in
|
||||
let address = try? Factory.Instance.createAddress(addr: sipUri)
|
||||
if address != nil {
|
||||
linphoneFriend.edit()
|
||||
linphoneFriend.addAddress(address: address!)
|
||||
linphoneFriend.done()
|
||||
|
||||
let addressTmp = linphoneFriend.address?.clone()?.asStringUriOnly() ?? ""
|
||||
addedAvatarListModel.append(
|
||||
ContactAvatarModel(
|
||||
friend: linphoneFriend,
|
||||
name: linphoneFriend.name ?? "",
|
||||
address: addressTmp,
|
||||
withPresence: true
|
||||
)
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(
|
||||
name: NSNotification.Name("ContactAdded"),
|
||||
object: nil,
|
||||
userInfo: ["address": addressTmp]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarListModel += addedAvatarListModel
|
||||
self.avatarListModel = self.avatarListModel.sorted { $0.name < $1.name }
|
||||
}
|
||||
})
|
||||
self.friendList?.addDelegate(delegate: self.friendListDelegate!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !(contact.givenName.isEmpty && contact.familyName.isEmpty) {
|
||||
|
|
@ -186,6 +245,35 @@ final class ContactsManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func textToImageInMainThread(firstName: String, lastName: String, completion: @escaping (UIImage) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
let lblNameInitialize = UILabel()
|
||||
lblNameInitialize.frame.size = CGSize(width: 200.0, height: 200.0)
|
||||
lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 80)
|
||||
lblNameInitialize.textColor = UIColor(Color.grayMain2c600)
|
||||
|
||||
let textToDisplay = (firstName.first.map { String($0) } ?? "") + (lastName.first.map { String($0) } ?? "")
|
||||
|
||||
lblNameInitialize.text = textToDisplay.uppercased()
|
||||
lblNameInitialize.textAlignment = .center
|
||||
lblNameInitialize.backgroundColor = UIColor(Color.grayMain2c200)
|
||||
lblNameInitialize.layer.cornerRadius = 10.0
|
||||
lblNameInitialize.clipsToBounds = true
|
||||
|
||||
UIGraphicsBeginImageContext(lblNameInitialize.frame.size)
|
||||
defer { UIGraphicsEndImageContext() }
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
completion(UIImage())
|
||||
return
|
||||
}
|
||||
|
||||
lblNameInitialize.layer.render(in: context)
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
|
||||
completion(image)
|
||||
}
|
||||
}
|
||||
|
||||
func textToImage(firstName: String, lastName: String) -> UIImage {
|
||||
let lblNameInitialize = UILabel()
|
||||
lblNameInitialize.frame.size = CGSize(width: 200.0, height: 200.0)
|
||||
|
|
@ -314,12 +402,15 @@ final class ContactsManager: ObservableObject {
|
|||
if directory != nil {
|
||||
DispatchQueue.main.async {
|
||||
do {
|
||||
let urlName = URL(string: name + prefix)
|
||||
let imagePath = urlName != nil ? urlName!.absoluteString.replacingOccurrences(of: "%", with: "") : "ImageError"
|
||||
|
||||
let decodedData: () = try data.write(to: directory!.appendingPathComponent(imagePath + ".png"))
|
||||
|
||||
completion(decodedData, imagePath + ".png")
|
||||
if let urlName = URL(string: name + prefix) {
|
||||
let imagePath = urlName.absoluteString.replacingOccurrences(of: "%", with: "")
|
||||
|
||||
let decodedData: () = try data.write(to: directory!.appendingPathComponent(imagePath + ".png"))
|
||||
|
||||
completion(decodedData, imagePath + ".png")
|
||||
} else {
|
||||
completion((), "")
|
||||
}
|
||||
} catch {
|
||||
print("Error: ", error)
|
||||
completion((), "")
|
||||
|
|
@ -341,25 +432,20 @@ final class ContactsManager: ObservableObject {
|
|||
}
|
||||
|
||||
func getFriendWithAddress(address: Address?) -> Friend? {
|
||||
if address != nil {
|
||||
let clonedAddress = address!.clone()
|
||||
clonedAddress!.clean()
|
||||
let sipUri = clonedAddress!.asStringUriOnly()
|
||||
|
||||
if self.friendList != nil && !self.friendList!.friends.isEmpty {
|
||||
var friend: Friend?
|
||||
friend = self.friendList!.friends.first(where: {$0.addresses.contains(where: {$0.asStringUriOnly() == sipUri})})
|
||||
if friend == nil && self.linphoneFriendList != nil && !self.linphoneFriendList!.friends.isEmpty {
|
||||
friend = self.linphoneFriendList!.friends.first(where: {$0.addresses.contains(where: {$0.asStringUriOnly() == sipUri})})
|
||||
}
|
||||
|
||||
return friend
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
guard let address = address, let clonedAddress = address.clone() else {
|
||||
return nil
|
||||
}
|
||||
clonedAddress.clean()
|
||||
let sipUri = clonedAddress.asStringUriOnly()
|
||||
|
||||
var friend: Friend?
|
||||
if let friendList = self.friendList {
|
||||
friend = friendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) })
|
||||
}
|
||||
if friend == nil, let linphoneFriendList = self.linphoneFriendList {
|
||||
friend = linphoneFriendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) })
|
||||
}
|
||||
return friend
|
||||
}
|
||||
|
||||
func getFriendWithAddressInCoreQueue(address: Address?, completion: @escaping (Friend?) -> Void) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import linphone // needed for unwrapped function linphone_core_set_push_and_app_
|
|||
import Combine
|
||||
import UniformTypeIdentifiers
|
||||
import Network
|
||||
import SwiftUI
|
||||
|
||||
#if USE_CRASHLYTICS
|
||||
import Firebase
|
||||
|
|
@ -42,9 +43,8 @@ final class CoreContext: ObservableObject {
|
|||
@Published var loggingInProgress: Bool = false
|
||||
@Published var coreIsStarted: Bool = false
|
||||
@Published var accounts: [AccountModel] = []
|
||||
@Published var enteredForeground = false
|
||||
@Published var shortcuts: [ShortcutModel] = []
|
||||
private var mCore: Core!
|
||||
var mCore: Core!
|
||||
private var mIterateSuscription: AnyCancellable?
|
||||
|
||||
var bearerAuthInfoPendingPasswordUpdate: AuthInfo?
|
||||
|
|
@ -148,13 +148,14 @@ final class CoreContext: ObservableObject {
|
|||
self.mCore.friendListSubscriptionEnabled = true
|
||||
|
||||
// Migration
|
||||
self.mCore.maxSizeForAutoDownloadIncomingFiles = 0
|
||||
self.mCore.config!.setBool(section: "sip", key: "auto_answer_replacing_calls", value: false)
|
||||
self.mCore.config!.setBool(section: "sip", key: "deliver_imdn", value: false)
|
||||
self.mCore.config!.setString(section: "misc", key: "log_collection_upload_server_url", value: "https://files.linphone.org:443/http-file-transfer-server/hft.php")
|
||||
self.mCore.config!.setString(section: "misc", key: "file_transfer_server_url", value: "https://files.linphone.org:443/http-file-transfer-server/hft.php")
|
||||
self.mCore.config!.setString(section: "misc", key: "version_check_url_root", value: "https://download.linphone.org/releases")
|
||||
|
||||
self.mCore.imdnToEverybodyThreshold = 1
|
||||
|
||||
let shortcutsCount = self.mCore.config!.getInt(section: "ui", key: "shortcut_count", defaultValue: 0)
|
||||
if shortcutsCount > 0 {
|
||||
var shortcuts: [ShortcutModel] = []
|
||||
|
|
|
|||
|
|
@ -242,6 +242,15 @@ class CorePreferences {
|
|||
}
|
||||
}
|
||||
|
||||
static var defaultDomain: String {
|
||||
get {
|
||||
return Config.get().getString(section: "app", key: "default_domain", defaultString: "sip.linphone.org")
|
||||
}
|
||||
set {
|
||||
Config.get().setString(section: "app", key: "default_domain", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private func copy(from: String, to: String, overrideIfExists: Bool = false) {
|
||||
let fileManager = FileManager.default
|
||||
if fileManager.fileExists(atPath: to), !overrideIfExists {
|
||||
|
|
|
|||
|
|
@ -1055,13 +1055,13 @@
|
|||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Some features require a %@ account, such as group messaging, video conferences…\\n\\nThese features are hidden when you register with a third party SIP account.\\n\\nTo enable it in a commercial project, please contact us."
|
||||
"value" : "Some features require a %@ account, such as group messaging, video conferences…\n\nThese features are hidden when you register with a third party SIP account.\n\nTo enable it in a commercial project, please contact us."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Certaines fonctionnalités telles que les conversations de groupe, les vidéo-conférences, etc… nécessitent un compte %@.\\n\\nCes fonctionnalités seront masquées si vous utilisez un compte SIP tiers.\\n\\nPour les activer dans un projet commercial, merci de nous contacter."
|
||||
"value" : "Certaines fonctionnalités telles que les conversations de groupe, les vidéo-conférences, etc… nécessitent un compte %@.\n\nCes fonctionnalités seront masquées si vous utilisez un compte SIP tiers.\n\nPour les activer dans un projet commercial, merci de nous contacter."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3486,6 +3486,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"conversation_one_to_one_hidden_subject" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dummy subject"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"conversation_reply_to_message_title" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
|
|
@ -7677,12 +7688,12 @@
|
|||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Your communications are safe thanks to our <b>end-to-end encryption</b>."
|
||||
"value" : "Your communications are safe thanks to our **end-to-end encryption**."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Vos communications sont en sécurité grâce au **chiffrement de bout en bout**."
|
||||
}
|
||||
}
|
||||
|
|
@ -7709,12 +7720,12 @@
|
|||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "A <b>free</b> and open source application since <b>2001</b>."
|
||||
"value" : "A **free** and open source application since **2001**."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Une application open source et un **service gratuit** depuis **2001**."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config xmlns="http://www.linphone.org/xsds/lpconfig.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.linphone.org/xsds/lpconfig.xsd lpconfig.xsd">
|
||||
<section name="proxy_default_values">
|
||||
<entry name="avpf" overwrite="true">1</entry>
|
||||
<entry name="dial_escape_plus" overwrite="true">0</entry>
|
||||
<entry name="publish" overwrite="true">1</entry>
|
||||
<entry name="publish_expires" overwrite="true">120</entry>
|
||||
<entry name="quality_reporting_collector" overwrite="true">sip:voip-metrics@sip.linphone.org;transport=tls</entry>
|
||||
<entry name="quality_reporting_enabled" overwrite="true">1</entry>
|
||||
<entry name="quality_reporting_interval" overwrite="true">180</entry>
|
||||
<entry name="reg_expires" overwrite="true">31536000</entry>
|
||||
<entry name="reg_identity" overwrite="true">sip:?@sip.linphone.org</entry>
|
||||
<entry name="reg_proxy" overwrite="true"><sip:sip.linphone.org;transport=tls></entry>
|
||||
<entry name="reg_route" overwrite="true"><sip:sip.linphone.org;transport=tls></entry>
|
||||
<entry name="reg_sendregister" overwrite="true">1</entry>
|
||||
<entry name="nat_policy_ref" overwrite="true">nat_policy_default_values</entry>
|
||||
<entry name="realm" overwrite="true">sip.linphone.org</entry>
|
||||
<entry name="conference_factory_uri" overwrite="true">sip:conference-factory@sip.linphone.org</entry>
|
||||
<entry name="audio_video_conference_factory_uri" overwrite="true">sip:videoconference-factory@sip.linphone.org</entry>
|
||||
<entry name="push_notification_allowed" overwrite="true">1</entry>
|
||||
<entry name="avpf" overwrite="true">1</entry>
|
||||
<entry name="dial_escape_plus" overwrite="true">0</entry>
|
||||
<entry name="publish" overwrite="true">1</entry>
|
||||
<entry name="publish_expires" overwrite="true">120</entry>
|
||||
<entry name="quality_reporting_collector" overwrite="true">sip:voip-metrics@sip.linphone.org;transport=tls</entry>
|
||||
<entry name="quality_reporting_enabled" overwrite="true">1</entry>
|
||||
<entry name="quality_reporting_interval" overwrite="true">180</entry>
|
||||
<entry name="reg_expires" overwrite="true">31536000</entry>
|
||||
<entry name="reg_identity" overwrite="true">sip:?@sip.linphone.org</entry>
|
||||
<entry name="reg_proxy" overwrite="true"><sip:sip.linphone.org;transport=tls></entry>
|
||||
<entry name="reg_route" overwrite="true"><sip:sip.linphone.org;transport=tls></entry>
|
||||
<entry name="reg_sendregister" overwrite="true">1</entry>
|
||||
<entry name="nat_policy_ref" overwrite="true">nat_policy_default_values</entry>
|
||||
<entry name="realm" overwrite="true">sip.linphone.org</entry>
|
||||
<entry name="conference_factory_uri" overwrite="true">sip:conference-factory@sip.linphone.org</entry>
|
||||
<entry name="audio_video_conference_factory_uri" overwrite="true">sip:videoconference-factory@sip.linphone.org</entry>
|
||||
<entry name="push_notification_allowed" overwrite="true">1</entry>
|
||||
<entry name="remote_push_notification_allowed" overwrite="true">1</entry>
|
||||
<entry name="cpim_in_basic_chat_rooms_enabled" overwrite="true">1</entry>
|
||||
<entry name="rtp_bundle" overwrite="true">1</entry>
|
||||
<entry name="lime_server_url" overwrite="true">https://lime.linphone.org/lime-server/lime-server.php</entry>
|
||||
<entry name="cpim_in_basic_chat_rooms_enabled" overwrite="true">1</entry>
|
||||
<entry name="rtp_bundle" overwrite="true">1</entry>
|
||||
<entry name="lime_server_url" overwrite="true">https://lime.linphone.org/lime-server/lime-server.php</entry>
|
||||
<entry name="lime_algo" overwrite="true">c25519</entry>
|
||||
<entry name="supported" overwrite="true"></entry>
|
||||
</section>
|
||||
<section name="nat_policy_default_values">
|
||||
<entry name="stun_server" overwrite="true">stun.linphone.org</entry>
|
||||
<entry name="protocols" overwrite="true">stun,ice</entry>
|
||||
<entry name="stun_server" overwrite="true">stun.linphone.org</entry>
|
||||
<entry name="protocols" overwrite="true">stun,ice</entry>
|
||||
</section>
|
||||
<section name="sip">
|
||||
<entry name="media_encryption" overwrite="true">zrtp</entry>
|
||||
<entry name="media_encryption_mandatory">1</entry>
|
||||
<entry name="media_encryption" overwrite="true">zrtp</entry>
|
||||
<entry name="media_encryption_mandatory">1</entry>
|
||||
</section>
|
||||
<section name="net">
|
||||
<entry name="friendlist_subscription_enabled" overwrite="true">1</entry>
|
||||
<entry name="friendlist_subscription_enabled" overwrite="true">1</entry>
|
||||
</section>
|
||||
</config>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config xmlns="http://www.linphone.org/xsds/lpconfig.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.linphone.org/xsds/lpconfig.xsd lpconfig.xsd">
|
||||
<section name="proxy_default_values">
|
||||
<entry name="avpf" overwrite="true">0</entry>
|
||||
<entry name="dial_escape_plus" overwrite="true">0</entry>
|
||||
<entry name="publish" overwrite="true">0</entry>
|
||||
<entry name="publish_expires" overwrite="true">-1</entry>
|
||||
<entry name="quality_reporting_collector" overwrite="true"></entry>
|
||||
<entry name="quality_reporting_enabled" overwrite="true">0</entry>
|
||||
<entry name="quality_reporting_interval" overwrite="true">0</entry>
|
||||
<entry name="reg_expires" overwrite="true">3600</entry>
|
||||
<entry name="reg_identity" overwrite="true"></entry>
|
||||
<entry name="reg_proxy" overwrite="true"></entry>
|
||||
<entry name="reg_route" overwrite="true"></entry>
|
||||
<entry name="reg_sendregister" overwrite="true">1</entry>
|
||||
<entry name="nat_policy_ref" overwrite="true"></entry>
|
||||
<entry name="realm" overwrite="true"></entry>
|
||||
<entry name="conference_factory_uri" overwrite="true"></entry>
|
||||
<entry name="audio_video_conference_factory_uri" overwrite="true"></entry>
|
||||
<entry name="push_notification_allowed" overwrite="true">0</entry>
|
||||
<entry name="cpim_in_basic_chat_rooms_enabled" overwrite="true">0</entry>
|
||||
<entry name="rtp_bundle" overwrite="true">0</entry>
|
||||
<entry name="lime_server_url" overwrite="true"></entry>
|
||||
<entry name="avpf" overwrite="true">0</entry>
|
||||
<entry name="dial_escape_plus" overwrite="true">0</entry>
|
||||
<entry name="publish" overwrite="true">0</entry>
|
||||
<entry name="publish_expires" overwrite="true">-1</entry>
|
||||
<entry name="quality_reporting_collector" overwrite="true"></entry>
|
||||
<entry name="quality_reporting_enabled" overwrite="true">0</entry>
|
||||
<entry name="quality_reporting_interval" overwrite="true">0</entry>
|
||||
<entry name="reg_expires" overwrite="true">3600</entry>
|
||||
<entry name="reg_identity" overwrite="true"></entry>
|
||||
<entry name="reg_proxy" overwrite="true"></entry>
|
||||
<entry name="reg_route" overwrite="true"></entry>
|
||||
<entry name="reg_sendregister" overwrite="true">1</entry>
|
||||
<entry name="nat_policy_ref" overwrite="true"></entry>
|
||||
<entry name="realm" overwrite="true"></entry>
|
||||
<entry name="conference_factory_uri" overwrite="true"></entry>
|
||||
<entry name="audio_video_conference_factory_uri" overwrite="true"></entry>
|
||||
<entry name="push_notification_allowed" overwrite="true">0</entry>
|
||||
<entry name="cpim_in_basic_chat_rooms_enabled" overwrite="true">0</entry>
|
||||
<entry name="rtp_bundle" overwrite="true">0</entry>
|
||||
<entry name="lime_server_url" overwrite="true"></entry>
|
||||
<entry name="lime_algo" overwrite="true"></entry>
|
||||
<entry name="supported" overwrite="true">outbound</entry>
|
||||
</section>
|
||||
<section name="nat_policy_default_values">
|
||||
<entry name="stun_server" overwrite="true">stun.linphone.org</entry>
|
||||
<entry name="protocols" overwrite="true">stun,ice</entry>
|
||||
<entry name="stun_server" overwrite="true">stun.linphone.org</entry>
|
||||
<entry name="protocols" overwrite="true">stun,ice</entry>
|
||||
</section>
|
||||
<section name="sip">
|
||||
<entry name="media_encryption">srtp</entry>
|
||||
<entry name="media_encryption_mandatory" overwrite="true">0</entry>
|
||||
<entry name="media_encryption">srtp</entry>
|
||||
<entry name="media_encryption_mandatory" overwrite="true">0</entry>
|
||||
</section>
|
||||
</config>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ sip_tcp_port=-1
|
|||
sip_tls_port=-1
|
||||
media_encryption=none
|
||||
update_presence_model_timestamp_before_publish_expires_refresh=1
|
||||
use_rfc2833=1
|
||||
use_info=1
|
||||
|
||||
[net]
|
||||
#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit"
|
||||
|
|
@ -28,19 +30,29 @@ tunnel=disabled
|
|||
auto_download_incoming_voice_recordings=1
|
||||
auto_download_incoming_icalendars=1
|
||||
|
||||
|
||||
[tunnel]
|
||||
host=
|
||||
port=443
|
||||
|
||||
[misc]
|
||||
log_collection_upload_server_url=https://files.linphone.org:443/http-file-transfer-server/hft.php
|
||||
file_transfer_server_url=https://files.linphone.org:443/http-file-transfer-server/hft.php
|
||||
log_collection_upload_server_url=https://files.linphone.org/http-file-transfer-server/hft.php
|
||||
file_transfer_server_url=https://files.linphone.org/http-file-transfer-server/hft.php
|
||||
version_check_url_root=https://download.linphone.org/releases
|
||||
max_calls=10
|
||||
history_max_size=100
|
||||
conference_layout=1
|
||||
hide_empty_chat_rooms=1
|
||||
|
||||
[fec]
|
||||
fec_enabled=1
|
||||
|
||||
[magic_search]
|
||||
return_empty_friends=1
|
||||
|
||||
[chat]
|
||||
imdn_to_everybody_threshold=1
|
||||
|
||||
[ui]
|
||||
contacts_filter=sip.linphone.org
|
||||
|
||||
## End of default rc
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ accept_any_encryption=1
|
|||
guess_hostname=1
|
||||
register_only_when_network_is_up=1
|
||||
auto_net_state_mon=1
|
||||
auto_answer_replacing_calls=0
|
||||
auto_answer_replacing_calls=1
|
||||
ping_with_options=0
|
||||
use_cpim=1
|
||||
zrtp_key_agreements_suites=MS_ZRTP_KEY_AGREEMENT_K255_KYB512
|
||||
zrtp_key_agreements_suites=MS_ZRTP_KEY_AGREEMENT_K255_MLK512,MS_ZRTP_KEY_AGREEMENT_K255_KYB512
|
||||
chat_messages_aggregation_delay=1000
|
||||
chat_messages_aggregation=1
|
||||
update_presence_model_timestamp_before_publish_expires_refresh=1
|
||||
|
|
@ -27,6 +27,9 @@ rls_uri=sips:rls@sip.linphone.org
|
|||
[sound]
|
||||
#remove this property for any application that is not Linphone public version itself
|
||||
ec_calibrator_cool_tones=1
|
||||
disable_ringing=1
|
||||
|
||||
[audio]
|
||||
|
||||
[video]
|
||||
auto_resize_preview_to_keep_ratio=1
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class TelecomManager: ObservableObject {
|
|||
@Published var meetingWaitingRoomDisplayed: Bool = false
|
||||
@Published var meetingWaitingRoomSelected: Address?
|
||||
@Published var meetingWaitingRoomName: String = ""
|
||||
@Published var participantsInvited: Bool = false
|
||||
|
||||
var actionToFulFill: CXCallAction?
|
||||
var callkitAudioSessionActivated: Bool?
|
||||
|
|
@ -430,6 +431,29 @@ class TelecomManager: ObservableObject {
|
|||
func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) {
|
||||
let callLog = call.callLog
|
||||
let callId = callLog?.callId ?? ""
|
||||
if !callInProgress && participantsInvited {
|
||||
if let remoteAddress = call.remoteAddress {
|
||||
let uuid = UUID()
|
||||
let name = remoteAddress.asStringUriOnly()
|
||||
let handle = CXHandle(type: .generic, value: remoteAddress.asStringUriOnly())
|
||||
let startCallAction = CXStartCallAction(call: uuid, handle: handle)
|
||||
let transaction = CXTransaction(action: startCallAction)
|
||||
|
||||
let callInfo = CallInfo.newOutgoingCallInfo(addr: remoteAddress, isSas: false, displayName: name, isVideo: true, isConference: true)
|
||||
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
|
||||
providerDelegate.uuids.updateValue(uuid, forKey: callId)
|
||||
|
||||
setHeldOtherCalls(core: core, exceptCallid: callId)
|
||||
requestTransaction(transaction, action: "startCall")
|
||||
DispatchQueue.main.async {
|
||||
self.participantsInvited = false
|
||||
withAnimation {
|
||||
self.callDisplayed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cstate == .PushIncomingReceived {
|
||||
Log.info("PushIncomingReceived in core delegate, display callkit call")
|
||||
TelecomManager.shared.displayIncomingCall(call: call, handle: "Calling", hasVideo: false, callId: callId, displayName: "Calling")
|
||||
|
|
|
|||
|
|
@ -45,251 +45,15 @@ struct LoginFragment: View {
|
|||
NavigationView {
|
||||
ZStack {
|
||||
GeometryReader { geometry in
|
||||
ScrollView(.vertical) {
|
||||
VStack {
|
||||
ZStack {
|
||||
Image("mountain")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 100)
|
||||
.clipped()
|
||||
|
||||
if isShowBack {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, -75)
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
onBackPressed?()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
}
|
||||
|
||||
Text("assistant_account_login")
|
||||
.default_text_style_white_800(styleSize: 20)
|
||||
.padding(.top, 20)
|
||||
}
|
||||
.padding(.top, 35)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(String(localized: "username")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("username", text: $accountLoginViewModel.username)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.disabled(coreContext.loggedIn)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isNameFocused)
|
||||
|
||||
Text(String(localized: "password")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ZStack(alignment: .trailing) {
|
||||
Group {
|
||||
if isSecured {
|
||||
SecureField("password", text: $accountLoginViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
} else {
|
||||
TextField("password", text: $accountLoginViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
isSecured.toggle()
|
||||
}, label: {
|
||||
Image(self.isSecured ? "eye-slash" : "eye")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
})
|
||||
}
|
||||
.disabled(coreContext.loggedIn)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
|
||||
Button(action: {
|
||||
sharedMainViewModel.changeDisplayProfileMode()
|
||||
self.accountLoginViewModel.login()
|
||||
coreContext.loggingInProgress = true
|
||||
}, label: {
|
||||
Text(coreContext.loggedIn ? "manage_account_delete" : "assistant_account_login")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background((accountLoginViewModel.username.isEmpty || accountLoginViewModel.passwd.isEmpty) ? Color.orangeMain100 : Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.disabled(accountLoginViewModel.username.isEmpty || accountLoginViewModel.passwd.isEmpty)
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
Text(.init(String(format: ("[%@](%@)"), String(localized: "assistant_forgotten_password"), "https://subscribe.linphone.org/")))
|
||||
.underline()
|
||||
.tint(Color.grayMain2c600)
|
||||
.default_text_style_600(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.bottom, 30)
|
||||
|
||||
HStack {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
Text("or")
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
|
||||
NavigationLink(destination: {
|
||||
QrCodeScannerFragment()
|
||||
}, label: {
|
||||
HStack {
|
||||
Image("qr-code")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("assistant_scan_qr_code")
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
|
||||
NavigationLink(isActive: $isLinkSIPActive, destination: {
|
||||
ThirdPartySipAccountWarningFragment(accountLoginViewModel: accountLoginViewModel)
|
||||
}, label: {
|
||||
Text("assistant_login_third_party_sip_account")
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.disabled(!sharedMainViewModel.generalTermsAccepted)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
self.linkActive = "SIP"
|
||||
if !sharedMainViewModel.generalTermsAccepted {
|
||||
withAnimation {
|
||||
self.isShowPopup.toggle()
|
||||
}
|
||||
} else {
|
||||
self.isLinkSIPActive = true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .center) {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("assistant_no_account_yet")
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
NavigationLink(destination: RegisterFragment(registerViewModel: RegisterViewModel()), isActive: $isLinkREGActive, label: { Text("assistant_account_register")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
})
|
||||
.disabled(!sharedMainViewModel.generalTermsAccepted)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal, 10)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
self.linkActive = "REG"
|
||||
if !sharedMainViewModel.generalTermsAccepted {
|
||||
withAnimation {
|
||||
self.isShowPopup.toggle()
|
||||
}
|
||||
} else {
|
||||
self.isLinkREGActive = true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
if #available(iOS 16.4, *) {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
|
||||
if self.isShowPopup {
|
||||
|
|
@ -336,10 +100,257 @@ struct LoginFragment: View {
|
|||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
|
||||
func innerScrollView(geometry: GeometryProxy) -> some View {
|
||||
VStack {
|
||||
ZStack {
|
||||
HStack {
|
||||
if isShowBack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
onBackPressed?()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Color.clear
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text("assistant_account_login")
|
||||
.default_text_style_800(styleSize: 20)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(String(localized: "username")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("username", text: $accountLoginViewModel.username)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.disabled(coreContext.loggedIn)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isNameFocused)
|
||||
|
||||
Text(String(localized: "password")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ZStack(alignment: .trailing) {
|
||||
Group {
|
||||
if isSecured {
|
||||
SecureField("password", text: $accountLoginViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
} else {
|
||||
TextField("password", text: $accountLoginViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
isSecured.toggle()
|
||||
}, label: {
|
||||
Image(self.isSecured ? "eye-slash" : "eye")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
})
|
||||
}
|
||||
.disabled(coreContext.loggedIn)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
|
||||
Button(action: {
|
||||
sharedMainViewModel.changeDisplayProfileMode()
|
||||
self.accountLoginViewModel.login()
|
||||
coreContext.loggingInProgress = true
|
||||
}, label: {
|
||||
Text(coreContext.loggedIn ? "manage_account_delete" : "assistant_account_login")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background((accountLoginViewModel.username.isEmpty || accountLoginViewModel.passwd.isEmpty) ? Color.orangeMain100 : Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.disabled(accountLoginViewModel.username.isEmpty || accountLoginViewModel.passwd.isEmpty)
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
Text(.init(String(format: ("[%@](%@)"), String(localized: "assistant_forgotten_password"), "https://subscribe.linphone.org/")))
|
||||
.underline()
|
||||
.tint(Color.grayMain2c600)
|
||||
.default_text_style_600(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.bottom, 30)
|
||||
|
||||
HStack {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
Text("or")
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
|
||||
NavigationLink(destination: {
|
||||
QrCodeScannerFragment()
|
||||
}, label: {
|
||||
HStack {
|
||||
Image("qr-code")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("assistant_scan_qr_code")
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
|
||||
NavigationLink(isActive: $isLinkSIPActive, destination: {
|
||||
ThirdPartySipAccountWarningFragment(accountLoginViewModel: accountLoginViewModel)
|
||||
}, label: {
|
||||
Text("assistant_login_third_party_sip_account")
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.disabled(!sharedMainViewModel.generalTermsAccepted)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
self.linkActive = "SIP"
|
||||
if !sharedMainViewModel.generalTermsAccepted {
|
||||
withAnimation {
|
||||
self.isShowPopup.toggle()
|
||||
}
|
||||
} else {
|
||||
self.isLinkSIPActive = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .center) {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("assistant_no_account_yet")
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
NavigationLink(destination: RegisterFragment(registerViewModel: RegisterViewModel()), isActive: $isLinkREGActive, label: { Text("assistant_account_register")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
})
|
||||
.disabled(!sharedMainViewModel.generalTermsAccepted)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal, 10)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
self.linkActive = "REG"
|
||||
if !sharedMainViewModel.generalTermsAccepted {
|
||||
withAnimation {
|
||||
self.isShowPopup.toggle()
|
||||
}
|
||||
} else {
|
||||
self.isLinkREGActive = true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
Image("mountain2")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 60)
|
||||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
|
||||
func acceptGeneralTerms() {
|
||||
sharedMainViewModel.changeGeneralTerms()
|
||||
self.isShowPopup.toggle()
|
||||
|
|
|
|||
|
|
@ -29,170 +29,21 @@ struct PermissionsFragment: View {
|
|||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ScrollView(.vertical) {
|
||||
VStack {
|
||||
ZStack {
|
||||
Image("mountain")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 100)
|
||||
.clipped()
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, -75)
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
|
||||
Text("assistant_permissions_title")
|
||||
.default_text_style_white_800(styleSize: 20)
|
||||
.padding(.top, 20)
|
||||
}
|
||||
.padding(.top, 35)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
Text(String(format: String(localized: "assistant_permissions_subtitle"), Bundle.main.displayName))
|
||||
.default_text_style(styleSize: 15)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("bell-ringing")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("assistant_permissions_post_notifications_title")
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(.leading, 10)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("address-book")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
Text(.init(String(format: String(localized: "assistant_permissions_read_contacts_title"), Bundle.main.displayName)))
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(.leading, 10)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("microphone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("assistant_permissions_record_audio_title")
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(.leading, 10)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("assistant_permissions_access_camera_title")
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(.leading, 10)
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.frame(maxHeight: .infinity)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
sharedMainViewModel.changeWelcomeView()
|
||||
}
|
||||
}, label: {
|
||||
Text("assistant_permissions_skip_permissions")
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
permissionManager.getPermissions()
|
||||
} label: {
|
||||
Text("assistant_permissions_grant_all_of_them")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom.isEqual(to: 0.0) ? 20 : 0)
|
||||
if #available(iOS 16.4, *) {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.navigationBarHidden(true)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
.onReceive(permissionManager.$allPermissionsHaveBeenDisplayed, perform: { (granted) in
|
||||
if granted {
|
||||
withAnimation {
|
||||
|
|
@ -201,6 +52,161 @@ struct PermissionsFragment: View {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func innerScrollView(geometry: GeometryProxy) -> some View {
|
||||
VStack {
|
||||
ZStack {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text("assistant_permissions_title")
|
||||
.default_text_style_800(styleSize: 20)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
Text(String(format: String(localized: "assistant_permissions_subtitle"), Bundle.main.displayName))
|
||||
.default_text_style(styleSize: 15)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("bell-ringing")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("assistant_permissions_post_notifications_title")
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(.leading, 10)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("address-book")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
Text(.init(String(format: String(localized: "assistant_permissions_read_contacts_title"), Bundle.main.displayName)))
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(.leading, 10)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("microphone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("assistant_permissions_record_audio_title")
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(.leading, 10)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("assistant_permissions_access_camera_title")
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(.leading, 10)
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.frame(maxHeight: .infinity)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
sharedMainViewModel.changeWelcomeView()
|
||||
}
|
||||
}, label: {
|
||||
Text("assistant_permissions_skip_permissions")
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
permissionManager.getPermissions()
|
||||
} label: {
|
||||
Text("assistant_permissions_grant_all_of_them")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom)
|
||||
|
||||
Image("mountain2")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 60)
|
||||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
|
|
|||
|
|
@ -41,121 +41,14 @@ struct RegisterCodeConfirmationFragment: View {
|
|||
NavigationView {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
ScrollView(.vertical) {
|
||||
VStack {
|
||||
ZStack {
|
||||
Image("mountain")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 100)
|
||||
.clipped()
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, -75)
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
|
||||
Text("assistant_account_register")
|
||||
.default_text_style_white_800(styleSize: 20)
|
||||
.padding(.top, 20)
|
||||
}
|
||||
.padding(.top, 35)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
ZStack {
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Image("confirm_sms_code_illu")
|
||||
.padding(.bottom, -geometry.safeAreaInsets.bottom)
|
||||
}
|
||||
}
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
|
||||
Text(String(format: NSLocalizedString("assistant_account_creation_sms_confirmation_explanation", comment: ""), registerViewModel.phoneNumber))
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
VStack {
|
||||
ZStack {
|
||||
|
||||
HStack(spacing: spaceBetweenBoxes) {
|
||||
otpText(text: registerViewModel.otp1, focused: registerViewModel.otpField.isEmpty)
|
||||
otpText(text: registerViewModel.otp2, focused: registerViewModel.otpField.count == 1)
|
||||
otpText(text: registerViewModel.otp3, focused: registerViewModel.otpField.count == 2)
|
||||
otpText(text: registerViewModel.otp4, focused: registerViewModel.otpField.count == 3)
|
||||
}
|
||||
|
||||
TextField("", text: $registerViewModel.otpField)
|
||||
.default_text_style_600(styleSize: 80)
|
||||
.frame(width: isFocused ? 0 : textFieldOriginalWidth, height: textBoxHeight)
|
||||
.textContentType(.oneTimeCode)
|
||||
.foregroundColor(.clear)
|
||||
.accentColor(.clear)
|
||||
.background(.clear)
|
||||
.keyboardType(.numberPad)
|
||||
.focused($isFocused)
|
||||
.onChange(of: registerViewModel.otpField) { _ in
|
||||
limitText(textLimit)
|
||||
if registerViewModel.otpField.count > 3 {
|
||||
registerViewModel.validateCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.padding(.vertical, 20)
|
||||
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("assistant_account_creation_wrong_phone_number")
|
||||
.default_text_style_orange_600(styleSize: 15)
|
||||
.frame(height: 35)
|
||||
})
|
||||
.padding(.horizontal, 15)
|
||||
.padding(.vertical, 5)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
if #available(iOS 16.4, *) {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
.onAppear {
|
||||
registerViewModel.otpField = ""
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,12 +60,127 @@ struct RegisterCodeConfirmationFragment: View {
|
|||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
func innerScrollView(geometry: GeometryProxy) -> some View {
|
||||
VStack {
|
||||
ZStack {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text("assistant_account_register")
|
||||
.default_text_style_800(styleSize: 20)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
ZStack {
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Image("confirm_sms_code_illu")
|
||||
.padding(.bottom, -geometry.safeAreaInsets.bottom)
|
||||
}
|
||||
}
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
|
||||
Text(String(format: NSLocalizedString("assistant_account_creation_sms_confirmation_explanation", comment: ""), registerViewModel.phoneNumber))
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
VStack {
|
||||
ZStack {
|
||||
|
||||
HStack(spacing: spaceBetweenBoxes) {
|
||||
otpText(text: registerViewModel.otp1, focused: registerViewModel.otpField.isEmpty)
|
||||
otpText(text: registerViewModel.otp2, focused: registerViewModel.otpField.count == 1)
|
||||
otpText(text: registerViewModel.otp3, focused: registerViewModel.otpField.count == 2)
|
||||
otpText(text: registerViewModel.otp4, focused: registerViewModel.otpField.count == 3)
|
||||
}
|
||||
|
||||
TextField("", text: $registerViewModel.otpField)
|
||||
.default_text_style_600(styleSize: 80)
|
||||
.frame(width: isFocused ? 0 : textFieldOriginalWidth, height: textBoxHeight)
|
||||
.textContentType(.oneTimeCode)
|
||||
.foregroundColor(.clear)
|
||||
.accentColor(.clear)
|
||||
.background(.clear)
|
||||
.keyboardType(.numberPad)
|
||||
.focused($isFocused)
|
||||
.onChange(of: registerViewModel.otpField) { _ in
|
||||
limitText(textLimit)
|
||||
if registerViewModel.otpField.count > 3 {
|
||||
registerViewModel.validateCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.padding(.vertical, 20)
|
||||
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("assistant_account_creation_wrong_phone_number")
|
||||
.default_text_style_orange_600(styleSize: 15)
|
||||
.frame(height: 35)
|
||||
})
|
||||
.padding(.horizontal, 15)
|
||||
.padding(.vertical, 5)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("mountain2")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 60)
|
||||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
.onAppear {
|
||||
registerViewModel.otpField = ""
|
||||
}
|
||||
}
|
||||
|
||||
private func otpText(text: String, focused: Bool) -> some View {
|
||||
|
||||
return Text(text)
|
||||
|
|
|
|||
|
|
@ -39,266 +39,15 @@ struct RegisterFragment: View {
|
|||
NavigationView {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
ScrollView(.vertical) {
|
||||
VStack {
|
||||
ZStack {
|
||||
Image("mountain")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 100)
|
||||
.clipped()
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, -75)
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
|
||||
Text("assistant_account_register")
|
||||
.default_text_style_white_800(styleSize: 20)
|
||||
.padding(.top, 20)
|
||||
}
|
||||
.padding(.top, 35)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(String(localized: "username")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("username", text: $registerViewModel.username)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isNameFocused ? Color.orangeMain500 : (!registerViewModel.usernameError.isEmpty ? Color.redDanger500 : Color.gray200), lineWidth: 1)
|
||||
)
|
||||
.focused($isNameFocused)
|
||||
.onChange(of: registerViewModel.username) { _ in
|
||||
if !registerViewModel.usernameError.isEmpty {
|
||||
registerViewModel.usernameError = ""
|
||||
}
|
||||
}
|
||||
|
||||
Text(registerViewModel.usernameError)
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style_600(styleSize: 15)
|
||||
.padding(.bottom)
|
||||
|
||||
Text(String(localized: "phone_number")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
HStack {
|
||||
Menu {
|
||||
Picker("", selection: $registerViewModel.dialPlanValueSelected) {
|
||||
ForEach(Array(registerViewModel.dialPlansLabelList.enumerated()), id: \.offset) { index, dialPlan in
|
||||
Text(dialPlan).tag(registerViewModel.dialPlansShortLabelList[index])
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text(registerViewModel.dialPlanValueSelected)
|
||||
|
||||
Image("caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.blue)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 5)
|
||||
|
||||
Divider()
|
||||
|
||||
TextField("phone_number", text: $registerViewModel.phoneNumber)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.padding(.leading, 5)
|
||||
.keyboardType(.numberPad)
|
||||
.onChange(of: registerViewModel.phoneNumber) { _ in
|
||||
if !registerViewModel.phoneNumberError.isEmpty {
|
||||
registerViewModel.phoneNumberError = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPhoneNumberFocused ? Color.orangeMain500 : (!registerViewModel.phoneNumberError.isEmpty ? Color.redDanger500 : Color.gray200), lineWidth: 1)
|
||||
)
|
||||
.focused($isPhoneNumberFocused)
|
||||
|
||||
Text(registerViewModel.phoneNumberError)
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style_600(styleSize: 15)
|
||||
.padding(.bottom)
|
||||
|
||||
Text(String(localized: "password")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ZStack(alignment: .trailing) {
|
||||
Group {
|
||||
if isSecured {
|
||||
SecureField("password", text: $registerViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
.onChange(of: registerViewModel.passwd) { _ in
|
||||
if !registerViewModel.passwordError.isEmpty {
|
||||
registerViewModel.passwordError = ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TextField("password", text: $registerViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
.onChange(of: registerViewModel.passwd) { _ in
|
||||
if !registerViewModel.passwordError.isEmpty {
|
||||
registerViewModel.passwordError = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
isSecured.toggle()
|
||||
}, label: {
|
||||
Image(self.isSecured ? "eye-slash" : "eye")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
})
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPasswordFocused ? Color.orangeMain500 : (!registerViewModel.passwordError.isEmpty ? Color.redDanger500 : Color.gray200), lineWidth: 1)
|
||||
)
|
||||
|
||||
Text(registerViewModel.passwordError)
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style_600(styleSize: 15)
|
||||
.padding(.bottom)
|
||||
|
||||
NavigationLink(isActive: $registerViewModel.isLinkActive, destination: {
|
||||
RegisterCodeConfirmationFragment(registerViewModel: registerViewModel)
|
||||
}, label: {
|
||||
Text("assistant_account_create")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background((registerViewModel.username.isEmpty || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) ? Color.orangeMain100 : Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.disabled(!registerViewModel.isLinkActive)
|
||||
.padding(.bottom)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
if !(registerViewModel.username.isEmpty || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) {
|
||||
withAnimation {
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("assistant_create_account_using_email_on_our_web_platform")
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
|
||||
Button(action: {
|
||||
UIApplication.shared.open(URL(string: "https://subscribe.linphone.org/register/email")!)
|
||||
}, label: {
|
||||
Text("assistant_web_platform_link")
|
||||
.default_text_style_orange_600(styleSize: 15)
|
||||
.frame(height: 35)
|
||||
})
|
||||
.padding(.horizontal, 15)
|
||||
.padding(.vertical, 5)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .center) {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("assistant_already_have_an_account")
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("assistant_account_login")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
if #available(iOS 16.4, *) {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
|
||||
if self.isShowPopup {
|
||||
|
|
@ -335,11 +84,268 @@ struct RegisterFragment: View {
|
|||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
func innerScrollView(geometry: GeometryProxy) -> some View {
|
||||
VStack {
|
||||
ZStack {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text("assistant_account_register")
|
||||
.default_text_style_800(styleSize: 20)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(String(localized: "username")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("username", text: $registerViewModel.username)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isNameFocused ? Color.orangeMain500 : (!registerViewModel.usernameError.isEmpty ? Color.redDanger500 : Color.gray200), lineWidth: 1)
|
||||
)
|
||||
.focused($isNameFocused)
|
||||
.onChange(of: registerViewModel.username) { _ in
|
||||
if !registerViewModel.usernameError.isEmpty {
|
||||
registerViewModel.usernameError = ""
|
||||
}
|
||||
}
|
||||
|
||||
Text(registerViewModel.usernameError)
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style_600(styleSize: 15)
|
||||
.padding(.bottom)
|
||||
|
||||
Text(String(localized: "phone_number")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
HStack {
|
||||
Menu {
|
||||
Picker("", selection: $registerViewModel.dialPlanValueSelected) {
|
||||
ForEach(Array(registerViewModel.dialPlansLabelList.enumerated()), id: \.offset) { index, dialPlan in
|
||||
Text(dialPlan).tag(registerViewModel.dialPlansShortLabelList[index])
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text(registerViewModel.dialPlanValueSelected)
|
||||
|
||||
Image("caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.blue)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 5)
|
||||
|
||||
Divider()
|
||||
|
||||
TextField("phone_number", text: $registerViewModel.phoneNumber)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.padding(.leading, 5)
|
||||
.keyboardType(.numberPad)
|
||||
.onChange(of: registerViewModel.phoneNumber) { _ in
|
||||
if !registerViewModel.phoneNumberError.isEmpty {
|
||||
registerViewModel.phoneNumberError = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPhoneNumberFocused ? Color.orangeMain500 : (!registerViewModel.phoneNumberError.isEmpty ? Color.redDanger500 : Color.gray200), lineWidth: 1)
|
||||
)
|
||||
.focused($isPhoneNumberFocused)
|
||||
|
||||
Text(registerViewModel.phoneNumberError)
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style_600(styleSize: 15)
|
||||
.padding(.bottom)
|
||||
|
||||
Text(String(localized: "password")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ZStack(alignment: .trailing) {
|
||||
Group {
|
||||
if isSecured {
|
||||
SecureField("password", text: $registerViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
.onChange(of: registerViewModel.passwd) { _ in
|
||||
if !registerViewModel.passwordError.isEmpty {
|
||||
registerViewModel.passwordError = ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TextField("password", text: $registerViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
.onChange(of: registerViewModel.passwd) { _ in
|
||||
if !registerViewModel.passwordError.isEmpty {
|
||||
registerViewModel.passwordError = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
isSecured.toggle()
|
||||
}, label: {
|
||||
Image(self.isSecured ? "eye-slash" : "eye")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
})
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPasswordFocused ? Color.orangeMain500 : (!registerViewModel.passwordError.isEmpty ? Color.redDanger500 : Color.gray200), lineWidth: 1)
|
||||
)
|
||||
|
||||
Text(registerViewModel.passwordError)
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style_600(styleSize: 15)
|
||||
.padding(.bottom)
|
||||
|
||||
NavigationLink(isActive: $registerViewModel.isLinkActive, destination: {
|
||||
RegisterCodeConfirmationFragment(registerViewModel: registerViewModel)
|
||||
}, label: {
|
||||
Text("assistant_account_create")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background((registerViewModel.username.isEmpty || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) ? Color.orangeMain100 : Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.disabled(!registerViewModel.isLinkActive)
|
||||
.padding(.bottom)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
if !(registerViewModel.username.isEmpty || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) {
|
||||
withAnimation {
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("assistant_create_account_using_email_on_our_web_platform")
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
|
||||
Button(action: {
|
||||
UIApplication.shared.open(URL(string: "https://subscribe.linphone.org/register/email")!)
|
||||
}, label: {
|
||||
Text("assistant_web_platform_link")
|
||||
.default_text_style_orange_600(styleSize: 15)
|
||||
.frame(height: 35)
|
||||
})
|
||||
.padding(.horizontal, 15)
|
||||
.padding(.vertical, 5)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .center) {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("assistant_already_have_an_account")
|
||||
.default_text_style(styleSize: 15)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("assistant_account_login")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
Image("mountain2")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 60)
|
||||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
|
|
|||
|
|
@ -36,207 +36,216 @@ struct ThirdPartySipAccountLoginFragment: View {
|
|||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ScrollView(.vertical) {
|
||||
VStack {
|
||||
ZStack {
|
||||
Image("mountain")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 100)
|
||||
.clipped()
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, -75)
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
accountLoginViewModel.domain = "sip.linphone.org"
|
||||
accountLoginViewModel.transportType = "TLS"
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
|
||||
Text("assistant_login_third_party_sip_account")
|
||||
.default_text_style_white_800(styleSize: 20)
|
||||
.padding(.top, 20)
|
||||
}
|
||||
.padding(.top, 35)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(String(localized: "username")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("username", text: $accountLoginViewModel.username)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.disabled(coreContext.loggedIn)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isNameFocused)
|
||||
|
||||
Text(String(localized: "password")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ZStack(alignment: .trailing) {
|
||||
Group {
|
||||
if isSecured {
|
||||
SecureField("password", text: $accountLoginViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
} else {
|
||||
TextField("password", text: $accountLoginViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
isSecured.toggle()
|
||||
}, label: {
|
||||
Image(self.isSecured ? "eye-slash" : "eye")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
})
|
||||
}
|
||||
.disabled(coreContext.loggedIn)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
|
||||
Text(String(localized: "sip_address_domain")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("sip.linphone.org", text: $accountLoginViewModel.domain)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.disabled(coreContext.loggedIn)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isDomainFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isDomainFocused)
|
||||
|
||||
Text(String(localized: "sip_address_display_name"))
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("sip_address_display_name", text: $accountLoginViewModel.displayName)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.disabled(coreContext.loggedIn)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isDisplayNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isDisplayNameFocused)
|
||||
|
||||
Text(String(localized: "assistant_sip_account_transport_protocol"))
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
Menu {
|
||||
Button("TLS") {accountLoginViewModel.transportType = "TLS"}
|
||||
Button("TCP") {accountLoginViewModel.transportType = "TCP"}
|
||||
Button("UDP") {accountLoginViewModel.transportType = "UDP"}
|
||||
} label: {
|
||||
Text(accountLoginViewModel.transportType)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Image("caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
self.accountLoginViewModel.login()
|
||||
}, label: {
|
||||
Text(coreContext.loggedIn ? "manage_account_delete" : "assistant_account_login")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(
|
||||
(accountLoginViewModel.username.isEmpty || accountLoginViewModel.passwd.isEmpty || accountLoginViewModel.domain.isEmpty)
|
||||
? Color.orangeMain100
|
||||
: Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.disabled(accountLoginViewModel.username.isEmpty || accountLoginViewModel.passwd.isEmpty || accountLoginViewModel.domain.isEmpty)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom.isEqual(to: 0.0) ? 20 : 0)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
if #available(iOS 16.4, *) {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
}
|
||||
|
||||
func innerScrollView(geometry: GeometryProxy) -> some View {
|
||||
VStack {
|
||||
ZStack {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
accountLoginViewModel.domain = "sip.linphone.org"
|
||||
accountLoginViewModel.transportType = "TLS"
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text("assistant_login_third_party_sip_account")
|
||||
.default_text_style_800(styleSize: 20)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(String(localized: "username")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("username", text: $accountLoginViewModel.username)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.disabled(coreContext.loggedIn)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isNameFocused)
|
||||
|
||||
Text(String(localized: "password")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ZStack(alignment: .trailing) {
|
||||
Group {
|
||||
if isSecured {
|
||||
SecureField("password", text: $accountLoginViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
} else {
|
||||
TextField("password", text: $accountLoginViewModel.passwd)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
isSecured.toggle()
|
||||
}, label: {
|
||||
Image(self.isSecured ? "eye-slash" : "eye")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
})
|
||||
}
|
||||
.disabled(coreContext.loggedIn)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
|
||||
Text(String(localized: "sip_address_domain")+"*")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("sip.linphone.org", text: $accountLoginViewModel.domain)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.disabled(coreContext.loggedIn)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isDomainFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isDomainFocused)
|
||||
|
||||
Text(String(localized: "sip_address_display_name"))
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("sip_address_display_name", text: $accountLoginViewModel.displayName)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.disabled(coreContext.loggedIn)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isDisplayNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
.focused($isDisplayNameFocused)
|
||||
|
||||
Text(String(localized: "assistant_sip_account_transport_protocol"))
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
Menu {
|
||||
Button("TLS") {accountLoginViewModel.transportType = "TLS"}
|
||||
Button("TCP") {accountLoginViewModel.transportType = "TCP"}
|
||||
Button("UDP") {accountLoginViewModel.transportType = "UDP"}
|
||||
} label: {
|
||||
Text(accountLoginViewModel.transportType)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Image("caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
self.accountLoginViewModel.login()
|
||||
}, label: {
|
||||
Text(coreContext.loggedIn ? "manage_account_delete" : "assistant_account_login")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(
|
||||
(accountLoginViewModel.username.isEmpty || accountLoginViewModel.passwd.isEmpty || accountLoginViewModel.domain.isEmpty)
|
||||
? Color.orangeMain100
|
||||
: Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.disabled(accountLoginViewModel.username.isEmpty || accountLoginViewModel.passwd.isEmpty || accountLoginViewModel.domain.isEmpty)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom)
|
||||
|
||||
Image("mountain2")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 60)
|
||||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,154 +30,160 @@ struct ThirdPartySipAccountWarningFragment: View {
|
|||
var body: some View {
|
||||
NavigationView {
|
||||
GeometryReader { geometry in
|
||||
ScrollView(.vertical) {
|
||||
VStack {
|
||||
ZStack {
|
||||
Image("mountain")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 100)
|
||||
.clipped()
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, -75)
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
|
||||
Text("assistant_login_third_party_sip_account")
|
||||
.default_text_style_white_800(styleSize: 20)
|
||||
.padding(.top, 20)
|
||||
}
|
||||
.padding(.top, 35)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Spacer()
|
||||
HStack(alignment: .center) {
|
||||
Image("chat-teardrop-text-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
.padding(.horizontal)
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
.padding(.horizontal)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.bottom, 40)
|
||||
|
||||
Text(.init(String(format: String(localized: "assistant_third_party_sip_account_warning_explanation"), Bundle.main.displayName)))
|
||||
.default_text_style(styleSize: 15)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Text("[linphone.org/contact](https://linphone.org/contact)")
|
||||
.tint(Color.orangeMain500)
|
||||
.default_text_style_orange_600(styleSize: 15)
|
||||
.frame(height: 35)
|
||||
}
|
||||
.padding(.horizontal, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("assistant_third_party_sip_account_create_linphone_account")
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
|
||||
NavigationLink(destination: {
|
||||
ThirdPartySipAccountLoginFragment(accountLoginViewModel: accountLoginViewModel)
|
||||
}, label: {
|
||||
Text("assistant_third_party_sip_account_warning_ok")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom.isEqual(to: 0.0) ? 20 : 0)
|
||||
if #available(iOS 16.4, *) {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
func innerScrollView(geometry: GeometryProxy) -> some View {
|
||||
VStack {
|
||||
ZStack {
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text("assistant_login_third_party_sip_account")
|
||||
.default_text_style_800(styleSize: 20)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Spacer()
|
||||
HStack(alignment: .center) {
|
||||
Image("chat-teardrop-text-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
.padding(20)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
.padding(.horizontal)
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
.padding(20)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
.padding(.horizontal)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.bottom, 40)
|
||||
|
||||
Text(.init(String(format: String(localized: "assistant_third_party_sip_account_warning_explanation"), Bundle.main.displayName)))
|
||||
.default_text_style(styleSize: 15)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Text("[linphone.org/contact](https://linphone.org/contact)")
|
||||
.tint(Color.orangeMain500)
|
||||
.default_text_style_orange_600(styleSize: 15)
|
||||
.frame(height: 35)
|
||||
}
|
||||
.padding(.horizontal, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical)
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("assistant_third_party_sip_account_create_linphone_account")
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
|
||||
NavigationLink(destination: {
|
||||
ThirdPartySipAccountLoginFragment(accountLoginViewModel: accountLoginViewModel)
|
||||
}, label: {
|
||||
Text("assistant_third_party_sip_account_warning_ok")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom)
|
||||
|
||||
Image("mountain2")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 60)
|
||||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
|
|
|||
|
|
@ -128,8 +128,10 @@ class AccountLoginViewModel: ObservableObject {
|
|||
// Also set the newly added account as default
|
||||
core.defaultAccount = account
|
||||
|
||||
self.domain = "sip.linphone.org"
|
||||
self.transportType = "TLS"
|
||||
DispatchQueue.main.async {
|
||||
self.domain = "sip.linphone.org"
|
||||
self.transportType = "TLS"
|
||||
}
|
||||
|
||||
} catch { NSLog(error.localizedDescription) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
import Foundation
|
||||
import linphonesw
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
// swiftlint:disable line_length
|
||||
// swiftlint:disable type_body_length
|
||||
|
|
|
|||
|
|
@ -2262,16 +2262,14 @@ struct CallView: View {
|
|||
HStack(spacing: 0) {
|
||||
VStack {
|
||||
Button {
|
||||
if callViewModel.isOneOneCall && callViewModel.remoteAddress != nil {
|
||||
callViewModel.createOneToOneChatRoomWith(remote: callViewModel.remoteAddress!)
|
||||
}
|
||||
callViewModel.createConversation()
|
||||
} label: {
|
||||
HStack {
|
||||
if !callViewModel.operationInProgress {
|
||||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(callViewModel.isOneOneCall ? .white : Color.gray500)
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
} else {
|
||||
ProgressView()
|
||||
|
|
@ -2279,8 +2277,13 @@ struct CallView: View {
|
|||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.frame(width: 32, height: 32, alignment: .center)
|
||||
.onDisappear {
|
||||
if callViewModel.isOneOneCall && callViewModel.displayedConversation != nil {
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!)
|
||||
if callViewModel.displayedConversation != nil {
|
||||
indexPage = 2
|
||||
self.conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!)
|
||||
callViewModel.displayedConversation = nil
|
||||
withAnimation {
|
||||
telecomManager.callDisplayed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2288,9 +2291,8 @@ struct CallView: View {
|
|||
}
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: buttonSize))
|
||||
.frame(width: buttonSize, height: buttonSize)
|
||||
.background(callViewModel.isOneOneCall ? Color.gray500 : .white)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.disabled(!callViewModel.isOneOneCall)
|
||||
|
||||
Text("call_action_show_messages")
|
||||
.foregroundStyle(.white)
|
||||
|
|
@ -2631,16 +2633,14 @@ struct CallView: View {
|
|||
|
||||
VStack {
|
||||
Button {
|
||||
if callViewModel.isOneOneCall && callViewModel.remoteAddress != nil {
|
||||
callViewModel.createOneToOneChatRoomWith(remote: callViewModel.remoteAddress!)
|
||||
}
|
||||
callViewModel.createConversation()
|
||||
} label: {
|
||||
HStack {
|
||||
if !callViewModel.operationInProgress {
|
||||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(callViewModel.isOneOneCall ? .white : Color.gray500)
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
} else {
|
||||
ProgressView()
|
||||
|
|
@ -2648,7 +2648,7 @@ struct CallView: View {
|
|||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.frame(width: 32, height: 32, alignment: .center)
|
||||
.onDisappear {
|
||||
if callViewModel.isOneOneCall && callViewModel.displayedConversation != nil {
|
||||
if callViewModel.displayedConversation != nil {
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!)
|
||||
}
|
||||
}
|
||||
|
|
@ -2657,9 +2657,8 @@ struct CallView: View {
|
|||
}
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: buttonSize))
|
||||
.frame(width: buttonSize, height: buttonSize)
|
||||
.background(callViewModel.isOneOneCall ? Color.gray500 : .white)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.disabled(!callViewModel.isOneOneCall)
|
||||
|
||||
Text("call_action_show_messages")
|
||||
.foregroundStyle(.white)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import Foundation
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class CallMediaEncryptionModel: ObservableObject {
|
||||
var coreContext = CoreContext.shared
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import Foundation
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class SentVideoWindow: ObservableObject {
|
||||
var widthFactor: CGFloat = 1
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import Foundation
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class ParticipantModel: ObservableObject {
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import SwiftUI
|
|||
import linphonesw
|
||||
import AVFAudio
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
// swiftlint:disable line_length
|
||||
// swiftlint:disable type_body_length
|
||||
|
|
@ -95,6 +96,9 @@ class CallViewModel: ObservableObject {
|
|||
} catch _ {
|
||||
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: Notification.Name("CallViewModelReset"), object: nil, queue: nil) { notification in
|
||||
self.resetCallView()
|
||||
}
|
||||
}
|
||||
|
||||
func resetCallView() {
|
||||
|
|
@ -580,15 +584,15 @@ class CallViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
})
|
||||
}, onActiveSpeakerParticipantDevice: { (conference: Conference, participantDevice: ParticipantDevice) in
|
||||
if participantDevice.address != nil {
|
||||
}, onActiveSpeakerParticipantDevice: { (conference: Conference, participantDevice: ParticipantDevice?) in
|
||||
if participantDevice != nil && participantDevice!.address != nil {
|
||||
let activeSpeakerParticipantBis = self.activeSpeakerParticipant
|
||||
|
||||
let activeSpeakerParticipantTmp = ParticipantModel(
|
||||
address: participantDevice.address!,
|
||||
address: participantDevice!.address!,
|
||||
isJoining: false,
|
||||
onPause: participantDevice.state == .OnHold,
|
||||
isMuted: participantDevice.isMuted
|
||||
onPause: participantDevice!.state == .OnHold,
|
||||
isMuted: participantDevice!.isMuted
|
||||
)
|
||||
|
||||
var activeSpeakerNameTmp = ""
|
||||
|
|
@ -1162,141 +1166,179 @@ class CallViewModel: ObservableObject {
|
|||
Log.info("\(CallViewModel.TAG) \(list.count) participants added to conference")
|
||||
}
|
||||
|
||||
func createOneToOneChatRoomWith(remote: Address) {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
let account = core.defaultAccount
|
||||
if account == nil {
|
||||
Log.error(
|
||||
"\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())!"
|
||||
)
|
||||
return
|
||||
func createConversation() {
|
||||
if currentCall != nil {
|
||||
self.operationInProgress = true
|
||||
CoreContext.shared.doOnCoreQueue { _ in
|
||||
let existingConversation = self.lookupCurrentCallConversation(call: self.currentCall!)
|
||||
if existingConversation != nil {
|
||||
Log.info("\(CallViewModel.TAG) Found existing conversation \(LinphoneUtils.getConversationId(chatRoom: existingConversation!)), going to it")
|
||||
|
||||
let model = ConversationModel(chatRoom: existingConversation!)
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else {
|
||||
Log.info("\(CallViewModel.TAG) No existing conversation was found, let's create it")
|
||||
self.createCurrentCallConversation(call: self.currentCall!)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func lookupCurrentCallConversation(call: Call) -> ChatRoom? {
|
||||
let localAddress = call.callLog?.localAddress
|
||||
let remoteAddress = call.remoteAddress
|
||||
|
||||
let params: ConferenceParams? = nil
|
||||
let existingConversation: ChatRoom?
|
||||
if call.conference != nil {
|
||||
existingConversation = call.core?.searchChatRoom(
|
||||
params: params,
|
||||
localAddr: localAddress,
|
||||
remoteAddr: remoteAddress,
|
||||
participants: []
|
||||
)
|
||||
} else {
|
||||
let participants = [remoteAddress!]
|
||||
existingConversation = call.core?.searchChatRoom(
|
||||
params: params,
|
||||
localAddr: localAddress,
|
||||
remoteAddr: nil,
|
||||
participants: participants
|
||||
)
|
||||
}
|
||||
|
||||
if existingConversation != nil {
|
||||
Log.info("\(CallViewModel.TAG) Found existing conversation \(existingConversation!.peerAddress?.asStringUriOnly() ?? "") found for current call with local address \(localAddress?.asStringUriOnly() ?? "") and remote address \(remoteAddress?.asStringUriOnly() ?? "")")
|
||||
} else {
|
||||
Log.warn("\(CallViewModel.TAG) No existing conversation found for current call with local address \(localAddress?.asStringUriOnly() ?? "") and remote address \(remoteAddress?.asStringUriOnly() ?? "")")
|
||||
}
|
||||
|
||||
return existingConversation
|
||||
}
|
||||
|
||||
func createCurrentCallConversation(call: Call) {
|
||||
if let remoteAddress = call.remoteAddress {
|
||||
let participants = [remoteAddress]
|
||||
let core = call.core
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
do {
|
||||
let params: ChatRoomParams = try core.createDefaultChatRoomParams()
|
||||
params.groupEnabled = false
|
||||
params.subject = "Dummy subject"
|
||||
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
|
||||
let sameDomain = remote.domain == account?.params?.domain ?? ""
|
||||
if StartConversationViewModel.isEndToEndEncryptionMandatory() && sameDomain {
|
||||
Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating a E2E conversation")
|
||||
params.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.encryptionEnabled = true
|
||||
} else if !StartConversationViewModel.isEndToEndEncryptionMandatory() {
|
||||
if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating a E2E conversation"
|
||||
)
|
||||
params.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.encryptionEnabled = true
|
||||
} else {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
|
||||
)
|
||||
params.backend = ChatRoom.Backend.Basic
|
||||
params.encryptionEnabled = false
|
||||
if let params = getChatRoomParams(call: call) {
|
||||
do {
|
||||
if core != nil {
|
||||
let chatRoom = try core!.createChatRoom(params: params, participants: participants)
|
||||
if params.chatParams?.backend == ChatRoom.Backend.FlexisipChat {
|
||||
if chatRoom.state == ChatRoom.State.Created {
|
||||
let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(CallViewModel.TAG) 1-1 conversation \(chatRoomId) has been created")
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else {
|
||||
Log.info("\(CallViewModel.TAG) Conversation isn't in Created state yet, wait for it")
|
||||
self.chatRoomAddDelegate(core: core!, chatRoom: chatRoom)
|
||||
}
|
||||
} else {
|
||||
let id = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(CallViewModel.TAG) Conversation successfully created \(id)")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} catch {
|
||||
Log.error(
|
||||
"\(StartConversationViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())"
|
||||
"\(CallViewModel.TAG) Failed to create 1-1 conversation with \(remoteAddress.asStringUriOnly())"
|
||||
)
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_invalid_participant_error"
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getChatRoomParams(call: Call) -> ConferenceParams? {
|
||||
let localAddress = call.callLog?.localAddress
|
||||
let remoteAddress = call.remoteAddress
|
||||
let core = call.core
|
||||
if let account = LinphoneUtils.getAccountForAddress(address: localAddress!) ?? LinphoneUtils.getDefaultAccount() {
|
||||
do {
|
||||
let params = try coreContext.mCore.createConferenceParams(conference: call.conference)
|
||||
params.chatEnabled = true
|
||||
params.groupEnabled = false
|
||||
params.subject = NSLocalizedString("conversation_one_to_one_hidden_subject", comment: "")
|
||||
params.account = account
|
||||
|
||||
let participants = [remote]
|
||||
let localAddress = account?.params?.identityAddress
|
||||
let existingChatRoom = core.searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: participants)
|
||||
if existingChatRoom == nil {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account "
|
||||
+ "\(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it"
|
||||
)
|
||||
let chatRoom = try core.createChatRoom(params: params, localAddr: localAddress, participants: participants)
|
||||
if params.backend == ChatRoom.Backend.FlexisipChat {
|
||||
if chatRoom.state == ChatRoom.State.Created {
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(id) has been created")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet, wait for it")
|
||||
self.chatRoomAddDelegate(core: core, chatRoom: chatRoom)
|
||||
}
|
||||
} else {
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id)")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.warn(
|
||||
"\(StartConversationViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!"
|
||||
)
|
||||
if let chatParams = params.chatParams {
|
||||
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
|
||||
let model = ConversationModel(chatRoom: existingChatRoom!)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
let sameDomain = remoteAddress?.domain == CorePreferences.defaultDomain && remoteAddress?.domain == account.params?.domain
|
||||
if account.params != nil && (account.params!.instantMessagingEncryptionMandatory && sameDomain) {
|
||||
Log.info(
|
||||
"\(CallViewModel.TAG) Account is in secure mode & domain matches, requesting E2E encryption"
|
||||
)
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else if account.params != nil && !account.params!.instantMessagingEncryptionMandatory {
|
||||
if core != nil && LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core!) {
|
||||
Log.info(
|
||||
"\(CallViewModel.TAG) Account is in interop mode but LIME is available, requesting E2E encryption"
|
||||
)
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else {
|
||||
Log.info(
|
||||
"\(CallViewModel.TAG) Account is in interop mode but LIME isn't available, disabling E2E encryption"
|
||||
)
|
||||
chatParams.backend = ChatRoom.Backend.Basic
|
||||
params.securityLevel = Conference.SecurityLevel.None
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
Log.error(
|
||||
"\(CallViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remoteAddress?.asStringUriOnly() ?? "")"
|
||||
)
|
||||
return nil
|
||||
}
|
||||
return params
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())!")
|
||||
Log.error("\(CallViewModel.TAG) Can't create ConferenceParams \(remoteAddress?.asStringUriOnly() ?? "")")
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func goToConversation(model: ConversationModel) {
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1331,13 +1373,13 @@ class CallViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
} else if state == ChatRoom.State.CreationFailed {
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ struct ContactInnerFragment: View {
|
|||
}
|
||||
|
||||
Spacer()
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count
|
||||
&& !contactAvatarModel.nativeUri.isEmpty {
|
||||
|
||||
if !contactAvatarModel.nativeUri.isEmpty {
|
||||
Button(action: {
|
||||
editNativeContact()
|
||||
}, label: {
|
||||
|
|
@ -118,17 +118,9 @@ struct ContactInnerFragment: View {
|
|||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count {
|
||||
if contactViewModel.indexDisplayedFriend != nil {
|
||||
Avatar(contactAvatarModel: contactAvatarModel, avatarSize: 100)
|
||||
} else if contactViewModel.indexDisplayedFriend != nil
|
||||
&& contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
if contactViewModel.indexDisplayedFriend != nil
|
||||
&& contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count {
|
||||
|
||||
Text(contactAvatarModel.name)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
|
|
@ -144,7 +136,6 @@ struct ContactInnerFragment: View {
|
|||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
}
|
||||
.frame(minHeight: 150)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
@ -172,17 +163,18 @@ struct ContactInnerFragment: View {
|
|||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.foregroundStyle(contactAvatarModel.address.isEmpty ? Color.grayMain2c400 : Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.background(contactAvatarModel.address.isEmpty ? Color.grayMain2c100 : Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("contact_call_action")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
.disabled(contactAvatarModel.address.isEmpty)
|
||||
|
||||
Spacer()
|
||||
|
||||
|
|
@ -204,17 +196,18 @@ struct ContactInnerFragment: View {
|
|||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.foregroundStyle(contactAvatarModel.address.isEmpty ? Color.grayMain2c400 : Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.background(contactAvatarModel.address.isEmpty ? Color.grayMain2c100 : Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("contact_message_action")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
.disabled(contactAvatarModel.address.isEmpty)
|
||||
|
||||
Spacer()
|
||||
|
||||
|
|
@ -236,17 +229,18 @@ struct ContactInnerFragment: View {
|
|||
Image("video-camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.foregroundStyle(contactAvatarModel.address.isEmpty ? Color.grayMain2c400 : Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.background(contactAvatarModel.address.isEmpty ? Color.grayMain2c100 : Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("contact_video_call_action")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
.disabled(contactAvatarModel.address.isEmpty)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,9 +136,7 @@ struct EditContactFragment: View {
|
|||
.padding(.top, 2)
|
||||
.disabled(editContactViewModel.firstName.isEmpty)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
addOrEditFriend()
|
||||
}
|
||||
addOrEditFriend()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
@ -532,25 +530,28 @@ struct EditContactFragment: View {
|
|||
contact: newContact, linphoneFriend: true, existingFriend: editContactViewModel.selectedEditFriend) {
|
||||
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
if editContactViewModel.selectedEditFriend != nil
|
||||
&& editContactViewModel.selectedEditFriend!.name != editContactViewModel.firstName + " " + editContactViewModel.lastName {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
let result = ContactsManager.shared.lastSearch.firstIndex(where: {
|
||||
$0.friend!.name == newContact.firstName + " " + newContact.lastName
|
||||
})
|
||||
contactViewModel.indexDisplayedFriend = result
|
||||
DispatchQueue.main.async {
|
||||
if editContactViewModel.selectedEditFriend != nil {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
let result = ContactsManager.shared.lastSearch.firstIndex(where: {
|
||||
$0.friend!.name == newContact.firstName + " " + newContact.lastName
|
||||
})
|
||||
contactViewModel.indexDisplayedFriend = result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delayColorDismiss()
|
||||
if editContactViewModel.selectedEditFriend == nil {
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
|
||||
delayColorDismiss()
|
||||
if editContactViewModel.selectedEditFriend == nil {
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
}
|
||||
} else {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dismiss()
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import linphonesw
|
|||
import Combine
|
||||
|
||||
class ContactAvatarModel: ObservableObject, Identifiable {
|
||||
let id = UUID()
|
||||
|
||||
let friend: Friend?
|
||||
|
||||
|
|
@ -46,24 +47,23 @@ class ContactAvatarModel: ObservableObject, Identifiable {
|
|||
self.name = name
|
||||
self.address = address
|
||||
var addressesTmp: [String] = []
|
||||
if friend != nil {
|
||||
friend!.addresses.forEach { address in
|
||||
if let friend = friend {
|
||||
friend.addresses.forEach { address in
|
||||
addressesTmp.append(address.asStringUriOnly())
|
||||
}
|
||||
}
|
||||
self.addresses = addressesTmp
|
||||
self.nativeUri = friend?.nativeUri ?? ""
|
||||
self.withPresence = withPresence
|
||||
if friend != nil &&
|
||||
withPresence == true {
|
||||
if let friend = friend, withPresence == true {
|
||||
self.lastPresenceInfo = ""
|
||||
|
||||
self.presenceStatus = friend!.consolidatedPresence
|
||||
self.presenceStatus = friend.consolidatedPresence
|
||||
|
||||
if friend!.consolidatedPresence == .Online || friend!.consolidatedPresence == .Busy {
|
||||
if friend!.consolidatedPresence == .Online || friend!.presenceModel!.latestActivityTimestamp != -1 {
|
||||
self.lastPresenceInfo = (friend!.consolidatedPresence == .Online) ?
|
||||
"Online" : getCallTime(startDate: friend!.presenceModel!.latestActivityTimestamp)
|
||||
if friend.consolidatedPresence == .Online || friend.consolidatedPresence == .Busy {
|
||||
if friend.consolidatedPresence == .Online || friend.presenceModel?.latestActivityTimestamp != -1 {
|
||||
self.lastPresenceInfo = (friend.consolidatedPresence == .Online) ?
|
||||
"Online" : getCallTime(startDate: friend.presenceModel!.latestActivityTimestamp)
|
||||
} else {
|
||||
self.lastPresenceInfo = "Away"
|
||||
}
|
||||
|
|
@ -86,11 +86,12 @@ class ContactAvatarModel: ObservableObject, Identifiable {
|
|||
func addFriendDelegate() {
|
||||
friendDelegate = FriendDelegateStub(onPresenceReceived: { (friend: Friend) in
|
||||
let latestActivityTimestamp = friend.presenceModel?.latestActivityTimestamp ?? -1
|
||||
let consolidatedPresenceTmp = friend.consolidatedPresence
|
||||
DispatchQueue.main.async {
|
||||
self.presenceStatus = friend.consolidatedPresence
|
||||
if friend.consolidatedPresence == .Online || friend.consolidatedPresence == .Busy {
|
||||
if friend.consolidatedPresence == .Online || latestActivityTimestamp != -1 {
|
||||
self.lastPresenceInfo = friend.consolidatedPresence == .Online ?
|
||||
self.presenceStatus = consolidatedPresenceTmp
|
||||
if consolidatedPresenceTmp == .Online || consolidatedPresenceTmp == .Busy {
|
||||
if consolidatedPresenceTmp == .Online || latestActivityTimestamp != -1 {
|
||||
self.lastPresenceInfo = consolidatedPresenceTmp == .Online ?
|
||||
"Online" : self.getCallTime(startDate: latestActivityTimestamp)
|
||||
} else {
|
||||
self.lastPresenceInfo = "Away"
|
||||
|
|
@ -100,7 +101,10 @@ class ContactAvatarModel: ObservableObject, Identifiable {
|
|||
}
|
||||
}
|
||||
})
|
||||
friend?.addDelegate(delegate: friendDelegate!)
|
||||
|
||||
if friend != nil && friendDelegate != nil {
|
||||
friend!.addDelegate(delegate: friendDelegate!)
|
||||
}
|
||||
}
|
||||
|
||||
func removeFriendDelegate() {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import linphonesw
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
// swiftlint:disable line_length
|
||||
class ContactViewModel: ObservableObject {
|
||||
|
|
@ -43,7 +44,7 @@ class ContactViewModel: ObservableObject {
|
|||
let account = core.defaultAccount
|
||||
if account == nil {
|
||||
Log.error(
|
||||
"\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())!"
|
||||
"\(ConversationForwardMessageViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -53,37 +54,42 @@ class ContactViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
do {
|
||||
let params: ChatRoomParams = try core.createDefaultChatRoomParams()
|
||||
let params = try core.createConferenceParams(conference: nil)
|
||||
params.chatEnabled = true
|
||||
params.groupEnabled = false
|
||||
params.subject = "Dummy subject"
|
||||
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
params.subject = NSLocalizedString("conversation_one_to_one_hidden_subject", comment: "")
|
||||
params.account = account
|
||||
|
||||
let sameDomain = remote.domain == account?.params?.domain ?? ""
|
||||
if StartConversationViewModel.isEndToEndEncryptionMandatory() && sameDomain {
|
||||
Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating a E2E conversation")
|
||||
params.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.encryptionEnabled = true
|
||||
} else if !StartConversationViewModel.isEndToEndEncryptionMandatory() {
|
||||
guard let chatParams = params.chatParams else { return }
|
||||
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
|
||||
let sameDomain = remote.domain == CorePreferences.defaultDomain && remote.domain == account!.params?.domain
|
||||
if account!.params != nil && (account!.params!.instantMessagingEncryptionMandatory && sameDomain) {
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) Account is in secure mode & domain matches, creating an E2E encrypted conversation")
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else if account!.params != nil && (!account!.params!.instantMessagingEncryptionMandatory) {
|
||||
if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating a E2E conversation"
|
||||
"\(ConversationForwardMessageViewModel.TAG) Account is in interop mode but LIME is available, creating an E2E encrypted conversation"
|
||||
)
|
||||
params.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.encryptionEnabled = true
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
|
||||
"\(ConversationForwardMessageViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
|
||||
)
|
||||
params.backend = ChatRoom.Backend.Basic
|
||||
params.encryptionEnabled = false
|
||||
chatParams.backend = ChatRoom.Backend.Basic
|
||||
params.securityLevel = Conference.SecurityLevel.None
|
||||
}
|
||||
} else {
|
||||
Log.error(
|
||||
"\(StartConversationViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())"
|
||||
"\(ConversationForwardMessageViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())"
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_invalid_participant_error"
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
return
|
||||
|
|
@ -94,85 +100,56 @@ class ContactViewModel: ObservableObject {
|
|||
let existingChatRoom = core.searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: participants)
|
||||
if existingChatRoom == nil {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account "
|
||||
+ "\(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it"
|
||||
"\(ConversationForwardMessageViewModel.TAG) No existing 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it"
|
||||
)
|
||||
let chatRoom = try core.createChatRoom(params: params, localAddr: localAddress, participants: participants)
|
||||
if params.backend == ChatRoom.Backend.FlexisipChat {
|
||||
if chatRoom.state == ChatRoom.State.Created {
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(id) has been created")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
do {
|
||||
let chatRoom = try core.createChatRoom(params: params, participants: participants)
|
||||
if chatParams.backend == ChatRoom.Backend.FlexisipChat {
|
||||
let state = chatRoom.state
|
||||
if state == ChatRoom.State.Created {
|
||||
let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) 1-1 conversation \(chatRoomId) has been created")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) Conversation isn't in Created state yet (state is \(state)), wait for it")
|
||||
self.chatRoomAddDelegate(core: core, chatRoom: chatRoom)
|
||||
}
|
||||
} else {
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet, wait for it")
|
||||
self.chatRoomAddDelegate(core: core, chatRoom: chatRoom)
|
||||
}
|
||||
} else {
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id)")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) Conversation successfully created \(chatRoomId)")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Log.error("\(ConversationForwardMessageViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.warn(
|
||||
"\(StartConversationViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!"
|
||||
"\(ConversationForwardMessageViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!"
|
||||
)
|
||||
|
||||
let model = ConversationModel(chatRoom: existingChatRoom!)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class ContactsListViewModel: ObservableObject {
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class EditContactViewModel: ObservableObject {
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class FavoriteContactsListViewModel: ObservableObject {
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ struct ContentView: View {
|
|||
var body: some View {
|
||||
let pub = NotificationCenter.default
|
||||
.publisher(for: NSNotification.Name("ContactLoaded"))
|
||||
let pub2 = NotificationCenter.default
|
||||
.publisher(for: NSNotification.Name("ContactAdded"))
|
||||
.compactMap { $0.userInfo?["address"] as? String }
|
||||
let imageChanged = NotificationCenter.default
|
||||
.publisher(for: NSNotification.Name("ImageChanged"))
|
||||
GeometryReader { geometry in
|
||||
|
|
@ -100,7 +103,7 @@ struct ContentView: View {
|
|||
.padding(.leading, 10)
|
||||
|
||||
if callViewModel.callsCounter > 1 {
|
||||
Text(String(format: String(localized: "calls_count_label"), callViewModel.callsCounter))
|
||||
Text(String(format: String(localized: "calls_count_label"), callViewModel.callsCounter.description))
|
||||
.default_text_style_white(styleSize: 16)
|
||||
} else {
|
||||
Text("\(callViewModel.displayName)")
|
||||
|
|
@ -110,7 +113,7 @@ struct ContentView: View {
|
|||
Spacer()
|
||||
|
||||
if callViewModel.callsCounter == 1 {
|
||||
Text(String(localized: "\(callViewModel.isPaused || telecomManager.isPausedByRemote ? "call_state_paused" : "call_state_connected")"))
|
||||
Text(callViewModel.isPaused || telecomManager.isPausedByRemote ? String(localized: "call_state_paused") : String(localized: "call_state_connected"))
|
||||
.default_text_style_white(styleSize: 16)
|
||||
.padding(.trailing, 10)
|
||||
}
|
||||
|
|
@ -340,13 +343,31 @@ struct ContentView: View {
|
|||
openMenu()
|
||||
}
|
||||
.onAppear {
|
||||
imagePath = CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].getImagePath()
|
||||
if let accountModelIndex = accountProfileViewModel.accountModelIndex,
|
||||
accountModelIndex < CoreContext.shared.accounts.count {
|
||||
let imagePathTmp = CoreContext.shared.accounts[accountModelIndex].getImagePath()
|
||||
if !(imagePathTmp.lastPathComponent.isEmpty || imagePathTmp.lastPathComponent == "Error" || imagePathTmp.lastPathComponent == "ImageError.png") {
|
||||
imagePath = imagePathTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].usernaneAvatar) { _ in
|
||||
imagePath = CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].getImagePath()
|
||||
.onChange(of: CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex ?? 0].usernaneAvatar) { _ in
|
||||
if let accountModelIndex = accountProfileViewModel.accountModelIndex,
|
||||
accountModelIndex < CoreContext.shared.accounts.count {
|
||||
let imagePathTmp = CoreContext.shared.accounts[accountModelIndex].getImagePath()
|
||||
if !(imagePathTmp.lastPathComponent.isEmpty || imagePathTmp.lastPathComponent == "Error" || imagePathTmp.lastPathComponent == "ImageError.png") {
|
||||
sharedMainViewModel.changeDefaultAvatar(defaultAvatarURL: imagePathTmp)
|
||||
imagePath = imagePathTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
.onReceive(imageChanged) { _ in
|
||||
imagePath = CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].getImagePath()
|
||||
if let accountModelIndex = accountProfileViewModel.accountModelIndex,
|
||||
accountModelIndex < CoreContext.shared.accounts.count {
|
||||
let imagePathTmp = CoreContext.shared.accounts[accountModelIndex].getImagePath()
|
||||
sharedMainViewModel.changeDefaultAvatar(defaultAvatarURL: imagePathTmp)
|
||||
imagePath = imagePathTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1340,18 +1361,18 @@ struct ContentView: View {
|
|||
.zIndex(6)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
}
|
||||
.onChange(of: navigationManager.selectedCallId) { newCallId in
|
||||
if newCallId != nil {
|
||||
self.index = 2
|
||||
}
|
||||
}
|
||||
.onReceive(pub) { _ in
|
||||
conversationsListViewModel.computeChatRoomsList(filter: "")
|
||||
conversationsListViewModel.updateChatRoomsList()
|
||||
historyListViewModel.refreshHistoryAvatarModel()
|
||||
}
|
||||
.onReceive(pub2) { address in
|
||||
conversationsListViewModel.updateChatRoom(address: address)
|
||||
}
|
||||
}
|
||||
.overlay {
|
||||
if isMenuOpen {
|
||||
|
|
@ -1372,9 +1393,9 @@ struct ContentView: View {
|
|||
orientation = newOrientation
|
||||
}
|
||||
.onChange(of: scenePhase) { newPhase in
|
||||
CoreContext.shared.enteredForeground = newPhase == .active
|
||||
orientation = UIDevice.current.orientation
|
||||
if newPhase == .active {
|
||||
conversationsListViewModel.computeChatRoomsList(filter: "")
|
||||
accountProfileViewModel.setAvatarModel()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,10 +374,8 @@ struct ChatBubbleView: View {
|
|||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
if !CoreContext.shared.enteredForeground {
|
||||
conversationViewModel.selectedMessageToDisplayDetails = eventLogMessage
|
||||
conversationViewModel.prepareBottomSheetForDeliveryStatus()
|
||||
}
|
||||
conversationViewModel.selectedMessageToDisplayDetails = eventLogMessage
|
||||
conversationViewModel.prepareBottomSheetForDeliveryStatus()
|
||||
}
|
||||
.disabled(conversationViewModel.selectedMessage != nil)
|
||||
.padding(.top, -4)
|
||||
|
|
|
|||
|
|
@ -223,6 +223,11 @@ struct ConversationForwardMessageFragment: View {
|
|||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
MagicSearchSingleton.shared.searchForSuggestions()
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
magicSearch.searchForContacts(
|
||||
|
|
|
|||
|
|
@ -24,9 +24,12 @@ import UniformTypeIdentifiers
|
|||
// swiftlint:disable type_body_length
|
||||
struct ConversationFragment: View {
|
||||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@EnvironmentObject var navigationManager: NavigationManager
|
||||
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
|
|
@ -162,6 +165,13 @@ struct ConversationFragment: View {
|
|||
.background(Color.gray100.ignoresSafeArea(.keyboard))
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { newPhase in
|
||||
if newPhase == .active {
|
||||
if conversationViewModel.displayedConversation != nil && (navigationManager.peerAddr == nil || navigationManager.peerAddr!.contains(conversationViewModel.displayedConversation!.remoteSipUri)) {
|
||||
conversationViewModel.resetDisplayedChatRoom()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
}
|
||||
|
|
@ -662,7 +672,7 @@ struct ConversationFragment: View {
|
|||
.focused($isMessageTextFocused)
|
||||
.padding(.vertical, 5)
|
||||
.onChange(of: conversationViewModel.messageText) { text in
|
||||
if !text.isEmpty && !CoreContext.shared.enteredForeground {
|
||||
if !text.isEmpty {
|
||||
conversationViewModel.compose()
|
||||
}
|
||||
}
|
||||
|
|
@ -675,7 +685,7 @@ struct ConversationFragment: View {
|
|||
.default_text_style(styleSize: 15)
|
||||
.focused($isMessageTextFocused)
|
||||
.onChange(of: conversationViewModel.messageText) { text in
|
||||
if !text.isEmpty && !CoreContext.shared.enteredForeground {
|
||||
if !text.isEmpty {
|
||||
conversationViewModel.compose()
|
||||
}
|
||||
}
|
||||
|
|
@ -708,7 +718,7 @@ struct ConversationFragment: View {
|
|||
}
|
||||
} else {
|
||||
Button {
|
||||
if conversationViewModel.displayedConversationHistorySize > 0 {
|
||||
if conversationViewModel.displayedConversationHistorySize > 1 {
|
||||
NotificationCenter.default.post(name: .onScrollToBottom, object: nil)
|
||||
}
|
||||
conversationViewModel.sendMessage()
|
||||
|
|
|
|||
|
|
@ -69,13 +69,16 @@ struct ConversationsListFragment: View {
|
|||
.padding(.all)
|
||||
)
|
||||
}
|
||||
.onChange(of: scenePhase) { newPhase in
|
||||
if newPhase == .active {
|
||||
conversationsListViewModel.computeChatRoomsList(filter: "")
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.onChange(of: scenePhase) { newPhase in
|
||||
if newPhase == .active {
|
||||
if navigationManager.peerAddr != nil {
|
||||
conversationViewModel.getChatRoomWithStringAddress(conversationsList: conversationsListViewModel.conversationsList, stringAddr: navigationManager.peerAddr!)
|
||||
navigationManager.peerAddr = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,8 +93,6 @@ struct ConversationRow: View {
|
|||
@Binding var text: String
|
||||
|
||||
var body: some View {
|
||||
let pub = NotificationCenter.default
|
||||
.publisher(for: NSNotification.Name("ChatRoomsComputed"))
|
||||
HStack {
|
||||
Avatar(contactAvatarModel: conversation.avatarModel, avatarSize: 50)
|
||||
|
||||
|
|
@ -194,22 +195,6 @@ struct ConversationRow: View {
|
|||
.listRowInsets(EdgeInsets(top: 6, leading: 20, bottom: 6, trailing: 20))
|
||||
.listRowSeparator(.hidden)
|
||||
.background(.white)
|
||||
.onReceive(pub) { _ in
|
||||
if CoreContext.shared.enteredForeground && conversationViewModel.displayedConversation != nil
|
||||
&& (navigationManager.peerAddr == nil || navigationManager.peerAddr == conversationViewModel.displayedConversation!.remoteSipUri) {
|
||||
if conversationViewModel.displayedConversation != nil {
|
||||
conversationViewModel.resetDisplayedChatRoom(conversationsList: conversationsListViewModel.conversationsList)
|
||||
}
|
||||
}
|
||||
|
||||
CoreContext.shared.enteredForeground = false
|
||||
|
||||
if navigationManager.peerAddr != nil
|
||||
&& conversation.remoteSipUri.contains(navigationManager.peerAddr!) {
|
||||
conversationViewModel.getChatRoomWithStringAddress(conversationsList: conversationsListViewModel.conversationsList, stringAddr: navigationManager.peerAddr!)
|
||||
navigationManager.peerAddr = nil
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: conversation)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,9 +187,7 @@ struct StartConversationFragment: View {
|
|||
|
||||
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false)
|
||||
, startCallFunc: { addr in
|
||||
withAnimation {
|
||||
startConversationViewModel.createOneToOneChatRoomWith(remote: addr)
|
||||
}
|
||||
})
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
|
|
@ -266,10 +264,8 @@ struct StartConversationFragment: View {
|
|||
var suggestionsList: some View {
|
||||
ForEach(0..<contactsManager.lastSearchSuggestions.count, id: \.self) { index in
|
||||
Button {
|
||||
withAnimation {
|
||||
if contactsManager.lastSearchSuggestions[index].address != nil {
|
||||
startConversationViewModel.createOneToOneChatRoomWith(remote: contactsManager.lastSearchSuggestions[index].address!)
|
||||
}
|
||||
if let address = contactsManager.lastSearchSuggestions[index].address {
|
||||
startConversationViewModel.createOneToOneChatRoomWith(remote: address)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
|
|
@ -394,6 +390,9 @@ struct StartConversationFragment: View {
|
|||
.shadow(color: Color.orangeMain500, radius: 0, x: 0, y: 2)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
|
||||
.onDisappear {
|
||||
startConversationViewModel.messageText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,7 +208,6 @@ struct UIList: UIViewRepresentable {
|
|||
|
||||
if isScrolledToBottom && conversationViewModel.displayedConversationUnreadMessagesCount > 0 {
|
||||
conversationViewModel.markAsRead()
|
||||
conversationsListViewModel.computeChatRoomsList(filter: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -363,15 +362,16 @@ struct UIList: UIViewRepresentable {
|
|||
|
||||
NotificationCenter.default.addObserver(forName: .onScrollToBottom, object: nil, queue: nil) { _ in
|
||||
DispatchQueue.main.async {
|
||||
if !self.sections.isEmpty {
|
||||
if self.sections.first != nil
|
||||
&& parent.conversationViewModel.conversationMessagesSection.first != nil
|
||||
&& parent.conversationViewModel.displayedConversation != nil
|
||||
&& self.sections.first!.chatRoomID == parent.conversationViewModel.displayedConversation!.id
|
||||
&& self.sections.first!.rows.count == parent.conversationViewModel.conversationMessagesSection.first!.rows.count {
|
||||
self.tableView!.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
|
||||
}
|
||||
guard !self.sections.isEmpty,
|
||||
let firstSection = self.sections.first,
|
||||
let firstConversationSection = parent.conversationViewModel.conversationMessagesSection.first,
|
||||
let displayedConversation = parent.conversationViewModel.displayedConversation,
|
||||
let tableView = self.tableView,
|
||||
firstSection.chatRoomID == displayedConversation.id,
|
||||
firstSection.rows.count == firstConversationSection.rows.count else {
|
||||
return
|
||||
}
|
||||
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -467,7 +467,6 @@ struct UIList: UIViewRepresentable {
|
|||
|
||||
if self.parent.isScrolledToBottom && self.parent.conversationViewModel.displayedConversationUnreadMessagesCount > 0 {
|
||||
self.parent.conversationViewModel.markAsRead()
|
||||
self.parent.conversationsListViewModel.computeChatRoomsList(filter: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
private var coreContext = CoreContext.shared
|
||||
private var contactsManager = ContactsManager.shared
|
||||
|
||||
let chatRoom: ChatRoom
|
||||
var chatRoom: ChatRoom
|
||||
let isDisabledBecauseNotSecured: Bool = false
|
||||
|
||||
static let TAG = "[Conversation Model]"
|
||||
|
|
@ -50,8 +50,7 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
@Published var unreadMessagesCount: Int
|
||||
@Published var avatarModel: ContactAvatarModel
|
||||
|
||||
private var conferenceScheduler: ConferenceScheduler?
|
||||
private var conferenceSchedulerDelegate: ConferenceSchedulerDelegate?
|
||||
private var conferenceDelegate: ConferenceDelegate?
|
||||
|
||||
init(chatRoom: ChatRoom) {
|
||||
self.chatRoom = chatRoom
|
||||
|
|
@ -134,35 +133,28 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
}
|
||||
|
||||
do {
|
||||
let conferenceInfo = try Factory.Instance.createConferenceInfo()
|
||||
conferenceInfo.organizer = account!.params?.identityAddress
|
||||
conferenceInfo.subject = self.chatRoom.subject ?? "Conference"
|
||||
|
||||
var participantsList: [ParticipantInfo] = []
|
||||
var participantsList: [Address] = []
|
||||
self.chatRoom.participants.forEach { participant in
|
||||
do {
|
||||
let info = try Factory.Instance.createParticipantInfo(address: participant.address!)
|
||||
// For meetings, all participants must have Speaker role
|
||||
info.role = Participant.Role.Speaker
|
||||
participantsList.append(info)
|
||||
} catch let error {
|
||||
Log.error(
|
||||
"\(ConversationModel.TAG) Can't create ParticipantInfo: \(error)"
|
||||
)
|
||||
}
|
||||
participantsList.append(participant.address!)
|
||||
}
|
||||
|
||||
conferenceInfo.addParticipantInfos(participantInfos: participantsList)
|
||||
|
||||
Log.info(
|
||||
"\(ConversationModel.TAG) Creating group call with subject \(self.chatRoom.subject ?? "Conference") and \(participantsList.count) participant(s)"
|
||||
)
|
||||
|
||||
self.conferenceScheduler = try core.createConferenceScheduler(account: account)
|
||||
if self.conferenceScheduler != nil {
|
||||
self.conferenceAddDelegate(core: core, conferenceScheduler: self.conferenceScheduler!)
|
||||
// Will trigger the conference creation/update automatically
|
||||
self.conferenceScheduler!.info = conferenceInfo
|
||||
if let conference = LinphoneUtils.createGroupCall(core: core, account: account, subject: self.chatRoom.subject ?? "Conference") {
|
||||
let callParams = try? core.createCallParams(call: nil)
|
||||
if let callParams = callParams {
|
||||
callParams.videoEnabled = true
|
||||
callParams.videoDirection = .RecvOnly
|
||||
|
||||
Log.info("\(ConversationModel.TAG) Inviting \(participantsList.count) participant(s) into newly created conference")
|
||||
|
||||
try conference.inviteParticipants(addresses: participantsList, params: callParams)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
TelecomManager.shared.participantsInvited = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
Log.error(
|
||||
|
|
@ -172,145 +164,132 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
}
|
||||
}
|
||||
|
||||
func conferenceAddDelegate(core: Core, conferenceScheduler: ConferenceScheduler) {
|
||||
self.conferenceSchedulerDelegate = ConferenceSchedulerDelegateStub(onStateChanged: { (conferenceScheduler: ConferenceScheduler, state: ConferenceScheduler.State) in
|
||||
Log.info("\(ConversationModel.TAG) Conference scheduler state is \(state)")
|
||||
if state == ConferenceScheduler.State.Ready {
|
||||
conferenceScheduler.removeDelegate(delegate: self.conferenceSchedulerDelegate!)
|
||||
self.conferenceSchedulerDelegate = nil
|
||||
|
||||
let conferenceAddress = conferenceScheduler.info?.uri
|
||||
if conferenceAddress != nil {
|
||||
Log.info(
|
||||
"\(ConversationModel.TAG) Conference info created, address is \(conferenceAddress!.asStringUriOnly())"
|
||||
)
|
||||
|
||||
TelecomManager.shared.doCallWithCore(addr: conferenceAddress!, isVideo: true, isConference: true)
|
||||
} else {
|
||||
Log.error("\(ConversationModel.TAG) Conference info URI is null!")
|
||||
|
||||
func conferenceAddDelegate(core: Core, conference: Conference) {
|
||||
self.conferenceDelegate = ConferenceDelegateStub(onStateChanged: { (conference: Conference, state: Conference.State) in
|
||||
Log.info("\(ConversationModel.TAG) Conference state is \(state)")
|
||||
if state == .Created {
|
||||
NotificationCenter.default.post(name: Notification.Name("CallViewModelReset"), object: self)
|
||||
} else if state == .CreationFailed {
|
||||
Log.error("\(ConversationModel.TAG) Failed to create group call!")
|
||||
DispatchQueue.main.async {
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
} else if state == ConferenceScheduler.State.Error {
|
||||
conferenceScheduler.removeDelegate(delegate: self.conferenceSchedulerDelegate!)
|
||||
self.conferenceSchedulerDelegate = nil
|
||||
Log.error("\(ConversationModel.TAG) Failed to create group call!")
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
})
|
||||
conferenceScheduler.addDelegate(delegate: self.conferenceSchedulerDelegate!)
|
||||
|
||||
if self.conferenceDelegate != nil {
|
||||
conference.addDelegate(delegate: self.conferenceDelegate!)
|
||||
}
|
||||
}
|
||||
|
||||
func getContentTextMessage() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
let lastMessage = self.chatRoom.lastMessageInHistory
|
||||
if lastMessage != nil {
|
||||
var fromAddressFriend = lastMessage!.fromAddress != nil
|
||||
? self.contactsManager.getFriendWithAddress(address: lastMessage!.fromAddress)?.name ?? nil
|
||||
: nil
|
||||
|
||||
if !lastMessage!.isOutgoing && lastMessage!.chatRoom != nil && !lastMessage!.chatRoom!.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) {
|
||||
if fromAddressFriend == nil {
|
||||
if lastMessage!.fromAddress!.displayName != nil {
|
||||
fromAddressFriend = lastMessage!.fromAddress!.displayName! + ": "
|
||||
} else if lastMessage!.fromAddress!.username != nil {
|
||||
fromAddressFriend = lastMessage!.fromAddress!.username! + ": "
|
||||
} else {
|
||||
fromAddressFriend = String(lastMessage!.fromAddress!.asStringUriOnly().dropFirst(4)) + ": "
|
||||
}
|
||||
let lastMessage = self.chatRoom.lastMessageInHistory
|
||||
if lastMessage != nil {
|
||||
var fromAddressFriend = lastMessage!.fromAddress != nil
|
||||
? self.contactsManager.getFriendWithAddress(address: lastMessage!.fromAddress)?.name ?? nil
|
||||
: nil
|
||||
|
||||
if !lastMessage!.isOutgoing && lastMessage!.chatRoom != nil && !lastMessage!.chatRoom!.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) {
|
||||
if fromAddressFriend == nil {
|
||||
if lastMessage!.fromAddress!.displayName != nil {
|
||||
fromAddressFriend = lastMessage!.fromAddress!.displayName! + ": "
|
||||
} else if lastMessage!.fromAddress!.username != nil {
|
||||
fromAddressFriend = lastMessage!.fromAddress!.username! + ": "
|
||||
} else {
|
||||
fromAddressFriend! += ": "
|
||||
fromAddressFriend = String(lastMessage!.fromAddress!.asStringUriOnly().dropFirst(4)) + ": "
|
||||
}
|
||||
|
||||
} else {
|
||||
fromAddressFriend = nil
|
||||
fromAddressFriend! += ": "
|
||||
}
|
||||
|
||||
let lastMessageTextTmp = (fromAddressFriend ?? "")
|
||||
+ (lastMessage!.contents.first(where: {$0.isText == true})?.utf8Text ?? (lastMessage!.contents.first(where: {$0.isFile == true || $0.isFileTransfer == true})?.name ?? ""))
|
||||
|
||||
let lastMessageIsOutgoingTmp = lastMessage?.isOutgoing ?? false
|
||||
|
||||
let lastMessageStateTmp = lastMessage?.state.rawValue ?? 0
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.lastMessageText = lastMessageTextTmp
|
||||
|
||||
self.lastMessageIsOutgoing = lastMessageIsOutgoingTmp
|
||||
|
||||
self.lastMessageState = lastMessageStateTmp
|
||||
}
|
||||
} else {
|
||||
fromAddressFriend = nil
|
||||
}
|
||||
|
||||
let lastMessageTextTmp = (fromAddressFriend ?? "")
|
||||
+ (lastMessage!.contents.first(where: {$0.isText == true})?.utf8Text ?? (lastMessage!.contents.first(where: {$0.isFile == true || $0.isFileTransfer == true})?.name ?? ""))
|
||||
|
||||
let lastMessageIsOutgoingTmp = lastMessage?.isOutgoing ?? false
|
||||
|
||||
let lastMessageStateTmp = lastMessage?.state.rawValue ?? 0
|
||||
|
||||
// DispatchQueue.main.async {
|
||||
self.lastMessageText = lastMessageTextTmp
|
||||
|
||||
self.lastMessageIsOutgoing = lastMessageIsOutgoingTmp
|
||||
|
||||
self.lastMessageState = lastMessageStateTmp
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
func getChatRoomSubject() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
let addressFriend = (self.chatRoom.participants.first != nil && self.chatRoom.participants.first!.address != nil)
|
||||
? self.contactsManager.getFriendWithAddress(address: self.chatRoom.participants.first?.address)
|
||||
: nil
|
||||
|
||||
var subjectTmp = ""
|
||||
|
||||
if self.isGroup {
|
||||
subjectTmp = self.chatRoom.subject!
|
||||
} else if addressFriend != nil {
|
||||
subjectTmp = addressFriend!.name!
|
||||
} else {
|
||||
if self.chatRoom.participants.first != nil
|
||||
&& self.chatRoom.participants.first!.address != nil {
|
||||
|
||||
subjectTmp = self.chatRoom.participants.first!.address!.displayName != nil
|
||||
? self.chatRoom.participants.first!.address!.displayName!
|
||||
: (self.chatRoom.participants.first!.address!.username ?? String(self.chatRoom.participants.first!.address!.asStringUriOnly().dropFirst(4)))
|
||||
|
||||
}
|
||||
let addressFriend = (self.chatRoom.participants.first != nil && self.chatRoom.participants.first!.address != nil)
|
||||
? self.contactsManager.getFriendWithAddress(address: self.chatRoom.participants.first?.address)
|
||||
: nil
|
||||
|
||||
var subjectTmp = ""
|
||||
|
||||
if self.isGroup {
|
||||
subjectTmp = self.chatRoom.subject!
|
||||
} else if addressFriend != nil {
|
||||
subjectTmp = addressFriend!.name!
|
||||
} else {
|
||||
if self.chatRoom.participants.first != nil
|
||||
&& self.chatRoom.participants.first!.address != nil {
|
||||
|
||||
subjectTmp = self.chatRoom.participants.first!.address!.displayName != nil
|
||||
? self.chatRoom.participants.first!.address!.displayName!
|
||||
: (self.chatRoom.participants.first!.address!.username ?? String(self.chatRoom.participants.first!.address!.asStringUriOnly().dropFirst(4)))
|
||||
|
||||
}
|
||||
|
||||
let addressTmp = addressFriend?.address?.asStringUriOnly() ?? ""
|
||||
|
||||
let avatarModelTmp = addressFriend != nil && !self.isGroup
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
$0.friend!.name == addressFriend!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly()
|
||||
})
|
||||
?? ContactAvatarModel(
|
||||
friend: nil,
|
||||
name: subjectTmp,
|
||||
address: addressTmp,
|
||||
withPresence: false
|
||||
)
|
||||
: ContactAvatarModel(
|
||||
}
|
||||
|
||||
let addressTmp = addressFriend?.address?.asStringUriOnly() ?? ""
|
||||
|
||||
let avatarModelTmp: ContactAvatarModel
|
||||
if let addressFriend = addressFriend, !self.isGroup {
|
||||
if let existingAvatarModel = ContactsManager.shared.avatarListModel.first(where: {
|
||||
$0.friend?.name == addressFriend.name &&
|
||||
$0.friend?.address?.asStringUriOnly() == addressFriend.address?.asStringUriOnly()
|
||||
}) {
|
||||
avatarModelTmp = existingAvatarModel
|
||||
} else {
|
||||
avatarModelTmp = ContactAvatarModel(
|
||||
friend: nil,
|
||||
name: subjectTmp,
|
||||
address: addressTmp,
|
||||
withPresence: false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
avatarModelTmp = ContactAvatarModel(
|
||||
friend: nil,
|
||||
name: subjectTmp,
|
||||
address: self.chatRoom.peerAddress?.asStringUriOnly() ?? addressTmp,
|
||||
withPresence: false
|
||||
)
|
||||
|
||||
var participantsAddressTmp: [String] = []
|
||||
|
||||
self.chatRoom.participants.forEach { participant in
|
||||
participantsAddressTmp.append(participant.address?.asStringUriOnly() ?? "")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.subject = subjectTmp
|
||||
self.avatarModel = avatarModelTmp
|
||||
self.participantsAddress = participantsAddressTmp
|
||||
}
|
||||
}
|
||||
|
||||
var participantsAddressTmp: [String] = []
|
||||
|
||||
self.chatRoom.participants.forEach { participant in
|
||||
participantsAddressTmp.append(participant.address?.asStringUriOnly() ?? "")
|
||||
}
|
||||
|
||||
// DispatchQueue.main.async {
|
||||
self.subject = subjectTmp
|
||||
self.avatarModel = avatarModelTmp
|
||||
self.participantsAddress = participantsAddressTmp
|
||||
// }
|
||||
}
|
||||
|
||||
func getUnreadMessagesCount() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
let unreadMessagesCountTmp = self.chatRoom.unreadMessagesCount
|
||||
DispatchQueue.main.async {
|
||||
self.unreadMessagesCount = unreadMessagesCountTmp
|
||||
}
|
||||
}
|
||||
let unreadMessagesCountTmp = self.chatRoom.unreadMessagesCount
|
||||
// DispatchQueue.main.async {
|
||||
self.unreadMessagesCount = unreadMessagesCountTmp
|
||||
// }
|
||||
}
|
||||
|
||||
func refreshAvatarModel() {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import linphonesw
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
// swiftlint:disable line_length
|
||||
class ConversationForwardMessageViewModel: ObservableObject {
|
||||
|
|
@ -84,7 +85,7 @@ class ConversationForwardMessageViewModel: ObservableObject {
|
|||
let account = core.defaultAccount
|
||||
if account == nil {
|
||||
Log.error(
|
||||
"\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())!"
|
||||
"\(ConversationForwardMessageViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -94,37 +95,42 @@ class ConversationForwardMessageViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
do {
|
||||
let params: ChatRoomParams = try core.createDefaultChatRoomParams()
|
||||
let params = try core.createConferenceParams(conference: nil)
|
||||
params.chatEnabled = true
|
||||
params.groupEnabled = false
|
||||
params.subject = "Dummy subject"
|
||||
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
params.subject = NSLocalizedString("conversation_one_to_one_hidden_subject", comment: "")
|
||||
params.account = account
|
||||
|
||||
let sameDomain = remote.domain == account?.params?.domain ?? ""
|
||||
if StartConversationViewModel.isEndToEndEncryptionMandatory() && sameDomain {
|
||||
Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating a E2E conversation")
|
||||
params.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.encryptionEnabled = true
|
||||
} else if !StartConversationViewModel.isEndToEndEncryptionMandatory() {
|
||||
guard let chatParams = params.chatParams else { return }
|
||||
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
|
||||
let sameDomain = remote.domain == CorePreferences.defaultDomain && remote.domain == account!.params?.domain
|
||||
if account!.params != nil && (account!.params!.instantMessagingEncryptionMandatory && sameDomain) {
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) Account is in secure mode & domain matches, creating an E2E encrypted conversation")
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else if account!.params != nil && (!account!.params!.instantMessagingEncryptionMandatory) {
|
||||
if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating a E2E conversation"
|
||||
"\(ConversationForwardMessageViewModel.TAG) Account is in interop mode but LIME is available, creating an E2E encrypted conversation"
|
||||
)
|
||||
params.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.encryptionEnabled = true
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
|
||||
"\(ConversationForwardMessageViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
|
||||
)
|
||||
params.backend = ChatRoom.Backend.Basic
|
||||
params.encryptionEnabled = false
|
||||
chatParams.backend = ChatRoom.Backend.Basic
|
||||
params.securityLevel = Conference.SecurityLevel.None
|
||||
}
|
||||
} else {
|
||||
Log.error(
|
||||
"\(StartConversationViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())"
|
||||
"\(ConversationForwardMessageViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())"
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_invalid_participant_error"
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
return
|
||||
|
|
@ -135,85 +141,56 @@ class ConversationForwardMessageViewModel: ObservableObject {
|
|||
let existingChatRoom = core.searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: participants)
|
||||
if existingChatRoom == nil {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account "
|
||||
+ "\(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it"
|
||||
"\(ConversationForwardMessageViewModel.TAG) No existing 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it"
|
||||
)
|
||||
let chatRoom = try core.createChatRoom(params: params, localAddr: localAddress, participants: participants)
|
||||
if params.backend == ChatRoom.Backend.FlexisipChat {
|
||||
if chatRoom.state == ChatRoom.State.Created {
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(id) has been created")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
do {
|
||||
let chatRoom = try core.createChatRoom(params: params, participants: participants)
|
||||
if chatParams.backend == ChatRoom.Backend.FlexisipChat {
|
||||
let state = chatRoom.state
|
||||
if state == ChatRoom.State.Created {
|
||||
let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) 1-1 conversation \(chatRoomId) has been created")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) Conversation isn't in Created state yet (state is \(state)), wait for it")
|
||||
self.chatRoomAddDelegate(core: core, chatRoom: chatRoom)
|
||||
}
|
||||
} else {
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet, wait for it")
|
||||
self.chatRoomAddDelegate(core: core, chatRoom: chatRoom)
|
||||
}
|
||||
} else {
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id)")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) Conversation successfully created \(chatRoomId)")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Log.error("\(ConversationForwardMessageViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.warn(
|
||||
"\(StartConversationViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!"
|
||||
"\(ConversationForwardMessageViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!"
|
||||
)
|
||||
|
||||
let model = ConversationModel(chatRoom: existingChatRoom!)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -223,7 +200,7 @@ class ConversationForwardMessageViewModel: ObservableObject {
|
|||
let state = chatRoom.state
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
if state == ChatRoom.State.CreationFailed {
|
||||
Log.error("\(StartConversationViewModel.TAG) Conversation \(id) creation has failed!")
|
||||
Log.error("\(ConversationForwardMessageViewModel.TAG) Conversation \(id) creation has failed!")
|
||||
if let chatRoomDelegate = self.chatRoomDelegate {
|
||||
chatRoom.removeDelegate(delegate: chatRoomDelegate)
|
||||
}
|
||||
|
|
@ -237,9 +214,9 @@ class ConversationForwardMessageViewModel: ObservableObject {
|
|||
}, onConferenceJoined: { (chatRoom: ChatRoom, _: EventLog) in
|
||||
let state = chatRoom.state
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation \(id) \(chatRoom.subject ?? "") state changed: \(state)")
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) Conversation \(id) \(chatRoom.subject ?? "") state changed: \(state)")
|
||||
if state == ChatRoom.State.Created {
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation \(id) successfully created")
|
||||
Log.info("\(ConversationForwardMessageViewModel.TAG) Conversation \(id) successfully created")
|
||||
if let chatRoomDelegate = self.chatRoomDelegate {
|
||||
chatRoom.removeDelegate(delegate: chatRoomDelegate)
|
||||
}
|
||||
|
|
@ -262,7 +239,7 @@ class ConversationForwardMessageViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
} else if state == ChatRoom.State.CreationFailed {
|
||||
Log.error("\(StartConversationViewModel.TAG) Conversation \(id) creation has failed!")
|
||||
Log.error("\(ConversationForwardMessageViewModel.TAG) Conversation \(id) creation has failed!")
|
||||
if let chatRoomDelegate = self.chatRoomDelegate {
|
||||
chatRoom.removeDelegate(delegate: chatRoomDelegate)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@
|
|||
import Foundation
|
||||
import linphonesw
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
// swiftlint:disable line_length
|
||||
class ConversationsListViewModel: ObservableObject {
|
||||
|
|
@ -47,35 +48,180 @@ class ConversationsListViewModel: ObservableObject {
|
|||
let chatRooms = account != nil ? account!.chatRooms : core.chatRooms
|
||||
|
||||
self.conversationsListTmp = []
|
||||
DispatchQueue.main.async {
|
||||
self.conversationsList = []
|
||||
}
|
||||
|
||||
chatRooms.forEach { chatRoom in
|
||||
if filter.isEmpty {
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
self.conversationsListTmp.append(model)
|
||||
DispatchQueue.main.async {
|
||||
self.conversationsList.append(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.conversationsList = self.conversationsListTmp
|
||||
NotificationCenter.default.post(name: NSNotification.Name("ChatRoomsComputed"), object: nil)
|
||||
}
|
||||
|
||||
self.updateUnreadMessagesCount()
|
||||
}
|
||||
}
|
||||
|
||||
func updateChatRoomsList() {
|
||||
CoreContext.shared.doOnCoreQueue { _ in
|
||||
if !self.conversationsListTmp.isEmpty {
|
||||
self.contactsManager.avatarListModel.forEach { contactAvatarModel in
|
||||
self.conversationsListTmp.forEach { conversationModel in
|
||||
if conversationModel.participantsAddress.contains(contactAvatarModel.address) {
|
||||
if conversationModel.isGroup && conversationModel.participantsAddress.count > 1 {
|
||||
if let lastMessage = conversationModel.chatRoom.lastMessageInHistory, let fromAddress = lastMessage.fromAddress, fromAddress.asStringUriOnly().contains(contactAvatarModel.address) {
|
||||
var fromAddressFriend = self.contactsManager.getFriendWithAddress(address: fromAddress)?.name
|
||||
|
||||
if !lastMessage.isOutgoing && lastMessage.chatRoom != nil && !lastMessage.chatRoom!.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) {
|
||||
if fromAddressFriend == nil {
|
||||
if let displayName = fromAddress.displayName {
|
||||
fromAddressFriend = displayName + ": "
|
||||
} else if let username = fromAddress.username {
|
||||
fromAddressFriend = username + ": "
|
||||
} else {
|
||||
fromAddressFriend = String(fromAddress.asStringUriOnly().dropFirst(4)) + ": "
|
||||
}
|
||||
} else {
|
||||
fromAddressFriend! += ": "
|
||||
}
|
||||
} else {
|
||||
fromAddressFriend = nil
|
||||
}
|
||||
|
||||
let lastMessageTextTmp = (fromAddressFriend ?? "") + (lastMessage.contents.first(where: { $0.isText })?.utf8Text ?? (lastMessage.contents.first(where: { $0.isFile || $0.isFileTransfer })?.name ?? ""))
|
||||
|
||||
if let index = self.conversationsList.firstIndex(where: { $0.chatRoom === conversationModel.chatRoom }) {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.lastMessageText = lastMessageTextTmp
|
||||
self.conversationsList[index].lastMessageText = lastMessageTextTmp
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.lastMessageText = lastMessageTextTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !conversationModel.isGroup, let firstParticipantAddress = conversationModel.participantsAddress.first, firstParticipantAddress.contains(contactAvatarModel.address) {
|
||||
if let index = self.conversationsList.firstIndex(where: { $0.chatRoom === conversationModel.chatRoom }) {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.avatarModel = contactAvatarModel
|
||||
conversationModel.subject = contactAvatarModel.name
|
||||
self.conversationsList[index].avatarModel = contactAvatarModel
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.avatarModel = contactAvatarModel
|
||||
conversationModel.subject = contactAvatarModel.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateChatRoom(address: String) {
|
||||
CoreContext.shared.doOnCoreQueue { _ in
|
||||
if let contactAvatarModel = self.contactsManager.avatarListModel.first(where: { $0.addresses.contains(address) }) {
|
||||
self.conversationsListTmp.forEach { conversationModel in
|
||||
if conversationModel.participantsAddress.contains(contactAvatarModel.address) {
|
||||
if conversationModel.isGroup && conversationModel.participantsAddress.count > 1 {
|
||||
if let lastMessage = conversationModel.chatRoom.lastMessageInHistory, let fromAddress = lastMessage.fromAddress, fromAddress.asStringUriOnly().contains(contactAvatarModel.address) {
|
||||
var fromAddressFriend = self.contactsManager.getFriendWithAddress(address: fromAddress)?.name
|
||||
|
||||
if !lastMessage.isOutgoing && lastMessage.chatRoom != nil && !lastMessage.chatRoom!.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) {
|
||||
if fromAddressFriend == nil {
|
||||
if let displayName = fromAddress.displayName {
|
||||
fromAddressFriend = displayName + ": "
|
||||
} else if let username = fromAddress.username {
|
||||
fromAddressFriend = username + ": "
|
||||
} else {
|
||||
fromAddressFriend = String(fromAddress.asStringUriOnly().dropFirst(4)) + ": "
|
||||
}
|
||||
} else {
|
||||
fromAddressFriend! += ": "
|
||||
}
|
||||
} else {
|
||||
fromAddressFriend = nil
|
||||
}
|
||||
|
||||
let lastMessageTextTmp = (fromAddressFriend ?? "") + (lastMessage.contents.first(where: { $0.isText })?.utf8Text ?? (lastMessage.contents.first(where: { $0.isFile || $0.isFileTransfer })?.name ?? ""))
|
||||
|
||||
if let index = self.conversationsList.firstIndex(where: { $0.chatRoom === conversationModel.chatRoom }) {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.lastMessageText = lastMessageTextTmp
|
||||
self.conversationsList[index].lastMessageText = lastMessageTextTmp
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.lastMessageText = lastMessageTextTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !conversationModel.isGroup, let firstParticipantAddress = conversationModel.participantsAddress.first, firstParticipantAddress.contains(contactAvatarModel.address) {
|
||||
if let index = self.conversationsList.firstIndex(where: { $0.chatRoom === conversationModel.chatRoom }) {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.avatarModel = contactAvatarModel
|
||||
conversationModel.subject = contactAvatarModel.name
|
||||
self.conversationsList[index].avatarModel = contactAvatarModel
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.avatarModel = contactAvatarModel
|
||||
conversationModel.subject = contactAvatarModel.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addConversationDelegate() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
let account = core.defaultAccount
|
||||
let chatRoomsCounter = account?.chatRooms != nil ? account!.chatRooms.count : core.chatRooms.count
|
||||
|
||||
self.coreConversationDelegate = CoreDelegateStub(onMessagesReceived: { (_: Core, _: ChatRoom, _: [ChatMessage]) in
|
||||
self.computeChatRoomsList(filter: "")
|
||||
}, onMessageSent: { (_: Core, _: ChatRoom, _: ChatMessage) in
|
||||
self.computeChatRoomsList(filter: "")
|
||||
}, onChatRoomRead: { (_: Core, _: ChatRoom) in
|
||||
self.computeChatRoomsList(filter: "")
|
||||
}, onChatRoomStateChanged: { (core: Core, chatRoom: ChatRoom, state: ChatRoom.State) in
|
||||
self.coreConversationDelegate = CoreDelegateStub(onMessagesReceived: { (_: Core, chatRoom: ChatRoom, _: [ChatMessage]) in
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
|
||||
DispatchQueue.main.async {
|
||||
if index != nil {
|
||||
self.conversationsList.remove(at: index!)
|
||||
}
|
||||
self.conversationsList.insert(model, at: 0)
|
||||
}
|
||||
self.updateUnreadMessagesCount()
|
||||
}, onMessageSent: { (_: Core, chatRoom: ChatRoom, _: ChatMessage) in
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
|
||||
DispatchQueue.main.async {
|
||||
if index != nil {
|
||||
self.conversationsList.remove(at: index!)
|
||||
}
|
||||
self.conversationsList.insert(model, at: 0)
|
||||
}
|
||||
self.updateUnreadMessagesCount()
|
||||
}, onChatRoomRead: { (_: Core, chatRoom: ChatRoom) in
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
|
||||
DispatchQueue.main.async {
|
||||
if index != nil {
|
||||
self.conversationsList.remove(at: index!)
|
||||
self.conversationsList.insert(model, at: index!)
|
||||
} else {
|
||||
self.conversationsList.insert(model, at: 0)
|
||||
}
|
||||
}
|
||||
self.updateUnreadMessagesCount()
|
||||
}, onChatRoomStateChanged: { (core: Core, _: ChatRoom, state: ChatRoom.State) in
|
||||
// Log.info("[ConversationsListViewModel] Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]")
|
||||
if core.globalState == .On {
|
||||
switch state {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import linphonesw
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
// swiftlint:disable line_length
|
||||
class StartConversationViewModel: ObservableObject {
|
||||
|
|
@ -78,27 +79,31 @@ class StartConversationViewModel: ObservableObject {
|
|||
|
||||
let groupChatRoomSubject = self.messageText
|
||||
do {
|
||||
let params: ChatRoomParams = try core.createDefaultChatRoomParams()
|
||||
params.groupEnabled = true
|
||||
params.subject = groupChatRoomSubject
|
||||
params.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.encryptionEnabled = true
|
||||
let chatRoomParams = try core.createConferenceParams(conference: nil)
|
||||
chatRoomParams.chatEnabled = true
|
||||
chatRoomParams.groupEnabled = true
|
||||
chatRoomParams.subject = groupChatRoomSubject
|
||||
chatRoomParams.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
chatRoomParams.account = account
|
||||
|
||||
if let chatParams = chatRoomParams.chatParams {
|
||||
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
}
|
||||
|
||||
var participantsTmp: [Address] = []
|
||||
self.participants.forEach { participant in
|
||||
participantsTmp.append(participant.address)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.participants.removeAll()
|
||||
}
|
||||
|
||||
if account!.params != nil {
|
||||
let localAddress = account!.params!.identityAddress
|
||||
let chatRoom = try core.createChatRoom(params: chatRoomParams, participants: participantsTmp)
|
||||
|
||||
let chatRoom = try core.createChatRoom(
|
||||
params: params,
|
||||
localAddr: localAddress,
|
||||
participants: participantsTmp
|
||||
)
|
||||
|
||||
if params.backend == ChatRoom.Backend.FlexisipChat {
|
||||
if chatRoomParams.chatParams != nil && chatRoomParams.chatParams!.backend == ChatRoom.Backend.FlexisipChat {
|
||||
if chatRoom.state == ChatRoom.State.Created {
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info(
|
||||
|
|
@ -106,20 +111,9 @@ class StartConversationViewModel: ObservableObject {
|
|||
)
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else {
|
||||
Log.info(
|
||||
|
|
@ -132,20 +126,9 @@ class StartConversationViewModel: ObservableObject {
|
|||
Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id) \(groupChatRoomSubject)")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -163,11 +146,11 @@ class StartConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func createOneToOneChatRoomWith(remote: Address) {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
let account = core.defaultAccount
|
||||
if account == nil {
|
||||
Log.error(
|
||||
"\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())!"
|
||||
"\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -177,37 +160,42 @@ class StartConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
do {
|
||||
let params: ChatRoomParams = try core.createDefaultChatRoomParams()
|
||||
let params = try core.createConferenceParams(conference: nil)
|
||||
params.chatEnabled = true
|
||||
params.groupEnabled = false
|
||||
params.subject = "Dummy subject"
|
||||
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
params.subject = NSLocalizedString("conversation_one_to_one_hidden_subject", comment: "")
|
||||
params.account = account
|
||||
|
||||
let sameDomain = remote.domain == account?.params?.domain ?? ""
|
||||
if StartConversationViewModel.isEndToEndEncryptionMandatory() && sameDomain {
|
||||
Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating a E2E conversation")
|
||||
params.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.encryptionEnabled = true
|
||||
} else if !StartConversationViewModel.isEndToEndEncryptionMandatory() {
|
||||
guard let chatParams = params.chatParams else { return }
|
||||
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
|
||||
let sameDomain = remote.domain == CorePreferences.defaultDomain && remote.domain == account!.params?.domain
|
||||
if account!.params != nil && (account!.params!.instantMessagingEncryptionMandatory && sameDomain) {
|
||||
Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating an E2E encrypted conversation")
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else if account!.params != nil && (!account!.params!.instantMessagingEncryptionMandatory) {
|
||||
if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating a E2E conversation"
|
||||
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating an E2E encrypted conversation"
|
||||
)
|
||||
params.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.encryptionEnabled = true
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
|
||||
)
|
||||
params.backend = ChatRoom.Backend.Basic
|
||||
params.encryptionEnabled = false
|
||||
chatParams.backend = ChatRoom.Backend.Basic
|
||||
params.securityLevel = Conference.SecurityLevel.None
|
||||
}
|
||||
} else {
|
||||
Log.error(
|
||||
"\(StartConversationViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())"
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_invalid_participant_error"
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
return
|
||||
|
|
@ -218,85 +206,56 @@ class StartConversationViewModel: ObservableObject {
|
|||
let existingChatRoom = core.searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: participants)
|
||||
if existingChatRoom == nil {
|
||||
Log.info(
|
||||
"\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account "
|
||||
+ "\(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it"
|
||||
"\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it"
|
||||
)
|
||||
let chatRoom = try core.createChatRoom(params: params, localAddr: localAddress, participants: participants)
|
||||
if params.backend == ChatRoom.Backend.FlexisipChat {
|
||||
if chatRoom.state == ChatRoom.State.Created {
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(id) has been created")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
do {
|
||||
let chatRoom = try core.createChatRoom(params: params, participants: participants)
|
||||
if chatParams.backend == ChatRoom.Backend.FlexisipChat {
|
||||
let state = chatRoom.state
|
||||
if state == ChatRoom.State.Created {
|
||||
let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(chatRoomId) has been created")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet (state is \(state)), wait for it")
|
||||
self.chatRoomAddDelegate(core: core, chatRoom: chatRoom)
|
||||
}
|
||||
} else {
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet, wait for it")
|
||||
self.chatRoomAddDelegate(core: core, chatRoom: chatRoom)
|
||||
}
|
||||
} else {
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id)")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(chatRoomId)")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.warn(
|
||||
"\(StartConversationViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!"
|
||||
)
|
||||
|
||||
let model = ConversationModel(chatRoom: existingChatRoom!)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -304,9 +263,9 @@ class StartConversationViewModel: ObservableObject {
|
|||
func chatRoomAddDelegate(core: Core, chatRoom: ChatRoom) {
|
||||
self.chatRoomDelegate = ChatRoomDelegateStub(onStateChanged: { (chatRoom: ChatRoom, state: ChatRoom.State) in
|
||||
let state = chatRoom.state
|
||||
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let chatRoomId = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
if state == ChatRoom.State.CreationFailed {
|
||||
Log.error("\(StartConversationViewModel.TAG) Conversation \(id) creation has failed!")
|
||||
Log.error("\(StartConversationViewModel.TAG) Conversation \(chatRoomId) creation has failed!")
|
||||
if let chatRoomDelegate = self.chatRoomDelegate {
|
||||
chatRoom.removeDelegate(delegate: chatRoomDelegate)
|
||||
}
|
||||
|
|
@ -329,20 +288,9 @@ class StartConversationViewModel: ObservableObject {
|
|||
self.chatRoomDelegate = nil
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
if self.operationInProgress == false {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
self.displayedConversation = model
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else if state == ChatRoom.State.CreationFailed {
|
||||
Log.error("\(StartConversationViewModel.TAG) Conversation \(id) creation has failed!")
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class HelpViewModel: ObservableObject {
|
||||
private let TAG = "[HelpViewModel]"
|
||||
|
|
|
|||
|
|
@ -304,8 +304,10 @@ struct StartCallFragment: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
|
||||
@Sendable private func delayColor() async {
|
||||
|
|
|
|||
|
|
@ -112,24 +112,27 @@ class HistoryModel: ObservableObject {
|
|||
|
||||
func refreshAvatarModel() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
let addressFriendTmp = ContactsManager.shared.getFriendWithAddress(
|
||||
address: self.callLog.dir == .Outgoing ? self.callLog.toAddress! : self.callLog.fromAddress!
|
||||
)
|
||||
if addressFriendTmp != nil {
|
||||
guard let address = (self.callLog.dir == .Outgoing ? self.callLog.toAddress : self.callLog.fromAddress) else {
|
||||
DispatchQueue.main.async {
|
||||
self.avatarModel = ContactAvatarModel(friend: nil, name: self.addressName, address: self.address, withPresence: false)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let addressFriendTmp = ContactsManager.shared.getFriendWithAddress(address: address)
|
||||
if let addressFriendTmp = addressFriendTmp {
|
||||
self.addressFriend = addressFriendTmp
|
||||
|
||||
let addressNameTmp = self.addressName
|
||||
|
||||
let avatarModelTmp = addressFriendTmp != nil
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
$0.friend!.name == addressFriendTmp!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == addressFriendTmp!.address!.asStringUriOnly()
|
||||
let avatarModelTmp = ContactsManager.shared.avatarListModel.first(where: {
|
||||
guard let friend = $0.friend else { return false }
|
||||
return friend.name == addressFriendTmp.name && friend.address?.asStringUriOnly() == addressFriendTmp.address?.asStringUriOnly()
|
||||
}) ?? ContactAvatarModel(friend: nil, name: self.addressName, address: self.address, withPresence: false)
|
||||
: ContactAvatarModel(friend: nil, name: self.addressName, address: self.address, withPresence: false)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.addressFriend = addressFriendTmp
|
||||
self.addressName = addressFriendTmp!.name ?? addressNameTmp
|
||||
self.addressName = addressFriendTmp.name ?? addressNameTmp
|
||||
self.avatarModel = avatarModelTmp
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import linphonesw
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
class HistoryListViewModel: ObservableObject {
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import linphonesw
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
// swiftlint:disable line_length
|
||||
class StartCallViewModel: ObservableObject {
|
||||
|
|
@ -37,8 +38,7 @@ class StartCallViewModel: ObservableObject {
|
|||
|
||||
@Published var operationInProgress: Bool = false
|
||||
|
||||
private var conferenceScheduler: ConferenceScheduler?
|
||||
private var conferenceSchedulerDelegate: ConferenceSchedulerDelegate?
|
||||
private var conferenceDelegate: ConferenceDelegate?
|
||||
|
||||
init() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
|
|
@ -67,7 +67,7 @@ class StartCallViewModel: ObservableObject {
|
|||
let account = core.defaultAccount
|
||||
if account == nil {
|
||||
Log.error(
|
||||
"\(StartCallViewModel.TAG) No default account found, can't create group call!"
|
||||
"\(ConversationModel.TAG) No default account found, can't create group call!"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -77,86 +77,64 @@ class StartCallViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
do {
|
||||
let conferenceInfo = try Factory.Instance.createConferenceInfo()
|
||||
conferenceInfo.organizer = account!.params?.identityAddress
|
||||
conferenceInfo.subject = self.messageText
|
||||
|
||||
var participantsList: [ParticipantInfo] = []
|
||||
var participantsList: [Address] = []
|
||||
self.participants.forEach { participant in
|
||||
do {
|
||||
let info = try Factory.Instance.createParticipantInfo(address: participant.address)
|
||||
// For meetings, all participants must have Speaker role
|
||||
info.role = Participant.Role.Speaker
|
||||
participantsList.append(info)
|
||||
} catch let error {
|
||||
Log.error(
|
||||
"\(StartCallViewModel.TAG) Can't create ParticipantInfo: \(error)"
|
||||
)
|
||||
}
|
||||
participantsList.append(participant.address)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.participants.removeAll()
|
||||
}
|
||||
|
||||
conferenceInfo.addParticipantInfos(participantInfos: participantsList)
|
||||
|
||||
Log.info(
|
||||
"\(StartCallViewModel.TAG) Creating group call with subject \(self.messageText) and \(participantsList.count) participant(s)"
|
||||
"\(ConversationModel.TAG) Creating group call with subject \(self.messageText) and \(participantsList.count) participant(s)"
|
||||
)
|
||||
|
||||
self.conferenceScheduler = try core.createConferenceScheduler(account: account)
|
||||
if self.conferenceScheduler != nil {
|
||||
self.conferenceAddDelegate(core: core, conferenceScheduler: self.conferenceScheduler!)
|
||||
// Will trigger the conference creation/update automatically
|
||||
self.conferenceScheduler!.info = conferenceInfo
|
||||
if let conference = LinphoneUtils.createGroupCall(core: core, account: account, subject: self.messageText) {
|
||||
self.conferenceAddDelegate(core: core, conference: conference)
|
||||
let callParams = try? core.createCallParams(call: nil)
|
||||
if let callParams = callParams {
|
||||
callParams.videoEnabled = true
|
||||
callParams.videoDirection = .RecvOnly
|
||||
|
||||
Log.info("\(ConversationModel.TAG) Inviting \(participantsList.count) participant(s) into newly created conference")
|
||||
|
||||
try conference.inviteParticipants(addresses: participantsList, params: callParams)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
TelecomManager.shared.participantsInvited = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
Log.error(
|
||||
"\(StartCallViewModel.TAG) createGroupCall: \(error)"
|
||||
"\(ConversationModel.TAG) createGroupCall: \(error)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func conferenceAddDelegate(core: Core, conferenceScheduler: ConferenceScheduler) {
|
||||
self.conferenceSchedulerDelegate = ConferenceSchedulerDelegateStub(onStateChanged: { (conferenceScheduler: ConferenceScheduler, state: ConferenceScheduler.State) in
|
||||
Log.info("\(StartCallViewModel.TAG) Conference scheduler state is \(state)")
|
||||
if state == ConferenceScheduler.State.Ready {
|
||||
conferenceScheduler.removeDelegate(delegate: self.conferenceSchedulerDelegate!)
|
||||
self.conferenceSchedulerDelegate = nil
|
||||
|
||||
let conferenceAddress = conferenceScheduler.info?.uri
|
||||
if conferenceAddress != nil {
|
||||
Log.info(
|
||||
"\(StartCallViewModel.TAG) Conference info created, address is \(conferenceAddress?.asStringUriOnly() ?? "Error conference address")"
|
||||
)
|
||||
|
||||
self.startVideoCall(core: core, conferenceAddress: conferenceAddress!)
|
||||
} else {
|
||||
Log.error("\(StartCallViewModel.TAG) Conference info URI is null!")
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
|
||||
func conferenceAddDelegate(core: Core, conference: Conference) {
|
||||
self.conferenceDelegate = ConferenceDelegateStub(onStateChanged: { (conference: Conference, state: Conference.State) in
|
||||
Log.info("\(StartCallViewModel.TAG) Conference state is \(state)")
|
||||
if state == .Created {
|
||||
NotificationCenter.default.post(name: Notification.Name("CallViewModelReset"), object: self)
|
||||
DispatchQueue.main.async {
|
||||
self.operationInProgress = false
|
||||
}
|
||||
} else if state == ConferenceScheduler.State.Error {
|
||||
conferenceScheduler.removeDelegate(delegate: self.conferenceSchedulerDelegate!)
|
||||
self.conferenceSchedulerDelegate = nil
|
||||
} else if state == .CreationFailed {
|
||||
Log.error("\(StartCallViewModel.TAG) Failed to create group call!")
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
|
||||
DispatchQueue.main.async {
|
||||
ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
self.operationInProgress = false
|
||||
}
|
||||
}
|
||||
})
|
||||
conferenceScheduler.addDelegate(delegate: self.conferenceSchedulerDelegate!)
|
||||
|
||||
if self.conferenceDelegate != nil {
|
||||
conference.addDelegate(delegate: self.conferenceDelegate!)
|
||||
}
|
||||
}
|
||||
|
||||
func startVideoCall(core: Core, conferenceAddress: Address) {
|
||||
|
|
|
|||
|
|
@ -69,8 +69,9 @@ struct AddParticipantsFragment: View {
|
|||
.multilineTextAlignment(.leading)
|
||||
.default_text_style_orange_800(styleSize: 16)
|
||||
.padding(.top, 20)
|
||||
Text(String(format: String(localized: "selected_participants_count"), $addParticipantsViewModel.participantsToAdd.count))
|
||||
.default_text_style_300(styleSize: 12)
|
||||
|
||||
Text(String(format: String(localized: "selected_participants_count"), $addParticipantsViewModel.participantsToAdd.count.description))
|
||||
.default_text_style_300(styleSize: 12)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ struct MeetingFragment: View {
|
|||
let model = MeetingViewModel()
|
||||
model.subject = "Meeting subject"
|
||||
model.conferenceUri = "linphone.com/lalalal.fr"
|
||||
model.description = "description du meeting ça va être la bringue wesh wesh gros bien ou bien ça roule"
|
||||
model.description = ""
|
||||
return MeetingFragment(meetingViewModel: model
|
||||
, meetingsListViewModel: MeetingsListViewModel()
|
||||
, isShowScheduleMeetingFragment: .constant(true)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class MeetingModel: ObservableObject {
|
||||
|
||||
|
|
|
|||
|
|
@ -274,6 +274,19 @@ class MeetingViewModel: ObservableObject {
|
|||
if let conferenceInfo = (self.displayedMeeting != nil ? self.displayedMeeting!.confInfo : try? Factory.Instance.createConferenceInfo()) {
|
||||
let localAccount = core.defaultAccount
|
||||
conferenceInfo.organizer = localAccount?.params?.identityAddress
|
||||
|
||||
// Allows to have a chat room within the conference
|
||||
conferenceInfo.setCapability(streamType: StreamType.Text, enable: true)
|
||||
|
||||
// Enable end-to-end encryption if client supports it
|
||||
if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) {
|
||||
Log.info("\(MeetingViewModel.TAG) Requesting EndToEnd security level for conference")
|
||||
conferenceInfo.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else {
|
||||
Log.info("\(MeetingViewModel.TAG) Requesting PointToPoint security level for conference")
|
||||
conferenceInfo.securityLevel = Conference.SecurityLevel.PointToPoint
|
||||
}
|
||||
|
||||
self.fillConferenceInfo(confInfo: conferenceInfo)
|
||||
self.resetConferenceSchedulerAndListeners(core: core)
|
||||
self.conferenceScheduler?.account = localAccount
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class AccountProfileViewModel: ObservableObject {
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class AccountSettingsViewModel: ObservableObject {
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class SettingsViewModel: ObservableObject {
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import linphonesw
|
||||
import SwiftUI
|
||||
|
||||
class SharedMainViewModel: ObservableObject {
|
||||
|
||||
|
|
@ -26,12 +27,14 @@ class SharedMainViewModel: ObservableObject {
|
|||
@Published var welcomeViewDisplayed = false
|
||||
@Published var generalTermsAccepted = false
|
||||
@Published var displayProfileMode = false
|
||||
@Published var defaultAvatar: URL?
|
||||
|
||||
let welcomeViewKey = "welcome_view"
|
||||
let generalTermsKey = "general_terms"
|
||||
let displayProfileModeKey = "display_profile_mode"
|
||||
let defaultAvatarKey = "default_avatar"
|
||||
|
||||
var maxWidth = 400.0
|
||||
var maxWidth = 600.0
|
||||
|
||||
private init() {
|
||||
let preferences = UserDefaults.standard
|
||||
|
|
@ -53,6 +56,14 @@ class SharedMainViewModel: ObservableObject {
|
|||
} else {
|
||||
displayProfileMode = preferences.bool(forKey: displayProfileModeKey)
|
||||
}
|
||||
|
||||
if preferences.object(forKey: defaultAvatarKey) == nil {
|
||||
preferences.set(defaultAvatar, forKey: defaultAvatarKey)
|
||||
} else {
|
||||
if let defaultAvatarTmp = preferences.url(forKey: defaultAvatarKey) {
|
||||
defaultAvatar = defaultAvatarTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func changeWelcomeView() {
|
||||
|
|
@ -82,4 +93,11 @@ class SharedMainViewModel: ObservableObject {
|
|||
displayProfileMode = false
|
||||
preferences.set(displayProfileMode, forKey: displayProfileModeKey)
|
||||
}
|
||||
|
||||
func changeDefaultAvatar(defaultAvatarURL: URL) {
|
||||
let preferences = UserDefaults.standard
|
||||
|
||||
defaultAvatar = defaultAvatarURL
|
||||
preferences.set(defaultAvatar, forKey: defaultAvatarKey)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ struct WelcomePage1Fragment: View {
|
|||
.multilineTextAlignment(.center)
|
||||
|
||||
}
|
||||
Spacer()
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 60)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ struct WelcomePage2Fragment: View {
|
|||
.multilineTextAlignment(.center)
|
||||
|
||||
}
|
||||
Spacer()
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 60)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ struct WelcomePage3Fragment: View {
|
|||
.multilineTextAlignment(.center)
|
||||
|
||||
}
|
||||
Spacer()
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 60)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
|
|||
|
|
@ -28,118 +28,131 @@ struct WelcomeView: View {
|
|||
var body: some View {
|
||||
NavigationView {
|
||||
GeometryReader { geometry in
|
||||
ScrollView {
|
||||
VStack {
|
||||
ZStack {
|
||||
Image("mountain")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 100)
|
||||
.clipped()
|
||||
|
||||
VStack(alignment: .trailing) {
|
||||
NavigationLink(destination: {
|
||||
PermissionsFragment()
|
||||
}, label: {
|
||||
Text("welcome_carousel_skip")
|
||||
.underline()
|
||||
.default_text_style_600(styleSize: 15)
|
||||
|
||||
})
|
||||
.padding(.top, -35)
|
||||
.padding(.trailing, 20)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
self.index = 2
|
||||
}
|
||||
)
|
||||
Text("welcome_page_title")
|
||||
.welcome_text_style_white_800(styleSize: 35)
|
||||
.padding(.trailing, 100)
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.bottom, -25)
|
||||
Text(String(format: String(localized: "welcome_page_subtitle"), Bundle.main.displayName))
|
||||
.welcome_text_style_white_800(styleSize: 25)
|
||||
.padding(.leading, 100)
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.bottom, -10)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
}
|
||||
.padding(.top, 35)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
TabView(selection: $index) {
|
||||
ForEach((0..<3), id: \.self) { index in
|
||||
if index == 0 {
|
||||
WelcomePage1Fragment()
|
||||
} else if index == 1 {
|
||||
WelcomePage2Fragment()
|
||||
} else if index == 2 {
|
||||
WelcomePage3Fragment()
|
||||
} else {
|
||||
WelcomePage1Fragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
|
||||
.frame(minHeight: 300)
|
||||
.onAppear {
|
||||
setupAppearance()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if index == 2 {
|
||||
NavigationLink(destination: {
|
||||
PermissionsFragment()
|
||||
}, label: {
|
||||
Text("start")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom.isEqual(to: 0.0) ? 20 : 0)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
} else {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
index += 1
|
||||
}
|
||||
}, label: {
|
||||
Text("next")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom.isEqual(to: 0.0) ? 20 : 0)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
}
|
||||
if #available(iOS 16.4, *) {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
|
||||
func innerScrollView(geometry: GeometryProxy) -> some View {
|
||||
VStack {
|
||||
ZStack {
|
||||
VStack(alignment: .trailing) {
|
||||
NavigationLink(destination: {
|
||||
PermissionsFragment()
|
||||
}, label: {
|
||||
Text("welcome_carousel_skip")
|
||||
.underline()
|
||||
.default_text_style_600(styleSize: 15)
|
||||
|
||||
})
|
||||
.padding(.top, -35)
|
||||
.padding(.trailing, 20)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
self.index = 2
|
||||
}
|
||||
)
|
||||
Text("welcome_page_title")
|
||||
.default_text_style_800(styleSize: 35)
|
||||
.padding(.trailing, 100)
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.bottom, -25)
|
||||
Text(String(format: String(localized: "welcome_page_subtitle"), Bundle.main.displayName))
|
||||
.default_text_style_800(styleSize: 25)
|
||||
.padding(.leading, 100)
|
||||
.frame(width: geometry.size.width)
|
||||
.padding(.bottom, -10)
|
||||
}
|
||||
.frame(width: geometry.size.width)
|
||||
}
|
||||
.padding(.top, 35)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
TabView(selection: $index) {
|
||||
ForEach((0..<3), id: \.self) { index in
|
||||
if index == 0 {
|
||||
WelcomePage1Fragment()
|
||||
} else if index == 1 {
|
||||
WelcomePage2Fragment()
|
||||
} else if index == 2 {
|
||||
WelcomePage3Fragment()
|
||||
} else {
|
||||
WelcomePage1Fragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
|
||||
.frame(minHeight: 300)
|
||||
.onAppear {
|
||||
setupAppearance()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if index == 2 {
|
||||
NavigationLink(destination: {
|
||||
PermissionsFragment()
|
||||
}, label: {
|
||||
Text("start")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom.isEqual(to: 0.0) ? 20 : 0)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
} else {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
index += 1
|
||||
}
|
||||
}, label: {
|
||||
Text("next")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom.isEqual(to: 0.0) ? 20 : 0)
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
}
|
||||
|
||||
Image("mountain2")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geometry.size.width, height: 60)
|
||||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
|
||||
func setupAppearance() {
|
||||
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor(Color.orangeMain500)
|
||||
if #available(iOS 16.0, *) {
|
||||
|
|
|
|||
|
|
@ -70,4 +70,47 @@ class LinphoneUtils: NSObject {
|
|||
core.defaultAccount?.params?.limeServerUrl != nil &&
|
||||
core.defaultAccount?.params?.conferenceFactoryUri != nil
|
||||
}
|
||||
|
||||
public class func createGroupCall(core: Core, account: Account?, subject: String) -> Conference? {
|
||||
do {
|
||||
let conferenceParams = try core.createConferenceParams(conference: nil)
|
||||
conferenceParams.videoEnabled = true
|
||||
conferenceParams.account = account
|
||||
conferenceParams.subject = subject
|
||||
|
||||
// Enable end-to-end encryption if client supports it
|
||||
if isEndToEndEncryptedChatAvailable(core: core) {
|
||||
Log.info("\(#function) Requesting EndToEnd security level for conference")
|
||||
conferenceParams.securityLevel = .EndToEnd
|
||||
} else {
|
||||
Log.info("\(#function) Requesting PointToPoint security level for conference")
|
||||
conferenceParams.securityLevel = .PointToPoint
|
||||
}
|
||||
|
||||
// Allows to have a chat room within the conference
|
||||
conferenceParams.chatEnabled = true
|
||||
|
||||
Log.info("\(#function) Creating group call with subject \(conferenceParams.subject ?? "Unknown")")
|
||||
|
||||
let confWithParams = try core.createConferenceWithParams(params: conferenceParams)
|
||||
|
||||
return confWithParams
|
||||
} catch let error {
|
||||
Log.info("\(#function) Error while creating group call: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public class func getConversationId(chatRoom: ChatRoom) -> String {
|
||||
return chatRoom.identifier ?? ""
|
||||
|
||||
}
|
||||
|
||||
public class func getDefaultAccount() -> Account? {
|
||||
return CoreContext.shared.mCore.defaultAccount ?? (CoreContext.shared.mCore.accountList.first ?? nil)
|
||||
}
|
||||
|
||||
public class func getAccountForAddress(address: Address) -> Account? {
|
||||
return CoreContext.shared.mCore.accountList.first { $0.params?.identityAddress?.weakEqual(address2: address) == true }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class Log: LoggingServiceDelegate {
|
|||
#endif
|
||||
}
|
||||
|
||||
func onLogMessageWritten(logService: linphonesw.LoggingService, domain: String, level: linphonesw.LogLevel, message: String) {
|
||||
func onLogMessageWritten(logService: LoggingService, domain: String, level: LogLevel, message: String) {
|
||||
output(message, level.rawValue, domain)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import linphonesw
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
final class MagicSearchSingleton: ObservableObject {
|
||||
|
||||
|
|
@ -107,8 +108,8 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func searchForContacts(sourceFlags: Int) {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
func searchForContactsWithoutCoreThread(sourceFlags: Int) {
|
||||
if self.magicSearch != nil {
|
||||
var needResetCache = false
|
||||
|
||||
if let oldFilter = self.previousFilter {
|
||||
|
|
@ -118,7 +119,6 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
}
|
||||
self.previousFilter = self.currentFilter
|
||||
|
||||
|
||||
if needResetCache {
|
||||
self.magicSearch.resetSearchCache()
|
||||
}
|
||||
|
|
@ -131,6 +131,31 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func searchForContacts(sourceFlags: Int) {
|
||||
if self.magicSearch != nil {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
var needResetCache = false
|
||||
|
||||
if let oldFilter = self.previousFilter {
|
||||
if oldFilter.count > self.currentFilter.count || oldFilter != self.currentFilter {
|
||||
needResetCache = true
|
||||
}
|
||||
}
|
||||
self.previousFilter = self.currentFilter
|
||||
|
||||
if needResetCache {
|
||||
self.magicSearch.resetSearchCache()
|
||||
}
|
||||
|
||||
self.magicSearch.getContactsListAsync(
|
||||
filter: self.currentFilter,
|
||||
domain: self.allContact ? "" : self.domainDefaultAccount,
|
||||
sourceFlags: sourceFlags,
|
||||
aggregation: MagicSearch.Aggregation.Friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func searchForSuggestions() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
var needResetCache = false
|
||||
|
|
|
|||
6
Podfile
6
Podfile
|
|
@ -4,12 +4,6 @@ source "https://gitlab.linphone.org/BC/public/podspec.git"
|
|||
source "https://github.com/CocoaPods/Specs.git"
|
||||
|
||||
def basic_pods
|
||||
if ENV['PODFILE_PATH'].nil?
|
||||
pod 'linphone-sdk', '~> 5.4.0-alpha'
|
||||
else
|
||||
pod 'linphone-sdk', :path => ENV['PODFILE_PATH'] # local sdk
|
||||
end
|
||||
|
||||
crashlytics
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue