feat: mobile app package (#1075)
This commit is contained in:
61
packages/mobile/src/Screens/InputModal/FileInputModal.tsx
Normal file
61
packages/mobile/src/Screens/InputModal/FileInputModal.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { ButtonCell } from '@Root/Components/ButtonCell'
|
||||
import { SectionedTableCell } from '@Root/Components/SectionedTableCell'
|
||||
import { TableSection } from '@Root/Components/TableSection'
|
||||
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
|
||||
import { ModalStackNavigationProp } from '@Root/ModalStack'
|
||||
import { SCREEN_INPUT_MODAL_FILE_NAME } from '@Root/Screens/screens'
|
||||
import { ThemeServiceContext } from '@Style/ThemeService'
|
||||
import React, { FC, useContext, useEffect, useRef, useState } from 'react'
|
||||
import { TextInput } from 'react-native'
|
||||
import { Container, Input } from './InputModal.styled'
|
||||
|
||||
type Props = ModalStackNavigationProp<typeof SCREEN_INPUT_MODAL_FILE_NAME>
|
||||
|
||||
export const FileInputModal: FC<Props> = props => {
|
||||
const { file, renameFile } = props.route.params
|
||||
const themeService = useContext(ThemeServiceContext)
|
||||
const application = useSafeApplicationContext()
|
||||
|
||||
const fileNameInputRef = useRef<TextInput>(null)
|
||||
|
||||
const [fileName, setFileName] = useState(file.name)
|
||||
|
||||
const onSubmit = async () => {
|
||||
const trimmedFileName = fileName.trim()
|
||||
if (trimmedFileName === '') {
|
||||
setFileName(file.name)
|
||||
await application?.alertService.alert('File name cannot be empty')
|
||||
fileNameInputRef.current?.focus()
|
||||
return
|
||||
}
|
||||
await renameFile(file, trimmedFileName)
|
||||
void application.sync.sync()
|
||||
props.navigation.goBack()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fileNameInputRef.current?.focus()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<TableSection>
|
||||
<SectionedTableCell textInputCell first={true}>
|
||||
<Input
|
||||
ref={fileNameInputRef as any}
|
||||
placeholder={'File name'}
|
||||
onChangeText={setFileName}
|
||||
value={fileName}
|
||||
autoCorrect={false}
|
||||
autoCapitalize={'none'}
|
||||
keyboardAppearance={themeService?.keyboardColorForActiveTheme()}
|
||||
underlineColorAndroid={'transparent'}
|
||||
onSubmitEditing={onSubmit}
|
||||
/>
|
||||
</SectionedTableCell>
|
||||
|
||||
<ButtonCell maxHeight={45} disabled={fileName.length === 0} title={'Save'} bold onPress={onSubmit} />
|
||||
</TableSection>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
15
packages/mobile/src/Screens/InputModal/InputModal.styled.ts
Normal file
15
packages/mobile/src/Screens/InputModal/InputModal.styled.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import styled from 'styled-components/native'
|
||||
|
||||
export const Container = styled.View`
|
||||
flex: 1;
|
||||
background-color: ${({ theme }) => theme.stylekitBackgroundColor};
|
||||
`
|
||||
|
||||
export const Input = styled.TextInput.attrs(({ theme }) => ({
|
||||
placeholderTextColor: theme.stylekitNeutralColor,
|
||||
}))`
|
||||
font-size: ${({ theme }) => theme.mainTextFontSize}px;
|
||||
padding: 0px;
|
||||
color: ${({ theme }) => theme.stylekitForegroundColor};
|
||||
height: 100%;
|
||||
`
|
||||
134
packages/mobile/src/Screens/InputModal/PasscodeInputModal.tsx
Normal file
134
packages/mobile/src/Screens/InputModal/PasscodeInputModal.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { PasscodeKeyboardType, UnlockTiming } from '@Lib/ApplicationState'
|
||||
import { ApplicationContext } from '@Root/ApplicationContext'
|
||||
import { ButtonCell } from '@Root/Components/ButtonCell'
|
||||
import { Option, SectionedOptionsTableCell } from '@Root/Components/SectionedOptionsTableCell'
|
||||
import { SectionedTableCell } from '@Root/Components/SectionedTableCell'
|
||||
import { TableSection } from '@Root/Components/TableSection'
|
||||
import { ModalStackNavigationProp } from '@Root/ModalStack'
|
||||
import { SCREEN_INPUT_MODAL_PASSCODE } from '@Root/Screens/screens'
|
||||
import { ThemeServiceContext } from '@Style/ThemeService'
|
||||
import React, { useContext, useMemo, useRef, useState } from 'react'
|
||||
import { Keyboard, KeyboardType, Platform, TextInput } from 'react-native'
|
||||
import { Container, Input } from './InputModal.styled'
|
||||
|
||||
type Props = ModalStackNavigationProp<typeof SCREEN_INPUT_MODAL_PASSCODE>
|
||||
export const PasscodeInputModal = (props: Props) => {
|
||||
// Context
|
||||
const application = useContext(ApplicationContext)
|
||||
const themeService = useContext(ThemeServiceContext)
|
||||
|
||||
// State
|
||||
const [settingPassocode, setSettingPassocode] = useState(false)
|
||||
const [text, setText] = useState('')
|
||||
const [confirmText, setConfirmText] = useState('')
|
||||
const [keyboardType, setKeyboardType] = useState<KeyboardType>('default')
|
||||
|
||||
// Refs
|
||||
const textRef = useRef<TextInput>(null)
|
||||
const confirmTextRef = useRef<TextInput>(null)
|
||||
|
||||
const onTextSubmit = () => {
|
||||
if (!confirmText) {
|
||||
confirmTextRef.current?.focus()
|
||||
} else {
|
||||
Keyboard.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (settingPassocode) {
|
||||
return
|
||||
}
|
||||
setSettingPassocode(true)
|
||||
if (text !== confirmText) {
|
||||
void application?.alertService?.alert(
|
||||
'The two values you entered do not match. Please try again.',
|
||||
'Invalid Confirmation',
|
||||
'OK',
|
||||
)
|
||||
setSettingPassocode(false)
|
||||
} else {
|
||||
await application?.addPasscode(text)
|
||||
await application?.getAppState().setPasscodeKeyboardType(keyboardType as PasscodeKeyboardType)
|
||||
await application?.getAppState().setPasscodeTiming(UnlockTiming.OnQuit)
|
||||
setSettingPassocode(false)
|
||||
props.navigation.goBack()
|
||||
}
|
||||
}
|
||||
|
||||
const keyboardOptions: Option[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: 'General',
|
||||
key: 'default' as PasscodeKeyboardType,
|
||||
selected: keyboardType === 'default',
|
||||
},
|
||||
{
|
||||
title: 'Numeric',
|
||||
key: 'numeric' as PasscodeKeyboardType,
|
||||
selected: keyboardType === 'numeric',
|
||||
},
|
||||
],
|
||||
[keyboardType],
|
||||
)
|
||||
|
||||
const onKeyboardTypeSelect = (option: Option) => {
|
||||
setKeyboardType(option.key as KeyboardType)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<TableSection>
|
||||
<SectionedTableCell textInputCell first={true}>
|
||||
<Input
|
||||
ref={textRef as any}
|
||||
key={Platform.OS === 'android' ? keyboardType + '1' : undefined}
|
||||
placeholder="Enter a passcode"
|
||||
onChangeText={setText}
|
||||
value={text}
|
||||
secureTextEntry
|
||||
autoCorrect={false}
|
||||
autoCapitalize={'none'}
|
||||
keyboardType={keyboardType}
|
||||
keyboardAppearance={themeService?.keyboardColorForActiveTheme()}
|
||||
autoFocus={true}
|
||||
underlineColorAndroid={'transparent'}
|
||||
onSubmitEditing={onTextSubmit}
|
||||
/>
|
||||
</SectionedTableCell>
|
||||
|
||||
<SectionedTableCell textInputCell first={false}>
|
||||
<Input
|
||||
ref={confirmTextRef as any}
|
||||
key={Platform.OS === 'android' ? keyboardType + '2' : undefined}
|
||||
placeholder="Confirm passcode"
|
||||
onChangeText={setConfirmText}
|
||||
value={confirmText}
|
||||
secureTextEntry
|
||||
autoCorrect={false}
|
||||
autoCapitalize={'none'}
|
||||
keyboardType={keyboardType}
|
||||
keyboardAppearance={themeService?.keyboardColorForActiveTheme()}
|
||||
underlineColorAndroid={'transparent'}
|
||||
onSubmitEditing={onSubmit}
|
||||
/>
|
||||
</SectionedTableCell>
|
||||
|
||||
<SectionedOptionsTableCell
|
||||
title={'Keyboard Type'}
|
||||
leftAligned
|
||||
options={keyboardOptions}
|
||||
onPress={onKeyboardTypeSelect}
|
||||
/>
|
||||
|
||||
<ButtonCell
|
||||
maxHeight={45}
|
||||
disabled={settingPassocode || text.length === 0}
|
||||
title={'Save'}
|
||||
bold
|
||||
onPress={onSubmit}
|
||||
/>
|
||||
</TableSection>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
92
packages/mobile/src/Screens/InputModal/TagInputModal.tsx
Normal file
92
packages/mobile/src/Screens/InputModal/TagInputModal.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { useFocusEffect } from '@react-navigation/native'
|
||||
import { ApplicationContext } from '@Root/ApplicationContext'
|
||||
import { ButtonCell } from '@Root/Components/ButtonCell'
|
||||
import { SectionedTableCell } from '@Root/Components/SectionedTableCell'
|
||||
import { TableSection } from '@Root/Components/TableSection'
|
||||
import { ModalStackNavigationProp } from '@Root/ModalStack'
|
||||
import { SCREEN_INPUT_MODAL_TAG } from '@Root/Screens/screens'
|
||||
import { SNNote, SNTag, TagMutator } from '@standardnotes/snjs'
|
||||
import { ThemeServiceContext } from '@Style/ThemeService'
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||
import { TextInput } from 'react-native'
|
||||
import { Container, Input } from './InputModal.styled'
|
||||
|
||||
type Props = ModalStackNavigationProp<typeof SCREEN_INPUT_MODAL_TAG>
|
||||
export const TagInputModal = (props: Props) => {
|
||||
// Context
|
||||
const application = useContext(ApplicationContext)
|
||||
const themeService = useContext(ThemeServiceContext)
|
||||
|
||||
// State
|
||||
const [text, setText] = useState('')
|
||||
|
||||
// Refs
|
||||
const textRef = useRef<TextInput>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (props.route.params.tagUuid) {
|
||||
const tag = application?.items.findItem(props.route.params.tagUuid) as SNTag
|
||||
setText(tag.title)
|
||||
}
|
||||
}, [application, props.route.params.tagUuid])
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
setTimeout(() => {
|
||||
textRef.current?.focus()
|
||||
}, 1)
|
||||
}, []),
|
||||
)
|
||||
|
||||
const onSubmit = useCallback(async () => {
|
||||
if (props.route.params.tagUuid) {
|
||||
const tag = application?.items.findItem(props.route.params.tagUuid) as SNTag
|
||||
await application?.mutator.changeItem(tag, mutator => {
|
||||
const tagMutator = mutator as TagMutator
|
||||
tagMutator.title = text
|
||||
if (props.route.params.noteUuid) {
|
||||
const note = application.items.findItem(props.route.params.noteUuid)
|
||||
if (note) {
|
||||
tagMutator.addNote(note as SNNote)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const tag = await application!.mutator.findOrCreateTag(text)
|
||||
if (props.route.params.noteUuid) {
|
||||
await application?.mutator.changeItem(tag, mutator => {
|
||||
const tagMutator = mutator as TagMutator
|
||||
const note = application.items.findItem(props.route.params.noteUuid!)
|
||||
if (note) {
|
||||
tagMutator.addNote(note as SNNote)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
void application?.sync.sync()
|
||||
props.navigation.goBack()
|
||||
}, [application, props.navigation, props.route.params.noteUuid, props.route.params.tagUuid, text])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<TableSection>
|
||||
<SectionedTableCell textInputCell first={true}>
|
||||
<Input
|
||||
ref={textRef as any}
|
||||
placeholder={props.route.params.tagUuid ? 'Tag name' : 'New tag name'}
|
||||
onChangeText={setText}
|
||||
value={text}
|
||||
autoCorrect={false}
|
||||
autoCapitalize={'none'}
|
||||
keyboardAppearance={themeService?.keyboardColorForActiveTheme()}
|
||||
underlineColorAndroid={'transparent'}
|
||||
onSubmitEditing={onSubmit}
|
||||
/>
|
||||
</SectionedTableCell>
|
||||
|
||||
<ButtonCell maxHeight={45} disabled={text.length === 0} title={'Save'} bold onPress={onSubmit} />
|
||||
</TableSection>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user