mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-24 04:28:58 +00:00
fix: Device syncing
- Added local pairing signals - Remove slash ending from keystorePath - Implemented localPairingState. Fixed sync new device workflow. - Error message view design update - Moved local pairing status to devices service - ConnectionString automatic validation - Async inputConnectionString - Added all installation properties to model. Minor renaming. - Removed emoji and color customization - Show display name, colorhash and color in device being synced - Add timeout to pairing server - Add device type Fix `DeviceSyncingView` sizing. Fix `inputConnectionString` async task slot.
This commit is contained in:
parent
d2aa9e97bf
commit
33d38a4081
@ -205,7 +205,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
|
||||
result.privacyService = privacy_service.newService(statusFoundation.events, result.settingsService,
|
||||
result.accountsService)
|
||||
result.savedAddressService = saved_address_service.newService(statusFoundation.events, result.networkService, result.settingsService)
|
||||
result.devicesService = devices_service.newService(statusFoundation.events, statusFoundation.threadpool, result.settingsService)
|
||||
result.devicesService = devices_service.newService(statusFoundation.events, statusFoundation.threadpool, result.settingsService, result.accountsService)
|
||||
result.mailserversService = mailservers_service.newService(statusFoundation.events, statusFoundation.threadpool,
|
||||
result.settingsService, result.nodeConfigurationService, statusFoundation.fleetConfiguration)
|
||||
result.nodeService = node_service.newService(statusFoundation.events, result.settingsService, result.nodeConfigurationService)
|
||||
@ -225,7 +225,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
|
||||
result.accountsService,
|
||||
result.generalService,
|
||||
result.profileService,
|
||||
result.keycardService
|
||||
result.keycardService,
|
||||
result.devicesService,
|
||||
)
|
||||
result.mainModule = main_module.newModule[AppController](
|
||||
result,
|
||||
@ -383,6 +384,7 @@ proc start*(self: AppController) =
|
||||
self.keychainService.init()
|
||||
self.generalService.init()
|
||||
self.accountsService.init()
|
||||
self.devicesService.init()
|
||||
|
||||
self.startupModule.load()
|
||||
|
||||
@ -405,7 +407,6 @@ proc load(self: AppController) =
|
||||
self.activityCenterService.init()
|
||||
self.savedAddressService.init()
|
||||
self.aboutService.init()
|
||||
self.devicesService.init()
|
||||
self.ensService.init()
|
||||
self.tokensService.init()
|
||||
self.gifService.init()
|
||||
|
@ -8,7 +8,7 @@ import ../../../../app_service/service/bookmarks/dto/[bookmark]
|
||||
import ../../../../app_service/service/community/dto/[community]
|
||||
import ../../../../app_service/service/activity_center/dto/[notification]
|
||||
import ../../../../app_service/service/contacts/dto/[contacts, status_update]
|
||||
import ../../../../app_service/service/devices/dto/[device]
|
||||
import ../../../../app_service/service/devices/dto/[installation]
|
||||
import ../../../../app_service/service/settings/dto/[settings]
|
||||
import ../../../../app_service/service/saved_address/[dto]
|
||||
import ../../../../app_service/service/wallet_account/[key_pair_dto]
|
||||
@ -19,7 +19,7 @@ type MessageSignal* = ref object of Signal
|
||||
pinnedMessages*: seq[PinnedMessageUpdateDto]
|
||||
chats*: seq[ChatDto]
|
||||
contacts*: seq[ContactsDto]
|
||||
devices*: seq[DeviceDto]
|
||||
installations*: seq[InstallationDto]
|
||||
emojiReactions*: seq[ReactionDto]
|
||||
communities*: seq[CommunityDto]
|
||||
communitiesSettings*: seq[CommunitySettingsDto]
|
||||
@ -87,7 +87,7 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal =
|
||||
|
||||
if event["event"]{"installations"} != nil:
|
||||
for jsonDevice in event["event"]["installations"]:
|
||||
signal.devices.add(jsonDevice.toDeviceDto())
|
||||
signal.installations.add(jsonDevice.toInstallationDto())
|
||||
|
||||
if event["event"]{"emojiReactions"} != nil:
|
||||
for jsonReaction in event["event"]["emojiReactions"]:
|
||||
|
23
src/app/core/signals/remote_signals/pairing.nim
Normal file
23
src/app/core/signals/remote_signals/pairing.nim
Normal file
@ -0,0 +1,23 @@
|
||||
import json, tables
|
||||
import base
|
||||
import ../../../../app_service/service/accounts/dto/accounts
|
||||
|
||||
|
||||
|
||||
type LocalPairingSignal* = ref object of Signal
|
||||
eventType*: string
|
||||
action*: int
|
||||
error*: string
|
||||
account*: AccountDto
|
||||
|
||||
proc fromEvent*(T: type LocalPairingSignal, event: JsonNode): LocalPairingSignal =
|
||||
result = LocalPairingSignal()
|
||||
let e = event["event"]
|
||||
if e.contains("type"):
|
||||
result.eventType = e["type"].getStr
|
||||
if e.contains("action"):
|
||||
result.action = e["action"].getInt
|
||||
if e.contains("error"):
|
||||
result.error = e["error"].getStr
|
||||
if e.contains("data"):
|
||||
result.account = e["data"].toAccountDto()
|
@ -51,6 +51,7 @@ type SignalType* {.pure.} = enum
|
||||
WakuBackedUpProfile = "waku.backedup.profile"
|
||||
WakuBackedUpSettings = "waku.backedup.settings"
|
||||
WakuBackedUpKeycards = "waku.backedup.keycards"
|
||||
LocalPairing = "localPairing"
|
||||
Unknown
|
||||
|
||||
proc event*(self:SignalType):string =
|
||||
|
@ -106,6 +106,8 @@ QtObject:
|
||||
of SignalType.WakuBackedUpProfile: WakuBackedUpProfileSignal.fromEvent(jsonSignal)
|
||||
of SignalType.WakuBackedUpSettings: WakuBackedUpSettingsSignal.fromEvent(jsonSignal)
|
||||
of SignalType.WakuBackedUpKeycards: WakuBackedUpKeycardsSignal.fromEvent(jsonSignal)
|
||||
# pairing
|
||||
of SignalType.LocalPairing: LocalPairingSignal.fromEvent(jsonSignal)
|
||||
else: Signal()
|
||||
|
||||
result.signalType = signalType
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
import ./remote_signals/[base, chronicles_logs, community, discovery_summary, envelope, expired, mailserver, messages,
|
||||
peerstats, signal_type, stats, wallet, whisper_filter, keycard, update_available, status_updates, waku_backed_up_profile,
|
||||
waku_backed_up_settings, waku_backed_up_keycards, waku_fetching_backup_progress]
|
||||
waku_backed_up_settings, waku_backed_up_keycards, waku_fetching_backup_progress, pairing]
|
||||
|
||||
export base, chronicles_logs, community, discovery_summary, envelope, expired, mailserver, messages, peerstats,
|
||||
signal_type, stats, wallet, whisper_filter, keycard, update_available, status_updates, waku_backed_up_profile,
|
||||
waku_backed_up_settings, waku_backed_up_keycards, waku_fetching_backup_progress
|
||||
waku_backed_up_settings, waku_backed_up_keycards, waku_fetching_backup_progress, pairing
|
||||
|
@ -5,6 +5,10 @@ import ../../../../core/eventemitter
|
||||
import ../../../../../app_service/service/settings/service as settings_service
|
||||
import ../../../../../app_service/service/devices/service as devices_service
|
||||
|
||||
import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_module
|
||||
|
||||
const UNIQUE_SYNCING_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER* = "SyncingSection-AccountsModule-Authentication"
|
||||
|
||||
logScope:
|
||||
topics = "profile-section-devices-module-controller"
|
||||
|
||||
@ -37,8 +41,23 @@ proc init*(self: Controller) =
|
||||
self.delegate.onDevicesLoadingErrored()
|
||||
|
||||
self.events.on(SIGNAL_UPDATE_DEVICE) do(e: Args):
|
||||
var args = UpdateDeviceArgs(e)
|
||||
self.delegate.updateOrAddDevice(args.deviceId, args.name, args.enabled)
|
||||
var args = UpdateInstallationArgs(e)
|
||||
self.delegate.updateOrAddDevice(args.installation)
|
||||
|
||||
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args):
|
||||
let args = SharedKeycarModuleArgs(e)
|
||||
if args.uniqueIdentifier != UNIQUE_SYNCING_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER:
|
||||
return
|
||||
self.delegate.onUserAuthenticated(args.pin, args.password, args.keyUid)
|
||||
|
||||
self.events.on(SIGNAL_LOCAL_PAIRING_EVENT) do(e: Args):
|
||||
var args = LocalPairingEventArgs(e)
|
||||
self.delegate.onLocalPairingEvent(args.eventType, args.action, args.error)
|
||||
|
||||
self.events.on(SIGNAL_LOCAL_PAIRING_STATUS_UPDATE) do(e: Args):
|
||||
var args = LocalPairingStatus(e)
|
||||
self.delegate.onLocalPairingStatusUpdate(args)
|
||||
|
||||
|
||||
proc getMyInstallationId*(self: Controller): string =
|
||||
return self.settingsService.getInstallationId()
|
||||
@ -46,6 +65,9 @@ proc getMyInstallationId*(self: Controller): string =
|
||||
proc asyncLoadDevices*(self: Controller) =
|
||||
self.devicesService.asyncLoadDevices()
|
||||
|
||||
proc getAllDevices*(self: Controller): seq[InstallationDto] =
|
||||
return self.devicesService.getAllDevices()
|
||||
|
||||
proc setDeviceName*(self: Controller, name: string) =
|
||||
self.devicesService.setDeviceName(name)
|
||||
|
||||
@ -60,3 +82,26 @@ proc enableDevice*(self: Controller, deviceId: string, enable: bool) =
|
||||
self.devicesService.enable(deviceId)
|
||||
else:
|
||||
self.devicesService.disable(deviceId)
|
||||
|
||||
#
|
||||
# Pairing status
|
||||
#
|
||||
|
||||
proc authenticateUser*(self: Controller, keyUid: string) =
|
||||
let data = SharedKeycarModuleAuthenticationArgs(
|
||||
uniqueIdentifier: UNIQUE_SYNCING_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER,
|
||||
keyUid: keyUid)
|
||||
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
|
||||
|
||||
#
|
||||
# Backend actions
|
||||
#
|
||||
|
||||
proc validateConnectionString*(self: Controller, connectionString: string): string =
|
||||
return self.devicesService.validateConnectionString(connectionString)
|
||||
|
||||
proc getConnectionStringForBootstrappingAnotherDevice*(self: Controller, keyUid: string, password: string): string =
|
||||
return self.devicesService.getConnectionStringForBootstrappingAnotherDevice(keyUid, password)
|
||||
|
||||
proc inputConnectionStringForBootstrapping*(self: Controller, connectionString: string): string =
|
||||
return self.devicesService.inputConnectionStringForBootstrapping(connectionString)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import NimQml
|
||||
import ../../../../../app_service/service/devices/service as devices_service
|
||||
import ../../../../../app_service/service/devices/service
|
||||
|
||||
|
||||
type
|
||||
@ -17,7 +17,7 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
|
||||
method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method updateOrAddDevice*(self: AccessInterface, installationId: string, name: string, enabled: bool) {.base.} =
|
||||
method updateOrAddDevice*(self: AccessInterface, installation: InstallationDto) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method viewDidLoad*(self: AccessInterface) {.base.} =
|
||||
@ -26,7 +26,7 @@ method viewDidLoad*(self: AccessInterface) {.base.} =
|
||||
method getMyInstallationId*(self: AccessInterface): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onDevicesLoaded*(self: AccessInterface, allDevices: seq[DeviceDto]) {.base.} =
|
||||
method onDevicesLoaded*(self: AccessInterface, allDevices: seq[InstallationDto]) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onDevicesLoadingErrored*(self: AccessInterface) {.base.} =
|
||||
@ -46,3 +46,24 @@ method advertise*(self: AccessInterface) {.base.} =
|
||||
|
||||
method enableDevice*(self: AccessInterface, installationId: string, enable: bool) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method authenticateUser*(self: AccessInterface, keyUid: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onUserAuthenticated*(self: AccessInterface, pin: string, password: string, keyUid: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
proc validateConnectionString*(self: AccessInterface, connectionString: string): string =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method getConnectionStringForBootstrappingAnotherDevice*(self: AccessInterface, keyUid: string, password: string): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method inputConnectionStringForBootstrapping*(self: AccessInterface, connectionString: string): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onLocalPairingEvent*(self: AccessInterface, eventType: EventType, action: Action, error: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onLocalPairingStatusUpdate*(self: AccessInterface, status: LocalPairingStatus) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
@ -1,31 +1,33 @@
|
||||
import ../../../../../app_service/service/devices/service as devices_service
|
||||
import ../../../../../app_service/service/devices/dto/[installation]
|
||||
|
||||
type
|
||||
Item* = ref object
|
||||
installationId: string
|
||||
name: string
|
||||
enabled: bool
|
||||
installation: InstallationDto
|
||||
isCurrentDevice: bool
|
||||
|
||||
proc initItem*(installationId, name: string, enabled, isCurrentDevice: bool): Item =
|
||||
proc initItem*(installation: InstallationDto, isCurrentDevice: bool): Item =
|
||||
result = Item()
|
||||
result.installationId = installationId
|
||||
result.name = name
|
||||
result.enabled = enabled
|
||||
result.installation = installation
|
||||
result.isCurrentDevice = isCurrentDevice
|
||||
|
||||
proc installationId*(self: Item): string =
|
||||
self.installationId
|
||||
proc installation*(self: Item): InstallationDto =
|
||||
return self.installation
|
||||
|
||||
proc `installation=`*(self: Item, installation: InstallationDto) =
|
||||
self.installation = installation
|
||||
|
||||
proc name*(self: Item): string =
|
||||
self.name
|
||||
self.installation.metadata.name
|
||||
|
||||
proc `name=`*(self: Item, value: string) =
|
||||
self.name = value
|
||||
self.installation.metadata.name = value
|
||||
|
||||
proc enabled*(self: Item): bool =
|
||||
self.enabled
|
||||
self.installation.enabled
|
||||
|
||||
proc `enabled=`*(self: Item, value: bool) =
|
||||
self.enabled = value
|
||||
self.installation.enabled = value
|
||||
|
||||
proc isCurrentDevice*(self: Item): bool =
|
||||
self.isCurrentDevice
|
||||
|
@ -1,12 +1,18 @@
|
||||
import NimQml, Tables, sequtils
|
||||
import item
|
||||
import ../../../../../app_service/service/devices/dto/[installation]
|
||||
|
||||
type
|
||||
ModelRole {.pure.} = enum
|
||||
Name = UserRole + 1,
|
||||
InstallationId
|
||||
IsCurrentDevice
|
||||
InstallationId = UserRole + 1,
|
||||
Identity
|
||||
Version
|
||||
Enabled
|
||||
Timestamp
|
||||
Name
|
||||
DeviceType
|
||||
FcmToken
|
||||
IsCurrentDevice
|
||||
|
||||
QtObject:
|
||||
type Model* = ref object of QAbstractListModel
|
||||
@ -35,10 +41,15 @@ QtObject:
|
||||
|
||||
method roleNames(self: Model): Table[int, string] =
|
||||
{
|
||||
ModelRole.Name.int:"name",
|
||||
ModelRole.InstallationId.int:"installationId",
|
||||
ModelRole.Identity.int:"identity",
|
||||
ModelRole.Version.int:"version",
|
||||
ModelRole.Enabled.int:"enabled",
|
||||
ModelRole.Timestamp.int:"timestamp",
|
||||
ModelRole.Name.int:"name",
|
||||
ModelRole.DeviceType.int:"deviceType",
|
||||
ModelRole.FcmToken.int:"fcmToken",
|
||||
ModelRole.IsCurrentDevice.int:"isCurrentDevice",
|
||||
ModelRole.Enabled.int:"enabled"
|
||||
}.toTable
|
||||
|
||||
method data(self: Model, index: QModelIndex, role: int): QVariant =
|
||||
@ -50,14 +61,24 @@ QtObject:
|
||||
let enumRole = role.ModelRole
|
||||
|
||||
case enumRole:
|
||||
of ModelRole.Name:
|
||||
result = newQVariant(item.name)
|
||||
of ModelRole.InstallationId:
|
||||
result = newQVariant(item.installationId)
|
||||
result = newQVariant(item.installation.id)
|
||||
of ModelRole.Identity:
|
||||
result = newQVariant(item.installation.identity)
|
||||
of ModelRole.Version:
|
||||
result = newQVariant(item.installation.version)
|
||||
of ModelRole.Enabled:
|
||||
result = newQVariant(item.installation.enabled)
|
||||
of ModelRole.Timestamp:
|
||||
result = newQVariant(item.installation.timestamp)
|
||||
of ModelRole.Name:
|
||||
result = newQVariant(item.installation.metadata.name)
|
||||
of ModelRole.DeviceType:
|
||||
result = newQVariant(item.installation.metadata.deviceType)
|
||||
of ModelRole.FcmToken:
|
||||
result = newQVariant(item.installation.metadata.fcmToken)
|
||||
of ModelRole.IsCurrentDevice:
|
||||
result = newQVariant(item.isCurrentDevice)
|
||||
of ModelRole.Enabled:
|
||||
result = newQVariant(item.enabled)
|
||||
|
||||
proc addItems*(self: Model, items: seq[Item]) =
|
||||
if(items.len == 0):
|
||||
@ -84,23 +105,22 @@ QtObject:
|
||||
|
||||
proc findIndexByInstallationId(self: Model, installationId: string): int =
|
||||
for i in 0..<self.items.len:
|
||||
if installationId == self.items[i].installationId():
|
||||
if installationId == self.items[i].installation.id:
|
||||
return i
|
||||
return -1
|
||||
|
||||
proc isItemWithInstallationIdAdded*(self: Model, installationId: string): bool =
|
||||
return self.findIndexByInstallationId(installationId) != -1
|
||||
|
||||
proc updateItem*(self: Model, installationId: string, name: string, enabled: bool) =
|
||||
var i = self.findIndexByInstallationId(installationId)
|
||||
proc updateItem*(self: Model, installation: InstallationDto) =
|
||||
var i = self.findIndexByInstallationId(installation.id)
|
||||
if(i == -1):
|
||||
return
|
||||
|
||||
let first = self.createIndex(i, 0, nil)
|
||||
let last = self.createIndex(i, 0, nil)
|
||||
self.items[i].name = name
|
||||
self.items[i].enabled = enabled
|
||||
self.dataChanged(first, last, @[ModelRole.Name.int, ModelRole.Enabled.int])
|
||||
self.items[i].installation = installation
|
||||
self.dataChanged(first, last, @[])
|
||||
|
||||
proc getIsDeviceSetup*(self: Model, installationId: string): bool =
|
||||
return anyIt(self.items, it.installationId == installationId and it.name != "")
|
||||
return anyIt(self.items, it.installation.id == installationId and it.name != "")
|
||||
|
@ -2,6 +2,7 @@ import NimQml, chronicles
|
||||
import io_interface
|
||||
import ../io_interface as delegate_interface
|
||||
import view, controller, model, item
|
||||
import logging
|
||||
|
||||
import ../../../../core/eventemitter
|
||||
import ../../../../../app_service/service/settings/service as settings_service
|
||||
@ -58,10 +59,10 @@ method onDevicesLoadingErrored*(self: Module) =
|
||||
self.view.setDevicesLoading(false)
|
||||
self.view.setDevicesLoadingError(true)
|
||||
|
||||
method onDevicesLoaded*(self: Module, allDevices: seq[DeviceDto]) =
|
||||
method onDevicesLoaded*(self: Module, allDevices: seq[InstallationDto]) =
|
||||
var items: seq[Item]
|
||||
for d in allDevices:
|
||||
let item = initItem(d.id, d.metadata.name, d.enabled, self.isMyDevice(d.id))
|
||||
let item = initItem(d, self.isMyDevice(d.id))
|
||||
items.add(item)
|
||||
self.view.model().addItems(items)
|
||||
self.view.setDevicesLoading(false)
|
||||
@ -86,12 +87,34 @@ method syncAllDevices*(self: Module) =
|
||||
method advertise*(self: Module) =
|
||||
self.controller.advertise()
|
||||
|
||||
method enableDevice*(self: Module, deviceId: string, enable: bool) =
|
||||
self.controller.enableDevice(deviceId, enable)
|
||||
method enableDevice*(self: Module, installationId: string, enable: bool) =
|
||||
self.controller.enableDevice(installationId, enable)
|
||||
|
||||
method updateOrAddDevice*(self: Module, deviceId: string, name: string, enabled: bool) =
|
||||
if(self.view.model().isItemWithInstallationIdAdded(deviceId)):
|
||||
self.view.model().updateItem(deviceId, name, enabled)
|
||||
method updateOrAddDevice*(self: Module, installation: InstallationDto) =
|
||||
if(self.view.model().isItemWithInstallationIdAdded(installation.id)):
|
||||
self.view.model().updateItem(installation)
|
||||
else:
|
||||
let item = initItem(deviceId, name, enabled, self.isMyDevice(deviceId))
|
||||
let item = initItem(installation, self.isMyDevice(installation.id))
|
||||
self.view.model().addItem(item)
|
||||
|
||||
|
||||
method authenticateUser*(self: Module, keyUid: string) =
|
||||
self.controller.authenticateUser(keyUid)
|
||||
|
||||
method onUserAuthenticated*(self: Module, pin: string, password: string, keyUid: string) =
|
||||
self.view.emitUserAuthenticated(pin, password, keyUid)
|
||||
|
||||
proc validateConnectionString*(self: Module, connectionString: string): string =
|
||||
return self.controller.validateConnectionString(connectionString)
|
||||
|
||||
method getConnectionStringForBootstrappingAnotherDevice*(self: Module, keyUid: string, password: string): string =
|
||||
return self.controller.getConnectionStringForBootstrappingAnotherDevice(keyUid, password)
|
||||
|
||||
method inputConnectionStringForBootstrapping*(self: Module, connectionString: string): string =
|
||||
return self.controller.inputConnectionStringForBootstrapping(connectionString)
|
||||
|
||||
method onLocalPairingEvent*(self: Module, eventType: EventType, action: Action, error: string) =
|
||||
self.view.onLocalPairingEvent(eventType, action, error)
|
||||
|
||||
method onLocalPairingStatusUpdate*(self: Module, status: LocalPairingStatus) =
|
||||
self.view.onLocalPairingStatusUpdate(status)
|
@ -1,5 +1,7 @@
|
||||
import NimQml
|
||||
import io_interface, model
|
||||
import ../../../../../app_service/service/devices/service
|
||||
|
||||
|
||||
QtObject:
|
||||
type
|
||||
@ -9,11 +11,13 @@ QtObject:
|
||||
modelVariant: QVariant
|
||||
devicesLoading: bool
|
||||
devicesLoadingError: bool
|
||||
localPairingStatus: LocalPairingStatus
|
||||
|
||||
proc delete*(self: View) =
|
||||
self.model.delete
|
||||
self.modelVariant.delete
|
||||
self.QObject.delete
|
||||
self.localPairingStatus.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface): View =
|
||||
new(result, delete)
|
||||
@ -23,6 +27,7 @@ QtObject:
|
||||
result.devicesLoadingError = false
|
||||
result.model = newModel()
|
||||
result.modelVariant = newQVariant(result.model)
|
||||
result.localPairingStatus = newLocalPairingStatus()
|
||||
|
||||
proc load*(self: View) =
|
||||
self.delegate.viewDidLoad()
|
||||
@ -82,3 +87,45 @@ QtObject:
|
||||
|
||||
proc enableDevice*(self: View, installationId: string, enable: bool) {.slot.} =
|
||||
self.delegate.enableDevice(installationId, enable)
|
||||
|
||||
# LocalPairing status
|
||||
|
||||
proc localPairingStatusChanged*(self: View) {.signal.}
|
||||
|
||||
proc getLocalPairingState*(self: View): int {.slot.} =
|
||||
return self.localPairingStatus.state.int
|
||||
QtProperty[int] localPairingState:
|
||||
read = getLocalPairingState
|
||||
notify = localPairingStatusChanged
|
||||
|
||||
proc getLocalPairingError*(self: View): string {.slot.} =
|
||||
return self.localPairingStatus.error
|
||||
proc localPairingPairingErrorChanged*(self: View) {.signal.}
|
||||
QtProperty[string] localPairingError:
|
||||
read = getLocalPairingError
|
||||
notify = localPairingStatusChanged
|
||||
|
||||
proc localPairingEvent(self: View, eventType: int, action: int, error: string) {.signal.}
|
||||
proc onLocalPairingEvent*(self: View, eventType: EventType, action: Action, error: string) =
|
||||
self.localPairingEvent(ord(eventType), ord(action), error)
|
||||
|
||||
proc onLocalPairingStatusUpdate*(self: View, status: LocalPairingStatus) =
|
||||
self.localPairingStatus = status
|
||||
self.localPairingStatusChanged()
|
||||
|
||||
# LocalPairing actions
|
||||
|
||||
proc userAuthenticated*(self: View, pin: string, password: string, keyUid: string) {.signal.}
|
||||
proc emitUserAuthenticated*(self: View, pin: string, password: string, keyUid: string) =
|
||||
self.userAuthenticated(pin, password, keyUid)
|
||||
proc authenticateUser*(self: View, keyUid: string) {.slot.} =
|
||||
self.delegate.authenticateUser(keyUid)
|
||||
|
||||
proc validateConnectionString*(self: View, connectionString: string): string {.slot.} =
|
||||
return self.delegate.validateConnectionString(connectionString)
|
||||
|
||||
proc getConnectionStringForBootstrappingAnotherDevice*(self: View, keyUid: string, password: string): string {.slot.} =
|
||||
return self.delegate.getConnectionStringForBootstrappingAnotherDevice(keyUid, password)
|
||||
|
||||
proc inputConnectionStringForBootstrapping*(self: View, connectionString: string): string {.slot.} =
|
||||
return self.delegate.inputConnectionStringForBootstrapping(connectionString)
|
||||
|
@ -11,6 +11,7 @@ import ../../../app_service/service/accounts/service as accounts_service
|
||||
import ../../../app_service/service/keychain/service as keychain_service
|
||||
import ../../../app_service/service/profile/service as profile_service
|
||||
import ../../../app_service/service/keycard/service as keycard_service
|
||||
import ../../../app_service/service/devices/service as devices_service
|
||||
import ../../../app_service/common/account_constants
|
||||
|
||||
import ../shared_modules/keycard_popup/io_interface as keycard_shared_module
|
||||
@ -35,6 +36,7 @@ type
|
||||
keychainService: keychain_service.Service
|
||||
profileService: profile_service.Service
|
||||
keycardService: keycard_service.Service
|
||||
devicesService: devices_service.Service
|
||||
connectionIds: seq[UUID]
|
||||
keychainConnectionIds: seq[UUID]
|
||||
tmpProfileImageDetails: ProfileImageDetails
|
||||
@ -53,6 +55,7 @@ type
|
||||
tmpCardMetadata: CardMetadata
|
||||
tmpKeychainErrorOccurred: bool
|
||||
tmpRecoverUsingSeedPhraseWhileLogin: bool
|
||||
tmpConnectionString: string
|
||||
|
||||
proc newController*(delegate: io_interface.AccessInterface,
|
||||
events: EventEmitter,
|
||||
@ -60,7 +63,8 @@ proc newController*(delegate: io_interface.AccessInterface,
|
||||
accountsService: accounts_service.Service,
|
||||
keychainService: keychain_service.Service,
|
||||
profileService: profile_service.Service,
|
||||
keycardService: keycard_service.Service):
|
||||
keycardService: keycard_service.Service,
|
||||
devicesService: devices_service.Service):
|
||||
Controller =
|
||||
result = Controller()
|
||||
result.delegate = delegate
|
||||
@ -70,6 +74,7 @@ proc newController*(delegate: io_interface.AccessInterface,
|
||||
result.keychainService = keychainService
|
||||
result.profileService = profileService
|
||||
result.keycardService = keycardService
|
||||
result.devicesService = devicesService
|
||||
result.tmpPinMatch = false
|
||||
result.tmpSeedPhraseLength = 0
|
||||
result.tmpKeychainErrorOccurred = false
|
||||
@ -160,6 +165,11 @@ proc init*(self: Controller) =
|
||||
self.delegate.onDisplayKeycardSharedModuleFlow()
|
||||
self.connectionIds.add(handlerId)
|
||||
|
||||
handlerId = self.events.onWithUUID(SIGNAL_LOCAL_PAIRING_STATUS_UPDATE) do(e: Args):
|
||||
let args = LocalPairingStatus(e)
|
||||
self.delegate.onLocalPairingStatusUpdate(args)
|
||||
self.connectionIds.add(handlerId)
|
||||
|
||||
proc shouldStartWithOnboardingScreen*(self: Controller): bool =
|
||||
return self.accountsService.openedAccounts().len == 0
|
||||
|
||||
@ -543,4 +553,16 @@ proc generateRandomPUK*(self: Controller): string =
|
||||
proc storeMetadataForNewKeycardUser(self: Controller) =
|
||||
## Stores metadata, default Status account only, to the keycard for a newly created keycard user.
|
||||
let paths = @[account_constants.PATH_DEFAULT_WALLET]
|
||||
self.runStoreMetadataFlow(self.getDisplayName(), self.getPin(), paths)
|
||||
self.runStoreMetadataFlow(self.getDisplayName(), self.getPin(), paths)
|
||||
|
||||
proc getConnectionString*(self: Controller): string =
|
||||
return self.tmpConnectionString
|
||||
|
||||
proc setConnectionString*(self: Controller, connectionString: string) =
|
||||
self.tmpConnectionString = connectionString
|
||||
|
||||
proc validateLocalPairingConnectionString*(self: Controller, connectionString: string): string =
|
||||
return self.devicesService.validateConnectionString(connectionString)
|
||||
|
||||
proc inputConnectionStringForBootstrapping*(self: Controller, connectionString: string): string =
|
||||
return self.devicesService.inputConnectionStringForBootstrapping(connectionString)
|
@ -79,6 +79,8 @@ type StateType* {.pure.} = enum
|
||||
ProfileFetchingTimeout = "ProfileFetchingTimeout"
|
||||
ProfileFetchingAnnouncement = "ProfileFetchingAnnouncement"
|
||||
LostKeycardOptions = "LostKeycardOptions"
|
||||
SyncDeviceWithSyncCode = "SyncDeviceWithSyncCode"
|
||||
SyncDeviceResult = "SyncDeviceResult"
|
||||
|
||||
|
||||
## This is the base class for all state we may have in onboarding/login flow.
|
||||
|
@ -87,6 +87,8 @@ include profile_fetching_timeout_state
|
||||
include profile_fetching_announcement_state
|
||||
include recover_old_user_state
|
||||
include lost_keycard_options_state
|
||||
include sync_device_with_sync_code
|
||||
include sync_device_result
|
||||
|
||||
include state_factory_general_implementation
|
||||
include state_factory_onboarding_implementation
|
||||
|
@ -121,7 +121,10 @@ proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: St
|
||||
return newRecoverOldUserState(flowType, backState)
|
||||
if stateToBeCreated == StateType.LostKeycardOptions:
|
||||
return newLostKeycardOptionsState(flowType, backState)
|
||||
|
||||
if stateToBeCreated == StateType.SyncDeviceWithSyncCode:
|
||||
return newSyncDeviceWithSyncCodeState(flowType, backState)
|
||||
if stateToBeCreated == StateType.SyncDeviceResult:
|
||||
return newSyncDeviceResultState(flowType, backState)
|
||||
error "No implementation available for state ", state=stateToBeCreated
|
||||
|
||||
proc findBackStateWithTargetedStateType*(currentState: State, targetedStateType: StateType): State =
|
||||
|
12
src/app/modules/startup/internal/sync_device_result.nim
Normal file
12
src/app/modules/startup/internal/sync_device_result.nim
Normal file
@ -0,0 +1,12 @@
|
||||
type
|
||||
SyncDeviceResultState* = ref object of State
|
||||
|
||||
proc newSyncDeviceResultState*(flowType: FlowType, backState: State): SyncDeviceResultState =
|
||||
result = SyncDeviceResultState()
|
||||
result.setup(flowType, StateType.SyncDeviceResult, backState)
|
||||
|
||||
proc delete*(self: SyncDeviceResultState) =
|
||||
self.State.delete
|
||||
|
||||
method executePrimaryCommand*(self: SyncDeviceResultState, controller: Controller) =
|
||||
controller.login()
|
@ -0,0 +1,16 @@
|
||||
type
|
||||
SyncDeviceWithSyncCodeState* = ref object of State
|
||||
|
||||
proc newSyncDeviceWithSyncCodeState*(flowType: FlowType, backState: State): SyncDeviceWithSyncCodeState =
|
||||
result = SyncDeviceWithSyncCodeState()
|
||||
result.setup(flowType, StateType.SyncDeviceWithSyncCode, backState)
|
||||
|
||||
proc delete*(self: SyncDeviceWithSyncCodeState) =
|
||||
self.State.delete
|
||||
|
||||
method executePrimaryCommand*(self: SyncDeviceWithSyncCodeState, controller: Controller) =
|
||||
let connectionString = controller.getConnectionString()
|
||||
discard controller.inputConnectionStringForBootstrapping(connectionString)
|
||||
|
||||
method getNextPrimaryState*(self: SyncDeviceWithSyncCodeState, controller: Controller): State =
|
||||
return createState(StateType.SyncDeviceResult, self.flowType, nil)
|
@ -1,4 +1,4 @@
|
||||
type
|
||||
type
|
||||
WelcomeStateNewUser* = ref object of State
|
||||
|
||||
proc newWelcomeStateNewUser*(flowType: FlowType, backState: State): WelcomeStateNewUser =
|
||||
|
@ -15,8 +15,7 @@ method executeBackCommand*(self: WelcomeStateOldUser, controller: Controller) =
|
||||
controller.runLoginFlow()
|
||||
|
||||
method getNextPrimaryState*(self: WelcomeStateOldUser, controller: Controller): State =
|
||||
# We will handle here a click on `Scan sync code`
|
||||
discard
|
||||
return createState(StateType.SyncDeviceWithSyncCode, FlowType.FirstRunOldUserSyncCode, self)
|
||||
|
||||
method getNextSecondaryState*(self: WelcomeStateOldUser, controller: Controller): State =
|
||||
return createState(StateType.RecoverOldUser, self.flowType, self)
|
@ -2,6 +2,7 @@ import NimQml
|
||||
import ../../../app_service/service/accounts/service as accounts_service
|
||||
import models/login_account_item as login_acc_item
|
||||
from ../../../app_service/service/keycard/service import KeycardEvent, KeyDetails
|
||||
from ../../../app_service/service/devices/dto/local_pairing_status import LocalPairingStatus
|
||||
|
||||
const UNIQUE_STARTUP_MODULE_IDENTIFIER* = "SartupModule"
|
||||
|
||||
@ -168,6 +169,18 @@ method checkFetchingStatusAndProceedWithAppLoading*(self: AccessInterface) {.bas
|
||||
method startAppAfterDelay*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method getConnectionString*(self: AccessInterface): string {.base} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method setConnectionString*(self: AccessInterface, connectionString: string) {.base} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method validateLocalPairingConnectionString*(self: AccessInterface, connectionString: string): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onLocalPairingStatusUpdate*(self: AccessInterface, status: LocalPairingStatus) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
# This way (using concepts) is used only for the modules managed by AppController
|
||||
type
|
||||
DelegateInterface* = concept c
|
||||
|
@ -16,6 +16,7 @@ import ../../../app_service/service/accounts/service as accounts_service
|
||||
import ../../../app_service/service/general/service as general_service
|
||||
import ../../../app_service/service/profile/service as profile_service
|
||||
import ../../../app_service/service/keycard/service as keycard_service
|
||||
import ../../../app_service/service/devices/service as devices_service
|
||||
|
||||
import ../shared_modules/keycard_popup/module as keycard_shared_module
|
||||
|
||||
@ -45,6 +46,7 @@ type
|
||||
keycardService: keycard_service.Service
|
||||
accountsService: accounts_service.Service
|
||||
keychainService: keychain_service.Service
|
||||
devicesService: devices_service.Service
|
||||
keycardSharedModule: keycard_shared_module.AccessInterface
|
||||
|
||||
proc newModule*[T](delegate: T,
|
||||
@ -53,7 +55,8 @@ proc newModule*[T](delegate: T,
|
||||
accountsService: accounts_service.Service,
|
||||
generalService: general_service.Service,
|
||||
profileService: profile_service.Service,
|
||||
keycardService: keycard_service.Service):
|
||||
keycardService: keycard_service.Service,
|
||||
devicesService: devices_service.Service):
|
||||
Module[T] =
|
||||
result = Module[T]()
|
||||
result.delegate = delegate
|
||||
@ -61,10 +64,11 @@ proc newModule*[T](delegate: T,
|
||||
result.keycardService = keycardService
|
||||
result.accountsService = accountsService
|
||||
result.keychainService = keychainService
|
||||
result.devicesService = devicesService
|
||||
result.view = view.newView(result)
|
||||
result.viewVariant = newQVariant(result.view)
|
||||
result.controller = controller.newController(result, events, generalService, accountsService, keychainService,
|
||||
profileService, keycardService)
|
||||
profileService, keycardService, devicesService)
|
||||
|
||||
method delete*[T](self: Module[T]) =
|
||||
singletonInstance.engine.setRootContextProperty("startupModule", newQVariant())
|
||||
@ -98,9 +102,9 @@ method load*[T](self: Module[T]) =
|
||||
|
||||
if(self.controller.shouldStartWithOnboardingScreen()):
|
||||
if main_constants.IS_MACOS:
|
||||
self.view.setCurrentStartupState(newNotificationState(FlowType.General, nil))
|
||||
self.view.setCurrentStartupState(newNotificationState(state.FlowType.General, nil))
|
||||
else:
|
||||
self.view.setCurrentStartupState(newWelcomeState(FlowType.General, nil))
|
||||
self.view.setCurrentStartupState(newWelcomeState(state.FlowType.General, nil))
|
||||
else:
|
||||
let openedAccounts = self.controller.getOpenedAccounts()
|
||||
var items: seq[login_acc_item.Item]
|
||||
@ -282,15 +286,15 @@ method setSelectedLoginAccount*[T](self: Module[T], item: login_acc_item.Item) =
|
||||
self.controller.cancelCurrentFlow()
|
||||
self.controller.setSelectedLoginAccount(item.getKeyUid(), item.getKeycardCreatedAccount())
|
||||
if item.getKeycardCreatedAccount():
|
||||
self.view.setCurrentStartupState(newLoginState(FlowType.AppLogin, nil))
|
||||
self.view.setCurrentStartupState(newLoginState(state.FlowType.AppLogin, nil))
|
||||
self.controller.runLoginFlow()
|
||||
else:
|
||||
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
|
||||
if value == LS_VALUE_STORE:
|
||||
self.view.setCurrentStartupState(newLoginState(FlowType.AppLogin, nil))
|
||||
self.view.setCurrentStartupState(newLoginState(state.FlowType.AppLogin, nil))
|
||||
self.controller.tryToObtainDataFromKeychain()
|
||||
else:
|
||||
self.view.setCurrentStartupState(newLoginKeycardEnterPasswordState(FlowType.AppLogin, nil))
|
||||
self.view.setCurrentStartupState(newLoginKeycardEnterPasswordState(state.FlowType.AppLogin, nil))
|
||||
self.view.setSelectedLoginAccount(item)
|
||||
|
||||
method emitAccountLoginError*[T](self: Module[T], error: string) =
|
||||
@ -323,8 +327,8 @@ method onFetchingFromWakuMessageReceived*[T](self: Module[T], section: string, t
|
||||
if currStateObj.isNil:
|
||||
error "cannot resolve current state for fetching data model update"
|
||||
return
|
||||
if currStateObj.flowType() != FlowType.FirstRunOldUserImportSeedPhrase and
|
||||
currStateObj.flowType() != FlowType.FirstRunOldUserKeycardImport:
|
||||
if currStateObj.flowType() != state.FlowType.FirstRunOldUserImportSeedPhrase and
|
||||
currStateObj.flowType() != state.FlowType.FirstRunOldUserKeycardImport:
|
||||
error "update fetching data model is out of context for the flow", flow=currStateObj.flowType()
|
||||
return
|
||||
if totalMessages > 0:
|
||||
@ -365,7 +369,7 @@ proc logoutAndDisplayError[T](self: Module[T], error: string, errType: StartupEr
|
||||
self.delegate.logout()
|
||||
if self.controller.isSelectedLoginAccountKeycardAccount() and
|
||||
errType == StartupErrorType.ConvertToRegularAccError:
|
||||
self.view.setCurrentStartupState(newLoginState(FlowType.AppLogin, nil))
|
||||
self.view.setCurrentStartupState(newLoginState(state.FlowType.AppLogin, nil))
|
||||
self.controller.runLoginFlow()
|
||||
self.moveToStartupState()
|
||||
self.emitStartupError(error, errType)
|
||||
@ -380,8 +384,8 @@ method onNodeLogin*[T](self: Module[T], error: string) =
|
||||
quit() # quit the app
|
||||
|
||||
if error.len == 0:
|
||||
if currStateObj.flowType() == FlowType.FirstRunOldUserImportSeedPhrase or
|
||||
currStateObj.flowType() == FlowType.FirstRunOldUserKeycardImport:
|
||||
if currStateObj.flowType() == state.FlowType.FirstRunOldUserImportSeedPhrase or
|
||||
currStateObj.flowType() == state.FlowType.FirstRunOldUserKeycardImport:
|
||||
self.prepareAndInitFetchingData()
|
||||
self.controller.connectToFetchingFromWakuEvents()
|
||||
self.delayStartingApp()
|
||||
@ -389,7 +393,7 @@ method onNodeLogin*[T](self: Module[T], error: string) =
|
||||
if err.len > 0:
|
||||
self.logoutAndDisplayError(err, StartupErrorType.UnknownType)
|
||||
return
|
||||
elif currStateObj.flowType() == FlowType.LostKeycardConvertToRegularAccount:
|
||||
elif currStateObj.flowType() == state.FlowType.LostKeycardConvertToRegularAccount:
|
||||
let err = self.controller.convertToRegularAccount()
|
||||
if err.len > 0:
|
||||
self.logoutAndDisplayError(err, StartupErrorType.ConvertToRegularAccError)
|
||||
@ -408,7 +412,7 @@ method onNodeLogin*[T](self: Module[T], error: string) =
|
||||
self.delegate.finishAppLoading()
|
||||
else:
|
||||
self.moveToStartupState()
|
||||
if currStateObj.flowType() == FlowType.AppLogin:
|
||||
if currStateObj.flowType() == state.FlowType.AppLogin:
|
||||
self.emitAccountLoginError(error)
|
||||
else:
|
||||
self.emitStartupError(error, StartupErrorType.SetupAccError)
|
||||
@ -477,9 +481,9 @@ method onSharedKeycarModuleFlowTerminated*[T](self: Module[T], lastStepInTheCurr
|
||||
if currStateObj.isNil:
|
||||
error "cannot resolve current state for onboarding/login flow continuation"
|
||||
return
|
||||
if currStateObj.flowType() == FlowType.FirstRunNewUserNewKeycardKeys or
|
||||
currStateObj.flowType() == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
|
||||
currStateObj.flowType() == FlowType.LostKeycardReplacement:
|
||||
if currStateObj.flowType() == state.FlowType.FirstRunNewUserNewKeycardKeys or
|
||||
currStateObj.flowType() == state.FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
|
||||
currStateObj.flowType() == state.FlowType.LostKeycardReplacement:
|
||||
let newState = currStateObj.getBackState()
|
||||
if newState.isNil:
|
||||
error "cannot resolve new state for onboarding/login flow continuation after shared flow is terminated"
|
||||
@ -498,3 +502,15 @@ method addToKeycardUidPairsToCheckForAChangeAfterLogin*[T](self: Module[T], oldK
|
||||
|
||||
method removeAllKeycardUidPairsForCheckingForAChangeAfterLogin*[T](self: Module[T]) =
|
||||
self.delegate.removeAllKeycardUidPairsForCheckingForAChangeAfterLogin()
|
||||
|
||||
method getConnectionString*[T](self: Module[T]): string =
|
||||
return self.controller.getConnectionString()
|
||||
|
||||
method setConnectionString*[T](self: Module[T], connectionString: string) =
|
||||
self.controller.setConnectionString(connectionString)
|
||||
|
||||
method validateLocalPairingConnectionString*[T](self: Module[T], connectionString: string): string =
|
||||
return self.controller.validateLocalPairingConnectionString(connectionString)
|
||||
|
||||
method onLocalPairingStatusUpdate*[T](self: Module[T], status: LocalPairingStatus) =
|
||||
self.view.onLocalPairingStatusUpdate(status)
|
@ -8,6 +8,9 @@ import models/login_account_model as login_acc_model
|
||||
import models/login_account_item as login_acc_item
|
||||
import models/fetching_data_model as fetch_model
|
||||
|
||||
import ../../../app_service/service/devices/dto/local_pairing_status as local_pairing_status
|
||||
import ../../../app_service/service/visual_identity/dto as visual_identity
|
||||
|
||||
type
|
||||
AppState* {.pure.} = enum
|
||||
StartupState = 0
|
||||
@ -32,6 +35,7 @@ QtObject:
|
||||
remainingAttempts: int
|
||||
fetchingDataModel: fetch_model.Model
|
||||
fetchingDataModelVariant: QVariant
|
||||
localPairingStatus: LocalPairingStatus
|
||||
|
||||
proc delete*(self: View) =
|
||||
self.currentStartupStateVariant.delete
|
||||
@ -46,6 +50,7 @@ QtObject:
|
||||
self.fetchingDataModel.delete
|
||||
if not self.fetchingDataModelVariant.isNil:
|
||||
self.fetchingDataModelVariant.delete
|
||||
self.localPairingStatus.delete
|
||||
self.QObject.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface): View =
|
||||
@ -63,6 +68,7 @@ QtObject:
|
||||
result.loginAccountsModel = login_acc_model.newModel()
|
||||
result.loginAccountsModelVariant = newQVariant(result.loginAccountsModel)
|
||||
result.remainingAttempts = -1
|
||||
result.localPairingStatus = newLocalPairingStatus()
|
||||
|
||||
signalConnect(result.currentStartupState, "backActionClicked()", result, "onBackActionClicked()", 2)
|
||||
signalConnect(result.currentStartupState, "primaryActionClicked()", result, "onPrimaryActionClicked()", 2)
|
||||
@ -295,4 +301,56 @@ QtObject:
|
||||
if self.fetchingDataModelVariant.isNil:
|
||||
self.fetchingDataModelVariant = newQVariant(self.fetchingDataModel)
|
||||
self.fetchingDataModel.init(sections)
|
||||
self.fetchingDataModelChanged()
|
||||
self.fetchingDataModelChanged()
|
||||
|
||||
proc setConnectionString*(self: View, connectionString: string) {.slot.} =
|
||||
self.delegate.setConnectionString(connectionString)
|
||||
|
||||
proc getConnectionString*(self: View): string {.slot.} =
|
||||
return self.delegate.getConnectionString()
|
||||
|
||||
proc localPairingStatusChanged*(self: View) {.signal.}
|
||||
|
||||
proc getLocalPairingState*(self: View): int {.slot.} =
|
||||
return self.localPairingStatus.state.int
|
||||
QtProperty[int] localPairingState:
|
||||
read = getLocalPairingState
|
||||
notify = localPairingStatusChanged
|
||||
|
||||
proc getLocalPairingError*(self: View): string {.slot.} =
|
||||
return self.localPairingStatus.error
|
||||
QtProperty[string] localPairingError:
|
||||
read = getLocalPairingError
|
||||
notify = localPairingStatusChanged
|
||||
|
||||
proc getLocalPairingName*(self: View): string {.slot.} =
|
||||
return self.localPairingStatus.account.name
|
||||
QtProperty[string] localPairingName:
|
||||
read = getLocalPairingName
|
||||
notify = localPairingStatusChanged
|
||||
|
||||
proc getLocalPairingColorId*(self: View): int {.slot.} =
|
||||
return self.localPairingStatus.account.colorId
|
||||
QtProperty[int] localPairingColorId:
|
||||
read = getLocalPairingColorId
|
||||
notify = localPairingStatusChanged
|
||||
|
||||
proc getLocalPairingColorHash*(self: View): string {.slot.} =
|
||||
return self.localPairingStatus.account.colorHash.toJson()
|
||||
QtProperty[string] localPairingColorHash:
|
||||
read = getLocalPairingColorHash
|
||||
notify = localPairingStatusChanged
|
||||
|
||||
proc getLocalPairingImage*(self: View): string {.slot.} =
|
||||
if self.localPairingStatus.account.images.len != 0:
|
||||
return self.localPairingStatus.account.images[0].uri
|
||||
QtProperty[string] localPairingImage:
|
||||
read = getLocalPairingImage
|
||||
notify = localPairingStatusChanged
|
||||
|
||||
proc onLocalPairingStatusUpdate*(self: View, status: LocalPairingStatus) =
|
||||
self.localPairingStatus = status
|
||||
self.localPairingStatusChanged()
|
||||
|
||||
proc validateLocalPairingConnectionString*(self: View, connectionString: string): string {.slot.} =
|
||||
return self.delegate.validateLocalPairingConnectionString(connectionString)
|
@ -1,8 +1,20 @@
|
||||
include ../../common/json_utils
|
||||
include ../../../app/core/tasks/common
|
||||
|
||||
type
|
||||
AsyncLoadDevicesTaskArg = ref object of QObjectTaskArg
|
||||
|
||||
type
|
||||
AsyncInputConnectionStringArg = ref object of QObjectTaskArg
|
||||
connectionString: string
|
||||
configJSON: string
|
||||
|
||||
const asyncLoadDevicesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
|
||||
let arg = decode[AsyncLoadDevicesTaskArg](argEncoded)
|
||||
let response = status_installations.getOurInstallations()
|
||||
|
||||
arg.finish(response)
|
||||
|
||||
const asyncInputConnectionStringTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
|
||||
let arg = decode[AsyncInputConnectionStringArg](argEncoded)
|
||||
let response = status_go.inputConnectionStringForBootstrapping(arg.connectionString, arg.configJSON)
|
||||
arg.finish(response)
|
||||
|
@ -4,30 +4,37 @@ import json
|
||||
|
||||
include ../../../common/[json_utils]
|
||||
|
||||
type Metadata* = object
|
||||
# NOTE: DeviceType equeals to:
|
||||
# - on Desktop (`hostOS` from system.nim):
|
||||
# "windows", "macosx", "linux", "netbsd", "freebsd",
|
||||
# "openbsd", "solaris", "aix", "haiku", "standalone".
|
||||
# - on Mobile (from platform.cljs):
|
||||
# "android", "ios"
|
||||
|
||||
type InstallationMetadata* = object
|
||||
name*: string
|
||||
deviceType*: string
|
||||
fcmToken*: string
|
||||
|
||||
type DeviceDto* = object
|
||||
type InstallationDto* = object
|
||||
id*: string
|
||||
identity*: string
|
||||
version*: int
|
||||
enabled*: bool
|
||||
timestamp*: int64
|
||||
metadata*: Metadata
|
||||
metadata*: InstallationMetadata
|
||||
|
||||
proc isEmpty*(self: Metadata): bool =
|
||||
proc isEmpty*(self: InstallationMetadata): bool =
|
||||
return self.name.len == 0
|
||||
|
||||
proc toMetadata(jsonObj: JsonNode): Metadata =
|
||||
result = Metadata()
|
||||
proc toMetadata(jsonObj: JsonNode): InstallationMetadata =
|
||||
result = InstallationMetadata()
|
||||
discard jsonObj.getProp("name", result.name)
|
||||
discard jsonObj.getProp("deviceType", result.deviceType)
|
||||
discard jsonObj.getProp("fcmToken", result.fcmToken)
|
||||
|
||||
proc toDeviceDto*(jsonObj: JsonNode): DeviceDto =
|
||||
result = DeviceDto()
|
||||
proc toInstallationDto*(jsonObj: JsonNode): InstallationDto =
|
||||
result = InstallationDto()
|
||||
discard jsonObj.getProp("id", result.id)
|
||||
discard jsonObj.getProp("identity", result.identity)
|
||||
discard jsonObj.getProp("version", result.version)
|
57
src/app_service/service/devices/dto/local_pairing_event.nim
Normal file
57
src/app_service/service/devices/dto/local_pairing_event.nim
Normal file
@ -0,0 +1,57 @@
|
||||
import ../../../../app/core/eventemitter
|
||||
import ../../accounts/dto/accounts
|
||||
|
||||
type
|
||||
EventType* {.pure.} = enum
|
||||
EventUnknown = -1,
|
||||
EventConnectionError = 0,
|
||||
EventConnectionSuccess = 1,
|
||||
EventTransferError = 2,
|
||||
EventTransferSuccess = 3,
|
||||
EventReceivedAccount = 4,
|
||||
EventProcessSuccess = 5,
|
||||
EventProcessError = 6
|
||||
|
||||
type
|
||||
Action* {.pure.} = enum
|
||||
ActionUnknown = 0
|
||||
ActionConnect = 1,
|
||||
ActionPairingAccount = 2,
|
||||
ActionSyncDevice = 3,
|
||||
|
||||
type
|
||||
LocalPairingEventArgs* = ref object of Args
|
||||
eventType*: EventType
|
||||
action*: Action
|
||||
error*: string
|
||||
account*: AccountDTO
|
||||
|
||||
proc parse*(self: string): EventType =
|
||||
case self:
|
||||
of "connection-error":
|
||||
return EventConnectionError
|
||||
of "connection-success":
|
||||
return EventConnectionSuccess
|
||||
of "transfer-error":
|
||||
return EventTransferError
|
||||
of "transfer-success":
|
||||
return EventTransferSuccess
|
||||
of "process-success":
|
||||
return EventProcessSuccess
|
||||
of "process-error":
|
||||
return EventProcessError
|
||||
of "received-account":
|
||||
return EventReceivedAccount
|
||||
else:
|
||||
return EventUnknown
|
||||
|
||||
proc parse*(self: int): Action =
|
||||
case self:
|
||||
of 1:
|
||||
return ActionConnect
|
||||
of 2:
|
||||
return ActionPairingAccount
|
||||
of 3:
|
||||
return ActionSyncDevice
|
||||
else:
|
||||
return ActionUnknown
|
66
src/app_service/service/devices/dto/local_pairing_status.nim
Normal file
66
src/app_service/service/devices/dto/local_pairing_status.nim
Normal file
@ -0,0 +1,66 @@
|
||||
import ../../../../app/core/eventemitter
|
||||
import ../../accounts/dto/accounts
|
||||
import local_pairing_event
|
||||
|
||||
type
|
||||
LocalPairingState* {.pure.} = enum
|
||||
Idle = 0
|
||||
WaitingForConnection
|
||||
Transferring
|
||||
Error
|
||||
Finished
|
||||
|
||||
type
|
||||
LocalPairingMode* {.pure.} = enum
|
||||
Idle = 0
|
||||
BootstrapingOtherDevice
|
||||
BootstrapingThisDevice
|
||||
|
||||
type
|
||||
LocalPairingStatus* = ref object of Args
|
||||
mode*: LocalPairingMode
|
||||
state*: LocalPairingState
|
||||
account*: AccountDTO
|
||||
error*: string
|
||||
|
||||
proc reset*(self: LocalPairingStatus) =
|
||||
self.mode = LocalPairingMode.Idle
|
||||
self.state = LocalPairingState.Idle
|
||||
self.error = ""
|
||||
|
||||
proc setup(self: LocalPairingStatus) =
|
||||
self.reset()
|
||||
|
||||
proc delete*(self: LocalPairingStatus) =
|
||||
discard
|
||||
|
||||
proc newLocalPairingStatus*(): LocalPairingStatus =
|
||||
new(result, delete)
|
||||
result.setup()
|
||||
|
||||
proc update*(self: LocalPairingStatus, eventType: EventType, action: Action, account: AccountDTO, error: string) =
|
||||
case eventType:
|
||||
of EventConnectionSuccess:
|
||||
self.state = LocalPairingState.WaitingForConnection
|
||||
of EventTransferSuccess:
|
||||
self.state = case self.mode:
|
||||
of LocalPairingMode.BootstrapingOtherDevice:
|
||||
LocalPairingState.Finished # For servers, `transfer` is last event
|
||||
of LocalPairingMode.BootstrapingThisDevice:
|
||||
LocalPairingState.Transferring # For clients, `process` is last event
|
||||
else:
|
||||
LocalPairingState.Idle
|
||||
of EventProcessSuccess:
|
||||
self.state = LocalPairingState.Finished
|
||||
of EventConnectionError:
|
||||
self.state = LocalPairingState.Error
|
||||
of EventTransferError:
|
||||
self.state = LocalPairingState.Error
|
||||
of EventProcessError:
|
||||
self.state = LocalPairingState.Error
|
||||
of EventReceivedAccount:
|
||||
self.account = account
|
||||
else:
|
||||
discard
|
||||
|
||||
self.error = error
|
@ -1,65 +1,92 @@
|
||||
import NimQml, json, sequtils, system, chronicles
|
||||
import NimQml, json, sequtils, system, chronicles, uuids
|
||||
|
||||
import std/os
|
||||
|
||||
import ./dto/installation as Installation_dto
|
||||
import ./dto/local_pairing_event
|
||||
import ./dto/local_pairing_status
|
||||
|
||||
import ../../../app/core/[main]
|
||||
import ../../../app/core/tasks/[qt, threadpool]
|
||||
import ./dto/device as device_dto
|
||||
import ../settings/service as settings_service
|
||||
import ../accounts/service as accounts_service
|
||||
|
||||
import ../../../app/global/global_singleton
|
||||
import ../../../app/core/[main]
|
||||
import ../../../app/core/signals/types
|
||||
import ../../../app/core/eventemitter
|
||||
import ../../../app/core/tasks/[qt, threadpool]
|
||||
import ../../../backend/installations as status_installations
|
||||
import ../../common/utils as utils
|
||||
import ../../../constants as main_constants
|
||||
|
||||
import status_go
|
||||
|
||||
export Installation_dto
|
||||
export local_pairing_event
|
||||
export local_pairing_status
|
||||
|
||||
export device_dto
|
||||
include async_tasks
|
||||
|
||||
logScope:
|
||||
topics = "devices-service"
|
||||
|
||||
type
|
||||
UpdateDeviceArgs* = ref object of Args
|
||||
deviceId*: string
|
||||
name*: string
|
||||
enabled*: bool
|
||||
UpdateInstallationArgs* = ref object of Args
|
||||
installation*: InstallationDto
|
||||
|
||||
type
|
||||
DevicesArg* = ref object of Args
|
||||
devices*: seq[DeviceDto]
|
||||
devices*: seq[InstallationDto]
|
||||
|
||||
# Signals which may be emitted by this service:
|
||||
const SIGNAL_UPDATE_DEVICE* = "updateDevice"
|
||||
const SIGNAL_DEVICES_LOADED* = "devicesLoaded"
|
||||
const SIGNAL_ERROR_LOADING_DEVICES* = "devicesErrorLoading"
|
||||
const SIGNAL_LOCAL_PAIRING_EVENT* = "localPairingEvent"
|
||||
const SIGNAL_LOCAL_PAIRING_STATUS_UPDATE* = "localPairingStatusUpdate"
|
||||
|
||||
QtObject:
|
||||
type Service* = ref object of QObject
|
||||
events: EventEmitter
|
||||
threadpool: ThreadPool
|
||||
settingsService: settings_service.Service
|
||||
accountsService: accounts_service.Service
|
||||
localPairingStatus: LocalPairingStatus
|
||||
|
||||
proc delete*(self: Service) =
|
||||
self.QObject.delete
|
||||
self.localPairingStatus.delete
|
||||
|
||||
proc newService*(
|
||||
events: EventEmitter,
|
||||
threadpool: ThreadPool,
|
||||
settingsService: settings_service.Service,
|
||||
): Service =
|
||||
proc newService*(events: EventEmitter,
|
||||
threadpool: ThreadPool,
|
||||
settingsService: settings_service.Service,
|
||||
accountsService: accounts_service.Service): Service =
|
||||
new(result, delete)
|
||||
result.QObject.setup
|
||||
result.events = events
|
||||
result.settingsService = settingsService
|
||||
result.threadpool = threadpool
|
||||
result.settingsService = settingsService
|
||||
result.accountsService = accountsService
|
||||
result.localPairingStatus = newLocalPairingStatus()
|
||||
|
||||
proc doConnect(self: Service) =
|
||||
self.events.on(SignalType.Message.event) do(e:Args):
|
||||
var receivedData = MessageSignal(e)
|
||||
if(receivedData.devices.len > 0):
|
||||
for d in receivedData.devices:
|
||||
let data = UpdateDeviceArgs(
|
||||
deviceId: d.id,
|
||||
name: d.metadata.name,
|
||||
enabled: d.enabled)
|
||||
self.events.emit(SIGNAL_UPDATE_DEVICE, data)
|
||||
let receivedData = MessageSignal(e)
|
||||
for dto in receivedData.installations:
|
||||
let data = UpdateInstallationArgs(
|
||||
installation: dto)
|
||||
self.events.emit(SIGNAL_UPDATE_DEVICE, data)
|
||||
|
||||
self.events.on(SignalType.LocalPairing.event) do(e:Args):
|
||||
let signalData = LocalPairingSignal(e)
|
||||
let data = LocalPairingEventArgs(
|
||||
eventType: signalData.eventType.parse(),
|
||||
action: signalData.action.parse(),
|
||||
account: signalData.account,
|
||||
error: signalData.error)
|
||||
self.events.emit(SIGNAL_LOCAL_PAIRING_EVENT, data)
|
||||
|
||||
self.localPairingStatus.update(data.eventType, data.action, data.account, data.error)
|
||||
self.events.emit(SIGNAL_LOCAL_PAIRING_STATUS_UPDATE, self.localPairingStatus)
|
||||
|
||||
proc init*(self: Service) =
|
||||
self.doConnect()
|
||||
@ -75,17 +102,17 @@ QtObject:
|
||||
proc asyncDevicesLoaded*(self: Service, response: string) {.slot.} =
|
||||
try:
|
||||
let rpcResponse = Json.decode(response, RpcResponse[JsonNode])
|
||||
let installations = map(rpcResponse.result.getElems(), proc(x: JsonNode): DeviceDto = x.toDeviceDto())
|
||||
let installations = map(rpcResponse.result.getElems(), proc(x: JsonNode): InstallationDto = x.toInstallationDto())
|
||||
self.events.emit(SIGNAL_DEVICES_LOADED, DevicesArg(devices: installations))
|
||||
except Exception as e:
|
||||
let errDesription = e.msg
|
||||
error "error loading devices: ", errDesription
|
||||
self.events.emit(SIGNAL_ERROR_LOADING_DEVICES, Args())
|
||||
|
||||
proc getAllDevices*(self: Service): seq[DeviceDto] =
|
||||
proc getAllDevices*(self: Service): seq[InstallationDto] =
|
||||
try:
|
||||
let response = status_installations.getOurInstallations()
|
||||
return map(response.result.getElems(), proc(x: JsonNode): DeviceDto = x.toDeviceDto())
|
||||
return map(response.result.getElems(), proc(x: JsonNode): InstallationDto = x.toInstallationDto())
|
||||
except Exception as e:
|
||||
let errDesription = e.msg
|
||||
error "error: ", errDesription
|
||||
@ -112,3 +139,46 @@ QtObject:
|
||||
proc disable*(self: Service, deviceId: string) =
|
||||
# Once we get more info from `status-go` we may emit success/failed signal from here.
|
||||
discard status_installations.disableInstallation(deviceId)
|
||||
|
||||
|
||||
#
|
||||
# Local Pairing
|
||||
#
|
||||
|
||||
proc inputConnectionStringForBootstrappingFinished(self: Service, result: string) =
|
||||
discard
|
||||
|
||||
proc validateConnectionString*(self: Service, connectionString: string): string =
|
||||
return status_go.validateConnectionString(connectionString)
|
||||
|
||||
proc getConnectionStringForBootstrappingAnotherDevice*(self: Service, keyUid: string, password: string): string =
|
||||
let configJSON = %* {
|
||||
"keyUID": keyUid,
|
||||
"keystorePath": joinPath(main_constants.ROOTKEYSTOREDIR, keyUid),
|
||||
"deviceType": hostOs,
|
||||
"password": utils.hashPassword(password),
|
||||
"timeout": 5 * 60 * 1000,
|
||||
}
|
||||
self.localPairingStatus.mode = LocalPairingMode.BootstrapingOtherDevice
|
||||
return status_go.getConnectionStringForBootstrappingAnotherDevice($configJSON)
|
||||
|
||||
proc inputConnectionStringForBootstrapping*(self: Service, connectionString: string): string =
|
||||
let installationId = $genUUID()
|
||||
let nodeConfigJson = self.accountsService.getDefaultNodeConfig(installationId)
|
||||
|
||||
let configJSON = %* {
|
||||
"keystorePath": main_constants.ROOTKEYSTOREDIR,
|
||||
"nodeConfig": nodeConfigJson,
|
||||
"deviceType": hostOs,
|
||||
"RootDataDir": main_constants.STATUSGODIR
|
||||
}
|
||||
self.localPairingStatus.mode = LocalPairingMode.BootstrapingThisDevice
|
||||
|
||||
let arg = AsyncInputConnectionStringArg(
|
||||
tptr: cast[ByteAddress](asyncInputConnectionStringTask),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "inputConnectionStringForBootstrappingFinished",
|
||||
connectionString: connectionString,
|
||||
configJSON: $configJSON
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
|
0
src/app_service/service/pairing/service.nim
Normal file
0
src/app_service/service/pairing/service.nim
Normal file
@ -16,5 +16,11 @@ proc toColorHashDto*(jsonObj: JsonNode): ColorHashDto =
|
||||
)
|
||||
return
|
||||
|
||||
proc toJson*(self: ColorHashDto): string =
|
||||
let json = newJArray()
|
||||
for segment in self:
|
||||
json.add(%* {"segmentLength": segment.len, "colorId": segment.colorIdx})
|
||||
return $json
|
||||
|
||||
proc toColorId*(jsonObj: JsonNode): int =
|
||||
return jsonObj.getInt()
|
||||
|
@ -48,8 +48,5 @@ proc getEmojiHashAsJson*(publicKey: string): string =
|
||||
return $$emojiHashOf(publicKey)
|
||||
|
||||
proc getColorHashAsJson*(publicKey: string): string =
|
||||
let colorHash = colorHashOf(publicKey)
|
||||
let json = newJArray()
|
||||
for segment in colorHash:
|
||||
json.add(%* {"segmentLength": segment.len, "colorId": segment.colorIdx})
|
||||
return $json
|
||||
return colorHashOf(publicKey).toJson()
|
||||
|
||||
|
@ -138,8 +138,8 @@ Loader {
|
||||
LoadingComponent {
|
||||
anchors.centerIn: parent
|
||||
radius: width/2
|
||||
height: root.asset.height
|
||||
width: root.asset.width
|
||||
height: root.asset.isImage ? root.asset.height : root.asset.bgHeight
|
||||
width: root.asset.isImage ? root.asset.width : root.asset.bgWidth
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,81 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
StatusListItem {
|
||||
id: root
|
||||
|
||||
property string deviceName: ""
|
||||
property string deviceType: ""
|
||||
property bool isCurrentDevice: false
|
||||
property int timestamp: 0
|
||||
|
||||
signal itemClicked
|
||||
signal setupSyncingButtonClicked
|
||||
|
||||
title: root.deviceName || qsTr("No device name")
|
||||
|
||||
asset.name: Utils.deviceIcon(root.deviceType)
|
||||
asset.bgColor: Theme.palette.primaryColor3
|
||||
asset.color: Theme.palette.primaryColor1
|
||||
asset.isLetterIdenticon: false
|
||||
|
||||
subTitle: {
|
||||
if (root.isCurrentDevice)
|
||||
return qsTr("This device");
|
||||
|
||||
if (d.secondsFromSync <= 120)
|
||||
return qsTr("Online now");
|
||||
|
||||
if (d.minutesFromSync <= 60)
|
||||
return qsTr("Online %n minutes(s) ago", "", d.minutesFromSync);
|
||||
|
||||
if (d.daysFromSync == 0)
|
||||
return qsTr("Last seen earlier today");
|
||||
|
||||
if (d.daysFromSync == 1)
|
||||
return qsTr("Last online yesterday");
|
||||
|
||||
if (d.daysFromSync <= 6)
|
||||
return qsTr("Last online [%1]").arg(Qt.locale().dayName[d.lastSyncDate.getDay()]);
|
||||
|
||||
return qsTr("Last online %1").arg(LocaleUtils.formatDate(lastSyncDate))
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property var lastSyncDate: new Date(root.timestamp)
|
||||
readonly property int millisecondsFromSync: lastSyncDate - Date.now()
|
||||
readonly property int secondsFromSync: millisecondsFromSync / 1000
|
||||
readonly property int minutesFromSync: secondsFromSync / 60
|
||||
readonly property int daysFromSync: new Date().getDay() - lastSyncDate.getDay()
|
||||
}
|
||||
|
||||
components: [
|
||||
StatusButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.enabled && !root.isCurrentDevice
|
||||
text: qsTr("Setup syncing")
|
||||
size: StatusBaseButton.Size.Small
|
||||
onClicked: {
|
||||
root.setupSyncingButtonClicked()
|
||||
}
|
||||
},
|
||||
StatusIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.enabled
|
||||
icon: "chevron-down"
|
||||
rotation: 270
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
}
|
@ -50,3 +50,4 @@ StatusChartPanel 0.1 StatusChartPanel.qml
|
||||
StatusStepper 0.1 StatusStepper.qml
|
||||
LoadingComponent 0.1 LoadingComponent.qml
|
||||
StatusQrCodeScanner 0.1 StatusQrCodeScanner.qml
|
||||
StatusSyncDeviceDelegate 0.1 StatusSyncDeviceDelegate.qml
|
||||
|
@ -262,7 +262,7 @@ Item {
|
||||
return
|
||||
}
|
||||
|
||||
statusBaseInput.valid = true
|
||||
let valid = true
|
||||
const rawText = statusBaseInput.edit.getText(0, statusBaseInput.edit.length)
|
||||
if (validators.length) {
|
||||
for (let idx in validators) {
|
||||
@ -270,7 +270,7 @@ Item {
|
||||
let result = validator.validate(rawText)
|
||||
|
||||
if (typeof result === "boolean" && result) {
|
||||
statusBaseInput.valid = statusBaseInput.valid && true
|
||||
valid = valid && true
|
||||
delete errors[validator.name]
|
||||
} else {
|
||||
if (!errors) {
|
||||
@ -287,8 +287,7 @@ Item {
|
||||
errors = errors
|
||||
|
||||
result.errorMessage = validator.errorMessage
|
||||
|
||||
statusBaseInput.valid = statusBaseInput.valid && false
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
if (errors){
|
||||
@ -313,6 +312,8 @@ Item {
|
||||
_previousText = text
|
||||
}
|
||||
|
||||
statusBaseInput.valid = valid
|
||||
|
||||
if (asyncValidators.length && !Object.values(errors).length) {
|
||||
for (let idx in asyncValidators) {
|
||||
let asyncValidator = asyncValidators[idx]
|
||||
|
@ -155,13 +155,6 @@ ThemePalette {
|
||||
property color secondaryBackgroundColor: "#414141"
|
||||
}
|
||||
|
||||
statusSwitchTab: QtObject {
|
||||
property color buttonBackgroundColor: primaryColor1
|
||||
property color barBackgroundColor: primaryColor3
|
||||
property color selectedTextColor: white
|
||||
property color textColor: primaryColor1
|
||||
}
|
||||
|
||||
statusSelect: QtObject {
|
||||
property color menuItemBackgroundColor: baseColor2
|
||||
property color menuItemHoverBackgroundColor: directColor7
|
||||
|
@ -153,13 +153,6 @@ ThemePalette {
|
||||
property color secondaryBackgroundColor: "#E2E6E8"
|
||||
}
|
||||
|
||||
statusSwitchTab: QtObject {
|
||||
property color buttonBackgroundColor: primaryColor1
|
||||
property color barBackgroundColor: primaryColor3
|
||||
property color selectedTextColor: white
|
||||
property color textColor: primaryColor1
|
||||
}
|
||||
|
||||
statusSelect: QtObject {
|
||||
property color menuItemBackgroundColor: white
|
||||
property color menuItemHoverBackgroundColor: baseColor2
|
||||
|
@ -241,11 +241,11 @@ QtObject {
|
||||
property color secondaryBackgroundColor
|
||||
}
|
||||
|
||||
property QtObject statusSwitchTab: QtObject {
|
||||
property color buttonBackgroundColor
|
||||
property color barBackgroundColor
|
||||
property color selectedTextColor
|
||||
property color textColor
|
||||
readonly property QtObject statusSwitchTab: QtObject {
|
||||
property color buttonBackgroundColor: primaryColor1
|
||||
property color barBackgroundColor: primaryColor3
|
||||
property color selectedTextColor: indirectColor1
|
||||
property color textColor: primaryColor1
|
||||
}
|
||||
|
||||
property QtObject statusSelect: QtObject {
|
||||
|
@ -266,6 +266,11 @@ QtObject {
|
||||
function encodeUtf8(str){
|
||||
return unescape(encodeURIComponent(str));
|
||||
}
|
||||
|
||||
function deviceIcon(deviceType) {
|
||||
const isMobileDevice = deviceType === "ios" || deviceType === "android"
|
||||
return isMobileDevice ? "mobile" : "desktop"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,6 +12,7 @@ import shared.popups.keycard 1.0
|
||||
import "controls"
|
||||
import "views"
|
||||
import "stores"
|
||||
import "../Profile/stores"
|
||||
|
||||
OnboardingBasePage {
|
||||
id: root
|
||||
@ -132,6 +133,12 @@ OnboardingBasePage {
|
||||
case Constants.startupState.profileFetchingSuccess:
|
||||
case Constants.startupState.profileFetchingTimeout:
|
||||
return fetchingDataViewComponent
|
||||
|
||||
case Constants.startupState.syncDeviceWithSyncCode:
|
||||
return syncDeviceViewComponent
|
||||
|
||||
case Constants.startupState.syncDeviceResult:
|
||||
return syncDeviceResultComponent
|
||||
}
|
||||
|
||||
return undefined
|
||||
@ -318,6 +325,20 @@ following the \"Add existing Status user\" flow, using your seed phrase.")
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: syncDeviceViewComponent
|
||||
SyncCodeView {
|
||||
startupStore: root.startupStore
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: syncDeviceResultComponent
|
||||
SyncDeviceResult {
|
||||
startupStore: root.startupStore
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: keycardPopup
|
||||
active: false
|
||||
|
@ -11,6 +11,13 @@ QtObject {
|
||||
property var fetchingDataModel: startupModuleInst ? startupModuleInst.fetchingDataModel
|
||||
: null
|
||||
|
||||
readonly property int localPairingState: startupModuleInst ? startupModuleInst.localPairingState : -1
|
||||
readonly property string localPairingError: startupModuleInst ? startupModuleInst.localPairingError : ""
|
||||
readonly property string localPairingName: startupModuleInst ? startupModuleInst.localPairingName : ""
|
||||
readonly property string localPairingImage: startupModuleInst ? startupModuleInst.localPairingImage : ""
|
||||
readonly property int localPairingColorId: startupModuleInst ? startupModuleInst.localPairingColorId : 0
|
||||
readonly property string localPairingColorHash: startupModuleInst ? startupModuleInst.localPairingColorHash : ""
|
||||
|
||||
function backAction() {
|
||||
root.currentStartupState.backAction()
|
||||
}
|
||||
@ -103,4 +110,12 @@ QtObject {
|
||||
function getSeedPhrase() {
|
||||
return root.startupModuleInst.getSeedPhrase()
|
||||
}
|
||||
|
||||
function validateLocalPairingConnectionString(connectionString) {
|
||||
return root.startupModuleInst.validateLocalPairingConnectionString(connectionString)
|
||||
}
|
||||
|
||||
function setConnectionString(connectionString) {
|
||||
root.startupModuleInst.setConnectionString(connectionString)
|
||||
}
|
||||
}
|
||||
|
@ -305,7 +305,7 @@ Item {
|
||||
}
|
||||
PropertyChanges {
|
||||
target: txtTitle
|
||||
text: qsTr("Sync to other device")
|
||||
text: qsTr("Sign in by syncing")
|
||||
}
|
||||
PropertyChanges {
|
||||
target: txtDesc
|
||||
@ -314,8 +314,7 @@ Item {
|
||||
}
|
||||
PropertyChanges {
|
||||
target: button1
|
||||
text: qsTr("Scan sync code")
|
||||
enabled: false // TODO: we don't have the sync flow developed yet
|
||||
text: qsTr("Scan or enter a sync code")
|
||||
}
|
||||
PropertyChanges {
|
||||
target: button2
|
||||
|
164
ui/app/AppLayouts/Onboarding/views/SyncCodeView.qml
Normal file
164
ui/app/AppLayouts/Onboarding/views/SyncCodeView.qml
Normal file
@ -0,0 +1,164 @@
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.Universal 2.12
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Controls.Validators 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
import shared 1.0
|
||||
import shared.panels 1.0
|
||||
import shared.popups 1.0
|
||||
import shared.controls 1.0
|
||||
|
||||
import "../popups"
|
||||
import "../controls"
|
||||
import "../stores"
|
||||
import "sync"
|
||||
import "../../Profile/stores"
|
||||
|
||||
import utils 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property StartupStore startupStore
|
||||
|
||||
implicitWidth: layout.implicitWidth
|
||||
implicitHeight: layout.implicitHeight
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property string connectionString
|
||||
|
||||
function validateConnectionString(connectionString) {
|
||||
const result = root.startupStore.validateLocalPairingConnectionString(connectionString)
|
||||
return result === ""
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: nextStateDelay
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.startupStore.setConnectionString(d.connectionString)
|
||||
root.startupStore.doPrimaryAction()
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: layout
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: 400
|
||||
spacing: 24
|
||||
|
||||
StatusBaseText {
|
||||
id: headlineText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: 22
|
||||
font.weight: Font.Bold
|
||||
color: Theme.palette.directColor1
|
||||
text: qsTr("Sign in by syncing")
|
||||
}
|
||||
|
||||
StatusSwitchTabBar {
|
||||
id: switchTabBar
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
currentIndex: 0
|
||||
|
||||
StatusSwitchTabButton {
|
||||
text: qsTr("Scan QR code")
|
||||
}
|
||||
|
||||
StatusSwitchTabButton {
|
||||
text: qsTr("Enter sync code")
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
width: parent.width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
implicitWidth: Math.max(mobileSync.implicitWidth, desktopSync.implicitWidth)
|
||||
implicitHeight: Math.max(mobileSync.implicitHeight, desktopSync.implicitHeight)
|
||||
currentIndex: switchTabBar.currentIndex
|
||||
clip: true
|
||||
|
||||
// StackLayout doesn't support alignment, so we create an `Item` wrappers
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
implicitWidth: mobileSync.implicitWidth
|
||||
implicitHeight: mobileSync.implicitHeight
|
||||
|
||||
SyncDeviceFromMobile {
|
||||
id: mobileSync
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
onConnectionStringFound: {
|
||||
d.processConnectionString(connectionString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
implicitWidth: desktopSync.implicitWidth
|
||||
implicitHeight: desktopSync.implicitHeight
|
||||
|
||||
SyncDeviceFromDesktop {
|
||||
id: desktopSync
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
input.readOnly: nextStateDelay.running
|
||||
input.validators: [
|
||||
StatusValidator {
|
||||
name: "asyncConnectionString"
|
||||
errorMessage: qsTr("This does not look like a sync code")
|
||||
validate: (value) => {
|
||||
return d.validateConnectionString(value)
|
||||
}
|
||||
}
|
||||
]
|
||||
input.onValidChanged: {
|
||||
if (!input.valid) {
|
||||
d.connectionString = ""
|
||||
return
|
||||
}
|
||||
d.connectionString = desktopSync.input.text
|
||||
nextStateDelay.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusFlatButton {
|
||||
text: qsTr("How to get a sync code")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onClicked: {
|
||||
instructionsPopup.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GetSyncCodeInstructionsPopup {
|
||||
id: instructionsPopup
|
||||
}
|
||||
}
|
73
ui/app/AppLayouts/Onboarding/views/SyncDeviceResult.qml
Normal file
73
ui/app/AppLayouts/Onboarding/views/SyncDeviceResult.qml
Normal file
@ -0,0 +1,73 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import shared.views 1.0
|
||||
import utils 1.0
|
||||
|
||||
import "../stores"
|
||||
import "../../Profile/stores"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property StartupStore startupStore
|
||||
|
||||
implicitWidth: layout.implicitWidth
|
||||
implicitHeight: layout.implicitHeight
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property bool finished: startupStore.localPairingState === Constants.LocalPairingState.Finished
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: 48
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: 22
|
||||
font.weight: Font.Bold
|
||||
color: Theme.palette.directColor1
|
||||
text: qsTr("Sign in by syncing")
|
||||
}
|
||||
|
||||
DeviceSyncingView {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
localPairingState: startupStore.localPairingState
|
||||
localPairingError: startupStore.localPairingError
|
||||
|
||||
userDisplayName: startupStore.localPairingName
|
||||
userImage: startupStore.localPairingImage
|
||||
userColorId: startupStore.localPairingColorId
|
||||
userColorHash: startupStore.localPairingColorHash
|
||||
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Sign in")
|
||||
opacity: d.finished ? 1 : 0
|
||||
enabled: d.finished
|
||||
onClicked: {
|
||||
// NOTE: Current status-go implementation automatically signs in
|
||||
// So we don't actually ever use this button.
|
||||
// I leave this code here for further implementaion by design.
|
||||
//const keyUid = "TODO: Get keyUid somehow"
|
||||
//root.startupStore.setSelectedLoginAccountByKeyUid(keyUid)
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 250 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import QtQuick 2.14
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property alias input: codeInput
|
||||
|
||||
StatusSyncCodeInput {
|
||||
id: codeInput
|
||||
implicitWidth: 400
|
||||
mode: StatusSyncCodeInput.WriteMode
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
signal connectionStringFound(connectionString: string)
|
||||
|
||||
implicitWidth: 330
|
||||
implicitHeight: 330
|
||||
|
||||
radius: 16
|
||||
color: Theme.palette.baseColor4
|
||||
|
||||
|
||||
StatusBaseText {
|
||||
id: text
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.dangerColor1
|
||||
text: qsTr("QR code scanning is not available yet")
|
||||
}
|
||||
}
|
@ -219,13 +219,14 @@ StatusSectionLayout {
|
||||
Loader {
|
||||
active: false
|
||||
asynchronous: true
|
||||
sourceComponent: DevicesView {
|
||||
sourceComponent: SyncingView {
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: parent.height
|
||||
|
||||
profileStore: root.store.profileStore
|
||||
devicesStore: root.store.devicesStore
|
||||
privacyStore: root.store.privacyStore
|
||||
sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.devicesSettings)
|
||||
sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.syncingSettings)
|
||||
contentWidth: d.contentWidth
|
||||
}
|
||||
}
|
||||
|
253
ui/app/AppLayouts/Profile/popups/SetupSyncingPopup.qml
Normal file
253
ui/app/AppLayouts/Profile/popups/SetupSyncingPopup.qml
Normal file
@ -0,0 +1,253 @@
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import QtQml.Models 2.14
|
||||
import QtQml.StateMachine 1.14 as DSM
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared.controls 1.0
|
||||
import shared.views 1.0
|
||||
|
||||
import "setupsyncing" as Views
|
||||
import "../stores"
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
property string password
|
||||
property string keyUid
|
||||
|
||||
property DevicesStore devicesStore
|
||||
property ProfileStore profileStore
|
||||
|
||||
width: 480
|
||||
padding: 16
|
||||
modal: true
|
||||
|
||||
title: qsTr("Sync a New Device")
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
signal generatingConnectionStringFailed
|
||||
signal connectionStringGenerated
|
||||
|
||||
signal localPairingStarted
|
||||
signal localPairingFailed
|
||||
signal localPairingFinished
|
||||
property string localPairingErrorMessage
|
||||
|
||||
property string connectionString
|
||||
property string errorMessage
|
||||
|
||||
function generateConnectionString() {
|
||||
|
||||
d.connectionString = ""
|
||||
d.errorMessage = ""
|
||||
|
||||
const result = root.devicesStore.getConnectionStringForBootstrappingAnotherDevice(root.keyUid, root.password)
|
||||
|
||||
try {
|
||||
const json = JSON.parse(result)
|
||||
d.errorMessage = json.error
|
||||
} catch (e) {
|
||||
d.connectionString = result
|
||||
}
|
||||
|
||||
if (d.errorMessage !== "") {
|
||||
d.generatingConnectionStringFailed()
|
||||
return
|
||||
}
|
||||
|
||||
displaySyncCodeView.secondsTimeout = 5 * 60 // This timeout should be moved to status-go.
|
||||
displaySyncCodeView.start()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.devicesStore
|
||||
function onLocalPairingStateChanged() {
|
||||
switch (root.devicesStore.localPairingState) {
|
||||
case Constants.LocalPairingState.WaitingForConnection:
|
||||
break;
|
||||
case Constants.LocalPairingState.Transferring:
|
||||
d.localPairingStarted()
|
||||
break
|
||||
case Constants.LocalPairingState.Error:
|
||||
d.localPairingFailed()
|
||||
break
|
||||
case Constants.LocalPairingState.Finished:
|
||||
d.localPairingFinished()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DSM.StateMachine {
|
||||
id: stateMachine
|
||||
|
||||
running: root.visible
|
||||
initialState: displaySyncCodeState
|
||||
|
||||
DSM.State {
|
||||
id: displaySyncCodeState
|
||||
|
||||
onEntered: {
|
||||
d.generateConnectionString()
|
||||
}
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: errorState
|
||||
signal: d.generatingConnectionStringFailed
|
||||
}
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: localPairingBaseState
|
||||
signal: d.localPairingStarted
|
||||
}
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: finalState
|
||||
signal: nextButton.clicked
|
||||
}
|
||||
|
||||
// Next 2 transitions are here temporarily.
|
||||
// TODO: Remove when server notifies with ProcessSuccess/ProcessError event.
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: localPairingFailedState
|
||||
signal: d.localPairingFailed
|
||||
}
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: localPairingSuccessState
|
||||
signal: d.localPairingFinished
|
||||
}
|
||||
}
|
||||
|
||||
DSM.State {
|
||||
id: localPairingBaseState
|
||||
|
||||
initialState: localPairingInProgressState
|
||||
|
||||
DSM.State {
|
||||
id: localPairingInProgressState
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: localPairingFailedState
|
||||
signal: d.localPairingFailed
|
||||
}
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: localPairingSuccessState
|
||||
signal: d.localPairingFinished
|
||||
}
|
||||
}
|
||||
|
||||
DSM.State {
|
||||
id: localPairingFailedState
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: finalState
|
||||
signal: nextButton.clicked
|
||||
}
|
||||
}
|
||||
|
||||
DSM.State {
|
||||
id: localPairingSuccessState
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: finalState
|
||||
signal: nextButton.clicked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DSM.State {
|
||||
id: errorState
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: finalState
|
||||
signal: nextButton.clicked
|
||||
}
|
||||
}
|
||||
|
||||
DSM.FinalState {
|
||||
id: finalState
|
||||
onEntered: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
|
||||
implicitWidth: Math.max(displaySyncCodeView.implicitWidth,
|
||||
localPairingView.implicitWidth,
|
||||
errorView.implicitWidth)
|
||||
|
||||
implicitHeight: Math.max(displaySyncCodeView.implicitHeight,
|
||||
localPairingView.implicitHeight,
|
||||
errorView.implicitHeight)
|
||||
|
||||
Views.DisplaySyncCode {
|
||||
id: displaySyncCodeView
|
||||
anchors.fill: parent
|
||||
visible: displaySyncCodeState.active
|
||||
connectionString: d.connectionString
|
||||
secondsTimeout: 5 * 60
|
||||
onRequestConnectionString: {
|
||||
d.generateConnectionString()
|
||||
}
|
||||
}
|
||||
|
||||
DeviceSyncingView {
|
||||
id: localPairingView
|
||||
anchors.fill: parent
|
||||
visible: localPairingBaseState.active
|
||||
devicesModel: root.devicesStore.devicesModel
|
||||
userDisplayName: root.profileStore.displayName
|
||||
userPublicKey: root.profileStore.pubkey
|
||||
userImage: root.profileStore.icon
|
||||
|
||||
localPairingState: root.devicesStore.localPairingState
|
||||
localPairingError: root.devicesStore.localPairingError
|
||||
}
|
||||
|
||||
Views.ErrorMessage {
|
||||
id: errorView
|
||||
anchors.fill: parent
|
||||
visible: errorState.active
|
||||
primaryText: qsTr("Failed to generate sync code")
|
||||
secondaryText: d.errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
footer: StatusDialogFooter {
|
||||
rightButtons: ObjectModel {
|
||||
StatusButton {
|
||||
id: nextButton
|
||||
visible: !!text
|
||||
enabled: !localPairingInProgressState.active
|
||||
text: {
|
||||
if (displaySyncCodeState.active
|
||||
|| localPairingInProgressState.active
|
||||
|| localPairingSuccessState.active)
|
||||
return qsTr("Done");
|
||||
if (localPairingFailedState.active
|
||||
|| errorState.active)
|
||||
return qsTr("Close");
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import QtQml.Models 2.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
|
||||
import "../stores"
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
property DevicesStore devicesStore
|
||||
property var deviceModel
|
||||
|
||||
readonly property string deviceName: d.deviceName
|
||||
|
||||
title: qsTr("Personalize %1").arg(deviceModel.name)
|
||||
width: implicitWidth
|
||||
padding: 16
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property string deviceName: ""
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
nameInput.text = deviceModel.name
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
StatusInput {
|
||||
id: nameInput
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Device name")
|
||||
}
|
||||
}
|
||||
|
||||
footer: StatusDialogFooter {
|
||||
rightButtons: ObjectModel {
|
||||
StatusButton {
|
||||
text: qsTr("Done")
|
||||
enabled: nameInput.text !== ""
|
||||
onClicked : {
|
||||
root.devicesStore.setName(nameInput.text.trim())
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
BackupSeedModal 1.0 BackupSeedModal.qml
|
||||
SetupSyncingPopup 1.0 SetupSyncingPopup.qml
|
||||
|
@ -0,0 +1,238 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtGraphicalEffects 1.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
import shared.panels 1.0
|
||||
import utils 1.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property int secondsTimeout: 5 * 60
|
||||
property string connectionString: ""
|
||||
|
||||
signal requestConnectionString()
|
||||
|
||||
function start() {
|
||||
d.qrBlurred = true
|
||||
d.codeExpired = false
|
||||
d.secondsLeft = root.secondsTimeout
|
||||
expireTimer.start()
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property int secondsLeft: root.secondsTimeout
|
||||
property int secondsRatio: 1 // This property can be used to speed up testing of syncCode expiration
|
||||
property bool qrBlurred: true
|
||||
property bool codeExpired: false
|
||||
|
||||
onCodeExpiredChanged: {
|
||||
if (codeExpired)
|
||||
syncCodeInput.showPassword = false
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: expireTimer
|
||||
interval: root.secondsTimeout * 1000 / d.secondsRatio
|
||||
onTriggered: {
|
||||
d.codeExpired = true
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timeLeftUpdateTimer
|
||||
interval: 1000 / d.secondsRatio
|
||||
repeat: true
|
||||
running: expireTimer.running
|
||||
onTriggered: {
|
||||
d.secondsLeft = Math.max(0, --d.secondsLeft)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: 254
|
||||
implicitHeight: 254
|
||||
|
||||
Image {
|
||||
id: qrCode
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
smooth: false
|
||||
source: globalUtils.qrCode(root.connectionString)
|
||||
}
|
||||
|
||||
FastBlur {
|
||||
anchors.fill: qrCode
|
||||
source: qrCode
|
||||
radius: d.codeExpired || d.qrBlurred ? 40 : 0
|
||||
transparentBorder: true
|
||||
|
||||
Behavior on radius {
|
||||
NumberAnimation { duration: 500 }
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
id: revealButton
|
||||
anchors.centerIn: parent
|
||||
visible: !d.codeExpired && d.qrBlurred
|
||||
normalColor: Theme.palette.primaryColor1
|
||||
hoverColor: Theme.palette.miscColor1;
|
||||
textColor: Theme.palette.indirectColor1
|
||||
font.weight: Font.Medium
|
||||
icon.name: "show"
|
||||
text: qsTr("Reveal QR")
|
||||
onClicked: {
|
||||
d.qrBlurred = !d.qrBlurred
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
id: regenerateButton
|
||||
anchors.centerIn: parent
|
||||
visible: d.codeExpired
|
||||
normalColor: Theme.palette.primaryColor1
|
||||
hoverColor: Theme.palette.miscColor1;
|
||||
textColor: Theme.palette.indirectColor1
|
||||
font.weight: Font.Medium
|
||||
icon.name: "refresh"
|
||||
text: qsTr("Regenerate")
|
||||
onClicked: {
|
||||
root.requestConnectionString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 16
|
||||
|
||||
StatusBaseText {
|
||||
font.pixelSize: 17
|
||||
text: qsTr("Code valid for: ")
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: timeoutText
|
||||
width: fontMetrics.advanceWidth("10:00")
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.pixelSize: 17
|
||||
color: d.secondsLeft < 60 ? Theme.palette.dangerColor1 : Theme.palette.directColor1
|
||||
|
||||
text: {
|
||||
const minutes = Math.floor(d.secondsLeft / 60);
|
||||
const seconds = d.secondsLeft % 60;
|
||||
return `${minutes}:${String(seconds).padStart(2,'0')}`;
|
||||
}
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: timeoutText.font
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Extract this to a component.
|
||||
// Also used in `PasswordView` and several other files.
|
||||
// https://github.com/status-im/status-desktop/issues/6136
|
||||
|
||||
StyledText {
|
||||
id: inputLabel
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 7
|
||||
text: qsTr("Sync code")
|
||||
font.weight: Font.Medium
|
||||
font.pixelSize: 13
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
|
||||
Input {
|
||||
id: syncCodeInput
|
||||
|
||||
property bool showPassword
|
||||
readonly property bool effectiveShowPassword: showPassword && !d.codeExpired
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 24
|
||||
readOnly: true
|
||||
keepHeight: true
|
||||
textField.echoMode: effectiveShowPassword ? TextInput.Normal : TextInput.Password
|
||||
textField.rightPadding: syncCodeButtons.width + Style.current.padding / 2
|
||||
textField.color: Style.current.textColor
|
||||
textField.selectByMouse: !d.codeExpired
|
||||
text: root.connectionString
|
||||
|
||||
Row {
|
||||
id: syncCodeButtons
|
||||
anchors.verticalCenter: syncCodeInput.verticalCenter
|
||||
anchors.right: parent.right
|
||||
spacing: 8
|
||||
rightPadding: 8
|
||||
leftPadding: 8
|
||||
|
||||
StatusFlatRoundButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 24
|
||||
height: 24
|
||||
icon.name: syncCodeInput.effectiveShowPassword ? "hide" : "show"
|
||||
icon.color: Theme.palette.baseColor1
|
||||
enabled: !d.codeExpired
|
||||
onClicked: {
|
||||
syncCodeInput.showPassword = !syncCodeInput.showPassword
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
size: StatusBaseButton.Size.Tiny
|
||||
enabled: !d.codeExpired
|
||||
text: qsTr("Copy")
|
||||
onClicked: {
|
||||
const showPassword = syncCodeInput.showPassword
|
||||
syncCodeInput.showPassword = true
|
||||
syncCodeInput.textField.selectAll()
|
||||
syncCodeInput.textField.copy()
|
||||
syncCodeInput.textField.deselect()
|
||||
syncCodeInput.showPassword = showPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: !d.codeExpired
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("On your other device, navigate to the Syncing<br>screen and select Enter Sync Code.")
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: d.codeExpired
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("Your QR and Sync Code has expired.")
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string primaryText
|
||||
property string secondaryText
|
||||
|
||||
spacing: 12
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: parent.height / 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignBottom
|
||||
text: root.primaryText
|
||||
font.pixelSize: 17
|
||||
color: Theme.palette.dangerColor1
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 60
|
||||
Layout.rightMargin: 60
|
||||
Layout.preferredWidth: 360
|
||||
Layout.preferredHeight: parent.height / 2
|
||||
Layout.minimumHeight: detailsView.implicitHeight
|
||||
|
||||
ErrorDetails {
|
||||
id: detailsView
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
title: qsTr("Failed to start pairing server")
|
||||
details: root.secondaryText
|
||||
}
|
||||
}
|
||||
}
|
@ -6,10 +6,13 @@ QtObject {
|
||||
|
||||
property var devicesModule
|
||||
|
||||
property var devicesModel: devicesModule.model
|
||||
property var devicesModel: devicesModule ? devicesModule.model : null
|
||||
|
||||
// Module Properties
|
||||
property bool isDeviceSetup: devicesModule.isDeviceSetup
|
||||
property bool isDeviceSetup: devicesModule ? devicesModule.isDeviceSetup : false
|
||||
|
||||
readonly property int localPairingState: devicesModule ? devicesModule.localPairingState : -1
|
||||
readonly property string localPairingError: devicesModule ? devicesModule.localPairingError : ""
|
||||
|
||||
function loadDevices() {
|
||||
return root.devicesModule.loadDevices()
|
||||
@ -30,4 +33,21 @@ QtObject {
|
||||
function enableDevice(installationId, enable) {
|
||||
root.devicesModule.enableDevice(installationId, enable)
|
||||
}
|
||||
|
||||
function authenticateUser() {
|
||||
const keyUid = "" // TODO: Support Keycard
|
||||
root.devicesModule.authenticateUser(keyUid)
|
||||
}
|
||||
|
||||
function validateConnectionString(connectionString) {
|
||||
return root.devicesModule.validateConnectionString(connectionString)
|
||||
}
|
||||
|
||||
function getConnectionStringForBootstrappingAnotherDevice(keyUid, password) {
|
||||
return root.devicesModule.getConnectionStringForBootstrappingAnotherDevice(keyUid, password)
|
||||
}
|
||||
|
||||
function inputConnectionStringForBootstrapping(connectionString) {
|
||||
return root.devicesModule.inputConnectionStringForBootstrapping(connectionString)
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,9 @@ QtObject {
|
||||
append({subsection: Constants.settingsSubsection.ensUsernames,
|
||||
text: qsTr("ENS usernames"),
|
||||
icon: "username"})
|
||||
append({subsection: Constants.settingsSubsection.syncingSettings,
|
||||
text: qsTr("Syncing"),
|
||||
icon: "rotate"})
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,9 +122,6 @@ QtObject {
|
||||
append({subsection: Constants.settingsSubsection.language,
|
||||
text: qsTr("Language & Currency"),
|
||||
icon: "language"})
|
||||
append({subsection: Constants.settingsSubsection.devicesSettings,
|
||||
text: qsTr("Devices settings"),
|
||||
icon: "mobile"})
|
||||
append({subsection: Constants.settingsSubsection.advanced,
|
||||
text: qsTr("Advanced"),
|
||||
icon: "settings"})
|
||||
|
@ -15,6 +15,8 @@ QtObject {
|
||||
property string icon: !!Global.userProfile? Global.userProfile.icon : ""
|
||||
property bool userDeclinedBackupBanner: Global.appIsReady? localAccountSensitiveSettings.userDeclinedBackupBanner : false
|
||||
property var privacyStore: profileSectionModule.privacyModule
|
||||
readonly property string keyUid: userProfile.keyUid
|
||||
readonly property bool isKeycardUser: userProfile.isKeycardUser
|
||||
|
||||
readonly property string bio: profileModule.bio
|
||||
readonly property string socialLinksJson: profileModule.socialLinksJson
|
||||
|
@ -1,251 +0,0 @@
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import QtGraphicalEffects 1.13
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import shared.panels 1.0
|
||||
import shared.controls 1.0
|
||||
|
||||
import "../stores"
|
||||
|
||||
SettingsContentBase {
|
||||
id: root
|
||||
|
||||
property DevicesStore devicesStore
|
||||
property PrivacyStore privacyStore
|
||||
|
||||
property bool isSyncing: false
|
||||
|
||||
Component.onCompleted: {
|
||||
root.devicesStore.loadDevices()
|
||||
}
|
||||
|
||||
Column {
|
||||
width: root.contentWidth
|
||||
height: !!parent ? parent.height : 0
|
||||
spacing: Style.current.padding
|
||||
|
||||
Item {
|
||||
id: firstTimeSetup
|
||||
width: parent.width
|
||||
visible: !root.devicesStore.isDeviceSetup
|
||||
height: visible ? childrenRect.height : 0
|
||||
|
||||
StatusBaseText {
|
||||
id: deviceNameLbl
|
||||
text: qsTr("Please set a name for your device.")
|
||||
font.pixelSize: 14
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
|
||||
Input {
|
||||
id: deviceNameTxt
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: deviceNameLbl.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
placeholderText: qsTr("Specify a name")
|
||||
}
|
||||
|
||||
// TODO: replace with StatusQ component
|
||||
StatusButton {
|
||||
anchors.top: deviceNameTxt.bottom
|
||||
anchors.topMargin: 10
|
||||
anchors.right: deviceNameTxt.right
|
||||
text: qsTr("Continue")
|
||||
enabled: deviceNameTxt.text !== ""
|
||||
onClicked : root.devicesStore.setName(deviceNameTxt.text.trim())
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: advertiseDeviceItem
|
||||
width: parent.width
|
||||
visible: root.devicesStore.isDeviceSetup
|
||||
height: visible ? advertiseDevice.height + learnMoreText.height + Style.current.padding : 0
|
||||
|
||||
Item {
|
||||
id: advertiseDevice
|
||||
height: childrenRect.height
|
||||
width: 500
|
||||
|
||||
SVGImage {
|
||||
id: advertiseImg
|
||||
height: 32
|
||||
width: 32
|
||||
anchors.left: parent.left
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: Style.svg("messageActive")
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: parent
|
||||
color: Style.current.blue
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: advertiseDeviceTitle
|
||||
text: qsTr("Advertise device")
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.Bold
|
||||
color: Theme.palette.primaryColor1
|
||||
anchors.left: advertiseImg.right
|
||||
anchors.leftMargin: Style.current.padding
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: advertiseDeviceDesk
|
||||
text: qsTr("Pair your devices to sync contacts and chats between them")
|
||||
font.pixelSize: 14
|
||||
anchors.top: advertiseDeviceTitle.bottom
|
||||
anchors.topMargin: 6
|
||||
anchors.left: advertiseImg.right
|
||||
anchors.leftMargin: Style.current.padding
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: advertiseDevice
|
||||
onClicked: root.devicesStore.advertise()
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: learnMoreText
|
||||
anchors.top: advertiseDevice.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
text: qsTr("Learn more")
|
||||
font.pixelSize: 16
|
||||
color: Theme.palette.primaryColor1
|
||||
anchors.left: parent.left
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: Global.openLink("https://status.im/user_guides/pairing_devices.html")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
visible: root.devicesStore.devicesModule.devicesLoading
|
||||
text: qsTr("Loading devices...")
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
visible: root.devicesStore.devicesModule.devicesLoadingError
|
||||
text: qsTr("Error loading devices. Please try again later.")
|
||||
}
|
||||
|
||||
Item {
|
||||
id: deviceListItem
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
visible: !root.devicesStore.devicesModule.devicesLoading &&
|
||||
!root.devicesStore.devicesModule.devicesLoadingError &&
|
||||
root.devicesStore.isDeviceSetup
|
||||
|
||||
|
||||
StatusBaseText {
|
||||
id: deviceListLbl
|
||||
text: qsTr("Paired devices")
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.Bold
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
|
||||
StatusListView {
|
||||
id: listView
|
||||
anchors.top: deviceListLbl.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
// This is a placeholder fix to the display. This whole page will be redesigned
|
||||
height: 300
|
||||
spacing: 5
|
||||
width: parent.width
|
||||
model: root.devicesStore.devicesModel
|
||||
// TODO: replace with StatusQ component
|
||||
delegate: Item {
|
||||
height: childrenRect.height
|
||||
SVGImage {
|
||||
id: enabledIcon
|
||||
source: Style.svg("messageActive")
|
||||
height: 24
|
||||
width: 24
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: parent
|
||||
color: devicePairedSwitch.checked ? Style.current.blue : Style.current.darkGrey
|
||||
}
|
||||
}
|
||||
StatusBaseText {
|
||||
id: deviceItemLbl
|
||||
text: {
|
||||
let deviceId = model.installationId.split("-")[0].substr(0, 5)
|
||||
let labelText = `${model.name || qsTr("No info")} ` +
|
||||
`(${model.isCurrentDevice ? qsTr("you") + ", ": ""}${deviceId})`;
|
||||
return labelText;
|
||||
}
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 14
|
||||
anchors.left: enabledIcon.right
|
||||
anchors.leftMargin: Style.current.padding
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
StatusSwitch {
|
||||
id: devicePairedSwitch
|
||||
visible: !model.isCurrentDevice
|
||||
checked: model.enabled
|
||||
anchors.left: deviceItemLbl.right
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.top: deviceItemLbl.top
|
||||
onClicked: root.devicesStore.enableDevice(model.installationId, devicePairedSwitch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
id: syncAllBtn
|
||||
anchors.top: listView.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
// anchors.bottom: parent.bottom
|
||||
// anchors.bottomMargin: Style.current.padding
|
||||
anchors.horizontalCenter: listView.horizontalCenter
|
||||
|
||||
text: isSyncing ?
|
||||
qsTr("Syncing...") :
|
||||
qsTr("Sync all devices")
|
||||
enabled: !isSyncing
|
||||
onClicked : {
|
||||
isSyncing = true;
|
||||
root.devicesStore.syncAll()
|
||||
// Currently we don't know how long it takes, so we just disable for 10s, to avoid spamming
|
||||
timer.setTimeout(function(){
|
||||
isSyncing = false
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
id: backupBtn
|
||||
anchors.top: syncAllBtn.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.horizontalCenter: listView.horizontalCenter
|
||||
text: qsTr("Backup Data")
|
||||
onClicked : {
|
||||
let lastUpdate = root.privacyStore.backupData() * 1000
|
||||
console.log("Backup done at: ", LocaleUtils.formatDateTime(lastUpdate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
}
|
||||
}
|
||||
}
|
273
ui/app/AppLayouts/Profile/views/SyncingView.qml
Normal file
273
ui/app/AppLayouts/Profile/views/SyncingView.qml
Normal file
@ -0,0 +1,273 @@
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import QtQml.Models 2.14
|
||||
import QtGraphicalEffects 1.13
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Core.Utils 0.1 as StatusQUtils
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import shared.panels 1.0
|
||||
import shared.controls 1.0
|
||||
import shared.controls.chat 1.0
|
||||
|
||||
import "../stores"
|
||||
import "../popups"
|
||||
import "../controls"
|
||||
import "../../stores"
|
||||
|
||||
SettingsContentBase {
|
||||
id: root
|
||||
|
||||
property DevicesStore devicesStore
|
||||
property ProfileStore profileStore
|
||||
property PrivacyStore privacyStore
|
||||
|
||||
property bool isSyncing: false
|
||||
|
||||
Component.onCompleted: {
|
||||
root.devicesStore.loadDevices()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
width: root.contentWidth
|
||||
spacing: Style.current.padding
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
/*
|
||||
Device INFO:
|
||||
id: "abcdabcd-1234-5678-9012-12a34b5cd678",
|
||||
identity: ""
|
||||
version: 1
|
||||
enabled: true
|
||||
timestamp: 0
|
||||
metadata:
|
||||
name: "MacBook-1"
|
||||
deviceType: "macosx"
|
||||
fcmToken: ""
|
||||
*/
|
||||
|
||||
readonly property var instructionsModel: [
|
||||
qsTr("Verify your login with password or KeyCard"),
|
||||
qsTr("Reveal a temporary QR and Sync Code") + "*",
|
||||
qsTr("Share that information with your new device"),
|
||||
]
|
||||
|
||||
|
||||
function personalizeDevice(model) {
|
||||
Global.openPopup(personalizeDevicePopup, {
|
||||
"deviceModel": model
|
||||
})
|
||||
}
|
||||
|
||||
function setupSyncing() {
|
||||
const keyUid = root.profileStore.isKeycardUser ? root.profileStore.keyUid : ""
|
||||
root.devicesStore.authenticateUser(keyUid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: devicesStore.devicesModule
|
||||
|
||||
function onUserAuthenticated(pin, password, keyUid) {
|
||||
if (!password)
|
||||
return
|
||||
// Authentication flow returns empty keyUid for non-keycard user.
|
||||
const effectiveKeyUid = root.profileStore.isKeycardUser
|
||||
? keyUid
|
||||
: root.profileStore.keyUid
|
||||
Global.openPopup(setupSyncingPopup, {
|
||||
password,
|
||||
keyUid: effectiveKeyUid
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Devices")
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
visible: root.devicesStore.devicesModule.devicesLoading
|
||||
text: qsTr("Loading devices...")
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
visible: root.devicesStore.devicesModule.devicesLoadingError
|
||||
text: qsTr("Error loading devices. Please try again later.")
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 17
|
||||
Layout.bottomMargin: 17
|
||||
|
||||
implicitHeight: contentHeight
|
||||
|
||||
spacing: Style.current.padding
|
||||
model: root.devicesStore.devicesModel
|
||||
|
||||
visible: !root.devicesStore.devicesModule.devicesLoading &&
|
||||
!root.devicesStore.devicesModule.devicesLoadingError &&
|
||||
root.devicesStore.isDeviceSetup
|
||||
|
||||
delegate: StatusSyncDeviceDelegate {
|
||||
width: ListView.view.width
|
||||
deviceName: model.name
|
||||
deviceType: model.deviceType
|
||||
timestamp: model.timestamp
|
||||
isCurrentDevice: model.isCurrentDevice
|
||||
onSetupSyncingButtonClicked: {
|
||||
d.setupSyncing(SetupSyncingPopup.GenerateSyncCode)
|
||||
}
|
||||
onClicked: {
|
||||
d.personalizeDevice(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 17
|
||||
|
||||
implicitWidth: instructionsLayout.implicitWidth
|
||||
+ instructionsLayout.anchors.leftMargin
|
||||
+ instructionsLayout.anchors.rightMargin
|
||||
|
||||
implicitHeight: instructionsLayout.implicitHeight
|
||||
+ instructionsLayout.anchors.topMargin
|
||||
+ instructionsLayout.anchors.bottomMargin
|
||||
|
||||
color: Theme.palette.primaryColor3
|
||||
radius: 8
|
||||
|
||||
ColumnLayout {
|
||||
id: instructionsLayout
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: 24
|
||||
bottomMargin: 24
|
||||
leftMargin: 16
|
||||
rightMargin: 16
|
||||
}
|
||||
|
||||
spacing: 17
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -8
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Theme.palette.primaryColor1
|
||||
font.pixelSize: 17
|
||||
font.weight: Font.Bold
|
||||
text: qsTr("Sync a New Device")
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 15
|
||||
font.weight: Font.Medium
|
||||
text: qsTr("You own your data. Sync it among your devices.")
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
rows: d.instructionsModel.length
|
||||
flow: GridLayout.TopToBottom
|
||||
|
||||
Repeater {
|
||||
model: d.instructionsModel
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
text: index + 1
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: d.instructionsModel
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
color: Theme.palette.directColor1
|
||||
font.pixelSize: 15
|
||||
text: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
// type: StatusRoundButton.Type.Secondary
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
normalColor: Theme.palette.primaryColor1
|
||||
hoverColor: Theme.palette.miscColor1;
|
||||
textColor: Theme.palette.indirectColor1
|
||||
font.weight: Font.Medium
|
||||
text: qsTr("Setup Syncing")
|
||||
onClicked: {
|
||||
d.setupSyncing()
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 13
|
||||
text: "* " + qsTr("This is best done in private. The code will grant access to your profile.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
id: backupBtn
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 17
|
||||
text: qsTr("Backup Data")
|
||||
onClicked : {
|
||||
let lastUpdate = root.privacyStore.backupData() * 1000
|
||||
console.log("Backup done at: ", LocaleUtils.formatDateTime(lastUpdate))
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: personalizeDevicePopup
|
||||
|
||||
SyncDeviceCustomizationPopup {
|
||||
anchors.centerIn: parent
|
||||
devicesStore: root.devicesStore
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: setupSyncingPopup
|
||||
|
||||
SetupSyncingPopup {
|
||||
anchors.centerIn: parent
|
||||
devicesStore: root.devicesStore
|
||||
profileStore: root.profileStore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
ui/imports/shared/controls/ErrorDetails.qml
Normal file
71
ui/imports/shared/controls/ErrorDetails.qml
Normal file
@ -0,0 +1,71 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string title
|
||||
property string details
|
||||
readonly property string detailsVisible: d.detailsVisible
|
||||
|
||||
implicitWidth: layout.implicitWidthj
|
||||
+ layout.anchors.leftMargin
|
||||
+ layout.anchors.rigthMargin
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
+ layout.anchors.topMargin
|
||||
+ layout.anchors.bottomMargin
|
||||
|
||||
radius: 8
|
||||
color: Theme.palette.baseColor4
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property bool detailsVisible: false
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 4
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
text: root.title
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
visible: !d.detailsVisible
|
||||
text: qsTr("Show error details")
|
||||
color: Theme.palette.primaryColor1
|
||||
font.pixelSize: 12
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
d.detailsVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
visible: d.detailsVisible
|
||||
text: root.details
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
spacing: 4
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property int listItemHeight: 40
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("1. Open Status App on your desktop device")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("2. Open")
|
||||
}
|
||||
StatusRoundIcon {
|
||||
asset.name: "settings"
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.directColor1
|
||||
text: qsTr("Settings")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("3. Navigate to the ")
|
||||
}
|
||||
StatusRoundIcon {
|
||||
asset.name: "rotate"
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: qsTr("Syncing tab")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: qsTr("4. Click")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: qsTr("Setup Syncing")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: qsTr("5. Scan or enter the code ")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
}
|
||||
}
|
98
ui/imports/shared/controls/GetSyncCodeMobileInstructions.qml
Normal file
98
ui/imports/shared/controls/GetSyncCodeMobileInstructions.qml
Normal file
@ -0,0 +1,98 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
spacing: 4
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property int listItemHeight: 40
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("1. Open Status App on your mobile device")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("2. Open your")
|
||||
}
|
||||
StatusRoundIcon {
|
||||
asset.name: "profile"
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.directColor1
|
||||
text: qsTr("Profile")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("3. Go to")
|
||||
}
|
||||
StatusRoundIcon {
|
||||
asset.name: "rotate"
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: qsTr("Syncing")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: qsTr("4. Tap")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: qsTr("Sync new device")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: d.listItemHeight
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: qsTr("5. Scan or enter the code ")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
}
|
||||
}
|
77
ui/imports/shared/controls/StatusSyncCodeInput.qml
Normal file
77
ui/imports/shared/controls/StatusSyncCodeInput.qml
Normal file
@ -0,0 +1,77 @@
|
||||
import QtQuick 2.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
StatusInput {
|
||||
id: root
|
||||
|
||||
// TODO: Use https://github.com/status-im/status-desktop/issues/6136
|
||||
|
||||
enum Mode {
|
||||
WriteMode,
|
||||
ReadMode
|
||||
}
|
||||
|
||||
property int mode: StatusSyncCodeInput.Mode.WriteMode
|
||||
property bool readOnly: false
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property bool writeMode: root.mode === StatusSyncCodeInput.Mode.WriteMode
|
||||
}
|
||||
|
||||
label: d.writeMode ? qsTr("Paste sync code") : qsTr("Sync code")
|
||||
input.edit.readOnly: root.readOnly
|
||||
input.placeholderText: d.writeMode ? qsTr("eg. %1").arg("0x2Ef19") : ""
|
||||
input.font: Theme.palette.monoFont.name
|
||||
input.placeholderFont: root.input.font
|
||||
|
||||
input.rightComponent: {
|
||||
switch (root.mode) {
|
||||
case StatusSyncCodeInput.Mode.WriteMode:
|
||||
return root.valid ? validCodeIconComponent
|
||||
: pasteButtonComponent
|
||||
case StatusSyncCodeInput.Mode.ReadMode:
|
||||
return copyButtonComponent
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: copyButtonComponent
|
||||
|
||||
StatusButton {
|
||||
size: StatusBaseButton.Size.Tiny
|
||||
text: qsTr("Copy")
|
||||
onClicked: {
|
||||
root.input.edit.selectAll();
|
||||
root.input.edit.copy();
|
||||
root.input.edit.deselect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pasteButtonComponent
|
||||
|
||||
StatusButton {
|
||||
size: StatusBaseButton.Size.Tiny
|
||||
enabled: !root.readOnly && root.input.edit.canPaste
|
||||
text: qsTr("Paste")
|
||||
onClicked: {
|
||||
root.input.edit.selectAll();
|
||||
root.input.edit.paste();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: validCodeIconComponent
|
||||
|
||||
StatusIcon {
|
||||
icon: "tiny/checkmark"
|
||||
color: Theme.palette.successColor1
|
||||
}
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ Item {
|
||||
value: icon
|
||||
}
|
||||
|
||||
height: visible ? contentContainer.height : 0
|
||||
implicitWidth: contentContainer.implicitWidth
|
||||
implicitHeight: contentContainer.implicitHeight
|
||||
|
||||
QtObject {
|
||||
|
@ -16,6 +16,7 @@ SearchBox 1.0 SearchBox.qml
|
||||
SeedPhraseTextArea 1.0 SeedPhraseTextArea.qml
|
||||
SendToContractWarning 1.0 SendToContractWarning.qml
|
||||
SettingsRadioButton 1.0 SettingsRadioButton.qml
|
||||
StatusSyncingInstructions 1.0 StatusSyncingInstructions.qml
|
||||
StyledButton 1.0 StyledButton.qml
|
||||
StyledTextArea 1.0 StyledTextArea.qml
|
||||
StyledTextEdit 1.0 StyledTextEdit.qml
|
||||
@ -32,3 +33,7 @@ TransactionDetailsHeader.qml 1.0 TransactionDetailsHeader.qml
|
||||
TokenDelegate 1.0 TokenDelegate.qml
|
||||
StyledTextEditWithLoadingState 1.0 StyledTextEditWithLoadingState.qml
|
||||
LoadingTokenDelegate 1.0 LoadingTokenDelegate.qml
|
||||
StatusSyncCodeInput 1.0 StatusSyncCodeInput.qml
|
||||
GetSyncCodeMobileInstructions 1.0 GetSyncCodeMobileInstructions.qml
|
||||
GetSyncCodeDesktopInstructions 1.0 GetSyncCodeDesktopInstructions.qml
|
||||
ErrorDetails 1.0 ErrorDetails.qml
|
||||
|
68
ui/imports/shared/popups/GetSyncCodeInstructionsPopup.qml
Normal file
68
ui/imports/shared/popups/GetSyncCodeInstructionsPopup.qml
Normal file
@ -0,0 +1,68 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
enum Source {
|
||||
Mobile,
|
||||
Desktop
|
||||
}
|
||||
|
||||
title: qsTr("How to get a sync code on...")
|
||||
padding: 40
|
||||
footer: null
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
StatusSwitchTabBar {
|
||||
id: switchTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: 400
|
||||
currentIndex: 0
|
||||
|
||||
StatusSwitchTabButton {
|
||||
text: qsTr("Mobile")
|
||||
}
|
||||
|
||||
StatusSwitchTabButton {
|
||||
text: qsTr("Desktop")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 41
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: Math.max(mobileSync.implicitWidth, desktopSync.implicitWidth)
|
||||
implicitHeight: Math.max(mobileSync.implicitHeight, desktopSync.implicitHeight)
|
||||
currentIndex: switchTabBar.currentIndex
|
||||
|
||||
GetSyncCodeMobileInstructions {
|
||||
id: mobileSync
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
GetSyncCodeDesktopInstructions {
|
||||
id: desktopSync
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -24,3 +24,4 @@ ImportCommunityPopup 1.0 ImportCommunityPopup.qml
|
||||
DisplayNamePopup 1.0 DisplayNamePopup.qml
|
||||
SendContactRequestModal 1.0 SendContactRequestModal.qml
|
||||
AccountsModalHeader 1.0 AccountsModalHeader.qml
|
||||
GetSyncCodeInstructionsPopup 1.0 GetSyncCodeInstructionsPopup.qml
|
||||
|
188
ui/imports/shared/views/DeviceSyncingView.qml
Normal file
188
ui/imports/shared/views/DeviceSyncingView.qml
Normal file
@ -0,0 +1,188 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
import shared.controls.chat 1.0
|
||||
import utils 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property alias devicesModel: listView.model
|
||||
property string userDisplayName
|
||||
property string userColorId
|
||||
property string userColorHash
|
||||
property string userPublicKey
|
||||
property string userImage
|
||||
|
||||
property int localPairingState: Constants.LocalPairingState.Idle
|
||||
property string localPairingError
|
||||
|
||||
implicitWidth: layout.implicitWidth
|
||||
implicitHeight: layout.implicitHeight
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property int deviceDelegateWidth: 220
|
||||
readonly property bool pairingFailed: root.localPairingState === Constants.LocalPairingState.Error
|
||||
readonly property bool pairingSuccess: root.localPairingState === Constants.LocalPairingState.Finished
|
||||
readonly property bool pairingInProgress: !d.pairingFailed && !d.pairingSuccess
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// This is used in the profile section. The user's pubkey is available
|
||||
// so we can calculate the hash and colorId
|
||||
Loader {
|
||||
id: profileSectionUserImage
|
||||
active: root.userPublicKey != ""
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
sourceComponent: UserImage {
|
||||
name: root.userDisplayName
|
||||
pubkey: root.userPublicKey
|
||||
image: root.userImage
|
||||
interactive: false
|
||||
imageWidth: 80
|
||||
imageHeight: 80
|
||||
}
|
||||
}
|
||||
|
||||
// This is used in the onboarding once a sync code is received. The
|
||||
// user's pubkey is unknown, but we have the multiaccount information
|
||||
// available (from the plaintext accounts db), so we use the colorHash
|
||||
// and colorId directly
|
||||
Loader {
|
||||
id: colorUserImage
|
||||
active: root.userPublicKey == ""
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
sourceComponent: UserImage {
|
||||
name: root.userDisplayName
|
||||
colorId: root.userColorId
|
||||
colorHash: root.userColorHash
|
||||
image: root.userImage
|
||||
interactive: false
|
||||
imageWidth: 80
|
||||
imageHeight: 80
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Theme.palette.directColor1
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: 22
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
text: root.userDisplayName
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 31
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: 17
|
||||
color: d.pairingFailed ? Theme.palette.dangerColor1 : Theme.palette.directColor1
|
||||
text: {
|
||||
if (d.pairingInProgress)
|
||||
return qsTr("Device found!");
|
||||
if (d.pairingSuccess)
|
||||
return qsTr("Device synced!");
|
||||
if (d.pairingFailed)
|
||||
return qsTr("Device failed to sync");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 25
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
visible: !!text
|
||||
text: {
|
||||
if (d.pairingInProgress)
|
||||
return qsTr("Syncing your profile and settings preferences");
|
||||
if (d.pairingSuccess)
|
||||
return qsTr("Your devices are now in sync");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
StatusSyncDeviceDelegate {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: d.deviceDelegateWidth
|
||||
visible: !d.pairingFailed
|
||||
subTitle: qsTr("Synced device")
|
||||
enabled: false
|
||||
loading: d.pairingInProgress
|
||||
deviceName: qsTr("No device name")
|
||||
isCurrentDevice: false
|
||||
}
|
||||
|
||||
ErrorDetails {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 60
|
||||
Layout.rightMargin: 60
|
||||
Layout.preferredWidth: 360
|
||||
Layout.topMargin: 12
|
||||
visible: d.pairingFailed
|
||||
title: qsTr("Failed to sync devices")
|
||||
details: root.localPairingError
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 25
|
||||
Layout.bottomMargin: 25
|
||||
implicitHeight: 1
|
||||
implicitWidth: d.deviceDelegateWidth
|
||||
color: Theme.palette.baseColor4
|
||||
opacity: listView.count ? 1 : 0
|
||||
}
|
||||
|
||||
StatusScrollView {
|
||||
id: scrollView
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillHeight: true
|
||||
|
||||
clip: true
|
||||
padding: 0
|
||||
|
||||
contentWidth: d.deviceDelegateWidth
|
||||
contentHeight: listView.contentHeight
|
||||
implicitWidth: contentWidth + leftPadding + rightPadding
|
||||
implicitHeight: contentHeight + topPadding + bottomPadding
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
width: scrollView.availableWidth
|
||||
height: scrollView.availableHeight
|
||||
|
||||
spacing: 4
|
||||
clip: true
|
||||
delegate: StatusSyncDeviceDelegate {
|
||||
width: ListView.view.width
|
||||
enabled: false
|
||||
deviceName: model.name
|
||||
deviceType: model.deviceType
|
||||
timestamp: model.timestamp
|
||||
isCurrentDevice: model.isCurrentDevice
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -168,8 +168,9 @@ StatusMenu {
|
||||
}
|
||||
|
||||
ProfileHeader {
|
||||
width: parent.width
|
||||
visible: root.isProfile
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
|
||||
displayNameVisible: false
|
||||
displayNamePlusIconsVisible: true
|
||||
|
@ -10,3 +10,4 @@ PasswordView 1.0 PasswordView.qml
|
||||
ProfileDialogView 1.0 ProfileDialogView.qml
|
||||
AssetsView 1.0 AssetsView.qml
|
||||
HistoryView 1.0 HistoryView.qml
|
||||
DeviceSyncingView 1.0 DeviceSyncingView.qml
|
||||
|
@ -90,6 +90,8 @@ QtObject {
|
||||
readonly property string profileFetchingTimeout: "ProfileFetchingTimeout"
|
||||
readonly property string profileFetchingAnnouncement: "ProfileFetchingAnnouncement"
|
||||
readonly property string lostKeycardOptions: "LostKeycardOptions"
|
||||
readonly property string syncDeviceWithSyncCode: "SyncDeviceWithSyncCode"
|
||||
readonly property string syncDeviceResult: "SyncDeviceResult"
|
||||
}
|
||||
|
||||
readonly property QtObject predefinedKeycardData: QtObject {
|
||||
@ -325,7 +327,7 @@ QtObject {
|
||||
property int appearance: 5
|
||||
property int language: 6
|
||||
property int notifications: 7
|
||||
property int devicesSettings: 8
|
||||
property int syncingSettings: 8
|
||||
property int browserSettings: 9
|
||||
property int advanced: 10
|
||||
property int about: 11
|
||||
@ -585,6 +587,32 @@ QtObject {
|
||||
readonly property int telegram: 6
|
||||
}
|
||||
|
||||
readonly property QtObject localPairingEventType: QtObject {
|
||||
readonly property int eventUnknown: -1
|
||||
readonly property int eventConnectionError: 0
|
||||
readonly property int eventConnectionSuccess: 1
|
||||
readonly property int eventTransferError: 2
|
||||
readonly property int eventTransferSuccess: 3
|
||||
readonly property int eventReceivedAccount: 4
|
||||
readonly property int eventProcessSuccess: 5
|
||||
readonly property int eventProcessError: 6
|
||||
}
|
||||
|
||||
readonly property QtObject localPairingAction: QtObject {
|
||||
readonly property int actionUnknown: 0
|
||||
readonly property int actionConnect: 1
|
||||
readonly property int actionPairingAccount: 2
|
||||
readonly property int actionSyncDevice: 3
|
||||
}
|
||||
|
||||
enum LocalPairingState {
|
||||
Idle = 0,
|
||||
WaitingForConnection = 1,
|
||||
Transferring = 2,
|
||||
Error = 3,
|
||||
Finished = 4
|
||||
}
|
||||
|
||||
readonly property QtObject regularExpressions: QtObject {
|
||||
readonly property var alphanumericalExpanded: /^$|^[a-zA-Z0-9\-_ ]+$/
|
||||
}
|
||||
|
2
vendor/nim-status-go
vendored
2
vendor/nim-status-go
vendored
@ -1 +1 @@
|
||||
Subproject commit 4d2d359aec6a9db5fb684c97ebd52b82065d60f3
|
||||
Subproject commit 73fe79616ce6c7a6e5e79f6425d20cd74b788ffd
|
2
vendor/status-go
vendored
2
vendor/status-go
vendored
@ -1 +1 @@
|
||||
Subproject commit 5ecb7b68eefada3725c360f68fba7ac92b612c82
|
||||
Subproject commit bac7eb08ca233a4ab9177ac16fa4fe0c2b68f79e
|
Loading…
x
Reference in New Issue
Block a user