Merge pull request #166 from matrix-org/valr/mobile_support
Add mobile store links
This commit is contained in:
commit
92ba3546a2
@ -20,6 +20,9 @@ import {
|
|||||||
ClientKind,
|
ClientKind,
|
||||||
ClientId,
|
ClientId,
|
||||||
Platform,
|
Platform,
|
||||||
|
AppleStoreLink,
|
||||||
|
PlayStoreLink,
|
||||||
|
FDroidLink,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { LinkKind } from '../parser/types';
|
import { LinkKind } from '../parser/types';
|
||||||
import logo from '../imgs/element.svg';
|
import logo from '../imgs/element.svg';
|
||||||
@ -31,7 +34,7 @@ export const Element: LinkedClient = {
|
|||||||
logo: logo,
|
logo: logo,
|
||||||
homepage: 'https://element.io',
|
homepage: 'https://element.io',
|
||||||
maturity: Maturity.STABLE,
|
maturity: Maturity.STABLE,
|
||||||
description: 'Fully-featured Matrix client for the Web',
|
description: 'Fully-featured Matrix client',
|
||||||
platforms: [Platform.Desktop, Platform.Android, Platform.iOS],
|
platforms: [Platform.Desktop, Platform.Android, Platform.iOS],
|
||||||
experimental: false,
|
experimental: false,
|
||||||
clientId: ClientId.Element,
|
clientId: ClientId.Element,
|
||||||
@ -59,6 +62,11 @@ export const Element: LinkedClient = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
linkSupport: () => true,
|
linkSupport: () => true,
|
||||||
|
installLinks: [
|
||||||
|
new AppleStoreLink('vector', 'id1083446067'),
|
||||||
|
new PlayStoreLink('im.vector.app'),
|
||||||
|
new FDroidLink('im.vector.app'),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ElementDevelop: LinkedClient = {
|
export const ElementDevelop: LinkedClient = {
|
||||||
@ -94,4 +102,5 @@ export const ElementDevelop: LinkedClient = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
linkSupport: () => true,
|
linkSupport: () => true,
|
||||||
|
installLinks: [],
|
||||||
};
|
};
|
||||||
|
@ -69,6 +69,7 @@ const Fractal: TextClient = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
description: 'Fractal is a Matrix Client written in Rust',
|
description: 'Fractal is a Matrix Client written in Rust',
|
||||||
|
installLinks: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Fractal;
|
export default Fractal;
|
||||||
|
@ -86,6 +86,7 @@ const Nheko: TextClient = {
|
|||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
'A native desktop app for Matrix that feels more like a mainstream chat app.',
|
'A native desktop app for Matrix that feels more like a mainstream chat app.',
|
||||||
|
installLinks: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Nheko;
|
export default Nheko;
|
||||||
|
@ -86,6 +86,7 @@ const Weechat: TextClient = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
description: 'Command-line Matrix interface using Weechat',
|
description: 'Command-line Matrix interface using Weechat',
|
||||||
|
installLinks: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Weechat;
|
export default Weechat;
|
||||||
|
@ -53,6 +53,79 @@ export enum ClientId {
|
|||||||
Fractal = 'fractal',
|
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
|
* The descriptive details of a client
|
||||||
*/
|
*/
|
||||||
@ -67,6 +140,7 @@ export interface ClientDescription {
|
|||||||
clientId: ClientId;
|
clientId: ClientId;
|
||||||
experimental: boolean;
|
experimental: boolean;
|
||||||
linkSupport: (link: SafeLink) => boolean;
|
linkSupport: (link: SafeLink) => boolean;
|
||||||
|
installLinks: InstallLink[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -51,7 +51,8 @@ limitations under the License.
|
|||||||
|
|
||||||
.button {
|
.button {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
width: 130px;
|
min-width: 130px;
|
||||||
|
max-width: 165px;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,6 +67,15 @@ limitations under the License.
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: $app-background;
|
background-color: $app-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.installLink {
|
||||||
|
display: inline-block;
|
||||||
|
height: 40px;
|
||||||
|
margin: 8px 16px 8px 0;
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.clientTileLink {
|
.clientTileLink {
|
||||||
|
@ -14,14 +14,19 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import classNames from 'classnames';
|
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 { SafeLink } from '../parser/types';
|
||||||
import Tile from './Tile';
|
import Tile from './Tile';
|
||||||
import Button from './Button';
|
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';
|
import './ClientTile.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -29,17 +34,54 @@ interface IProps {
|
|||||||
link: SafeLink;
|
link: SafeLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IInstallBadgeImages {
|
||||||
|
[index: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const installBadgeImages : IInstallBadgeImages = {
|
||||||
|
"fdroid": fdroidBadge,
|
||||||
|
"apple-app-store": appStoreBadge,
|
||||||
|
"play-store": playStoreBadge
|
||||||
|
};
|
||||||
|
|
||||||
const ClientTile: React.FC<IProps> = ({ client, link }: IProps) => {
|
const ClientTile: React.FC<IProps> = ({ client, link }: IProps) => {
|
||||||
const inviteLine =
|
const inviteLine =
|
||||||
client.kind === ClientKind.TEXT_CLIENT ? (
|
client.kind === ClientKind.TEXT_CLIENT ? (
|
||||||
<p>{client.toInviteString(link)}</p>
|
<p>{client.toInviteString(link)}</p>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
const { uaResults } = useContext(UAContext);
|
||||||
|
|
||||||
const className = classNames('clientTile', {
|
const className = classNames('clientTile', {
|
||||||
clientTileLink: client.kind === ClientKind.LINKED_CLIENT,
|
clientTileLink: client.kind === ClientKind.LINKED_CLIENT,
|
||||||
});
|
});
|
||||||
|
|
||||||
let inviteButton: JSX.Element = <></>;
|
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 = <p>{matchingInstallLinks.map((installLink) => {
|
||||||
|
return <a
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label={installLink.description}
|
||||||
|
key={installLink.channelId}
|
||||||
|
href={installLink.createInstallURL(link)}
|
||||||
|
className="installLink"
|
||||||
|
target="_blank">
|
||||||
|
<img src={installBadgeImages[installLink.channelId]} alt={installLink.description} />
|
||||||
|
</a>;
|
||||||
|
})}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
if (client.kind === ClientKind.LINKED_CLIENT) {
|
if (client.kind === ClientKind.LINKED_CLIENT) {
|
||||||
inviteButton = <Button>Accept invite</Button>;
|
inviteButton = <Button>Accept invite</Button>;
|
||||||
} else {
|
} else {
|
||||||
@ -62,6 +104,7 @@ const ClientTile: React.FC<IProps> = ({ client, link }: IProps) => {
|
|||||||
<div>
|
<div>
|
||||||
<h1>{client.name}</h1>
|
<h1>{client.name}</h1>
|
||||||
<p>{client.description}</p>
|
<p>{client.description}</p>
|
||||||
|
{installButtons}
|
||||||
{inviteLine}
|
{inviteLine}
|
||||||
{inviteButton}
|
{inviteButton}
|
||||||
</div>
|
</div>
|
||||||
@ -69,7 +112,11 @@ const ClientTile: React.FC<IProps> = ({ client, link }: IProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (client.kind === ClientKind.LINKED_CLIENT) {
|
if (client.kind === ClientKind.LINKED_CLIENT) {
|
||||||
clientTile = <a href={client.toUrl(link).toString()}>{clientTile}</a>;
|
if (!hasNativeClient) {
|
||||||
|
clientTile = (
|
||||||
|
<a href={client.toUrl(link).toString()}>{clientTile}</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return clientTile;
|
return clientTile;
|
||||||
|
7
src/imgs/app-store-us-alt.svg
Normal file
7
src/imgs/app-store-us-alt.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
BIN
src/imgs/fdroid-badge.png
Normal file
BIN
src/imgs/fdroid-badge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
41
src/imgs/google-play-us.svg
Normal file
41
src/imgs/google-play-us.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
Loading…
Reference in New Issue
Block a user