feat: device pairing

This commit is contained in:
Richard Ramos 2020-07-03 20:42:44 -04:00 committed by Iuri Matias
parent 1caad96fde
commit 93d420758f
10 changed files with 218 additions and 12 deletions

View File

@ -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)

View File

@ -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)

View 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()

View File

@ -53,7 +53,7 @@ QtObject:
return
var signal: Signal = Signal(signalType: signalType)
case signalType:
of SignalType.Message:
signal = messages.fromEvent(jsonSignal)

View File

@ -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 =

View File

@ -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

View File

@ -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)

View File

@ -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])

View 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

View File

@ -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