diff --git a/src/app/modules/main/activity_center/controller.nim b/src/app/modules/main/activity_center/controller.nim index a96394cbd10..37b40eef66e 100644 --- a/src/app/modules/main/activity_center/controller.nim +++ b/src/app/modules/main/activity_center/controller.nim @@ -2,12 +2,13 @@ import ./io_interface import ../../../global/app_signals import ../../../core/eventemitter -import ../../../../app_service/service/activity_center/service as activity_center_service -import ../../../../app_service/service/contacts/service as contacts_service -import ../../../../app_service/service/message/service as message_service -import ../../../../app_service/service/community/service as community_service -import ../../../../app_service/service/chat/service as chat_service -import ../../../../app_service/service/devices/service as devices_service +import app_service/service/activity_center/service as activity_center_service +import app_service/service/contacts/service as contacts_service +import app_service/service/message/service as message_service +import app_service/service/community/service as community_service +import app_service/service/chat/service as chat_service +import app_service/service/devices/service as devices_service +import app_service/service/general/service as general_service type Controller* = ref object of RootObj @@ -19,6 +20,7 @@ type chatService: chat_service.Service communityService: community_service.Service devicesService: devices_service.Service + generalService: general_service.Service proc newController*( delegate: io_interface.AccessInterface, @@ -29,6 +31,7 @@ proc newController*( chatService: chat_service.Service, communityService: community_service.Service, devicesService: devices_service.Service, + generalService: general_service.Service, ): Controller = result = Controller() result.delegate = delegate @@ -39,6 +42,7 @@ proc newController*( result.chatService = chatService result.communityService = communityService result.devicesService = devicesService + result.generalService = generalService proc delete*(self: Controller) = discard @@ -169,3 +173,6 @@ proc getActivityCenterReadType*(self: Controller): ActivityCenterReadType = proc enableInstallationAndSync*(self: Controller, installationId: string) = self.devicesService.enableInstallationAndSync(installationId) + +proc tryFetchingAgain*(self: Controller) = + self.generalService.asyncFetchWakuBackupMessages() diff --git a/src/app/modules/main/activity_center/io_interface.nim b/src/app/modules/main/activity_center/io_interface.nim index 41bd7f60f4a..2a0ac3afd78 100644 --- a/src/app/modules/main/activity_center/io_interface.nim +++ b/src/app/modules/main/activity_center/io_interface.nim @@ -113,3 +113,6 @@ method setActivityGroupCounters*(self: AccessInterface, counters: Table[Activity method enableInstallationAndSync*(self: AccessInterface, installationId: string) {.base.} = raise newException(ValueError, "No implementation available") + +method tryFetchingAgain*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/activity_center/module.nim b/src/app/modules/main/activity_center/module.nim index 18214734979..41f881e09e1 100644 --- a/src/app/modules/main/activity_center/module.nim +++ b/src/app/modules/main/activity_center/module.nim @@ -8,12 +8,13 @@ import ../../shared_models/message_item_qobject as msg_item_qobj import ../../../global/global_singleton import ../../../global/app_sections_config as conf import ../../../core/eventemitter -import ../../../../app_service/service/activity_center/service as activity_center_service -import ../../../../app_service/service/contacts/service as contacts_service -import ../../../../app_service/service/message/service as message_service -import ../../../../app_service/service/chat/service as chat_service -import ../../../../app_service/service/community/service as community_service -import ../../../../app_service/service/devices/service as devices_service +import app_service/service/activity_center/service as activity_center_service +import app_service/service/contacts/service as contacts_service +import app_service/service/message/service as message_service +import app_service/service/chat/service as chat_service +import app_service/service/community/service as community_service +import app_service/service/devices/service as devices_service +import app_service/service/general/service as general_service export io_interface @@ -35,6 +36,7 @@ proc newModule*( chatService: chat_service.Service, communityService: community_service.Service, devicesService: devices_service.Service, + generalService: general_service.Service, ): Module = result = Module() result.delegate = delegate @@ -49,6 +51,7 @@ proc newModule*( chatService, communityService, devicesService, + generalService, ) result.moduleLoaded = false @@ -288,3 +291,6 @@ method setActivityGroupCounters*(self: Module, counters: Table[ActivityCenterGro method enableInstallationAndSync*(self: Module, installationId: string) = self.controller.enableInstallationAndSync(installationId) + +method tryFetchingAgain*(self: Module) = + self.controller.tryFetchingAgain() diff --git a/src/app/modules/main/activity_center/view.nim b/src/app/modules/main/activity_center/view.nim index 7828086e0e8..7af99b558ae 100644 --- a/src/app/modules/main/activity_center/view.nim +++ b/src/app/modules/main/activity_center/view.nim @@ -207,3 +207,6 @@ QtObject: proc enableInstallationAndSync*(self: View, installationId: string) {.slot.} = self.delegate.enableInstallationAndSync(installationId) + + proc tryFetchingAgain*(self: View) {.slot.} = + self.delegate.tryFetchingAgain() diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index f217f2231bd..39d84f142dd 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -231,7 +231,7 @@ proc newModule*[T]( networkService, tokenService) result.gifsModule = gifs_module.newModule(result, events, gifService) result.activityCenterModule = activity_center_module.newModule(result, events, activityCenterService, contactsService, - messageService, chatService, communityService, devicesService) + messageService, chatService, communityService, devicesService, generalService) result.communitiesModule = communities_module.newModule(result, events, communityService, contactsService, communityTokensService, networkService, transactionService, tokenService, chatService, walletAccountService, keycardService) result.appSearchModule = app_search_module.newModule(result, events, contactsService, chatService, communityService, diff --git a/src/app/modules/onboarding/io_interface.nim b/src/app/modules/onboarding/io_interface.nim index 68346f1e3d9..c853f8a5799 100644 --- a/src/app/modules/onboarding/io_interface.nim +++ b/src/app/modules/onboarding/io_interface.nim @@ -46,5 +46,6 @@ type DelegateInterface* = concept c c.onboardingDidLoad() c.appReady() + c.userLoggedIn() c.finishAppLoading() c.userLoggedIn() diff --git a/src/app/modules/onboarding/module.nim b/src/app/modules/onboarding/module.nim index 34fa87f28b6..b4fa551ea30 100644 --- a/src/app/modules/onboarding/module.nim +++ b/src/app/modules/onboarding/module.nim @@ -138,7 +138,7 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string) seedPhrase, recoverAccount = true, keycardInstanceUID = "", - ) + ) of SecondaryFlow.LoginWithSyncing: # The pairing was already done directly through inputConnectionStringForBootstrapping, we can login self.controller.loginLocalPairingAccount( @@ -169,14 +169,18 @@ proc finishAppLoading2[T](self: Module[T]) = self.delegate.finishAppLoading() -method onNodeLogin*[T](self: Module[T], error: string, account: AccountDto, settings: SettingsDto) = - if error.len != 0: - # TODO: Handle error - echo "ERROR from onNodeLogin: ", error +method onNodeLogin*[T](self: Module[T], err: string, account: AccountDto, settings: SettingsDto) = + if err.len != 0: + error "error from onNodeLogin", err return self.controller.setLoggedInAccount(account) + let err2 = self.delegate.userLoggedIn() + if err2.len != 0: + error "error from userLoggedIn", err2 + return + if self.localPairingStatus != nil and self.localPairingStatus.installation != nil and self.localPairingStatus.installation.id != "": # We tried to login by pairing, so finilize the process self.controller.finishPairingThroughSeedPhraseProcess(self.localPairingStatus.installation.id) diff --git a/src/app/modules/startup/controller.nim b/src/app/modules/startup/controller.nim index 47be149f07e..eeb1d90c627 100644 --- a/src/app/modules/startup/controller.nim +++ b/src/app/modules/startup/controller.nim @@ -219,8 +219,8 @@ proc generateImage*(self: Controller, imageUrl: string, aX: int, aY: int, bX: in ) return img.uri -proc fetchWakuMessages*(self: Controller) = - self.generalService.fetchWakuMessages() +proc asyncFetchWakuBackupMessages*(self: Controller) = + self.generalService.asyncFetchWakuBackupMessages() proc getCroppedProfileImage*(self: Controller): string = return self.tmpProfileImageDetails.croppedImage diff --git a/src/app/modules/startup/internal/profile_fetching_announcement_state.nim b/src/app/modules/startup/internal/profile_fetching_announcement_state.nim index 9d758549e7b..02d99819167 100644 --- a/src/app/modules/startup/internal/profile_fetching_announcement_state.nim +++ b/src/app/modules/startup/internal/profile_fetching_announcement_state.nim @@ -11,7 +11,7 @@ proc delete*(self: ProfileFetchingAnnouncementState) = method executePrimaryCommand*(self: ProfileFetchingAnnouncementState, controller: Controller) = if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase or self.flowType == FlowType.FirstRunOldUserKeycardImport: - controller.fetchWakuMessages() + controller.asyncFetchWakuBackupMessages() method getNextPrimaryState*(self: ProfileFetchingAnnouncementState, controller: Controller): State = if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase or diff --git a/src/app_service/service/activity_center/dto/notification.nim b/src/app_service/service/activity_center/dto/notification.nim index 70605e3deef..d99b78a49a7 100644 --- a/src/app_service/service/activity_center/dto/notification.nim +++ b/src/app_service/service/activity_center/dto/notification.nim @@ -34,6 +34,10 @@ type ActivityCenterNotificationType* {.pure.}= enum CommunityUnbanned = 22 NewInstallationReceived = 23 NewInstallationCreated = 24 + BackupSyncingFetching = 25 + BackupSyncingSuccess = 26 + BackupSyncingPartialFailure = 27 + BackupSyncingFailure = 28 type ActivityCenterGroup* {.pure.}= enum All = 0, @@ -178,6 +182,10 @@ proc activityCenterNotificationTypesByGroup*(group: ActivityCenterGroup) : seq[i ActivityCenterNotificationType.CommunityUnbanned.int, ActivityCenterNotificationType.NewInstallationReceived.int, ActivityCenterNotificationType.NewInstallationCreated.int, + ActivityCenterNotificationType.BackupSyncingFetching.int, + ActivityCenterNotificationType.BackupSyncingSuccess.int, + ActivityCenterNotificationType.BackupSyncingPartialFailure.int, + ActivityCenterNotificationType.BackupSyncingFailure.int, ] of ActivityCenterGroup.Mentions: return @[ActivityCenterNotificationType.Mention.int] diff --git a/src/app_service/service/general/async_tasks.nim b/src/app_service/service/general/async_tasks.nim new file mode 100644 index 00000000000..75bffbad704 --- /dev/null +++ b/src/app_service/service/general/async_tasks.nim @@ -0,0 +1,16 @@ + +type + AsyncFetchBackupWakuMessagesTaskArg = ref object of QObjectTaskArg + +proc asyncFetchWakuBackupMessagesTask(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncFetchBackupWakuMessagesTaskArg](argEncoded) + try: + let response = status_mailservers.requestAllHistoricMessagesWithRetries(forceFetchingBackup = true) + arg.finish(%* { + "response": response, + "error": "", + }) + except Exception as e: + arg.finish(%* { + "error": e.msg, + }) diff --git a/src/app_service/service/general/service.nim b/src/app_service/service/general/service.nim index 03a04b6ed4a..70c6bb06139 100644 --- a/src/app_service/service/general/service.nim +++ b/src/app_service/service/general/service.nim @@ -6,8 +6,13 @@ import ../../../app/core/eventemitter import ../../../app/core/tasks/[qt, threadpool] import ../../../constants as app_constants +from app_service/service/activity_center/service import SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_LOADED, ActivityCenterNotificationsArgs +from app_service/service/activity_center/dto/notification import parseActivityCenterNotifications + import ../accounts/dto/accounts +include async_tasks + const TimerIntervalInMilliseconds = 1000 # 1 second const SIGNAL_GENERAL_TIMEOUT* = "timeoutSignal" @@ -37,7 +42,12 @@ QtObject: createDir(app_constants.ROOTKEYSTOREDIR) proc startMessenger*(self: Service) = - discard status_general.startMessenger() + let response = status_general.startMessenger() + if response.result.contains("activityCenterNotifications"): + let notifications = JsonNode(%{"notifications": response.result["activityCenterNotifications"]}) + let activityCenterNotificationsTuple = parseActivityCenterNotifications(notifications) + self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_LOADED, + ActivityCenterNotificationsArgs(activityCenterNotifications: activityCenterNotificationsTuple[1])) proc logout*(self: Service) = discard status_general.logout() @@ -87,13 +97,28 @@ QtObject: else: self.runTimer() - proc fetchWakuMessages*(self: Service) = + proc asyncFetchWakuBackupMessages*(self: Service) = + let arg = AsyncFetchBackupWakuMessagesTaskArg( + tptr: asyncFetchWakuBackupMessagesTask, + vptr: cast[uint](self.vptr), + slot: "onFetchWakuBackupMessagesDone", + ) + self.threadpool.start(arg) + + proc onFetchWakuBackupMessagesDone(self: Service, response: string) {.slot.} = try: - let response = status_mailservers.requestAllHistoricMessagesWithRetries(forceFetchingBackup = true) - if(not response.error.isNil): - error "could not set display name" + let rpcResponseObj = response.parseJson + + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + raise newException(CatchableError, rpcResponseObj{"error"}.getStr) + + if rpcResponseObj["response"]["result"].contains("activityCenterNotifications"): + let notifications = JsonNode(%{"notifications": rpcResponseObj["response"]["result"]["activityCenterNotifications"]}) + let activityCenterNotificationsTuple = parseActivityCenterNotifications(notifications) + self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_LOADED, + ActivityCenterNotificationsArgs(activityCenterNotifications: activityCenterNotificationsTuple[1])) except Exception as e: - error "error: ", procName="fetchWakuMessages", errName = e.name, errDesription = e.msg + error "error:", procName="asyncFetchWakuBackupMessages", errName = e.name, errDesription = e.msg proc backupData*(self: Service): int64 = try: diff --git a/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml b/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml index bdc2c02a2ac..e9e7695a254 100644 --- a/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml +++ b/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml @@ -152,6 +152,11 @@ Popup { case ActivityCenterStore.ActivityCenterNotificationType.NewInstallationReceived: case ActivityCenterStore.ActivityCenterNotificationType.NewInstallationCreated: return newDeviceDetectedComponent + case ActivityCenterStore.ActivityCenterNotificationType.BackupSyncingFetching: + case ActivityCenterStore.ActivityCenterNotificationType.BackupSyncingSuccess: + case ActivityCenterStore.ActivityCenterNotificationType.BackupSyncingPartialFailure: + case ActivityCenterStore.ActivityCenterNotificationType.BackupSyncingFailure: + return backupSyncingComponent default: return null } @@ -338,6 +343,23 @@ Popup { } } + Component { + id: backupSyncingComponent + + ActivityNotificationProfileFetching { + id: activityNotificationProfileFetching + type: setType(notification) + filteredIndex: parent.filteredIndex + notification: parent.notification + onTryAgainClicked: { + // Force the type back to in progress since the fetching is async and the state will not update imediately + activityNotificationProfileFetching.type = ActivityNotificationProfileFetching.FetchingState.Fetching + + root.activityCenterStore.tryFetchingAgain() + } + } + } + Component { id: communityTokenReceivedComponent diff --git a/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml b/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml index e1ba5f8731a..662b0c20aac 100644 --- a/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml +++ b/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml @@ -42,7 +42,11 @@ QtObject { CommunityBanned = 21, CommunityUnbanned = 22, NewInstallationReceived = 23, - NewInstallationCreated = 24 + NewInstallationCreated = 24, + BackupSyncingFetching = 25, + BackupSyncingSuccess = 26, + BackupSyncingPartialFailure = 27, + BackupSyncingFailure = 28 } enum ActivityCenterReadType { @@ -123,4 +127,8 @@ QtObject { function enableInstallationAndSync(installationId) { root.activityCenterModuleInst.enableInstallationAndSync(installationId) } + + function tryFetchingAgain() { + root.activityCenterModuleInst.tryFetchingAgain() + } } diff --git a/ui/app/mainui/activitycenter/views/ActivityNotificationProfileFetching.qml b/ui/app/mainui/activitycenter/views/ActivityNotificationProfileFetching.qml new file mode 100644 index 00000000000..f42d61050a4 --- /dev/null +++ b/ui/app/mainui/activitycenter/views/ActivityNotificationProfileFetching.qml @@ -0,0 +1,157 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 + +import shared 1.0 +import shared.panels 1.0 +import utils 1.0 +import mainui.activitycenter.stores 1.0 + + +ActivityNotificationBase { + id: root + + required property int type // Possible values [FetchingState] + + signal tryAgainClicked + + function setType(notification) { + if (notification) { + switch (notification.notificationType) { + case ActivityCenterStore.ActivityCenterNotificationType.BackupSyncingFetching: + return ActivityNotificationProfileFetching.FetchingState.Fetching + case ActivityCenterStore.ActivityCenterNotificationType.BackupSyncingSuccess: + return ActivityNotificationProfileFetching.FetchingState.Success + case ActivityCenterStore.ActivityCenterNotificationType.BackupSyncingPartialFailure: + return ActivityNotificationProfileFetching.FetchingState.PartialFailure + case ActivityCenterStore.ActivityCenterNotificationType.BackupSyncingFailure: + return ActivityNotificationProfileFetching.FetchingState.Failure + } + } + return ActivityNotificationProfileFetching.FetchingState.Unknown + } + + enum FetchingState { + Unknown, + Fetching, + Success, + PartialFailure, + Failure + } + + QtObject { + id: d + + property string title: qsTr("Fetching profile details") + property string info: "" + property string badgeName: "" + property string ctaText: "" + property string badgeColor: "" + } + + bodyComponent: RowLayout { + spacing: 8 + + StatusSmartIdenticon { + Layout.preferredWidth: 40 + Layout.preferredHeight: 40 + Layout.alignment: Qt.AlignTop + Layout.leftMargin: Theme.padding + Layout.topMargin: 2 + + asset { + width: 24 + height: width + name: "download" + color: Theme.palette.primaryColor1 + bgWidth: 40 + bgHeight: 40 + bgColor: Theme.palette.primaryColor3 + } + + bridgeBadge.visible: true + bridgeBadge.border.width: 2 + bridgeBadge.color: d.badgeColor + bridgeBadge.image.source: Theme.svg(d.badgeName) + } + + ColumnLayout { + spacing: 2 + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + StatusMessageHeader { + Layout.fillWidth: true + displayNameLabel.text: d.title + timestamp: root.notification.timestamp + } + + RowLayout { + spacing: Theme.padding + + StatusBaseText { + Layout.fillWidth: true + text: d.info + font.italic: true + wrapMode: Text.WordWrap + color: Theme.palette.baseColor1 + } + } + } + } + + ctaComponent: StatusFlatButton { + size: StatusBaseButton.Size.Small + text: d.ctaText + onClicked: { + root.tryAgainClicked() + } + } + + states: [ + State { + when: root.type === ActivityNotificationProfileFetching.FetchingState.Fetching + PropertyChanges { + target: d + info: qsTr("Fetching all data may take some time") + badgeName: "dotsLoadings" + ctaText: "" + badgeColor: Theme.palette.baseColor3 + } + }, + State { + when: root.type === ActivityNotificationProfileFetching.FetchingState.Success + PropertyChanges { + target: d + info: qsTr("Profile details fetched successfully") + badgeName: "check"// TODO fix icon it looks bad + ctaText: "" + badgeColor: Theme.palette.successColor1 + } + }, + State { + when: root.type === ActivityNotificationProfileFetching.FetchingState.PartialFailure + PropertyChanges { + target: d + info: qsTr("Some profile details could not be fetched") + badgeName: "exclamation_outline" // TODO fix icon it looks bad + ctaText: qsTr("Try again") + badgeColor: Theme.palette.dangerColor1 + } + }, + State { + when: root.type === ActivityNotificationProfileFetching.FetchingState.Failure + PropertyChanges { + target: d + info: qsTr("Profile details could not be fetched") + badgeName: "exclamation_outline" // TODO fix icon it looks bad + ctaText: qsTr("Try again") + badgeColor: Theme.palette.dangerColor1 + } + } + ] +} \ No newline at end of file diff --git a/ui/main.qml b/ui/main.qml index 695c754e65a..6e34c6223fb 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -452,16 +452,13 @@ StatusWindow { onboardingStore: onboardingStoreLoader.item onFinished: (flow, data) => { - console.warn("!!! ONBOARDING FINISHED; flow:", flow, "; data:", JSON.stringify(data)) - let error = onboardingStoreLoader.item.finishOnboardingFlow(flow, data) if (error != "") { - console.error("!!! ONBOARDING FINISHED WITH ERROR:", error) - // TODO show error + // We should never end up here since we do all validations in the onboarding flow + console.error("onbaording finished with error", error) return } - console.warn("!!! Onboarding completed!") stack.clear() stack.push(splashScreenV2, { runningProgressAnimation: true }) } diff --git a/vendor/status-go b/vendor/status-go index e446e61ab5c..4ed5c9a4de1 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit e446e61ab5c2fa7fbc7fb19fa42c5813b0eea414 +Subproject commit 4ed5c9a4de1a2ccc326b9407230196559fe31091