Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/aab-tools-project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,186 changes: 1,186 additions & 0 deletions .idea/caches/deviceStreaming.xml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/quorum-mobile.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions TEST_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Test Setup Notes

## Known Issue: jest-expo UIManager Error

There's a known issue with `jest-expo` version 52.0.0 where it tries to define properties on `mockNativeModules.UIManager` which doesn't exist, causing:

```
TypeError: Object.defineProperty called on non-object
```

### Solution Options

#### Option 1: Use patch-package (Recommended)

1. Install patch-package:
```bash
yarn add -D patch-package postinstall-postinstall
```

2. Apply the patch manually by editing `node_modules/jest-expo/src/preset/setup.js`:
- Find line 120 (around `Object.keys(mockNativeModules.NativeUnimoduleProxy.viewManagersMetadata).forEach`)
- Add this code BEFORE that line:
```javascript
// Ensure UIManager exists before trying to define properties on it
if (!mockNativeModules.UIManager || typeof mockNativeModules.UIManager !== 'object') {
mockNativeModules.UIManager = {};
}
```

3. Create the patch:
```bash
npx patch-package jest-expo
```

4. Add to package.json scripts:
```json
"postinstall": "patch-package"
```

#### Option 2: Update jest-expo

Try updating to the latest version of jest-expo:
```bash
yarn add -D jest-expo@latest
```

#### Option 3: Manual Fix

Manually edit `node_modules/jest-expo/src/preset/setup.js` and add the UIManager check before line 120. This fix will be lost on `yarn install`, so Option 1 is recommended.
21 changes: 19 additions & 2 deletions app/(onboarding)/profile-setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useTheme } from '@/theme';
import { useOnboarding } from '@/context';
import { OnboardingLayout, StepNavigation } from '@/components/onboarding';
import { IconSymbol } from '@/components/ui/IconSymbol';
import { stripImageMetadata } from '@/utils/imageMetadata';

export default function ProfileSetupScreen() {
const { theme } = useTheme();
Expand Down Expand Up @@ -65,10 +66,18 @@ export default function ProfileSetupScreen() {
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
exif: false,
});

if (!result.canceled && result.assets[0]) {
setProfileImage(result.assets[0].uri);
try {
// Strip metadata from the image
const strippedUri = await stripImageMetadata(result.assets[0].uri);
setProfileImage(strippedUri);
} catch (error) {
console.warn('[ProfileSetup] Failed to strip metadata, using original:', error);
setProfileImage(result.assets[0].uri);
}
}
};

Expand All @@ -82,10 +91,18 @@ export default function ProfileSetupScreen() {
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
exif: false,
});

if (!result.canceled && result.assets[0]) {
setProfileImage(result.assets[0].uri);
try {
// Strip metadata from the image
const strippedUri = await stripImageMetadata(result.assets[0].uri);
setProfileImage(strippedUri);
} catch (error) {
console.warn('[ProfileSetup] Failed to strip metadata, using original:', error);
setProfileImage(result.assets[0].uri);
}
}
};

Expand Down
38 changes: 35 additions & 3 deletions components/ProfileModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useQueryClient } from '@tanstack/react-query';
import * as Clipboard from 'expo-clipboard';
import * as ImagePicker from 'expo-image-picker';
import React from 'react';
import { stripImageMetadata } from '@/utils/imageMetadata';
import {
ActivityIndicator,
Alert,
Expand Down Expand Up @@ -239,14 +240,45 @@ export default function ProfileModal({ visible, onClose }: ProfileModalProps) {
aspect: [1, 1],
quality: 0.8,
base64: true,
exif: false,
});

if (!result.canceled && result.assets[0]) {
const asset = result.assets[0];
// Convert to data URI for storage
const mimeType = asset.mimeType || 'image/jpeg';
const profileImage = `data:${mimeType};base64,${asset.base64}`;
updateProfile({ profileImage });

// Strip metadata before converting to base64
let profileImage: string;
try {
if (asset.uri) {
const strippedUri = await stripImageMetadata(asset.uri);
const response = await fetch(strippedUri);
const blob = await response.blob();
profileImage = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
const result = reader.result as string;
resolve(result); // Already includes data: prefix
};
reader.onerror = () => reject(new Error('Failed to read stripped image'));
reader.readAsDataURL(blob);
});
} else if (asset.base64) {
// Fallback if URI not available
profileImage = `data:${mimeType};base64,${asset.base64}`;
} else {
throw new Error('No image data available');
}
} catch (error) {
console.warn('[ProfileModal] Failed to strip metadata, using original:', error);
// Fallback to original base64
profileImage = asset.base64
? `data:${mimeType};base64,${asset.base64}`
: '';
}

