mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-17 02:58:07 +00:00
Add devices list in Account profile
This commit is contained in:
parent
e383826e91
commit
11053b2ca3
9 changed files with 329 additions and 18 deletions
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#4e6074" viewBox="0 0 256 256"><path d="M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM72,48v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24V80H48V48ZM208,208H48V96H208V208Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM72,48v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24V80H48V48ZM208,208H48V96H208V208Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 351 B |
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#4e6074" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm64-88a8,8,0,0,1-8,8H128a8,8,0,0,1-8-8V72a8,8,0,0,1,16,0v48h48A8,8,0,0,1,192,128Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm64-88a8,8,0,0,1-8,8H128a8,8,0,0,1-8-8V72a8,8,0,0,1,16,0v48h48A8,8,0,0,1,192,128Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
21
Linphone/Assets.xcassets/desktop.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/desktop.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "desktop.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/desktop.imageset/desktop.svg
vendored
Normal file
1
Linphone/Assets.xcassets/desktop.imageset/desktop.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M208,40H48A24,24,0,0,0,24,64V176a24,24,0,0,0,24,24h72v16H96a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16H136V200h72a24,24,0,0,0,24-24V64A24,24,0,0,0,208,40ZM48,56H208a8,8,0,0,1,8,8v80H40V64A8,8,0,0,1,48,56ZM208,184H48a8,8,0,0,1-8-8V160H216v16A8,8,0,0,1,208,184Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 373 B |
21
Linphone/Assets.xcassets/device-mobile-camera.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/device-mobile-camera.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "device-mobile-camera.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/device-mobile-camera.imageset/device-mobile-camera.svg
vendored
Normal file
1
Linphone/Assets.xcassets/device-mobile-camera.imageset/device-mobile-camera.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M176,16H80A24,24,0,0,0,56,40V216a24,24,0,0,0,24,24h96a24,24,0,0,0,24-24V40A24,24,0,0,0,176,16Zm8,200a8,8,0,0,1-8,8H80a8,8,0,0,1-8-8V40a8,8,0,0,1,8-8h96a8,8,0,0,1,8,8ZM140,60a12,12,0,1,1-12-12A12,12,0,0,1,140,60Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 335 B |
|
|
@ -3814,6 +3814,57 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"manage_account_device_last_connection" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Last connection:"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dernière connexion :"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"manage_account_device_remove" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Remove"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Supprimer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"manage_account_devices_title" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Devices"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Appareils"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"manage_account_dialog_international_prefix_help_message" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
|
|
@ -3864,6 +3915,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"manage_account_no_device" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "No device found…"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aucun appareil n'a été trouvé…"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"manage_account_remove_picture" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ struct AccountProfileFragment: View {
|
|||
|
||||
@Binding var isShowAccountProfileFragment: Bool
|
||||
@State var detailIsOpen: Bool = true
|
||||
@State var deviceIsOpen: Bool = false
|
||||
|
||||
@State private var showPhotoPicker = false
|
||||
@State private var selectedImage: UIImage?
|
||||
|
|
@ -384,7 +385,7 @@ struct AccountProfileFragment: View {
|
|||
HStack(spacing: 20) {
|
||||
Toggle("", isOn: Binding(
|
||||
get: { accountModel.isRegistrered },
|
||||
set: { newValue in
|
||||
set: { _ in
|
||||
accountProfileViewModel.toggleRegister()
|
||||
}
|
||||
))
|
||||
|
|
@ -408,46 +409,123 @@ struct AccountProfileFragment: View {
|
|||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.all)
|
||||
.background(Color.gray100)
|
||||
|
||||
/*
|
||||
HStack(alignment: .center) {
|
||||
Text("manage_account_details_title")
|
||||
Text("manage_account_devices_title")
|
||||
.default_text_style_800(styleSize: 18)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(detailIsOpen ? "caret-up" : "caret-down")
|
||||
Image(deviceIsOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 10)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 20)
|
||||
.background(Color.gray100)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
detailIsOpen.toggle()
|
||||
deviceIsOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if detailIsOpen {
|
||||
if deviceIsOpen {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 30) {
|
||||
Text("manage_account_dialog_international_prefix_help_message")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
VStack(spacing: 15) {
|
||||
ForEach(accountModel.devices.indices, id: \.self) { index in
|
||||
VStack {
|
||||
HStack {
|
||||
Image(accountModel.devices[index].isMobileDevice ? "device-mobile-camera" : "desktop")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
|
||||
Text(accountModel.devices[index].deviceName)
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Button(action: {
|
||||
deviceIsOpen = false
|
||||
accountModel.removeDevice(deviceIndex: index)
|
||||
deviceIsOpen = true
|
||||
}, label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("manage_account_device_remove")
|
||||
.default_text_style_orange_500(styleSize: 14)
|
||||
.frame(height: 35)
|
||||
}
|
||||
|
||||
})
|
||||
.padding(.horizontal, 10)
|
||||
.background(Color.orangeMain100)
|
||||
.cornerRadius(60)
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
|
||||
Text("manage_account_device_last_connection")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack {
|
||||
Image("calendar-blank")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
|
||||
Text(accountModel.devices[index].lastDate)
|
||||
.default_text_style(styleSize: 15)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Image("clock")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
|
||||
Text(accountModel.devices[index].lastTime)
|
||||
.default_text_style(styleSize: 15)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
.padding(.all, 20)
|
||||
.background(Color.gray100)
|
||||
.cornerRadius(15)
|
||||
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 30)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.all, 20)
|
||||
.frame(maxWidth: .infinity)
|
||||
.overlay(
|
||||
VStack {
|
||||
if accountModel.devices.indices.isEmpty {
|
||||
Text("manage_account_no_device")
|
||||
.default_text_style_500(styleSize: 16)
|
||||
}
|
||||
}
|
||||
.padding(.all)
|
||||
)
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.horizontal)
|
||||
.zIndex(-1)
|
||||
.zIndex(-2)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
|
|
@ -463,9 +541,11 @@ struct AccountProfileFragment: View {
|
|||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.all)
|
||||
*/
|
||||
}
|
||||
.frame(maxWidth: sharedMainViewModel.maxWidth)
|
||||
.onAppear {
|
||||
accountModel.requestDevicesList()
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import SwiftUI
|
|||
import Combine
|
||||
|
||||
class AccountModel: ObservableObject {
|
||||
static let TAG = "[AccountModel]"
|
||||
|
||||
let account: Account
|
||||
@Published var humanReadableRegistrationState: String = ""
|
||||
@Published var summary: String = ""
|
||||
|
|
@ -38,9 +40,14 @@ class AccountModel: ObservableObject {
|
|||
@Published var usernaneAvatar: String = ""
|
||||
@Published var imagePathAvatar: URL?
|
||||
|
||||
@Published var devices: [AccountDeviceModel] = []
|
||||
|
||||
private var accountDelegate: AccountDelegate?
|
||||
private var coreDelegate: CoreDelegate?
|
||||
|
||||
private var accountManagerServices: AccountManagerServices?
|
||||
private var requestDelegate: AccountManagerServicesRequestDelegate?
|
||||
|
||||
init(account: Account, core: Core) {
|
||||
self.account = account
|
||||
|
||||
|
|
@ -83,6 +90,8 @@ class AccountModel: ObservableObject {
|
|||
let displayName = account.displayName()
|
||||
let address = account.params?.identityAddress?.asString()
|
||||
|
||||
self.requestDevicesList()
|
||||
|
||||
let displayNameTmp = account.params?.identityAddress?.displayName ?? ""
|
||||
let usernaneAvatarTmp = account.contactAddress?.username ?? ""
|
||||
var photoAvatarModelTmp = ""
|
||||
|
|
@ -153,4 +162,114 @@ class AccountModel: ObservableObject {
|
|||
|
||||
return imagePath
|
||||
}
|
||||
|
||||
func requestDevicesList() {
|
||||
if account.params != nil && account.params!.identityAddress != nil, let identityAddress = account.params!.identityAddress {
|
||||
Log.info(
|
||||
"\(AccountModel.TAG) Request devices list for identity address \(identityAddress.asStringUriOnly())"
|
||||
)
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
do {
|
||||
self.accountManagerServices = try core.createAccountManagerServices()
|
||||
if self.accountManagerServices != nil {
|
||||
self.accountManagerServices!.language = Locale.current.identifier
|
||||
|
||||
do {
|
||||
let request = try self.accountManagerServices!.createGetDevicesListRequest(sipIdentity: identityAddress)
|
||||
self.addDelegate(request: request)
|
||||
} catch {
|
||||
print("\(AccountModel.TAG) Failed to create request: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addDelegate(request: AccountManagerServicesRequest) {
|
||||
self.requestDelegate = AccountManagerServicesRequestDelegateStub(
|
||||
onRequestSuccessful: { (request: AccountManagerServicesRequest, data: String) in
|
||||
Log.info("\(AccountModel.TAG) Request \(request) was successful, data is \(data)")
|
||||
}, onRequestError: { (request: AccountManagerServicesRequest, statusCode: Int, errorMessage: String, parameterErrors: Dictionary?) in
|
||||
Log.error(
|
||||
"\(AccountModel.TAG) Request \(request) returned an error with status code \(statusCode) and message \(errorMessage)"
|
||||
)
|
||||
// TODO Display Error Toast
|
||||
}, onDevicesListFetched: { (request: AccountManagerServicesRequest, accountDevices: [AccountDevice]) in
|
||||
Log.info("\(AccountModel.TAG) Fetched \(accountDevices.count) devices for our account")
|
||||
var devicesList: [AccountDeviceModel] = []
|
||||
accountDevices.forEach { accountDevice in
|
||||
devicesList.append(AccountDeviceModel(accountDevice: accountDevice))
|
||||
}
|
||||
|
||||
request.removeDelegate(delegate: self.requestDelegate!)
|
||||
DispatchQueue.main.async {
|
||||
self.devices = devicesList
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
request.addDelegate(delegate: self.requestDelegate!)
|
||||
request.submit()
|
||||
}
|
||||
|
||||
func removeDevice(deviceIndex: Int) {
|
||||
let removedDevice = self.devices[deviceIndex].accountDevice
|
||||
self.devices.remove(at: deviceIndex)
|
||||
if account.params != nil && account.params!.identityAddress != nil, let identityAddress = account.params!.identityAddress {
|
||||
Log.info(
|
||||
"\(AccountModel.TAG) Delete device for identity address \(identityAddress.asStringUriOnly())"
|
||||
)
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
do {
|
||||
self.accountManagerServices = try core.createAccountManagerServices()
|
||||
if self.accountManagerServices != nil {
|
||||
self.accountManagerServices!.language = Locale.current.identifier
|
||||
|
||||
do {
|
||||
let request = try self.accountManagerServices!.createDeleteDeviceRequest(sipIdentity: identityAddress, device: removedDevice)
|
||||
self.addDelegate(request: request)
|
||||
} catch {
|
||||
print("\(AccountModel.TAG) Failed to create request: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AccountDeviceModel: ObservableObject {
|
||||
let accountDevice: AccountDevice
|
||||
@Published var deviceName: String = ""
|
||||
@Published var lastDate: String = ""
|
||||
@Published var lastTime: String = ""
|
||||
@Published var isMobileDevice: Bool = true
|
||||
|
||||
init(accountDevice: AccountDevice) {
|
||||
self.accountDevice = accountDevice
|
||||
self.deviceName = accountDevice.name ?? ""
|
||||
|
||||
let timeInterval = TimeInterval(accountDevice.lastUpdateTimestamp ?? 0)
|
||||
let dateTmp = Date(timeIntervalSince1970: timeInterval)
|
||||
|
||||
let dateFormat = DateFormatter()
|
||||
dateFormat.dateFormat = Locale.current.identifier == "fr_FR" ? "dd/MM/YYYY" : "MM/dd/YYYY"
|
||||
let date = dateFormat.string(from: dateTmp)
|
||||
|
||||
let dateFormatBis = DateFormatter()
|
||||
dateFormatBis.dateFormat = "HH:mm"
|
||||
let time = dateFormatBis.string(from: dateTmp)
|
||||
|
||||
self.lastDate = date
|
||||
self.lastTime = time
|
||||
|
||||
self.isMobileDevice = accountDevice.userAgent.contains("LinphoneAndroid") || accountDevice.userAgent.contains(
|
||||
"LinphoneiOS"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue