Insole-production time tracker exported from the Create/Anything AI platform. Baseline snapshot before any reverse-engineering or cleanup. - apps/mobile: Expo Router app (iOS/Android/web), the only workspace - publisher/: standalone OpenNext/AWS deploy tooling for the web side - Backend (/api/tasks, /api/logs + DB) lives remotely, not in this repo
300 lines
8.3 KiB
TypeScript
300 lines
8.3 KiB
TypeScript
import type { ExistingContact, ContactQuery } from 'expo-contacts';
|
|
import { Fields, SortTypes } from 'expo-contacts/src/Contacts';
|
|
import Alert from './alerts.web';
|
|
import * as Notifications from 'expo-contacts';
|
|
const { PermissionStatus } = Notifications;
|
|
|
|
export { PermissionStatus, Fields, SortTypes };
|
|
|
|
const fakeContacts: ExistingContact[] = [
|
|
{
|
|
id: '1',
|
|
contactType: 'person',
|
|
name: 'John Doe',
|
|
firstName: 'John',
|
|
lastName: 'Doe',
|
|
phoneNumbers: [
|
|
{ number: '+1 (555) 123-4567', isPrimary: true, label: 'mobile' },
|
|
{ number: '+1 (555) 987-6543', isPrimary: false, label: 'home' },
|
|
],
|
|
emails: [
|
|
{ email: 'john.doe@example.com', isPrimary: true, label: 'work' },
|
|
{ email: 'john.personal@gmail.com', isPrimary: false, label: 'personal' },
|
|
],
|
|
addresses: [
|
|
{
|
|
street: '123 Main St',
|
|
city: 'New York',
|
|
region: 'NY',
|
|
postalCode: '10001',
|
|
country: 'USA',
|
|
label: 'home',
|
|
},
|
|
],
|
|
birthday: { day: 15, month: 5, year: 1990 },
|
|
note: 'Met at conference',
|
|
},
|
|
{
|
|
id: '2',
|
|
contactType: 'person',
|
|
name: 'Jane Smith',
|
|
firstName: 'Jane',
|
|
lastName: 'Smith',
|
|
phoneNumbers: [{ number: '+1 (555) 234-5678', isPrimary: true, label: 'mobile' }],
|
|
emails: [{ email: 'jane.smith@company.com', isPrimary: true, label: 'work' }],
|
|
addresses: [
|
|
{
|
|
street: '456 Oak Ave',
|
|
city: 'Los Angeles',
|
|
region: 'CA',
|
|
postalCode: '90210',
|
|
country: 'USA',
|
|
label: 'home',
|
|
},
|
|
],
|
|
birthday: { day: 3, month: 12, year: 1985 },
|
|
note: 'College friend',
|
|
},
|
|
{
|
|
id: '3',
|
|
contactType: 'person',
|
|
name: 'Bob Johnson',
|
|
firstName: 'Bob',
|
|
lastName: 'Johnson',
|
|
phoneNumbers: [{ number: '+1 (555) 345-6789', isPrimary: true, label: 'mobile' }],
|
|
emails: [{ email: 'bob.johnson@email.com', isPrimary: true, label: 'personal' }],
|
|
addresses: [],
|
|
birthday: { day: 22, month: 8, year: 1992 },
|
|
note: 'Neighbor',
|
|
},
|
|
{
|
|
id: '4',
|
|
contactType: 'person',
|
|
name: 'Alice Williams',
|
|
firstName: 'Alice',
|
|
lastName: 'Williams',
|
|
phoneNumbers: [
|
|
{ number: '+1 (555) 456-7890', isPrimary: true, label: 'mobile' },
|
|
{ number: '+1 (555) 111-2222', isPrimary: false, label: 'work' },
|
|
],
|
|
emails: [{ email: 'alice.williams@startup.com', isPrimary: true, label: 'work' }],
|
|
addresses: [
|
|
{
|
|
street: '789 Pine St',
|
|
city: 'San Francisco',
|
|
region: 'CA',
|
|
postalCode: '94102',
|
|
country: 'USA',
|
|
label: 'work',
|
|
},
|
|
],
|
|
birthday: { day: 10, month: 3, year: 1988 },
|
|
note: 'Business partner',
|
|
},
|
|
{
|
|
id: '5',
|
|
contactType: 'person',
|
|
name: 'Charlie Brown',
|
|
firstName: 'Charlie',
|
|
lastName: 'Brown',
|
|
phoneNumbers: [{ number: '+1 (555) 567-8901', isPrimary: true, label: 'mobile' }],
|
|
emails: [{ email: 'charlie.brown@gmail.com', isPrimary: true, label: 'personal' }],
|
|
addresses: [],
|
|
birthday: { day: 18, month: 11, year: 1995 },
|
|
note: 'Gym buddy',
|
|
},
|
|
];
|
|
|
|
let permissionStatus = {
|
|
status: PermissionStatus.UNDETERMINED,
|
|
expires: 'never',
|
|
granted: false,
|
|
canAskAgain: true,
|
|
};
|
|
|
|
// since we polyfill fake contacts, we always return true
|
|
export const isAvailableAsync = async () => {
|
|
return true;
|
|
};
|
|
|
|
export const requestPermissionsAsync = async () => {
|
|
if (permissionStatus.status === PermissionStatus.GRANTED) {
|
|
return permissionStatus;
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
Alert.alert(
|
|
'"Expo Go" Would Like to Access Your Contacts',
|
|
'Allow Expo projects to access your contacts',
|
|
[
|
|
{
|
|
text: "Don't Allow",
|
|
onPress: () => {
|
|
permissionStatus = {
|
|
status: PermissionStatus.DENIED,
|
|
expires: 'never',
|
|
granted: false,
|
|
canAskAgain: true,
|
|
};
|
|
resolve(permissionStatus);
|
|
},
|
|
style: 'default',
|
|
},
|
|
{
|
|
text: 'Continue',
|
|
onPress: () => {
|
|
permissionStatus = {
|
|
status: PermissionStatus.GRANTED,
|
|
expires: 'never',
|
|
granted: true,
|
|
canAskAgain: false,
|
|
};
|
|
resolve(permissionStatus);
|
|
},
|
|
style: 'default',
|
|
},
|
|
]
|
|
);
|
|
});
|
|
};
|
|
|
|
export const getPermissionsAsync = async () => {
|
|
return permissionStatus;
|
|
};
|
|
|
|
export const getContactsAsync = async (options: ContactQuery = {}) => {
|
|
const { sort = SortTypes.FirstName, pageSize, pageOffset } = options;
|
|
|
|
let contacts = [...fakeContacts];
|
|
|
|
if (sort === SortTypes.FirstName) {
|
|
contacts.sort((a, b) => (a.firstName || '').localeCompare(b.firstName || ''));
|
|
} else if (sort === SortTypes.LastName) {
|
|
contacts.sort((a, b) => (a.lastName || '').localeCompare(b.lastName || ''));
|
|
}
|
|
|
|
if (pageSize && pageOffset !== undefined) {
|
|
const startIndex = pageOffset * pageSize;
|
|
contacts = contacts.slice(startIndex, startIndex + pageSize);
|
|
}
|
|
|
|
return {
|
|
data: contacts,
|
|
hasNextPage: false,
|
|
hasPreviousPage: false,
|
|
total: fakeContacts.length,
|
|
};
|
|
};
|
|
|
|
export const getContactByIdAsync = async (id: string, _options: ContactQuery = {}) => {
|
|
const contact = fakeContacts.find((c) => c.id === id);
|
|
if (!contact) {
|
|
throw new Error(`Contact with id ${id} not found`);
|
|
}
|
|
|
|
return contact;
|
|
};
|
|
|
|
export const addContactAsync = async (contact: ExistingContact) => {
|
|
const newContact: ExistingContact = {
|
|
id: Date.now().toString(),
|
|
contactType: contact.contactType || 'person',
|
|
name: contact.name || '',
|
|
firstName: contact.firstName || '',
|
|
lastName: contact.lastName || '',
|
|
phoneNumbers: contact.phoneNumbers || [],
|
|
emails: contact.emails || [],
|
|
addresses: contact.addresses || [],
|
|
birthday: contact.birthday,
|
|
note: contact.note || '',
|
|
middleName: contact.middleName,
|
|
maidenName: contact.maidenName,
|
|
namePrefix: contact.namePrefix,
|
|
nameSuffix: contact.nameSuffix,
|
|
nickname: contact.nickname,
|
|
phoneticFirstName: contact.phoneticFirstName,
|
|
phoneticMiddleName: contact.phoneticMiddleName,
|
|
phoneticLastName: contact.phoneticLastName,
|
|
company: contact.company,
|
|
jobTitle: contact.jobTitle,
|
|
department: contact.department,
|
|
imageAvailable: contact.imageAvailable,
|
|
image: contact.image,
|
|
rawImage: contact.rawImage,
|
|
dates: contact.dates,
|
|
relationships: contact.relationships,
|
|
instantMessageAddresses: contact.instantMessageAddresses,
|
|
urlAddresses: contact.urlAddresses,
|
|
nonGregorianBirthday: contact.nonGregorianBirthday,
|
|
socialProfiles: contact.socialProfiles,
|
|
isFavorite: contact.isFavorite,
|
|
};
|
|
|
|
fakeContacts.push(newContact);
|
|
Alert.alert('Success', 'Contact added successfully!');
|
|
return newContact.id;
|
|
};
|
|
|
|
export const updateContactAsync = async (contact: ExistingContact) => {
|
|
const index = fakeContacts.findIndex((c) => c.id === contact.id);
|
|
if (index === -1) {
|
|
throw new Error(`Contact with id ${contact.id} not found`);
|
|
}
|
|
|
|
fakeContacts[index] = { ...fakeContacts[index], ...contact };
|
|
return contact.id;
|
|
};
|
|
|
|
export const removeContactAsync = async (contactId: string) => {
|
|
const index = fakeContacts.findIndex((c) => c.id === contactId);
|
|
if (index === -1) {
|
|
throw new Error(`Contact with id ${contactId} not found`);
|
|
}
|
|
|
|
fakeContacts.splice(index, 1);
|
|
setTimeout(() => {
|
|
Alert.alert('Success', 'Contact deleted successfully!');
|
|
}, 500);
|
|
return contactId;
|
|
};
|
|
|
|
const _createNoOpAsync = async () => {
|
|
Alert.alert('Not supported in the builder', 'Please use the Expo Go app to test this feature');
|
|
return { type: 'custom', data: null };
|
|
};
|
|
|
|
export const presentContactPickerAsync = async () => {
|
|
return _createNoOpAsync();
|
|
};
|
|
export const getGroupsAsync = async () => {
|
|
return _createNoOpAsync();
|
|
};
|
|
export const createGroupAsync = async () => {
|
|
return _createNoOpAsync();
|
|
};
|
|
export const removeGroupAsync = async () => {
|
|
return _createNoOpAsync();
|
|
};
|
|
export const updateGroupNameAsync = async () => {
|
|
return _createNoOpAsync();
|
|
};
|
|
|
|
export default {
|
|
Fields,
|
|
SortTypes,
|
|
PermissionStatus,
|
|
isAvailableAsync,
|
|
requestPermissionsAsync,
|
|
getPermissionsAsync,
|
|
getContactsAsync,
|
|
getContactByIdAsync,
|
|
addContactAsync,
|
|
updateContactAsync,
|
|
removeContactAsync,
|
|
presentContactPickerAsync,
|
|
getGroupsAsync,
|
|
createGroupAsync,
|
|
removeGroupAsync,
|
|
updateGroupNameAsync,
|
|
};
|