if (profileImage) {
updateProfile({ profileImage });

// Broadcast profile image update to all spaces
const spaces = getAllSpaces();
Expand Down
49 changes: 49 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Jest setup file for React Native/Expo
// This file runs before jest-expo's setup

// Fix for jest-expo bug: ensure UIManager exists before jest-expo tries to use it
// jest-expo's setup.js requires 'react-native/Libraries/BatchedBridge/NativeModules'
// and tries to define properties on mockNativeModules.UIManager, but UIManager doesn't exist

// Pre-require NativeModules and ensure UIManager exists
// This must happen synchronously before jest-expo's setup runs
const NativeModules = require('react-native/Libraries/BatchedBridge/NativeModules');

// Ensure UIManager exists as an object (jest-expo will try to define properties on it)
if (!NativeModules.UIManager || typeof NativeModules.UIManager !== 'object') {
Object.defineProperty(NativeModules, 'UIManager', {
value: {},
writable: true,
configurable: true,
enumerable: true,
});
}

// Ensure NativeUnimoduleProxy.viewManagersMetadata exists (jest-expo iterates over this)
if (!NativeModules.NativeUnimoduleProxy) {
Object.defineProperty(NativeModules, 'NativeUnimoduleProxy', {
value: { viewManagersMetadata: {} },
writable: true,
configurable: true,
enumerable: true,
});
} else if (!NativeModules.NativeUnimoduleProxy.viewManagersMetadata) {
NativeModules.NativeUnimoduleProxy.viewManagersMetadata = {};
}

// Ensure global objects exist for expo-modules-core
if (typeof globalThis !== 'undefined') {
if (!globalThis.expo) {
globalThis.expo = {};
}
if (!globalThis.expo.EventEmitter) {
// Mock EventEmitter for expo-modules-core
globalThis.expo.EventEmitter = class EventEmitter {
constructor() {}
addListener() { return this; }
removeListener() { return this; }
removeAllListeners() { return this; }
emit() { return false; }
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Automatically generated file. DO NOT MODIFY
*/
package expo.modules.quorumcrypto;

public final class BuildConfig {
public static final boolean DEBUG = false;
public static final String LIBRARY_PACKAGE_NAME = "expo.modules.quorumcrypto";
public static final String BUILD_TYPE = "release";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="expo.modules.quorumcrypto" >

<uses-sdk android:minSdkVersion="24" />

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": 3,
"artifactType": {
"type": "AAPT_FRIENDLY_MERGED_MANIFESTS",
"kind": "Directory"
},
"applicationId": "expo.modules.quorumcrypto",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"outputFile": "AndroidManifest.xml"
}
],
"elementType": "File"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
aarFormatVersion=1.0
aarMetadataVersion=1.0
minCompileSdk=1
minCompileSdkExtension=0
minAndroidGradlePluginVersion=1.0.0
coreLibraryDesugaringEnabled=false
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
# the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
# will be ignored by new version of the Android plugin for Gradle.

# Optimizations: If you don't want to optimize, use the proguard-android.txt configuration file
# instead of this one, which turns off the optimization flags.
-allowaccessmodification

# Preserve some attributes that may be required for reflection.
-keepattributes AnnotationDefault,
EnclosingMethod,
InnerClasses,
RuntimeVisibleAnnotations,
RuntimeVisibleParameterAnnotations,
RuntimeVisibleTypeAnnotations,
Signature

-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
-keep public class com.google.android.vending.licensing.ILicensingService
-dontnote com.android.vending.licensing.ILicensingService
-dontnote com.google.vending.licensing.ILicensingService
-dontnote com.google.android.vending.licensing.ILicensingService

# For native methods, see https://www.guardsquare.com/manual/configuration/examples#native
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}

# Keep setters in Views so that animations can still work.
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick.
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}

# For enumeration classes, see https://www.guardsquare.com/manual/configuration/examples#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}

# Preserve annotated Javascript interface methods.
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}

# The support libraries contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontnote android.support.**
-dontnote androidx.**
-dontwarn android.support.**
-dontwarn androidx.**

# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}

# These classes are duplicated between android.jar and org.apache.http.legacy.jar.
-dontnote org.apache.http.**
-dontnote android.net.http.**

# These classes are duplicated between android.jar and core-lambda-stubs.jar.
-dontnote java.lang.invoke.**
Loading