mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-22 04:21:44 +00:00
feat: device pairing
This commit is contained in:
parent
1caad96fde
commit
93d420758f
@ -40,6 +40,7 @@ proc init*(self: ProfileController, account: Account) =
|
||||
profile.appearance = appearance
|
||||
profile.id = pubKey
|
||||
|
||||
self.view.addDevices(devices.getAllDevices())
|
||||
self.view.setDeviceSetup(devices.isDeviceSetup())
|
||||
self.view.setNewProfile(profile)
|
||||
self.view.setMnemonic(mnemonic)
|
||||
@ -67,3 +68,5 @@ method onSignal(self: ProfileController, data: Signal) =
|
||||
# TODO: view should react to model changes
|
||||
self.status.chat.updateContacts(msgData.contacts)
|
||||
self.view.updateContactList(msgData.contacts)
|
||||
if msgData.installations.len > 0:
|
||||
self.view.addDevices(msgData.installations)
|
||||
|
@ -1,11 +1,11 @@
|
||||
import NimQml, sequtils
|
||||
import views/[mailservers_list, contact_list, profile_info]
|
||||
import ../../status/profile/[mailserver, profile]
|
||||
import views/[mailservers_list, contact_list, profile_info, device_list]
|
||||
import ../../status/profile/[mailserver, profile, devices]
|
||||
import ../../status/profile as status_profile
|
||||
import ../../status/contacts as status_contacts
|
||||
import ../../status/accounts as status_accounts
|
||||
import ../../status/status
|
||||
import ../../status/devices
|
||||
import ../../status/devices as status_devices
|
||||
import ../../status/chat/chat
|
||||
import qrcode/qrcode
|
||||
|
||||
@ -14,6 +14,7 @@ QtObject:
|
||||
profile*: ProfileInfoView
|
||||
mailserversList*: MailServersList
|
||||
contactList*: ContactList
|
||||
deviceList*: DeviceList
|
||||
mnemonic: string
|
||||
status*: Status
|
||||
isDeviceSetup: bool
|
||||
@ -24,6 +25,7 @@ QtObject:
|
||||
proc delete*(self: ProfileView) =
|
||||
if not self.mailserversList.isNil: self.mailserversList.delete
|
||||
if not self.contactList.isNil: self.contactList.delete
|
||||
if not self.deviceList.isNil: self.deviceList.delete
|
||||
if not self.profile.isNil: self.profile.delete
|
||||
self.QObject.delete
|
||||
|
||||
@ -33,6 +35,7 @@ QtObject:
|
||||
result.profile = newProfileInfoView()
|
||||
result.mailserversList = newMailServersList()
|
||||
result.contactList = newContactList()
|
||||
result.deviceList = newDeviceList()
|
||||
result.mnemonic = ""
|
||||
result.status = status
|
||||
result.isDeviceSetup = false
|
||||
@ -123,7 +126,23 @@ QtObject:
|
||||
self.setDeviceSetup(true)
|
||||
|
||||
proc syncAllDevices*(self: ProfileView) {.slot.} =
|
||||
devices.syncAllDevices()
|
||||
status_devices.syncAllDevices()
|
||||
|
||||
proc advertiseDevice*(self: ProfileView) {.slot.} =
|
||||
devices.advertise()
|
||||
status_devices.advertise()
|
||||
|
||||
proc addDevices*(self: ProfileView, devices: seq[Installation]) =
|
||||
for dev in devices:
|
||||
self.deviceList.addDeviceToList(dev)
|
||||
|
||||
proc getDeviceList(self: ProfileView): QVariant {.slot.} =
|
||||
return newQVariant(self.deviceList)
|
||||
|
||||
QtProperty[QVariant] deviceList:
|
||||
read = getDeviceList
|
||||
|
||||
proc enableInstallation*(self: ProfileView, installationId: string, enable: bool) {.slot.} =
|
||||
if enable:
|
||||
status_devices.enable(installationId)
|
||||
else:
|
||||
status_devices.disable(installationId)
|
69
src/app/profile/views/device_list.nim
Normal file
69
src/app/profile/views/device_list.nim
Normal file
@ -0,0 +1,69 @@
|
||||
import NimQml
|
||||
import Tables
|
||||
import ../../../status/devices as status_devices
|
||||
import ../../../status/profile/devices
|
||||
|
||||
type
|
||||
DeviceRoles {.pure.} = enum
|
||||
Name = UserRole + 1,
|
||||
InstallationId = UserRole + 2
|
||||
IsUserDevice = UserRole + 3
|
||||
IsEnabled = UserRole + 4
|
||||
|
||||
QtObject:
|
||||
type DeviceList* = ref object of QAbstractListModel
|
||||
devices*: seq[Installation]
|
||||
|
||||
proc setup(self: DeviceList) = self.QAbstractListModel.setup
|
||||
|
||||
proc delete(self: DeviceList) =
|
||||
self.devices = @[]
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc newDeviceList*(): DeviceList =
|
||||
new(result, delete)
|
||||
result.devices = @[]
|
||||
result.setup
|
||||
|
||||
method rowCount(self: DeviceList, index: QModelIndex = nil): int =
|
||||
return self.devices.len
|
||||
|
||||
method data(self: DeviceList, index: QModelIndex, role: int): QVariant =
|
||||
if not index.isValid:
|
||||
return
|
||||
if index.row < 0 or index.row >= self.devices.len:
|
||||
return
|
||||
let installation = self.devices[index.row]
|
||||
case role.DeviceRoles:
|
||||
of DeviceRoles.Name: result = newQVariant(installation.name)
|
||||
of DeviceRoles.InstallationId: result = newQVariant(installation.installationId)
|
||||
of DeviceRoles.IsUserDevice: result = newQVariant(installation.isUserDevice)
|
||||
of DeviceRoles.IsEnabled: result = newQVariant(installation.enabled)
|
||||
|
||||
method roleNames(self: DeviceList): Table[int, string] =
|
||||
{
|
||||
DeviceRoles.Name.int:"name",
|
||||
DeviceRoles.InstallationId.int:"installationId",
|
||||
DeviceRoles.IsUserDevice.int:"isUserDevice",
|
||||
DeviceRoles.IsEnabled.int:"isEnabled"
|
||||
}.toTable
|
||||
|
||||
proc addDeviceToList*(self: DeviceList, installation: Installation) =
|
||||
var i = 0;
|
||||
var found = false
|
||||
for dev in self.devices:
|
||||
if dev.installationId == installation.installationId:
|
||||
found = true
|
||||
break
|
||||
i = i + 1
|
||||
|
||||
if found:
|
||||
let topLeft = self.createIndex(i, 0, nil)
|
||||
let bottomRight = self.createIndex(i, 0, nil)
|
||||
self.devices[i].name = installation.name
|
||||
self.devices[i].enabled = installation.enabled
|
||||
self.dataChanged(topLeft, bottomRight, @[DeviceRoles.Name.int, DeviceRoles.IsEnabled.int])
|
||||
else:
|
||||
self.beginInsertRows(newQModelIndex(), self.devices.len, self.devices.len)
|
||||
self.devices.add(installation)
|
||||
self.endInsertRows()
|
@ -53,7 +53,7 @@ QtObject:
|
||||
return
|
||||
|
||||
var signal: Signal = Signal(signalType: signalType)
|
||||
|
||||
|
||||
case signalType:
|
||||
of SignalType.Message:
|
||||
signal = messages.fromEvent(jsonSignal)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import json, random
|
||||
import ../status/libstatus/accounts as status_accounts
|
||||
import ../status/chat/[chat, message]
|
||||
import ../status/profile/profile
|
||||
import ../status/profile/[profile, devices]
|
||||
import types
|
||||
|
||||
proc toMessage*(jsonMsg: JsonNode): Message
|
||||
@ -25,6 +25,10 @@ proc fromEvent*(event: JsonNode): Signal =
|
||||
for jsonChat in event["event"]["chats"]:
|
||||
signal.chats.add(jsonChat.toChat)
|
||||
|
||||
if event["event"]{"installations"} != nil:
|
||||
for jsonInstallation in event["event"]["installations"]:
|
||||
signal.installations.add(jsonInstallation.toInstallation)
|
||||
|
||||
result = signal
|
||||
|
||||
proc toChatMember*(jsonMember: JsonNode): ChatMember =
|
||||
|
@ -1,7 +1,7 @@
|
||||
import json, chronicles, json_serialization, tables
|
||||
import ../status/libstatus/types
|
||||
import ../status/chat/[chat, message]
|
||||
import ../status/profile/profile
|
||||
import ../status/profile/[profile, devices]
|
||||
|
||||
type SignalSubscriber* = ref object of RootObj
|
||||
|
||||
@ -28,6 +28,7 @@ type MessageSignal* = ref object of Signal
|
||||
messages*: seq[Message]
|
||||
chats*: seq[Chat]
|
||||
contacts*: seq[Profile]
|
||||
installations*: seq[Installation]
|
||||
|
||||
type Filter* = object
|
||||
chatId*: string
|
||||
|
@ -1,6 +1,8 @@
|
||||
import system
|
||||
import libstatus/settings
|
||||
import libstatus/types
|
||||
import libstatus/installations
|
||||
import profile/devices
|
||||
import json
|
||||
|
||||
proc setDeviceName*(name: string) =
|
||||
@ -8,7 +10,7 @@ proc setDeviceName*(name: string) =
|
||||
|
||||
proc isDeviceSetup*():bool =
|
||||
let installationId = getSetting[string]("installation-id", "", true)
|
||||
let responseResult = parseJSON($getOurInstallations())["result"]
|
||||
let responseResult = getOurInstallations()
|
||||
if responseResult.kind == JNull:
|
||||
return false
|
||||
for installation in responseResult:
|
||||
@ -21,3 +23,21 @@ proc syncAllDevices*() =
|
||||
|
||||
proc advertise*() =
|
||||
discard sendPairInstallation()
|
||||
|
||||
proc getAllDevices*():seq[Installation] =
|
||||
let responseResult = getOurInstallations()
|
||||
let installationId = getSetting[string]("installation-id", "", true)
|
||||
result = @[]
|
||||
if responseResult.kind != JNull:
|
||||
for inst in responseResult:
|
||||
var device = inst.toInstallation
|
||||
if device.installationId == installationId:
|
||||
device.isUserDevice = true
|
||||
result.add(device)
|
||||
|
||||
proc enable*(installationId: string) =
|
||||
# TODO handle errors
|
||||
discard enableInstallation(installationId)
|
||||
|
||||
proc disable*(installationId: string) =
|
||||
discard disableInstallation(installationId)
|
||||
|
@ -1,11 +1,18 @@
|
||||
import json, core, utils, system
|
||||
|
||||
var installations: JsonNode = %*{}
|
||||
var dirty: bool = true
|
||||
|
||||
proc setInstallationMetadata*(installationId: string, deviceName: string, deviceType: string): string =
|
||||
result = callPrivateRPC("setInstallationMetadata".prefix, %* [installationId, {"name": deviceName, "deviceType": deviceType}])
|
||||
# TODO: handle errors
|
||||
|
||||
proc getOurInstallations*(): string =
|
||||
result = callPrivateRPC("getOurInstallations".prefix, %* [])
|
||||
proc getOurInstallations*(useCached: bool = true): JsonNode =
|
||||
if useCached and not dirty:
|
||||
return installations
|
||||
installations = callPrivateRPC("getOurInstallations".prefix, %* []).parseJSON()["result"]
|
||||
dirty = false
|
||||
result = installations
|
||||
|
||||
proc syncDevices*(): string =
|
||||
# These are not being used at the moment
|
||||
@ -16,3 +23,8 @@ proc syncDevices*(): string =
|
||||
proc sendPairInstallation*(): string =
|
||||
result = callPrivateRPC("sendPairInstallation".prefix)
|
||||
|
||||
proc enableInstallation*(installationId: string): string =
|
||||
result = callPrivateRPC("enableInstallation".prefix, %* [installationId])
|
||||
|
||||
proc disableInstallation*(installationId: string): string =
|
||||
result = callPrivateRPC("disableInstallation".prefix, %* [installationId])
|
||||
|
14
src/status/profile/devices.nim
Normal file
14
src/status/profile/devices.nim
Normal file
@ -0,0 +1,14 @@
|
||||
import json, hashes
|
||||
|
||||
type Installation* = ref object
|
||||
installationId*: string
|
||||
name*: string
|
||||
deviceType*: string
|
||||
enabled*: bool
|
||||
isUserDevice*: bool
|
||||
|
||||
proc toInstallation*(jsonInstallation: JsonNode): Installation =
|
||||
result = Installation(installationid: jsonInstallation{"id"}.getStr, enabled: jsonInstallation{"enabled"}.getBool, name: "", deviceType: "", isUserDevice: false)
|
||||
if jsonInstallation["metadata"].kind != JNull:
|
||||
result.name = jsonInstallation["metadata"]["name"].getStr
|
||||
result.deviceType = jsonInstallation["metadata"]["deviceType"].getStr
|
@ -52,7 +52,6 @@ Item {
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
visible: !selectChatMembers
|
||||
anchors.top: deviceNameTxt.bottom
|
||||
anchors.topMargin: 10
|
||||
anchors.right: deviceNameTxt.right
|
||||
@ -64,6 +63,7 @@ Item {
|
||||
}
|
||||
|
||||
Item {
|
||||
id: advertiseDeviceItem
|
||||
anchors.left: syncContainer.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.top: sectionTitle.bottom
|
||||
@ -71,6 +71,7 @@ Item {
|
||||
anchors.right: syncContainer.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
visible: profileModel.deviceSetup
|
||||
height: childrenRect.height
|
||||
|
||||
Rectangle {
|
||||
id: advertiseDevice
|
||||
@ -133,6 +134,69 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: deviceListItem
|
||||
anchors.left: syncContainer.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.top: advertiseDeviceItem.bottom
|
||||
anchors.topMargin: Style.current.padding * 2
|
||||
anchors.bottom: syncAllBtn.top
|
||||
anchors.bottomMargin: Style.current.padding
|
||||
anchors.right: syncContainer.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
visible: profileModel.deviceSetup
|
||||
|
||||
|
||||
StyledText {
|
||||
id: deviceListLbl
|
||||
text: qsTr("Paired devices")
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: deviceListLbl.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
spacing: 5
|
||||
anchors.right: syncContainer.right
|
||||
anchors.left: syncContainer.left
|
||||
delegate: Item {
|
||||
height: childrenRect.height
|
||||
SVGImage {
|
||||
id: enabledIcon
|
||||
source: "/app/img/" + (devicePairedSwitch.checked ? "messageActive.svg" : "message.svg")
|
||||
height: 24
|
||||
width: 24
|
||||
}
|
||||
StyledText {
|
||||
id: deviceItemLbl
|
||||
text: {
|
||||
let deviceId = model.installationId.split("-")[0].substr(0, 5)
|
||||
let labelText = `${model.name || qsTr("No info")} (${model.isUserDevice ? qsTr("you") + ", ": ""}${deviceId})`;
|
||||
return labelText;
|
||||
}
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 14
|
||||
anchors.left: enabledIcon.right
|
||||
anchors.leftMargin: Style.current.padding
|
||||
}
|
||||
Switch {
|
||||
id: devicePairedSwitch
|
||||
visible: !model.isUserDevice
|
||||
checked: model.isEnabled
|
||||
anchors.left: deviceItemLbl.right
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.top: deviceItemLbl.top
|
||||
onClicked: profileModel.enableInstallation(model.installationId, devicePairedSwitch)
|
||||
}
|
||||
}
|
||||
model: profileModel.deviceList
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
id: syncAllBtn
|
||||
anchors.bottom: syncContainer.bottom
|
||||
|
Loading…
x
Reference in New Issue
Block a user