diff --git a/src/clients/Element.ts b/src/clients/Element.ts index 0693ede..37e3db0 100644 --- a/src/clients/Element.ts +++ b/src/clients/Element.ts @@ -20,6 +20,9 @@ import { ClientKind, ClientId, Platform, + AppleStoreLink, + PlayStoreLink, + FDroidLink, } from './types'; import { LinkKind } from '../parser/types'; import logo from '../imgs/element.svg'; @@ -31,7 +34,7 @@ export const Element: LinkedClient = { logo: logo, homepage: 'https://element.io', maturity: Maturity.STABLE, - description: 'Fully-featured Matrix client for the Web', + description: 'Fully-featured Matrix client', platforms: [Platform.Desktop, Platform.Android, Platform.iOS], experimental: false, clientId: ClientId.Element, @@ -59,6 +62,11 @@ export const Element: LinkedClient = { } }, linkSupport: () => true, + installLinks: [ + new AppleStoreLink('vector', 'id1083446067'), + new PlayStoreLink('im.vector.app'), + new FDroidLink('im.vector.app'), + ], }; export const ElementDevelop: LinkedClient = { @@ -94,4 +102,5 @@ export const ElementDevelop: LinkedClient = { } }, linkSupport: () => true, + installLinks: [], }; diff --git a/src/clients/Fractal.tsx b/src/clients/Fractal.tsx index bd0f068..f9ea9a2 100644 --- a/src/clients/Fractal.tsx +++ b/src/clients/Fractal.tsx @@ -69,6 +69,7 @@ const Fractal: TextClient = { }, description: 'Fractal is a Matrix Client written in Rust', + installLinks: [], }; export default Fractal; diff --git a/src/clients/Nheko.tsx b/src/clients/Nheko.tsx index 1195716..f9201d3 100644 --- a/src/clients/Nheko.tsx +++ b/src/clients/Nheko.tsx @@ -86,6 +86,7 @@ const Nheko: TextClient = { }, description: 'A native desktop app for Matrix that feels more like a mainstream chat app.', + installLinks: [], }; export default Nheko; diff --git a/src/clients/Weechat.tsx b/src/clients/Weechat.tsx index 105a979..805a4b2 100644 --- a/src/clients/Weechat.tsx +++ b/src/clients/Weechat.tsx @@ -86,6 +86,7 @@ const Weechat: TextClient = { }, description: 'Command-line Matrix interface using Weechat', + installLinks: [], }; export default Weechat; diff --git a/src/clients/types.ts b/src/clients/types.ts index 3278f96..9c4c266 100644 --- a/src/clients/types.ts +++ b/src/clients/types.ts @@ -53,6 +53,79 @@ export enum ClientId { Fractal = 'fractal', } +/** + * Define a native distribution channel for a client. + * E.g App store for apple, PlayStore or F-Droid for Android + */ +export interface InstallLink { + createInstallURL(deepLink: SafeLink) : string; + // in AppleStoreLink, we can set the cookie here for deeplinking + // onInstallChosen(deepLink: SafeLink); + platform: Platform; + channelId: string; + description: string; +} + +export class AppleStoreLink implements InstallLink { + constructor(private org: string, private appId: string) {} + + createInstallURL(deepLink: SafeLink) : string { + return `https://apps.apple.com/app/${encodeURIComponent(this.org)}/${encodeURIComponent(this.appId)}`; + } + + get platform() : Platform { + return Platform.iOS; + } + + get channelId(): string { + return "apple-app-store"; + } + + get description() { + return "Download on the App Store"; + } +} + +export class PlayStoreLink implements InstallLink { + constructor(private appId: string) {} + + createInstallURL(deepLink: SafeLink) : string { + return `https://play.google.com/store/apps/details?id=${encodeURIComponent(this.appId)}&referrer=${encodeURIComponent(deepLink.originalLink)}`; + } + + get platform() : Platform { + return Platform.Android; + } + + get channelId(): string { + return "play-store"; + } + + get description() { + return "Get it on Google Play"; + } +} + +export class FDroidLink implements InstallLink { + constructor(private appId: string) {} + + createInstallURL(deepLink: SafeLink) : string { + return `https://f-droid.org/packages/${encodeURIComponent(this.appId)}`; + } + + get platform() : Platform { + return Platform.Android; + } + + get channelId(): string { + return "fdroid"; + } + + get description() { + return "Get it on F-Droid"; + } +} + /* * The descriptive details of a client */ @@ -67,6 +140,7 @@ export interface ClientDescription { clientId: ClientId; experimental: boolean; linkSupport: (link: SafeLink) => boolean; + installLinks: InstallLink[]; } /* diff --git a/src/components/ClientTile.scss b/src/components/ClientTile.scss index 996c56f..a697aea 100644 --- a/src/components/ClientTile.scss +++ b/src/components/ClientTile.scss @@ -51,7 +51,8 @@ limitations under the License. .button { height: 40px; - width: 130px; + min-width: 130px; + max-width: 165px; margin-top: 16px; } } @@ -66,6 +67,15 @@ limitations under the License. &:hover { background-color: $app-background; } + + .installLink { + display: inline-block; + height: 40px; + margin: 8px 16px 8px 0; + img { + height: 100%; + } + } } .clientTileLink { diff --git a/src/components/ClientTile.tsx b/src/components/ClientTile.tsx index e8eddde..9464e61 100644 --- a/src/components/ClientTile.tsx +++ b/src/components/ClientTile.tsx @@ -14,14 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { useContext } from 'react'; import classNames from 'classnames'; +import { UAContext } from '@quentin-sommer/react-useragent'; -import { Client, ClientKind } from '../clients/types'; +import { Client, ClientKind, Platform } from '../clients/types'; import { SafeLink } from '../parser/types'; import Tile from './Tile'; import Button from './Button'; +import appStoreBadge from '../imgs/app-store-us-alt.svg'; +import playStoreBadge from '../imgs/google-play-us.svg'; +import fdroidBadge from '../imgs/fdroid-badge.png'; + import './ClientTile.scss'; interface IProps { @@ -29,17 +34,54 @@ interface IProps { link: SafeLink; } +interface IInstallBadgeImages { + [index: string]: string; +} + +const installBadgeImages : IInstallBadgeImages = { + "fdroid": fdroidBadge, + "apple-app-store": appStoreBadge, + "play-store": playStoreBadge +}; + const ClientTile: React.FC = ({ client, link }: IProps) => { const inviteLine = client.kind === ClientKind.TEXT_CLIENT ? (

{client.toInviteString(link)}

) : null; + const { uaResults } = useContext(UAContext); + const className = classNames('clientTile', { clientTileLink: client.kind === ClientKind.LINKED_CLIENT, }); let inviteButton: JSX.Element = <>; + const matchingInstallLinks = client.installLinks.filter((installLink) => { + if ((uaResults as any).ios) { + return installLink.platform === Platform.iOS; + } else if ((uaResults as any).android) { + return installLink.platform === Platform.Android; + } else { + return false; + } + }); + const hasNativeClient = matchingInstallLinks.length > 0; + let installButtons = undefined; + if (matchingInstallLinks.length) { + installButtons =

{matchingInstallLinks.map((installLink) => { + return + {installLink.description} + ; + })}

; + } + if (client.kind === ClientKind.LINKED_CLIENT) { inviteButton = ; } else { @@ -62,6 +104,7 @@ const ClientTile: React.FC = ({ client, link }: IProps) => {

{client.name}

{client.description}

+ {installButtons} {inviteLine} {inviteButton}
@@ -69,7 +112,11 @@ const ClientTile: React.FC = ({ client, link }: IProps) => { ); if (client.kind === ClientKind.LINKED_CLIENT) { - clientTile = {clientTile}; + if (!hasNativeClient) { + clientTile = ( + {clientTile} + ); + } } return clientTile; diff --git a/src/imgs/app-store-us-alt.svg b/src/imgs/app-store-us-alt.svg new file mode 100644 index 0000000..83437ba --- /dev/null +++ b/src/imgs/app-store-us-alt.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/imgs/fdroid-badge.png b/src/imgs/fdroid-badge.png new file mode 100644 index 0000000..7c8c3c5 Binary files /dev/null and b/src/imgs/fdroid-badge.png differ diff --git a/src/imgs/google-play-us.svg b/src/imgs/google-play-us.svg new file mode 100644 index 0000000..888691a --- /dev/null +++ b/src/imgs/google-play-us.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file