Skip to content

Commit 1c5317b

Browse files
committed
fix(mobile): improve encryption performance and fix FAB touch handling
1 parent bd238e9 commit 1c5317b

File tree

3 files changed

+49
-47
lines changed

3 files changed

+49
-47
lines changed

apps/mobile/v1/src/lib/encryption/core/aes.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,23 @@ let createCipheriv: any = null;
1313
let createDecipheriv: any = null;
1414
let QuickCryptoBuffer: any = null;
1515

16-
// Try to get Buffer from global scope (polyfilled by react-native-quick-crypto)
17-
try {
18-
// @ts-ignore - Buffer should be global after react-native-quick-crypto is loaded
19-
QuickCryptoBuffer = global.Buffer || Buffer;
20-
} catch (e) {
21-
// Buffer not available globally
22-
}
23-
2416
try {
2517
const quickCrypto = require('react-native-quick-crypto');
2618

2719
// Get the cipher functions
2820
createCipheriv = quickCrypto.createCipheriv;
2921
createDecipheriv = quickCrypto.createDecipheriv;
3022

23+
// Get Buffer from @craftzdog/react-native-buffer (same source as react-native-quick-crypto uses)
24+
try {
25+
const { Buffer: RNBuffer } = require('@craftzdog/react-native-buffer');
26+
QuickCryptoBuffer = RNBuffer;
27+
} catch (e) {
28+
// Fallback to global Buffer if available
29+
// @ts-ignore
30+
QuickCryptoBuffer = global.Buffer;
31+
}
32+
3133
if (createCipheriv && createDecipheriv && QuickCryptoBuffer) {
3234
console.log('[Encryption] Native AES-GCM available - will use fast implementation');
3335
} else {

apps/mobile/v1/src/screens/FolderNotes/components/NotesList/index.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2-
import { ActivityIndicator, Alert, Animated, DeviceEventEmitter, InteractionManager, Pressable, RefreshControl, StyleSheet, View } from 'react-native';
2+
import { ActivityIndicator, Alert, Animated, DeviceEventEmitter, InteractionManager, Pressable, RefreshControl, StyleSheet, TouchableOpacity, View } from 'react-native';
33
import { useSafeAreaInsets } from 'react-native-safe-area-context';
44

55
import { useUser } from '@clerk/clerk-expo';
@@ -757,26 +757,22 @@ export default function NotesList({ navigation, route, renderHeader, scrollY: pa
757757

758758
{/* Floating Action Button - Only visible when Create Note button scrolls off screen */}
759759
{viewType !== 'trash' && isCreateNoteButtonOffScreen && (
760-
<Animated.View
760+
<Pressable
761761
style={[
762762
styles.fab,
763763
{
764764
bottom: insets.bottom + 20,
765765
right: 20,
766-
transform: [{ translateY: fabTranslateY }],
767766
}
768767
]}
768+
onPress={() => navigation?.navigate('CreateNote', { folderId: route?.params?.folderId })}
769769
>
770-
<GlassView glassEffectStyle="regular" style={[styles.fabGlass, { backgroundColor: theme.isDark ? 'rgba(255, 255, 255, 0.01)' : 'rgba(0, 0, 0, 0.01)' }]}>
771-
<Pressable
772-
style={styles.fabButton}
773-
onPress={() => navigation?.navigate('CreateNote', { folderId: route?.params?.folderId })}
774-
android_ripple={{ color: 'rgba(255, 255, 255, 0.3)', radius: 28 }}
775-
>
770+
<GlassView glassEffectStyle="regular" style={styles.fabGlass} pointerEvents="none">
771+
<View style={[styles.fabButton, { backgroundColor: theme.isDark ? 'rgba(255, 255, 255, 0.01)' : 'rgba(0, 0, 0, 0.01)' }]}>
776772
<SquarePen size={20} color={theme.colors.foreground} />
777-
</Pressable>
773+
</View>
778774
</GlassView>
779-
</Animated.View>
775+
</Pressable>
780776
)}
781777
</View>
782778
);
@@ -803,7 +799,11 @@ const styles = StyleSheet.create({
803799
},
804800
fab: {
805801
position: 'absolute',
806-
elevation: 4,
802+
width: 56,
803+
height: 56,
804+
borderRadius: 28,
805+
zIndex: 999,
806+
elevation: 8,
807807
shadowColor: '#000',
808808
shadowOffset: { width: 0, height: 2 },
809809
shadowOpacity: 0.25,
@@ -814,13 +814,11 @@ const styles = StyleSheet.create({
814814
height: 56,
815815
borderRadius: 28,
816816
overflow: 'hidden',
817-
backgroundColor: 'rgba(0, 0, 0, 0.01)',
818817
},
819818
fabButton: {
820819
width: 56,
821820
height: 56,
822-
borderRadius: 28,
823-
justifyContent: 'center',
824821
alignItems: 'center',
822+
justifyContent: 'center',
825823
},
826824
});

apps/mobile/v1/src/services/api/encryption.ts

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,14 @@ export async function decryptNote(note: Note, userId: string): Promise<Note> {
4949

5050
// Yield to main thread to prevent UI blocking
5151
const yieldToMain = (): Promise<void> => {
52-
return new Promise(resolve => setTimeout(resolve, 0));
52+
return new Promise(resolve => {
53+
// Use requestAnimationFrame for smoother UI, fallback to setTimeout
54+
if (typeof requestAnimationFrame !== 'undefined') {
55+
requestAnimationFrame(() => setTimeout(resolve, 0));
56+
} else {
57+
setTimeout(resolve, 0);
58+
}
59+
});
5360
};
5461

5562
/**
@@ -66,32 +73,27 @@ export async function decryptNotes(
6673
const decryptStart = performance.now();
6774
console.log(`[PERF] Starting decryption of ${notes.length} notes...`);
6875

69-
const BATCH_SIZE = 10; // Process 10 notes at a time
76+
const BATCH_SIZE = 3; // Process 3 notes at a time for better UI responsiveness
7077
const result: Note[] = [];
7178

7279
try {
73-
// Process notes in batches to prevent UI blocking
74-
for (let i = 0; i < notes.length; i += BATCH_SIZE) {
75-
const batch = notes.slice(i, i + BATCH_SIZE);
76-
77-
const decryptedBatch = await Promise.all(
78-
batch.map(async (note) => {
79-
try {
80-
return await decryptNote(note, userId);
81-
} catch {
82-
return {
83-
...note,
84-
title: note.title || '[Encrypted - Unable to decrypt]',
85-
content: note.content || '[This note could not be decrypted]',
86-
};
87-
}
88-
})
89-
);
90-
91-
result.push(...decryptedBatch);
92-
93-
// Yield to main thread between batches to keep UI responsive
94-
if (i + BATCH_SIZE < notes.length) {
80+
// Process notes one at a time with yields for maximum UI responsiveness
81+
for (let i = 0; i < notes.length; i++) {
82+
const note = notes[i];
83+
84+
try {
85+
const decrypted = await decryptNote(note, userId);
86+
result.push(decrypted);
87+
} catch {
88+
result.push({
89+
...note,
90+
title: note.title || '[Encrypted - Unable to decrypt]',
91+
content: note.content || '[This note could not be decrypted]',
92+
});
93+
}
94+
95+
// Yield to main thread every 3 notes to keep UI responsive
96+
if ((i + 1) % BATCH_SIZE === 0 && i + 1 < notes.length) {
9597
await yieldToMain();
9698
}
9799
}

0 commit comments

Comments
 (0)