-
-
Notifications
You must be signed in to change notification settings - Fork 142
[Mobile] - Add view document features #651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
CorentinTh
merged 25 commits into
papra-hq:main
from
jibraniqbal666:feature/document-view-share
Dec 2, 2025
Merged
Changes from 15 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
7d48bf1
Keep the phone logged in
034e4f0
Added document sheet
3769a58
Make button outline
961e7f4
Fix colors
3217f79
Design accroding to base design
6b56e46
Design accroding to base design
c21b476
Added download and share
d843051
fix the login issue again
cfd3a9a
fix the login issue again
e2cf2a2
Fixed copilot suggestions
6fe33ac
Update document-action-sheet.tsx
jibraniqbal666 ced7d75
Update documents-list.screen.tsx
jibraniqbal666 9b2027b
change to cacheDirectory
4e77ff6
rewrite auth logic
66af4d2
Ran pnpm lint:fix
5202027
Update apps/mobile/src/modules/documents/documents.services.ts
jibraniqbal666 fec701f
Merge branch 'main' into feature/document-view-share
jibraniqbal666 f95d809
fix all lint
a6d87ec
fix type issues
3943c57
fix lint issues
02aef8b
fix types issues
b17dd40
fix types issues
3d71de4
fix lint issues
e42780b
fix type issues
dbcdcc1
fix type issues
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
322 changes: 322 additions & 0 deletions
322
apps/mobile/src/modules/documents-actions/components/document-action-sheet.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,322 @@ | ||
| import type { CoerceDate } from '@/modules/api/api.models'; | ||
| import type { Document } from '@/modules/documents/documents.types'; | ||
| import type { ThemeColors } from '@/modules/ui/theme.constants'; | ||
| import { MaterialCommunityIcons } from '@expo/vector-icons'; | ||
| import * as Sharing from 'expo-sharing'; | ||
| import { | ||
| Modal, | ||
| StyleSheet, | ||
| Text, | ||
| TouchableOpacity, | ||
| TouchableWithoutFeedback, | ||
| View, | ||
| } from 'react-native'; | ||
| import { useAuthClient } from '@/modules/api/providers/api.provider'; | ||
| import { configLocalStorage } from '@/modules/config/config.local-storage'; | ||
| import { fetchDocumentFile } from '@/modules/documents/documents.services'; | ||
| import { useAlert } from '@/modules/ui/providers/alert-provider'; | ||
| import { useThemeColor } from '@/modules/ui/providers/use-theme-color'; | ||
|
|
||
| type DocumentActionSheetProps = { | ||
| visible: boolean; | ||
| document: CoerceDate<Document> | undefined; | ||
| onClose: () => void; | ||
| onView: () => void; | ||
| onDownloadAndShare: () => void; | ||
| }; | ||
|
|
||
| export function DocumentActionSheet({ | ||
| visible, | ||
| document, | ||
| onClose, | ||
| onView, | ||
| onDownloadAndShare, | ||
| }: DocumentActionSheetProps) { | ||
| const themeColors = useThemeColor(); | ||
| const styles = createStyles({ themeColors }); | ||
| const { showAlert } = useAlert(); | ||
| const authClient = useAuthClient(); | ||
|
|
||
| if (document === undefined) { | ||
| return null; | ||
| } | ||
|
|
||
| // Check if document can be viewed in DocumentViewerScreen | ||
| // Supported types: images (image/*) and PDFs (application/pdf) | ||
| const isViewable | ||
| = document.mimeType.startsWith('image/') | ||
| || document.mimeType.startsWith('application/pdf'); | ||
|
|
||
| const formatFileSize = (bytes: number): string => { | ||
| if (bytes < 1024) { | ||
| return `${bytes} B`; | ||
| } | ||
| if (bytes < 1024 * 1024) { | ||
| return `${(bytes / 1024).toFixed(1)} KB`; | ||
| } | ||
| return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; | ||
| }; | ||
|
|
||
| const formatDate = (dateString: string): string => { | ||
| const date = new Date(dateString); | ||
| return date.toLocaleDateString('en-US', { | ||
| month: 'short', | ||
| day: 'numeric', | ||
| year: 'numeric', | ||
| }); | ||
| }; | ||
|
|
||
| const handleDownloadAndShare = async () => { | ||
| const baseUrl = await configLocalStorage.getApiServerBaseUrl(); | ||
|
|
||
| if (!baseUrl) { | ||
| showAlert({ | ||
| title: 'Error', | ||
| message: 'Base URL not found', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const canShare = await Sharing.isAvailableAsync(); | ||
| if (!canShare) { | ||
| showAlert({ | ||
| title: 'Sharing Failed', | ||
| message: 'Sharing is not available on this device. Please share the document manually.', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const fileUri = await fetchDocumentFile({ | ||
| document, | ||
| organizationId: document.organizationId, | ||
| baseUrl, | ||
| authClient, | ||
| }); | ||
|
|
||
| await Sharing.shareAsync(fileUri); | ||
| } catch (error) { | ||
| console.error('Error downloading document file:', error); | ||
| showAlert({ | ||
| title: 'Error', | ||
| message: 'Failed to download document file', | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <Modal | ||
| visible={visible} | ||
| transparent | ||
| animationType="slide" | ||
| onRequestClose={onClose} | ||
| > | ||
| <TouchableWithoutFeedback onPress={onClose}> | ||
| <View style={styles.overlay}> | ||
| <TouchableWithoutFeedback> | ||
| <View style={styles.sheet}> | ||
| {/* Handle bar */} | ||
| <View style={styles.handleBar} /> | ||
|
|
||
| {/* Document info */} | ||
| <View style={styles.documentInfo}> | ||
| <Text style={styles.documentName} numberOfLines={2}> | ||
| {document.name} | ||
| </Text> | ||
|
|
||
| {/* Document details */} | ||
| <View style={styles.detailsContainer}> | ||
| <View style={styles.detailRow}> | ||
| <MaterialCommunityIcons | ||
| name="file" | ||
| size={14} | ||
| color={themeColors.mutedForeground} | ||
| style={styles.detailIcon} | ||
| /> | ||
| <Text style={styles.detailText}>{formatFileSize(document.originalSize)}</Text> | ||
| </View> | ||
| <View style={styles.detailRow}> | ||
| <MaterialCommunityIcons | ||
| name="calendar" | ||
| size={14} | ||
| color={themeColors.mutedForeground} | ||
| style={styles.detailIcon} | ||
| /> | ||
| <Text style={styles.detailText}>{formatDate(document.createdAt)}</Text> | ||
| </View> | ||
| <View style={styles.detailRow}> | ||
| <MaterialCommunityIcons | ||
| name="file-document-outline" | ||
| size={14} | ||
| color={themeColors.mutedForeground} | ||
| style={styles.detailIcon} | ||
| /> | ||
| <Text style={styles.detailText} numberOfLines={1}> | ||
| {document.mimeType.split('/')[1] || document.mimeType} | ||
| </Text> | ||
| </View> | ||
| </View> | ||
| </View> | ||
|
|
||
| {/* Action buttons */} | ||
| <View style={styles.actions}> | ||
| {isViewable && ( | ||
| <TouchableOpacity | ||
| style={styles.actionButton} | ||
| onPress={() => { | ||
| onClose(); | ||
| onView(); | ||
| }} | ||
| activeOpacity={0.7} | ||
| > | ||
| <View style={[styles.actionIcon, styles.viewIcon]}> | ||
| <MaterialCommunityIcons | ||
| name="eye" | ||
| size={20} | ||
| color={themeColors.primary} | ||
| /> | ||
| </View> | ||
| <Text style={styles.actionText}>View</Text> | ||
| </TouchableOpacity> | ||
| )} | ||
|
|
||
| <TouchableOpacity | ||
| style={styles.actionButton} | ||
| onPress={() => { | ||
| onClose(); | ||
| handleDownloadAndShare(); | ||
| }} | ||
| activeOpacity={0.7} | ||
| > | ||
| <View style={[styles.actionIcon, styles.downloadIcon]}> | ||
| <MaterialCommunityIcons | ||
| name="download" | ||
| size={20} | ||
| color={themeColors.primary} | ||
| /> | ||
| </View> | ||
| <Text style={styles.actionText}>Share</Text> | ||
| </TouchableOpacity> | ||
| </View> | ||
|
|
||
| {/* Cancel button */} | ||
| <TouchableOpacity | ||
| style={styles.cancelButton} | ||
| onPress={onClose} | ||
| activeOpacity={0.7} | ||
| > | ||
| <Text style={styles.cancelButtonText}>Cancel</Text> | ||
| </TouchableOpacity> | ||
| </View> | ||
| </TouchableWithoutFeedback> | ||
| </View> | ||
| </TouchableWithoutFeedback> | ||
| </Modal> | ||
| ); | ||
| } | ||
|
|
||
| function createStyles({ themeColors }: { themeColors: ThemeColors }) { | ||
| return StyleSheet.create({ | ||
| overlay: { | ||
| flex: 1, | ||
| backgroundColor: 'rgba(0, 0, 0, 0.5)', | ||
| justifyContent: 'flex-end', | ||
| }, | ||
| sheet: { | ||
| backgroundColor: themeColors.secondaryBackground, | ||
| borderTopLeftRadius: 20, | ||
| borderTopRightRadius: 20, | ||
| paddingBottom: 34, // Safe area for bottom | ||
| paddingTop: 16, | ||
| }, | ||
| handleBar: { | ||
| width: 40, | ||
| height: 4, | ||
| backgroundColor: themeColors.border, | ||
| borderRadius: 2, | ||
| alignSelf: 'center', | ||
| marginBottom: 16, | ||
| }, | ||
| documentInfo: { | ||
| paddingHorizontal: 24, | ||
| paddingVertical: 16, | ||
| borderBottomWidth: 1, | ||
| borderBottomColor: themeColors.border, | ||
| }, | ||
| documentName: { | ||
| fontSize: 16, | ||
| fontWeight: '600', | ||
| color: themeColors.foreground, | ||
| textAlign: 'center', | ||
| marginBottom: 12, | ||
| }, | ||
| detailsContainer: { | ||
| flexDirection: 'row', | ||
| justifyContent: 'center', | ||
| flexWrap: 'wrap', | ||
| gap: 16, | ||
| marginTop: 8, | ||
| }, | ||
| detailRow: { | ||
| flexDirection: 'row', | ||
| alignItems: 'center', | ||
| gap: 6, | ||
| }, | ||
| detailIcon: { | ||
| marginRight: 2, | ||
| }, | ||
| detailText: { | ||
| fontSize: 12, | ||
| color: themeColors.mutedForeground, | ||
| }, | ||
| actions: { | ||
| flexDirection: 'row', | ||
| paddingHorizontal: 24, | ||
| paddingVertical: 16, | ||
| gap: 16, | ||
| }, | ||
| actionButton: { | ||
| flex: 1, | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| paddingVertical: 12, | ||
| backgroundColor: themeColors.secondaryBackground, | ||
| borderRadius: 12, | ||
| }, | ||
| actionIcon: { | ||
| width: 40, | ||
| height: 40, | ||
| borderRadius: 20, | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| marginBottom: 8, | ||
| }, | ||
| viewIcon: { | ||
| backgroundColor: `${themeColors.primary}15`, | ||
| }, | ||
| downloadIcon: { | ||
| backgroundColor: `${themeColors.primary}15`, | ||
| }, | ||
| actionText: { | ||
| fontSize: 14, | ||
| fontWeight: '500', | ||
| color: themeColors.foreground, | ||
| }, | ||
| cancelButton: { | ||
| marginHorizontal: 24, | ||
| marginTop: 12, | ||
| paddingVertical: 16, | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| backgroundColor: 'transparent', | ||
| borderWidth: 1, | ||
| borderColor: themeColors.border, | ||
| borderRadius: 12, | ||
| }, | ||
| cancelButtonText: { | ||
| fontSize: 16, | ||
| fontWeight: '600', | ||
| color: themeColors.foreground, | ||
| }, | ||
| }); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
onDownloadAndShareprop is passed but never used. The component defines its ownhandleDownloadAndSharefunction that is called instead. Consider removing this unused prop or implementing the callback if it's needed for parent component coordination.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in next PR