Skip to content
22 changes: 22 additions & 0 deletions app/app.android.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ checkbox {
margin-right: 0;
}

// the primary button is a 40 tall pill
button[variant='contained'] {
height: 40;
}

.sheetHeaderTitle {
font-size: 22;
margin-left: 8;
}

// a tonal chip: the surface has no glass here
.cardChip {
background-color: var(--colorPrimaryContainer);
Expand All @@ -17,3 +27,15 @@ checkbox {
border-radius: 30;
border-width: 1;
}

// a field: 56 tall and 4 around
.formRow {
height: 64;
}

// the outline takes the accent while the field has the focus
textfield[variant='outline'] {
floating-color: var(--colorPrimary);
stroke-color: var(--colorPrimary);
stroke-inactive-color: var(--colorOutline);
}
23 changes: 23 additions & 0 deletions app/app.ios.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ textfield[variant='none'] {
font-weight: 700;
}

// the primary button is a 44 tall capsule
button[variant='prominentGlass'],
button[variant='contained'] {
height: 44;
}

.sheetHeaderTitle {
font-size: 17;
font-weight: bold;
text-align: center;
}

// capsule text fields: the system fill, 12% gray in light and 24% in dark
textfield[variant='outline'],
textview[variant='outline'] {
Expand All @@ -94,3 +106,14 @@ textview[variant='outline'] {
min-height: 84;
padding: 12 16;
}

// a field: 48 tall and 4 around
.formRow {
height: 56;
}

textfield[variant='outline']:focused,
textview[variant='outline']:focused {
border-color: var(--colorPrimary);
border-width: 1.5;
}
24 changes: 24 additions & 0 deletions app/components/common/SheetHeader.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
import { lc } from '@nativescript-community/l';
import { appIcon } from '~/utils/appIcons';
import { colors } from '~/variables';

export let title: string;
export let doneText: string;
export let doneEnabled = true;
export let onCancel: () => void;
export let onDone: () => void;
let { colorOnPrimary, colorOnSurface, colorPrimary } = $colors;
$: ({ colorOnPrimary, colorOnSurface, colorPrimary } = $colors);
$: doneColor = __IOS__ ? colorOnPrimary : colorPrimary;

// iOS: a glass Cancel pill and a prominent action. Android: a close icon and a text action
const cancelProps = __IOS__ ? { text: lc('cancel'), variant: 'glass' } : { src: appIcon('close'), variant: 'text' };
const doneVariant = __IOS__ ? 'contained' : 'text';
</script>

<gridlayout columns="auto,*,auto" padding="12 16 8 16" {...$$restProps}>
<uibutton accessibilityLabel={lc('cancel')} {...cancelProps} on:tap={onCancel} />
<label class="sheetHeaderTitle" col={1} color={colorOnSurface} text={title} verticalAlignment="center" />
<uibutton col={2} color={doneColor} isEnabled={doneEnabled} opacity={doneEnabled ? 1 : 0.45} text={doneText} variant={doneVariant} on:tap={onDone} />
</gridlayout>
4 changes: 3 additions & 1 deletion app/components/list/CardsList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
DEFAULT_NB_COLUMNS_LANDSCAPE,
SETTINGS_CARD_ALWAYS_SHOW_NAME,
SETTINGS_NB_COLUMNS,
SETTINGS_NB_COLUMNS_LANDSCAPE
SETTINGS_NB_COLUMNS_LANDSCAPE,
SHEET_SOFT_INPUT_MODE
} from '~/utils/constants';
import { appIcon } from '~/utils/appIcons';
import { documentHasPKPassData } from '~/utils/pkpass';
Expand Down Expand Up @@ -82,6 +83,7 @@
const result = await showBottomSheet({
parent: this,
view: CreateCard,
windowSoftInputMode: SHEET_SOFT_INPUT_MODE,
ignoreTopSafeArea: true
});
DEV_LOG && console.log('CreateCard', result);
Expand Down
13 changes: 6 additions & 7 deletions app/components/view/CardEditSheet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import { lc } from '@nativescript-community/l';
import { Template } from '@nativescript-community/svelte-native/components';
import { TextField } from '@nativescript-community/ui-textfield';
import SheetHeader from '~/components/common/SheetHeader.svelte';
import { showBottomSheet } from '@nativescript-community/ui-bottomsheet/svelte';
import { showError } from '@shared/utils/showError';
import dayjs from 'dayjs';
import { ExtraFieldType, OCRDocument } from '~/models/OCRDocument';
import { qrcodeService } from '~/services/qrcode';
import { shortcutService } from '~/services/shortcuts';
import { appIcon } from '~/utils/appIcons';
import { SHEET_SOFT_INPUT_MODE } from '~/utils/constants';
import { pickColor } from '~/utils/ui';
import { colors } from '~/variables';
import ListItemAutoSize from '@shared/components/ListItemAutoSize.svelte';
Expand Down Expand Up @@ -108,6 +110,7 @@
parent: rootView,
skipCollapsedState: true,
view: ExtraFieldPicker,
windowSoftInputMode: SHEET_SOFT_INPUT_MODE,
props: { editing: !!field, ...(field?.extra || {}) }
});
if (!result) {
Expand Down Expand Up @@ -166,11 +169,7 @@
</script>

<gesturerootview bind:this={rootView} backgroundColor={__IOS__ ? colorBackground : colorSurface} rows="auto,*">
<gridlayout columns="auto,*,auto" padding="12 16 8 16">
<uibutton text={lc('cancel')} variant="text" on:tap={() => close(false)} />
<label col={1} color={colorOnSurface} fontSize={17} fontWeight="bold" text={lc('edit')} textAlignment="center" verticalAlignment="center" />
<uibutton col={2} text={lc('save')} variant={__IOS__ ? 'prominentGlass' : 'contained'} on:tap={save} />
</gridlayout>
<SheetHeader doneText={lc('save')} onCancel={() => close(false)} onDone={save} title={lc('edit')} />
<collectionview row={1} {itemTemplateSelector} items={listItems}>
<Template key="name" let:item>
<textfield hint={lc('name')} margin="4 16 4 16" text={name} variant={__IOS__ ? 'none' : 'filled'} on:textChange={(e) => (name = e.value)} />
Expand All @@ -190,13 +189,13 @@
<Template key="code" let:item>
<gridlayout columns="*,auto" margin="2 16 2 16">
<textfield error={item.code.error} hint={lc('barcode')} text={item.code.text} variant={__IOS__ ? 'none' : 'filled'} on:textChange={(e) => onCodeText(item.code, e.value, e.object)} />
<uibutton col={1} variant="text" {...{ src: appIcon('delete') }} on:tap={() => removeCode(item.code)} />
<uibutton col={1} color={colorError} variant="text" {...{ src: appIcon('delete') }} on:tap={() => removeCode(item.code)} />
</gridlayout>
</Template>

<Template key="field" let:item>
<ListItemAutoSize fontSize={17} item={{ title: item.field.name, subtitle: formatValue(item.field.extra) }} on:tap={() => editField(item.field)}>
<uibutton col={1} variant="text" {...{ src: appIcon('delete') }} on:tap={() => removeField(item.field)} />
<uibutton col={1} color={colorError} variant="text" {...{ src: appIcon('delete') }} on:tap={() => removeField(item.field)} />
</ListItemAutoSize>
</Template>

Expand Down
4 changes: 3 additions & 1 deletion app/components/view/CardView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
EVENT_DOCUMENT_UPDATED,
QRCODE_RESIZE_THRESHOLD,
SETTINGS_DOCUMENT_NAME_FORMAT,
SETTINGS_FORCE_WHITE_BACKGROUND_QRCODE
SETTINGS_FORCE_WHITE_BACKGROUND_QRCODE,
SHEET_SOFT_INPUT_MODE
} from '~/utils/constants';
import { appIcon } from '~/utils/appIcons';
import { optionsToMenuItems } from '~/utils/appMenu';
Expand Down Expand Up @@ -943,6 +944,7 @@
const saved = await showBottomSheet({
parent: page,
view: CardEditSheet,
windowSoftInputMode: SHEET_SOFT_INPUT_MODE,
detents: [{ fraction: 0.65 }, 'large'],
grabber: true,
props: { document }
Expand Down
60 changes: 38 additions & 22 deletions app/components/widgets/CreateCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import { FORMATS, getBarcodeFallbackString, qrcodeService } from '~/services/qrcode';
import { pickColor, showPopoverMenu } from '~/utils/ui';
import { colors } from '~/variables';
import { focusState } from '~/utils/svelte/actions';
import SheetHeader from '../common/SheetHeader.svelte';
import SvgView from '../common/SVGView.svelte';
</script>

Expand All @@ -21,6 +23,10 @@
let validCode = true;
const tMargin = '4 0 4 0';

function close() {
rootView.nativeView.bindingContext.closeCallback();
}

async function add() {
try {
if (!name || !color) {
Expand Down Expand Up @@ -80,26 +86,36 @@
}
</script>

<gesturerootview bind:this={rootView} columns="*,auto" padding={16} rows="auto,auto,auto,auto,auto,auto">
<label color={colorOnBackground} fontSize={20} fontWeight="bold" text={lc('create')} verticalAlignment="center" />
<stacklayout col={1} orientation="horizontal">
<label color={colorOnBackground} fontSize={16} text={lc('color')} verticalAlignment="center" />
<absolutelayout backgroundColor={color} borderColor={colorOutline} borderRadius="50%" borderWidth={2} height={40} marginLeft={10} width={40} on:tap={changeColor} />
</stacklayout>
<textfield colSpan={2} hint={lc('name')} margin={tMargin} row={1} text={name} variant="outline" on:textChange={(e) => (name = e.value)} />
<textfield colSpan={2} hint={lc('barcode')} margin={tMargin} row={2} text={code} variant="outline" on:textChange={onCodeTextChange} />
<textfield colSpan={2} editable={false} hint={lc('barcode_type')} margin={tMargin} row={3} text={codeFormat} textTransform="uppercase" variant="outline" on:tap={(e) => selectBarcodeFormat(e)} />
<SvgView backgroundColor="white" borderRadius={12} colSpan={2} height={100} item={{ svg }} row={4} stretch="aspectFit" visibility={code?.length ? 'visible' : 'collapse'} />
<label
colSpan={2}
fontSize={20}
fontWeight="bold"
row={4}
text={lc('invalid')}
textAlignment="center"
verticalAlignment="center"
visibility={code?.length && !validCode ? 'visible' : 'collapse'}
/>

<uibutton colSpan={2} horizontalAlignment="right" isEnabled={name?.length > 0} row={5} text={lc('add')} variant="text" on:tap={add} />
<gesturerootview bind:this={rootView} rows="auto,auto">
<SheetHeader doneEnabled={name?.length > 0} doneText={lc('add')} onCancel={close} onDone={add} title={lc('create')} />
<gridlayout columns="*,auto" padding="0 16 16 16" row={1} rows="auto,auto,auto,auto,auto,auto">
<textfield colSpan={2} hint={lc('name')} margin={tMargin} text={name} use:focusState variant="outline" on:textChange={(e) => (name = e.value)} />
<textfield colSpan={2} hint={lc('barcode')} margin={tMargin} row={1} text={code} use:focusState variant="outline" on:textChange={onCodeTextChange} />
<textfield
colSpan={2}
editable={false}
hint={lc('barcode_type')}
margin={tMargin}
row={2}
text={codeFormat}
textTransform="uppercase"
variant="outline"
on:tap={(e) => selectBarcodeFormat(e)}
/>
<gridlayout class="formRow" colSpan={2} columns="*,auto" row={3} on:tap={changeColor}>
<label color={colorOnBackground} fontSize={17} margin="0 4" text={lc('color')} verticalAlignment="center" />
<absolutelayout backgroundColor={color} borderColor={colorOutline} borderRadius="50%" borderWidth={2} col={1} height={40} verticalAlignment="center" width={40} />
</gridlayout>
<SvgView backgroundColor="white" borderRadius={12} colSpan={2} height={100} item={{ svg }} row={4} stretch="aspectFit" visibility={code?.length ? 'visible' : 'collapse'} />
<label
colSpan={2}
fontSize={20}
fontWeight="bold"
row={4}
text={lc('invalid')}
textAlignment="center"
verticalAlignment="center"
visibility={code?.length && !validCode ? 'visible' : 'collapse'}
/>
</gridlayout>
</gesturerootview>
51 changes: 28 additions & 23 deletions app/components/widgets/ExtraFieldPicker.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
import { showError } from '@shared/utils/showError';
import dayjs from 'dayjs';
import { formatDate } from '~/helpers/locale';
import SheetHeader from '~/components/common/SheetHeader.svelte';
import { focusState } from '~/utils/svelte/actions';
import { ExtraFieldType } from '~/models/OCRDocument';
import { pickDate, showPopoverMenu } from '~/utils/ui';
import { colors } from '~/variables';
</script>

<script lang="ts">
let { colorOnBackground, colorOnSurfaceVariant, colorOutlineVariant } = $colors;
$: ({ colorOnBackground, colorOnSurfaceVariant, colorOutlineVariant } = $colors);
let { colorOnSurfaceVariant, colorOutlineVariant } = $colors;
$: ({ colorOnSurfaceVariant, colorOutlineVariant } = $colors);

export let editing = false;
export let name = null;
Expand Down Expand Up @@ -91,6 +93,10 @@
}
}

function close() {
rootView.nativeView.bindingContext.closeCallback();
}

async function add() {
try {
if (!currentName || (!currentValue && currentType !== ExtraFieldType.Date)) {
Expand Down Expand Up @@ -123,25 +129,24 @@
}
</script>

<gesturerootview bind:this={rootView} padding={16} rows="auto,auto,auto,auto,auto">
<label color={colorOnBackground} fontSize={20} fontWeight="bold" marginBottom={16} text={editing ? lc('edit_extra_field') : lc('add_extra_field')} />
<gridlayout row={1}>
<textfield hint={lc('name')} margin={tMargin} paddingLeft={60} row={2} text={currentName} variant="outline" on:textChange={(e) => (currentName = e.value)} />
<uibutton class="icon-btn" color={colorOnSurfaceVariant} horizontalAlignment="left" text="mdi-menu-down" variant="text" verticalAlignment="middle" on:tap={selecteTemplateName} />
</gridlayout>
<textfield editable={false} hint={lc('type')} margin={tMargin} row={2} text={lc(currentType)} textTransform="uppercase" variant="outline" on:tap={(e) => selectType(e)} />

<textview
editable={currentType !== ExtraFieldType.Date}
hint={lc('value')}
keyboardType={getKeyboardType(currentType)}
margin={tMargin}
row={3}
text={currentType === ExtraFieldType.Date ? formatDate(currentTime, 'LL') : currentValue}
variant="outline"
on:textChange={(e) => (currentValue = e.value)}
on:tap={(e) => selectDate(e)}
/>

<uibutton horizontalAlignment="right" row={4} text={editing ? lc('edit') : lc('add')} variant="text" on:tap={add} />
<gesturerootview bind:this={rootView} rows="auto,auto">
<SheetHeader doneText={editing ? lc('edit') : lc('add')} onCancel={close} onDone={add} title={editing ? lc('edit_extra_field') : lc('add_extra_field')} />
<stacklayout padding="0 16 16 16" row={1}>
<gridlayout>
<textfield hint={lc('name')} margin={tMargin} paddingLeft={60} text={currentName} use:focusState variant="outline" on:textChange={(e) => (currentName = e.value)} />
<uibutton class="icon-btn" color={colorOnSurfaceVariant} horizontalAlignment="left" text="mdi-menu-down" variant="text" verticalAlignment="middle" on:tap={selecteTemplateName} />
</gridlayout>
<textfield editable={false} hint={lc('type')} margin={tMargin} text={lc(currentType)} textTransform="uppercase" variant="outline" on:tap={(e) => selectType(e)} />
<textview
editable={currentType !== ExtraFieldType.Date}
hint={lc('value')}
keyboardType={getKeyboardType(currentType)}
margin={tMargin}
text={currentType === ExtraFieldType.Date ? formatDate(currentTime, 'LL') : currentValue}
use:focusState
variant="outline"
on:textChange={(e) => (currentValue = e.value)}
on:tap={(e) => selectDate(e)}
/>
</stacklayout>
</gesturerootview>
3 changes: 3 additions & 0 deletions app/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,6 @@ export const DOCUMENT_DATA_FILENAME = 'data.json';
export const DELETED_DOCUMENTS_DATA_FILENAME = 'deleted_documents.json';
export const DELETED_FOLDERS_DATA_FILENAME = 'deleted_folders.json';
export const VALID_MARKER_FILENAME = '.valid';

// SOFT_INPUT_ADJUST_RESIZE: a sheet with fields moves up with the keyboard
export const SHEET_SOFT_INPUT_MODE = __ANDROID__ ? 16 : undefined;
16 changes: 16 additions & 0 deletions app/utils/svelte/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NativeViewElementNode } from '@nativescript-community/svelte-native/dom';
import type { TextBase } from '@nativescript/core';

/** css has no `:focus`: the field gets the `focused` pseudo class while it has the focus */
export function focusState(node: NativeViewElementNode<TextBase>) {
const onFocus = () => node.nativeView.addPseudoClass('focused');
const onBlur = () => node.nativeView.deletePseudoClass('focused');
node.addEventListener('focus', onFocus);
node.addEventListener('blur', onBlur);
return {
destroy() {
node.removeEventListener('focus', onFocus);
node.removeEventListener('blur', onBlur);
}
};
}
Loading