From 894d53c7220049bd0a26bf226e5bfc0d0701c872 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Thu, 28 Jul 2022 13:25:57 +0200 Subject: [PATCH 1/5] Implement new download apps dialog --- res/css/_components.pcss | 1 + res/css/views/dialogs/_AppDownloadDialog.pcss | 70 ++++++++++++ res/img/badges/f-droid.svg | 1 + res/img/badges/google-play.svg | 1 + res/img/badges/ios.svg | 1 + .../views/dialogs/AppDownloadDialog.tsx | 100 ++++++++++++++++++ .../views/elements/AccessibleButton.tsx | 2 +- src/hooks/useUserOnboardingTasks.ts | 4 +- src/i18n/strings/en_EN.json | 9 ++ 9 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 res/css/views/dialogs/_AppDownloadDialog.pcss create mode 100644 res/img/badges/f-droid.svg create mode 100644 res/img/badges/google-play.svg create mode 100644 res/img/badges/ios.svg create mode 100644 src/components/views/dialogs/AppDownloadDialog.tsx diff --git a/res/css/_components.pcss b/res/css/_components.pcss index c4a5d6a434d..111a59bbf7d 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -94,6 +94,7 @@ @import "./views/context_menus/_RoomNotificationContextMenu.pcss"; @import "./views/dialogs/_AddExistingToSpaceDialog.pcss"; @import "./views/dialogs/_AnalyticsLearnMoreDialog.pcss"; +@import "./views/dialogs/_AppDownloadDialog.pcss"; @import "./views/dialogs/_BugReportDialog.pcss"; @import "./views/dialogs/_BulkRedactDialog.pcss"; @import "./views/dialogs/_ChangelogDialog.pcss"; diff --git a/res/css/views/dialogs/_AppDownloadDialog.pcss b/res/css/views/dialogs/_AppDownloadDialog.pcss new file mode 100644 index 00000000000..7e154976176 --- /dev/null +++ b/res/css/views/dialogs/_AppDownloadDialog.pcss @@ -0,0 +1,70 @@ +.mx_AppDownloadDialog { + display: flex; + flex-direction: column; + gap: $spacing-32; + color: $primary-content; + + .mx_AppDownloadDialog_desktop { + display: flex; + flex-direction: column; + align-items: center; + gap: $spacing-16; + } + + .mx_AppDownloadDialog_mobile { + display: flex; + flex-direction: row; + gap: $spacing-24; + + .mx_AppDownloadDialog_app { + display: flex; + flex-direction: column; + flex-grow: 1; + flex-basis: 50%; + align-items: center; + gap: $spacing-16; + + .mx_QRCode { + padding: $spacing-24; + border: 1px solid $quinary-content; + border-radius: 4px; + align-self: stretch; + display: flex; + align-items: center; + flex-direction: column; + + .mx_VerificationQRCode { + height: 144px; + width: 144px; + image-rendering: pixelated; + border-radius: 0; + } + } + + .mx_AppDownloadDialog_info { + font-size: $font-12px; + color: $tertiary-content; + } + + .mx_AppDownloadDialog_links { + display: flex; + flex-direction: row; + gap: $spacing-8; + + .mx_AccessibleButton { + svg { + height: 40px; + } + } + } + } + } + + .mx_AppDownloadDialog_legal { + p { + margin: 0; + font-size: $font-12px; + color: $tertiary-content; + } + } +} diff --git a/res/img/badges/f-droid.svg b/res/img/badges/f-droid.svg new file mode 100644 index 00000000000..d97143c42be --- /dev/null +++ b/res/img/badges/f-droid.svg @@ -0,0 +1 @@ + diff --git a/res/img/badges/google-play.svg b/res/img/badges/google-play.svg new file mode 100644 index 00000000000..973d9d3afc5 --- /dev/null +++ b/res/img/badges/google-play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/badges/ios.svg b/res/img/badges/ios.svg new file mode 100644 index 00000000000..e723d1cc046 --- /dev/null +++ b/res/img/badges/ios.svg @@ -0,0 +1 @@ + diff --git a/src/components/views/dialogs/AppDownloadDialog.tsx b/src/components/views/dialogs/AppDownloadDialog.tsx new file mode 100644 index 00000000000..99d9333463b --- /dev/null +++ b/src/components/views/dialogs/AppDownloadDialog.tsx @@ -0,0 +1,100 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { FC } from "react"; + +import { _t } from "../../../languageHandler"; +import SdkConfig from "../../../SdkConfig"; +import AccessibleButton from "../elements/AccessibleButton"; +import QRCode from "../elements/QRCode"; +import Heading from "../typography/Heading"; +import BaseDialog from "./BaseDialog"; +import { IDialogProps } from "./IDialogProps"; +import { Icon as IOSBadge } from "../../../../res/img/badges/ios.svg"; +import { Icon as GooglePlayBadge } from "../../../../res/img/badges/google-play.svg"; +import { Icon as FDroidBadge } from "../../../../res/img/badges/f-droid.svg"; + +const urlAppStore = "https://apps.apple.com/app/vector/id1083446067"; +const urlGooglePlay = "https://play.google.com/store/apps/details?id=im.vector.app"; +const urlFDroid = "https://f-droid.org/repository/browse/?fdid=im.vector.app"; + +export const AppDownloadDialog: FC = ({ onFinished }: IDialogProps) => { + const brand = SdkConfig.get("brand"); + const desktopBuilds = SdkConfig.getObject("desktop_builds"); + return ( + + { desktopBuilds?.get("available") && ( +
+ + { _t("Download %(brand)s Desktop", { brand }) } + + + { _t("Download %(brand)s Desktop", { brand }) } + +
+ ) } +
+
+ + { _t("iOS") } + + +
or
+
+ + + +
+
+
+ + { _t("Android") } + + +
or
+
+ + + + + + +
+
+
+
+

{ _t("App Store® and the Apple logo® are trademarks of Apple Inc.") }

+

{ _t("Google Play and the Google Play logo are trademarks of Google LLC.") }

+
+
+ ); +}; diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index f54a8d4bff5..812d79a816f 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -69,7 +69,7 @@ type IProps = DynamicHtmlElementProps disabled?: boolean; className?: string; triggerOnMouseDown?: boolean; - onClick(e?: ButtonEvent): void | Promise; + onClick?(e?: ButtonEvent): void | Promise; }; interface IAccessibleButtonProps extends React.InputHTMLAttributes { diff --git a/src/hooks/useUserOnboardingTasks.ts b/src/hooks/useUserOnboardingTasks.ts index 41bd043463d..daef154de02 100644 --- a/src/hooks/useUserOnboardingTasks.ts +++ b/src/hooks/useUserOnboardingTasks.ts @@ -16,11 +16,13 @@ limitations under the License. import { useMemo } from "react"; +import { AppDownloadDialog } from "../components/views/dialogs/AppDownloadDialog"; import { UserTab } from "../components/views/dialogs/UserTab"; import { ButtonEvent } from "../components/views/elements/AccessibleButton"; import { Action } from "../dispatcher/actions"; import defaultDispatcher from "../dispatcher/dispatcher"; import { _t } from "../languageHandler"; +import Modal from "../Modal"; import { Notifier } from "../Notifier"; import PosthogTrackers from "../PosthogTrackers"; import { UseCase } from "../settings/enums/UseCase"; @@ -100,9 +102,9 @@ const tasks: InternalUserOnboardingTask[] = [ }, action: { label: _t("Download apps"), - href: "https://element.io/get-started#download", onClick: (ev: ButtonEvent) => { PosthogTrackers.trackInteraction("WebUserOnboardingTaskDownloadApps", ev); + Modal.createDialog(AppDownloadDialog, {}, "mx_AppDownloadDialog_wrapper", false, true); }, }, }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 00017859015..3ccbdd905b5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2450,6 +2450,15 @@ "We don't record or profile any account data": "We don't record or profile any account data", "We don't share information with third parties": "We don't share information with third parties", "You can turn this off anytime in settings": "You can turn this off anytime in settings", + "Download %(brand)s": "Download %(brand)s", + "Download %(brand)s Desktop": "Download %(brand)s Desktop", + "iOS": "iOS", + "Download on the App Store": "Download on the App Store", + "Android": "Android", + "Get it on Google Play": "Get it on Google Play", + "Get it on F-Droid": "Get it on F-Droid", + "App Store® and the Apple logo® are trademarks of Apple Inc.": "App Store® and the Apple logo® are trademarks of Apple Inc.", + "Google Play and the Google Play logo are trademarks of Google LLC.": "Google Play and the Google Play logo are trademarks of Google LLC.", "The following users may not exist": "The following users may not exist", "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?", "Invite anyway and never warn me again": "Invite anyway and never warn me again", From 71d703244fa8ecf04b5945ae13188a05cc0bf807 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Tue, 2 Aug 2022 11:11:10 +0200 Subject: [PATCH 2/5] Use mobile build URLs from sdk config --- .../views/dialogs/AppDownloadDialog.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/views/dialogs/AppDownloadDialog.tsx b/src/components/views/dialogs/AppDownloadDialog.tsx index 99d9333463b..0ecfdbeed5b 100644 --- a/src/components/views/dialogs/AppDownloadDialog.tsx +++ b/src/components/views/dialogs/AppDownloadDialog.tsx @@ -27,13 +27,21 @@ import { Icon as IOSBadge } from "../../../../res/img/badges/ios.svg"; import { Icon as GooglePlayBadge } from "../../../../res/img/badges/google-play.svg"; import { Icon as FDroidBadge } from "../../../../res/img/badges/f-droid.svg"; -const urlAppStore = "https://apps.apple.com/app/vector/id1083446067"; -const urlGooglePlay = "https://play.google.com/store/apps/details?id=im.vector.app"; -const urlFDroid = "https://f-droid.org/repository/browse/?fdid=im.vector.app"; +const fallbackAppStore = "https://apps.apple.com/app/vector/id1083446067"; +const fallbackGooglePlay = "https://play.google.com/store/apps/details?id=im.vector.app"; +const fallbackFDroid = "https://f-droid.org/repository/browse/?fdid=im.vector.app"; export const AppDownloadDialog: FC = ({ onFinished }: IDialogProps) => { const brand = SdkConfig.get("brand"); const desktopBuilds = SdkConfig.getObject("desktop_builds"); + const mobileBuilds = SdkConfig.getObject("mobile_builds"); + + const urlAppStore = mobileBuilds.get("ios") ?? fallbackAppStore; + + const urlAndroid = mobileBuilds.get("android") ?? mobileBuilds.get("fdroid") ?? fallbackGooglePlay; + const urlGooglePlay = mobileBuilds.get("android") ?? fallbackGooglePlay; + const urlFDroid = mobileBuilds.get("fdroid") ?? fallbackFDroid; + return ( { desktopBuilds?.get("available") && ( @@ -71,7 +79,7 @@ export const AppDownloadDialog: FC = ({ onFinished }: IDialogProps { _t("Android") } - +
or
Date: Tue, 2 Aug 2022 11:32:36 +0200 Subject: [PATCH 3/5] Fix potential nullity issue --- src/components/views/dialogs/AppDownloadDialog.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/dialogs/AppDownloadDialog.tsx b/src/components/views/dialogs/AppDownloadDialog.tsx index 0ecfdbeed5b..819e791c74b 100644 --- a/src/components/views/dialogs/AppDownloadDialog.tsx +++ b/src/components/views/dialogs/AppDownloadDialog.tsx @@ -36,11 +36,11 @@ export const AppDownloadDialog: FC = ({ onFinished }: IDialogProps const desktopBuilds = SdkConfig.getObject("desktop_builds"); const mobileBuilds = SdkConfig.getObject("mobile_builds"); - const urlAppStore = mobileBuilds.get("ios") ?? fallbackAppStore; + const urlAppStore = mobileBuilds?.get("ios") ?? fallbackAppStore; - const urlAndroid = mobileBuilds.get("android") ?? mobileBuilds.get("fdroid") ?? fallbackGooglePlay; - const urlGooglePlay = mobileBuilds.get("android") ?? fallbackGooglePlay; - const urlFDroid = mobileBuilds.get("fdroid") ?? fallbackFDroid; + const urlAndroid = mobileBuilds?.get("android") ?? mobileBuilds?.get("fdroid") ?? fallbackGooglePlay; + const urlGooglePlay = mobileBuilds?.get("android") ?? fallbackGooglePlay; + const urlFDroid = mobileBuilds?.get("fdroid") ?? fallbackFDroid; return ( From 3d1620133cf37ce89db82d4c3f0ba52a7fb60ccf Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Tue, 2 Aug 2022 11:38:56 +0200 Subject: [PATCH 4/5] Added screenshot test for app download dialog --- cypress/e2e/user-onboarding/user-onboarding-new.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cypress/e2e/user-onboarding/user-onboarding-new.ts b/cypress/e2e/user-onboarding/user-onboarding-new.ts index ca83fe8ed28..9c00370558f 100644 --- a/cypress/e2e/user-onboarding/user-onboarding-new.ts +++ b/cypress/e2e/user-onboarding/user-onboarding-new.ts @@ -52,6 +52,15 @@ describe("User Onboarding (new user)", () => { cy.percySnapshot("User onboarding page"); }); + it("app download dialog", () => { + cy.get('.mx_UserOnboardingPage').should('exist'); + cy.contains(".mx_UserOnboardingTask_action", "Download apps").click(); + cy.get('[role=dialog]') + .contains("#mx_BaseDialog_title", "Download Element") + .should("exist"); + cy.percySnapshot("App download dialog"); + }); + it("using find friends action should increase progress", () => { cy.get(".mx_ProgressBar").invoke("val").then((oldProgress) => { const findPeopleAction = cy.contains(".mx_UserOnboardingTask_action", "Find friends"); From 9422fbaa2db37718efba139857ab1a5cdce0f66c Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Tue, 2 Aug 2022 11:44:51 +0200 Subject: [PATCH 5/5] Undo event handler nullity changes to AccessibleButton --- .../views/dialogs/AppDownloadDialog.tsx | 18 +++++++++++------- .../views/elements/AccessibleButton.tsx | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/views/dialogs/AppDownloadDialog.tsx b/src/components/views/dialogs/AppDownloadDialog.tsx index 819e791c74b..ab5ebfd22cb 100644 --- a/src/components/views/dialogs/AppDownloadDialog.tsx +++ b/src/components/views/dialogs/AppDownloadDialog.tsx @@ -16,6 +16,9 @@ limitations under the License. import React, { FC } from "react"; +import { Icon as FDroidBadge } from "../../../../res/img/badges/f-droid.svg"; +import { Icon as GooglePlayBadge } from "../../../../res/img/badges/google-play.svg"; +import { Icon as IOSBadge } from "../../../../res/img/badges/ios.svg"; import { _t } from "../../../languageHandler"; import SdkConfig from "../../../SdkConfig"; import AccessibleButton from "../elements/AccessibleButton"; @@ -23,9 +26,6 @@ import QRCode from "../elements/QRCode"; import Heading from "../typography/Heading"; import BaseDialog from "./BaseDialog"; import { IDialogProps } from "./IDialogProps"; -import { Icon as IOSBadge } from "../../../../res/img/badges/ios.svg"; -import { Icon as GooglePlayBadge } from "../../../../res/img/badges/google-play.svg"; -import { Icon as FDroidBadge } from "../../../../res/img/badges/f-droid.svg"; const fallbackAppStore = "https://apps.apple.com/app/vector/id1083446067"; const fallbackGooglePlay = "https://play.google.com/store/apps/details?id=im.vector.app"; @@ -53,7 +53,8 @@ export const AppDownloadDialog: FC = ({ onFinished }: IDialogProps kind="primary" element="a" href={desktopBuilds?.get("url")} - target="_blank"> + target="_blank" + onClick={() => {}}> { _t("Download %(brand)s Desktop", { brand }) }
@@ -70,7 +71,8 @@ export const AppDownloadDialog: FC = ({ onFinished }: IDialogProps element="a" href={urlAppStore} target="_blank" - aria-label={_t("Download on the App Store")}> + aria-label={_t("Download on the App Store")} + onClick={() => {}}> @@ -86,14 +88,16 @@ export const AppDownloadDialog: FC = ({ onFinished }: IDialogProps element="a" href={urlGooglePlay} target="_blank" - aria-label={_t("Get it on Google Play")}> + aria-label={_t("Get it on Google Play")} + onClick={() => {}}> + aria-label={_t("Get it on F-Droid")} + onClick={() => {}}> diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 812d79a816f..f54a8d4bff5 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -69,7 +69,7 @@ type IProps = DynamicHtmlElementProps disabled?: boolean; className?: string; triggerOnMouseDown?: boolean; - onClick?(e?: ButtonEvent): void | Promise; + onClick(e?: ButtonEvent): void | Promise; }; interface IAccessibleButtonProps extends React.InputHTMLAttributes {