add: android starter onboarding [WIP].

This commit is contained in:
Darshan
2025-01-22 15:09:53 +05:30
parent c392012566
commit b16f8880cd
7 changed files with 368 additions and 155 deletions
+9
View File
@@ -3,6 +3,7 @@
import { trackEvent } from '$lib/actions/analytics';
import WizardExitModal from './wizardExitModal.svelte';
import { goto } from '$app/navigation';
import { wizard } from '$lib/stores/wizard';
type $$Props =
| {
@@ -13,6 +14,7 @@
invertColumns?: boolean;
hideAside?: boolean;
hideFooter?: boolean;
onExit?: () => void;
}
| {
title: string;
@@ -22,6 +24,7 @@
invertColumns?: boolean;
hideAside?: boolean;
hideFooter?: boolean;
onExit?: () => void;
};
export let title: $$Props['title'] = '';
@@ -31,6 +34,7 @@
export let invertColumns: $$Props['invertColumns'] = false;
export let hideAside = false;
export let hideFooter = false;
export let onExit: $$Props['onExit'] = undefined;
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
@@ -83,6 +87,11 @@
trackEvent('wizard_exit', {
from: 'prompt'
});
wizard.hide();
if (onExit) {
onExit();
}
}}>
<slot name="exit">
Are you sure you want to exit from this process? All data will be deleted. This action
@@ -0,0 +1,79 @@
<script lang="ts">
export let status: boolean = false;
</script>
<div class="connection-line u-flex u-cross-center">
<div
class="line-left"
style={`opacity: ${status ? 1 : 0.15}; transition: opacity 5s;`}
data-status={status}>
</div>
<div
class="tick-container"
style={`opacity: ${status ? 1 : 0.15}; transition: opacity 2.5s;`}
data-status={status}>
{#if status}
<span class="icon-check" style="color: #fd366e;"></span>
{/if}
</div>
<div
class="line-right"
style={`opacity: ${status ? 1 : 0.15}; transition: opacity 5s;`}
data-status={status}>
</div>
</div>
<style>
.connection-line {
width: 100%;
display: flex;
align-items: center;
}
.line-left[data-status='false'],
.line-right[data-status='false'] {
flex: 1;
height: 1px;
background: none;
border-top: 1px dashed #d8d8db;
}
.line-left[data-status='true'] {
flex: 1;
height: 1px;
background: linear-gradient(to left, #f02e65 0%, rgba(254, 149, 103, 0.15) 100%);
}
.line-right[data-status='true'] {
flex: 1;
height: 1px;
background: linear-gradient(to right, #f02e65 6.32%, rgba(254, 149, 103, 0.15) 116.81%);
}
.tick-container[data-status='false'] {
width: 7px;
height: 7px;
display: flex;
border-radius: 50%;
margin-inline: 0.25rem;
background-color: #ccc;
}
.tick-container[data-status='true'] {
display: flex;
width: 25px;
height: 25px;
color: #fd366e;
border-radius: 50%;
align-items: center;
justify-content: center;
border: 1.8px solid hsla(343, 98%, 60%, 0.32);
background-color: hsla(343, 98%, 60%, 0.08);
}
.icon-check {
color: #fd366e;
}
</style>
@@ -0,0 +1,63 @@
<script lang="ts">
import type { ComponentType } from 'svelte';
export let icon: ComponentType;
export let iconSize: number | undefined = undefined;
export let iconColor: string | undefined = undefined;
</script>
<div class="outer-card">
<div
class="inner-card"
style="
--icon-color: {iconColor || 'inherit'};
--icon-size: {iconSize ? `${iconSize}rem` : 'inherit'};
">
<svelte:component this={icon} />
</div>
</div>
<style lang="scss">
.outer-card {
display: flex;
align-items: center;
justify-content: center;
width: 94.304px;
height: 94.303px;
flex-shrink: 0;
border-radius: 26.944px;
border: 1.684px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.5);
box-shadow: 0px 8.757px 8.757px 0px rgba(0, 0, 0, 0.04);
}
.inner-card {
display: flex;
align-items: center;
justify-content: center;
width: 80.832px;
height: 80.831px;
flex-shrink: 0;
border-radius: 20.208px;
background: #fff;
border: 0.54px solid #ededf0;
box-shadow: 0px 6.736px 6.736px 0px rgba(0, 0, 0, 0.05);
& :global(svg) {
width: var(--icon-size);
height: var(--icon-size);
color: var(--icon-color);
}
}
:global(.theme-dark) {
& .outer-card {
background: rgba(255, 255, 255, 0.08);
}
& .inner-card {
border: 0.54px solid rgba(237, 237, 240, 0.08);
background: var(--color-bgColor-neutral-primary, #1d1d21);
}
}
</style>
@@ -1,46 +1,227 @@
<script lang="ts">
import { WizardWithSteps } from '$lib/layout';
import { Wizard } from '$lib/layout';
import { invalidate } from '$app/navigation';
import { wizard } from '$lib/stores/wizard';
import { createPlatform } from './wizard/store';
import { Dependencies } from '$lib/constants';
import type { WizardStepsType } from '$lib/layout/wizardWithSteps.svelte';
import Step1 from './wizard/android/step1.svelte';
import Step2 from './wizard/android/step2.svelte';
import Step3 from './wizard/android/step3.svelte';
import Step4 from './wizard/step4.svelte';
import {
Code,
Layout,
Icon,
Typography,
Fieldset,
InlineCode,
Spinner
} from '@appwrite.io/pink-svelte';
import { Button, Form, InputText } from '$lib/elements/forms';
import { IconAndroid, IconAppwrite } from '@appwrite.io/pink-icons-svelte';
import { Card } from '$lib/components';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { sdk } from '$lib/stores/sdk';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { addNotification } from '$lib/stores/notifications';
import { fade } from 'svelte/transition';
import OnboardingPlatformCard from '$routes/(console)/project-[project]/overview/platforms/components/OnboardingPlatformCard.svelte';
import ConnectionLine from '$routes/(console)/project-[project]/overview/platforms/components/ConnectionLine.svelte';
import { PlatformType } from '@appwrite.io/console';
import { sleep } from '$lib/helpers/promises';
async function onFinish() {
await Promise.all([invalidate(Dependencies.PROJECT), invalidate(Dependencies.PLATFORMS)]);
createPlatform.reset();
wizard.hide();
let showExitModal = false;
let isPlatformCreated = false;
let isCreatingPlatform = false;
let connectionSuccessful = false;
const projectId = $page.params.project;
const gitCloneCode =
'\ngit clone https://github.com/appwrite/starter-for-android\ncd starter-for-android\n';
async function createAndroidPlatform() {
try {
isCreatingPlatform = true;
await sleep(1500);
// await sdk.forConsole.projects.createPlatform(
// projectId,
// PlatformType.Android,
// $createPlatform.name,
// $createPlatform.key || undefined,
// undefined,
// undefined
// );
isPlatformCreated = true;
trackEvent(Submit.PlatformCreate, {
type: 'android'
});
addNotification({
type: 'success',
message: `Android platform has been created.`
});
} catch (error) {
trackError(error, Submit.PlatformCreate);
} finally {
isCreatingPlatform = false;
}
}
const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Settings',
component: Step1
});
stepsComponents.set(2, {
label: 'Install',
component: Step2,
optional: true
});
stepsComponents.set(3, {
label: 'Import',
component: Step3,
optional: true
});
stepsComponents.set(4, {
label: 'Build',
component: Step4,
optional: true
async function resetPlatformStore() {
await Promise.all([invalidate(Dependencies.PROJECT), invalidate(Dependencies.PLATFORMS)]);
createPlatform.reset();
}
onMount(() => {
const unsubscribe = sdk.forConsole.client.subscribe('console', (response) => {
if (response.events.includes(`projects.${projectId}.ping`) && isPlatformCreated) {
connectionSuccessful = true;
unsubscribe();
}
});
return () => {
unsubscribe();
};
});
</script>
<WizardWithSteps
title="Add an Android platform"
steps={stepsComponents}
on:finish={onFinish}
on:exit={onFinish}
finalAction="Go to dashboard" />
<Wizard title="Add an Android platform" bind:showExitModal confirmExit onExit={resetPlatformStore}>
<Form onSubmit={createAndroidPlatform}>
<Layout.Stack gap="xxl">
<!-- Step One -->
{#if !isPlatformCreated}
<Fieldset legend="Details">
<Layout.Stack gap="l" alignItems="flex-end">
<Layout.Stack gap="s">
<InputText
id="name"
label="Name"
placeholder="My Android App"
required
bind:value={$createPlatform.name} />
<!-- Tooltips on InputText don't work as of now -->
<InputText
id="key"
label="Package Name"
placeholder="com.company.appname"
tooltip="Your package name is generally the applicationId in your app-level build.gradle file."
required
bind:value={$createPlatform.key} />
</Layout.Stack>
<Button
fullWidthMobile
size="s"
submit
forceShowLoader
submissionLoader={isCreatingPlatform}
disabled={!$createPlatform.name ||
!$createPlatform.key ||
isCreatingPlatform}
on:click={async () => await createAndroidPlatform()}>
Create platform
</Button>
</Layout.Stack>
</Fieldset>
{:else}
<Layout.Stack gap="xxl">
<Card isTile padding="s" radius="s">
<Layout.Stack
direction="row"
justifyContent="space-between"
alignItems="center"
gap="xs">
<Layout.Stack direction="row" alignItems="center" gap="s">
<Icon size="m" icon={IconAndroid} />
<Typography.Text
variant="m-400"
color="--color-fgcolor-neutral-primary">
{$createPlatform.name} ({$createPlatform.key})
</Typography.Text>
</Layout.Stack>
</Layout.Stack>
</Card>
</Layout.Stack>
{/if}
<!-- Step Two -->
{#if isPlatformCreated}
<Fieldset legend="Clone starter">
<Layout.Stack gap="l">
<Typography.Text variant="m-500">
1. Clone the Android starter kit from GitHub using your terminal or
Android Studio.
</Typography.Text>
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
<div class="pink2-code-margin-fix">
<Code lang="bash" lineNumbers code={gitCloneCode} />
</div>
<Typography.Text variant="m-500">2. Configure the project</Typography.Text>
<Typography.Text variant="m-400">
Update the configuration in the file
<InlineCode size="s" code="data/repository/AppwriteRepository.kt" />
.
</Typography.Text>
<Typography.Text variant="m-500">3. Run the starter kit</Typography.Text>
<Typography.Text variant="m-400">
Launch the starter kit and click the
<InlineCode size="s" code="Send a ping" />
button to test the setup.
</Typography.Text>
</Layout.Stack>
</Fieldset>
{/if}
</Layout.Stack>
</Form>
<svelte:fragment slot="aside">
<Card padding="l">
<Layout.Stack gap="xxl">
<Layout.Stack direction="row" justifyContent="center" gap="none">
<OnboardingPlatformCard
iconSize={2.526}
iconColor="#3ddc84"
icon={IconAndroid} />
<ConnectionLine status={connectionSuccessful} />
<OnboardingPlatformCard
iconSize={2.526}
iconColor="#FD366E"
icon={IconAppwrite} />
</Layout.Stack>
<Layout.Stack direction="row" justifyContent="center" alignItems="center" gap="l">
{#if !connectionSuccessful}
<Spinner />
<Typography.Text variant="m-400">Waiting for connection...</Typography.Text>
{:else}
<!-- cannot apply fade on components -->
<div in:fade={{ duration: 2500 }}>
<Typography.Title size="m">Congratulations!</Typography.Title>
</div>
{/if}
</Layout.Stack>
</Layout.Stack>
</Card>
</svelte:fragment>
<svelte:fragment slot="footer">
<Button
size="s"
fullWidthMobile
secondary
disabled={isCreatingPlatform}
href={`${base}/project-${projectId}/overview`}>
Go to dashboard
</Button>
</svelte:fragment>
</Wizard>
<style lang="scss">
:global(.pink2-code-margin-fix pre) {
margin: revert;
}
</style>
@@ -1,60 +0,0 @@
<script lang="ts">
import { page } from '$app/stores';
import { FormList, InputText } from '$lib/elements/forms';
import { WizardStep } from '$lib/layout';
import { sdk } from '$lib/stores/sdk';
import { createPlatform } from '../store';
import { Submit, trackEvent } from '$lib/actions/analytics';
import { PlatformType } from '@appwrite.io/console';
const projectId = $page.params.project;
async function beforeSubmit() {
if ($createPlatform.$id) {
await sdk.forConsole.projects.updatePlatform(
projectId,
$createPlatform.$id,
$createPlatform.name,
$createPlatform.key || undefined,
$createPlatform.store || undefined,
$createPlatform.hostname || undefined
);
return;
}
const platform = await sdk.forConsole.projects.createPlatform(
projectId,
PlatformType.Android,
$createPlatform.name,
$createPlatform.key || undefined,
undefined,
undefined
);
trackEvent(Submit.PlatformCreate, {
type: 'android'
});
$createPlatform.$id = platform.$id;
}
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Android registration</svelte:fragment>
<FormList>
<InputText
id="name"
label="Name"
placeholder="My Android App"
required
bind:value={$createPlatform.name} />
<InputText
id="key"
label="Package Name"
placeholder="com.company.appname"
tooltip="Your package name is generally the applicationId in your app-level build.gradle file."
required
bind:value={$createPlatform.key} />
</FormList>
</WizardStep>
@@ -1,18 +0,0 @@
<script lang="ts">
import { Code } from '$lib/components';
import { WizardStep } from '$lib/layout';
import { versions } from '../store';
const example1 = `repositories {
mavenCentral()
}`;
const example2 = `implementation("io.appwrite:sdk-for-android:${$versions['client-android']}")`;
</script>
<WizardStep>
<svelte:fragment slot="title">Install</svelte:fragment>
<p>First, add this to your root level <code class="inline-code">build.gradle</code> file.</p>
<Code label="Groovy" labelIcon="android" language="kotlin" code={example1} withCopy />
<p class="common-section">And add this to your project's build.gradle file.</p>
<Code label="Groovy" labelIcon="android" language="kotlin" code={example2} withCopy />
</WizardStep>
@@ -1,41 +0,0 @@
<script lang="ts">
import { Code } from '$lib/components';
import Id from '$lib/components/id.svelte';
import { WizardStep } from '$lib/layout';
import { sdk } from '$lib/stores/sdk';
import { isCloud } from '$lib/system';
const { endpoint, project } = sdk.forProject.client.config;
const code = `import io.appwrite.Client
import io.appwrite.services.Account
${
isCloud
? `val client = Client(context).setProject("${project}")`
: `val client = Client(context)
.setEndpoint("${endpoint}")
.setProject("${project}")
.setSelfSigned(status: true) // For self signed certificates, only use for development`
}`;
</script>
<WizardStep>
<svelte:fragment slot="title">Initialize</svelte:fragment>
<h2 class="heading-level-7">Initialize your SDK</h2>
<p>
Initialize your SDK by pointing the client to your Appwrite project using your <Id
value={project}>Project ID</Id
>.
</p>
<div class="u-margin-block-start-16">
<Code
label="Android SDK"
labelIcon="android"
language="kotlin"
{code}
withCopy
withLineNumbers
noMargin />
</div>
</WizardStep>