feat: mobile app package (#1075)

This commit is contained in:
Mo
2022-06-09 09:45:15 -05:00
committed by GitHub
parent 58b63898de
commit 8248a38280
336 changed files with 47696 additions and 22563 deletions

View File

@@ -0,0 +1,54 @@
import { SnIcon } from '@Root/Components/SnIcon'
import { Text } from '@Screens/SideMenu/SideMenuCell.styled'
import { hexToRGBA } from '@Style/Utils'
import { StyleSheet } from 'react-native'
import styled from 'styled-components/native'
export const uploadedFileItemStyles = StyleSheet.create({
lockIcon: {
marginLeft: 8,
},
})
export const FileDataContainer = styled.View`
align-items: flex-start;
flex-direction: row;
padding-top: 12px;
`
export const FileIconContainer = styled.View`
margin-top: 2px;
margin-right: 16px;
`
export const FileDetailsWithExtraIconsContainer = styled.View`
flex-direction: row;
flex-shrink: 1;
flex-grow: 1;
align-items: center;
border-bottom-color: ${({ theme }) => hexToRGBA(theme.stylekitBorderColor, 0.75)};
border-bottom-width: 1px;
padding-bottom: 12px;
`
export const LockIconStyled = styled(SnIcon)`
background-color: green;
display: none;
`
export const FileDetailsContainer = styled.View`
flex-shrink: 1;
`
export const FileName = styled(Text)`
font-weight: normal;
font-size: 16px;
margin-bottom: 4px;
`
export const FileDateAndSizeContainer = styled.View`
flex-direction: row;
align-items: center;
`
export const FileDateAndSize = styled.Text`
color: ${({ theme }) => {
return theme.stylekitForegroundColor
}};
opacity: 0.5;
font-weight: normal;
font-size: 12px;
`

View File

@@ -0,0 +1,83 @@
import { AppStackNavigationProp } from '@Root/AppStack'
import { SnIcon } from '@Root/Components/SnIcon'
import { useFiles } from '@Root/Hooks/useFiles'
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
import { SCREEN_COMPOSE } from '@Root/Screens/screens'
import { UploadedFileItemActionType } from '@Screens/UploadedFilesList/UploadedFileItemAction'
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { FileItem, SNNote } from '@standardnotes/snjs'
import React, { FC, useContext, useEffect, useState } from 'react'
import { TouchableOpacity, View } from 'react-native'
import { ThemeContext } from 'styled-components'
import {
FileDataContainer,
FileDateAndSize,
FileDateAndSizeContainer,
FileDetailsContainer,
FileDetailsWithExtraIconsContainer,
FileIconContainer,
FileName,
uploadedFileItemStyles,
} from './UploadedFileItem.styled'
export type UploadedFileItemProps = {
file: FileItem
note: SNNote
isAttachedToNote: boolean
}
export type TAppStackNavigationProp = AppStackNavigationProp<typeof SCREEN_COMPOSE>['navigation']
export const UploadedFileItem: FC<UploadedFileItemProps> = ({ file, note }) => {
const application = useSafeApplicationContext()
const theme = useContext(ThemeContext)
const { showActionsMenu, handleFileAction } = useFiles({ note })
const [fileName, setFileName] = useState(file.name)
useEffect(() => {
setFileName(file.name)
}, [file.name])
const iconType = application.iconsController.getIconForFileType(file.mimeType)
return (
<TouchableOpacity
onPress={() => {
void handleFileAction({
type: UploadedFileItemActionType.PreviewFile,
payload: file,
})
}}
onLongPress={() => showActionsMenu(file)}
>
<View>
<FileDataContainer>
<FileIconContainer>
<SnIcon type={iconType} width={32} height={32} />
</FileIconContainer>
<FileDetailsWithExtraIconsContainer>
<FileDetailsContainer>
<FileName>{fileName}</FileName>
<FileDateAndSizeContainer>
<FileDateAndSize>
{file.created_at.toLocaleString()} · {formatSizeToReadableString(file.decryptedSize)}
</FileDateAndSize>
{file.protected && (
<SnIcon
type={'lock-filled'}
width={12}
height={12}
fill={theme.stylekitPalSky}
style={uploadedFileItemStyles.lockIcon}
/>
)}
</FileDateAndSizeContainer>
</FileDetailsContainer>
</FileDetailsWithExtraIconsContainer>
</FileDataContainer>
</View>
</TouchableOpacity>
)
}

View File

@@ -0,0 +1,17 @@
import { FileItem } from '@standardnotes/snjs'
export enum UploadedFileItemActionType {
AttachFileToNote,
DetachFileToNote,
DeleteFile,
ShareFile,
DownloadFile,
RenameFile,
ToggleFileProtection,
PreviewFile,
}
export type UploadedFileItemAction = {
type: UploadedFileItemActionType
payload: FileItem
}

View File

@@ -0,0 +1,66 @@
import { StyleSheet } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import styled from 'styled-components/native'
export const useUploadedFilesListStyles = () => {
const insets = useSafeAreaInsets()
return StyleSheet.create({
centeredView: {
justifyContent: 'flex-start',
alignItems: 'center',
flexShrink: 1,
flexGrow: 1,
paddingBottom: insets.bottom,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 4,
},
headerTabContainer: {
flexDirection: 'row',
},
noAttachmentsIconContainer: {
alignItems: 'center',
marginTop: 24,
},
noAttachmentsIcon: {
marginTop: 24,
marginBottom: 24,
},
})
}
export const UploadFilesListContainer = styled.View`
margin-top: 12px;
padding-right: 16px;
padding-left: 16px;
width: 100%;
height: 100%;
`
export const HeaderTabItem = styled.View<{
isActive: boolean
isLeftTab?: boolean
}>`
align-items: center;
padding: 8px;
flex-grow: 1;
background-color: ${({ theme, isActive }) => {
return isActive ? theme.stylekitInfoColor : theme.stylekitInfoContrastColor
}};
border-width: 1px;
border-color: ${({ theme }) => theme.stylekitInfoColor};
border-top-right-radius: ${({ isLeftTab }) => (isLeftTab ? 0 : '8px')};
border-bottom-right-radius: ${({ isLeftTab }) => (isLeftTab ? 0 : '8px')};
border-top-left-radius: ${({ isLeftTab }) => (isLeftTab ? '8px' : 0)};
border-bottom-left-radius: ${({ isLeftTab }) => (isLeftTab ? '8px' : 0)};
margin-left: ${({ isLeftTab }) => (isLeftTab ? 0 : '-1px')};
`
export const TabText = styled.Text<{ isActive: boolean }>`
font-weight: bold;
color: ${({ isActive, theme }) => {
return isActive ? theme.stylekitInfoContrastColor : theme.stylekitInfoColor
}};
`

View File

@@ -0,0 +1,151 @@
import { useNavigation } from '@react-navigation/native'
import { SearchBar } from '@Root/Components/SearchBar'
import { SnIcon } from '@Root/Components/SnIcon'
import { useFiles } from '@Root/Hooks/useFiles'
import { ModalStackNavigationProp } from '@Root/ModalStack'
import { SCREEN_UPLOADED_FILES_LIST } from '@Root/Screens/screens'
import { UploadedFileItem } from '@Root/Screens/UploadedFilesList/UploadedFileItem'
import {
HeaderTabItem,
TabText,
UploadFilesListContainer,
useUploadedFilesListStyles,
} from '@Root/Screens/UploadedFilesList/UploadedFilesList.styled'
import { FileItem } from '@standardnotes/snjs'
import { ICON_ATTACH } from '@Style/Icons'
import { ThemeService } from '@Style/ThemeService'
import React, { FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { FlatList, ListRenderItem, Text, View } from 'react-native'
import FAB from 'react-native-fab'
import IosSearchBar from 'react-native-search-bar'
import AndroidSearchBar from 'react-native-search-box'
import Icon from 'react-native-vector-icons/Ionicons'
import { ThemeContext } from 'styled-components'
export enum Tabs {
AttachedFiles,
AllFiles,
}
type Props = ModalStackNavigationProp<typeof SCREEN_UPLOADED_FILES_LIST>
export const UploadedFilesList: FC<Props> = props => {
const { AttachedFiles, AllFiles } = Tabs
const { note } = props.route.params
const theme = useContext(ThemeContext)
const styles = useUploadedFilesListStyles()
const navigation = useNavigation()
const [currentTab, setCurrentTab] = useState(AllFiles)
const [searchString, setSearchString] = useState('')
const [filesListScrolled, setFilesListScrolled] = useState(false)
const iosSearchBarInputRef = useRef<IosSearchBar>(null)
const androidSearchBarInputRef = useRef<typeof AndroidSearchBar>(null)
const filesListRef = useRef<FlatList>(null)
const { attachedFiles, allFiles, handlePressAttachFile } = useFiles({
note,
})
const filesList = currentTab === Tabs.AttachedFiles ? attachedFiles : allFiles
const filteredList = useMemo(() => {
return searchString
? filesList.filter(file => file.name.toLowerCase().includes(searchString.toLowerCase()))
: filesList
}, [filesList, searchString])
useEffect(() => {
let screenTitle = 'Files'
if (searchString) {
const filesCount = filteredList.length
screenTitle = `${filesCount} search result${filesCount !== 1 ? 's' : ''}`
}
navigation.setOptions({
title: screenTitle,
})
}, [filteredList.length, navigation, searchString])
const scrollListToTop = useCallback(() => {
if (filesListScrolled && filteredList.length > 0) {
filesListRef.current?.scrollToIndex({ animated: false, index: 0 })
setFilesListScrolled(false)
}
}, [filesListScrolled, filteredList.length])
const handleFilter = useCallback(
(textToSearch: string) => {
setSearchString(textToSearch)
scrollListToTop()
},
[scrollListToTop],
)
const { centeredView, header, headerTabContainer, noAttachmentsIcon, noAttachmentsIconContainer } = styles
const onScroll = () => {
if (filesListScrolled) {
return
}
setFilesListScrolled(true)
}
const renderItem: ListRenderItem<FileItem> = ({ item }) => {
return <UploadedFileItem key={item.uuid} file={item} note={note} isAttachedToNote={attachedFiles.includes(item)} />
}
return (
<View style={centeredView}>
<UploadFilesListContainer>
<View style={header}>
<View style={headerTabContainer}>
<HeaderTabItem
isActive={currentTab === AttachedFiles}
isLeftTab={true}
onTouchEnd={() => setCurrentTab(AttachedFiles)}
>
<TabText isActive={currentTab === AttachedFiles}>Attached</TabText>
</HeaderTabItem>
<HeaderTabItem isActive={currentTab === AllFiles} onTouchEnd={() => setCurrentTab(AllFiles)}>
<TabText isActive={currentTab === AllFiles}>All files</TabText>
</HeaderTabItem>
</View>
</View>
<View>
<SearchBar
onChangeText={handleFilter}
onSearchCancel={() => handleFilter('')}
iosSearchBarInputRef={iosSearchBarInputRef}
androidSearchBarInputRef={androidSearchBarInputRef}
/>
</View>
{filteredList.length > 0 ? (
<FlatList
ref={filesListRef}
data={filteredList}
renderItem={renderItem}
keyExtractor={item => item.uuid}
onScroll={onScroll}
/>
) : (
<View style={noAttachmentsIconContainer}>
<SnIcon type={'files-illustration'} style={noAttachmentsIcon} width={72} height={72} />
<Text>{searchString ? 'No files found' : 'No files attached to this note'}</Text>
</View>
)}
<FAB
buttonColor={theme.stylekitInfoColor}
iconTextColor={theme.stylekitInfoContrastColor}
onClickAction={() => handlePressAttachFile(currentTab)}
visible={true}
size={30}
iconTextComponent={<Icon name={ThemeService.nameForIcon(ICON_ATTACH)} />}
/>
</UploadFilesListContainer>
</View>
)
}