Skip to content
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

Add in ability to edit pins #95

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import NavigationService from './utils/NavigationService';
import Onboarding from './containers/Onboarding';
import Navigation from './nav';
import reducer from './reducers';
import { createDatabases } from './apis/database';
import { createOrUpdateDatabase } from './apis/database';
import { initialize } from './apis';
import { restore } from './actions/authentication';
import { setup } from './actions/connectivity';
Expand Down Expand Up @@ -45,7 +45,7 @@ class App extends Component {
componentDidMount() {
Promise.all([
...this.loadFontsAsync(),
createDatabases(),
createOrUpdateDatabase(),
I18n.initAsync(),
store.dispatch(positionActions.initialize()),
store.dispatch(
Expand Down
113 changes: 96 additions & 17 deletions actions/locations.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Sentry from 'sentry-expo';
import { Alert } from 'react-native';
import i18n from '../assets/i18n/i18n';
import * as apis from '../apis';
import { requestPushPermission } from './permissions';
import { containing } from './regions';
Expand All @@ -15,6 +17,14 @@ function receiveLocation(location) {
};
}

export const REMOVE_LOCATION = 'REMOVE_LOCATION';
function removeLocation(locationKey) {
return {
type: REMOVE_LOCATION,
locationKey,
};
}

export const CLEAR_LOCATIONS = 'CLEAR_LOCATIONS';
export function clearLocations() {
return {
Expand All @@ -40,6 +50,8 @@ function wrapper(work, location) {
await work;
await dispatch(updateUploadStatus(location, true));
dispatch(uploaded(location.key));
const uploadedLocation = { ...location, uploaded: true };
dispatch(receiveLocation(uploadedLocation));
} catch (err) {
Sentry.captureException(err, {
extra: {
Expand Down Expand Up @@ -69,6 +81,34 @@ export function restoreLocalLocations() {
};
}

export function deleteLocalLocation(locationKey) {
return async (dispatch) => {
const result = await new Promise((resolve) => {
Alert.alert(
i18n.t('location/delete'),
i18n.t('location/confirmDelete'),
[
{ text: i18n.t('button/cancel'), style: 'cancel', onPress: () => resolve(false) },
{ text: i18n.t('button/delete'), onPress: () => resolve(true) },
],
{
cancelable: true,
onDismiss: () => resolve(false),
}
);
});

if (result) {
database
.deleteLocation(locationKey)
.then(() => dispatch(removeLocation(locationKey)))
.catch((err) => {
Sentry.captureException(err);
});
}
};
}

export function fetchLocation(locationKey) {
return async (dispatch, getState) => {
const {
Expand Down Expand Up @@ -96,28 +136,13 @@ export function fetchAllLocationData(locationKey) {
};
}

export function createLocation(options) {
const { latitude, longitude, resources, status } = options;

function pushLocation(localLocation, locationData, apiCall) {
return async (dispatch, getState) => {
const {
authentication: { regionKey: hasRegion },
connected,
onboarding: { hasAddedLocation },
} = getState();

if (!hasAddedLocation) {
dispatch(setCompleted(COMPLETED_KEYS.hasAddedLocation));
}

const locationData = {
latitude,
longitude,
resources,
status,
};

const localLocation = await database.addLocalLocation(locationData);
const { key } = localLocation;

dispatch(offline(key));
Expand All @@ -134,13 +159,67 @@ export function createLocation(options) {
return;
}

const { created: location, saved } = await apis.createLocation(regionKey, locationData, key);
const { created: location, saved, outOfDate } = await apiCall(regionKey, key, localLocation);

if (outOfDate) {
Alert.alert(i18n.t('location/errorUpdate'), i18n.t('location/errorUpdateDescription'), [
{
text: i18n.t('button/ok'),
onPress: () => {},
},
]);
}

await dispatch(wrapper(saved, location));
}
};
}

export function updateLocation(options, oldLocationKey) {
const { latitude, longitude, resources, status } = options;

return async (dispatch, getState) => {
const { locations } = getState();

const oldLocation = locations[oldLocationKey];

const locationData = {
latitude,
longitude,
resources,
status,
};

const localLocation = await database.updateLocalLocation(locationData, oldLocation);

dispatch(pushLocation(localLocation, locationData, apis.updateLocation));
};
}

export function createLocation(options) {
const { latitude, longitude, resources, status } = options;

return async (dispatch, getState) => {
const {
onboarding: { hasAddedLocation },
} = getState();

if (!hasAddedLocation) {
dispatch(setCompleted(COMPLETED_KEYS.hasAddedLocation));
}

const locationData = {
latitude,
longitude,
resources,
status,
};

const localLocation = await database.addLocalLocation(locationData);
dispatch(pushLocation(localLocation, locationData, apis.createLocation));
};
}

export function pushLocalLocations() {
return async (dispatch, getState) => {
const {
Expand Down
105 changes: 91 additions & 14 deletions apis/database.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { SQLite } from 'expo';
import { SQLite, SecureStore } from 'expo';
import Sentry from 'sentry-expo';
import moment from 'moment';
import { pushRef } from './index';
import { convertArrayToLocations, convertToLocation, createLocationObject, saveCoordinates } from '../utils/database';

const DATABASE_VERSION_KEY = 'DATABASE_VERSION_KEY';
const DATABASE_VERSION = '2';

export function openDatabase(databaseName = 'locations.1.db') {
return SQLite.openDatabase(databaseName);
}
Expand All @@ -23,35 +26,75 @@ export function executeTransaction(statement, args = null) {
});
}

export function createDatabases() {
function createDatabases(table = 'locations') {
return executeTransaction(
'create table if not exists locations (id integer primary key not null, key text, coordinateKey text, createdAt text, resources text, status text, uploaded int)'
`CREATE TABLE IF NOT EXISTS ${table} (id integer primary key not null, key text, coordinateKey text, createdAt text, resources text, status text, uploaded int, updated int)`
);
}

export function clearDatabase() {
return executeTransaction('drop table locations');
return executeTransaction('DROP TABLE locations');
}

export async function backupDatabase() {
await executeTransaction('DROP TABLE backup');
await createDatabases('backup');
return executeTransaction('INSERT INTO backup SELECT * FROM locations');
}

export async function restoreBackup() {
await clearDatabase();
await createDatabases();
return executeTransaction('INSERT INTO locations SELECT * FROM backup');
}

export function deleteLocation(key) {
return executeTransaction('DELETE FROM locations WHERE key = ?', [key]);
}

export function updateUploadStatus(key, isUploaded) {
return executeTransaction('update locations set uploaded = ? where key = ?', [isUploaded, key]);
return executeTransaction('UPDATE locations SET uploaded = ? WHERE key = ?', [isUploaded, key]);
}

export function updateLocalLocation(options) {
const { resources, status, key } = options;
// Uncomment this if user is ever able to change location position
// SecureStore.setItemAsync(key, JSON.parse({ longitude, latitude }));
export function replaceLocalLocationWithRemote(remoteLocation) {
const { key, latitude, longitude, updated, status, resources } = remoteLocation;

saveCoordinates(key, latitude, longitude);

return executeTransaction('UPDATE locations SET status = ?, resources = ?, updated = ?, uploaded = 1 WHERE key = ?', [
status,
JSON.stringify(resources),
updated,
key,
]);
}

export async function updateLocalLocation(options, oldLocation) {
const { key, updated = 0 } = oldLocation;
const { latitude, longitude, resources, status } = options;

saveCoordinates(key, latitude, longitude);

const locationObject = createLocationObject(key, {
...oldLocation,
...options,
uploaded: false,
updated: oldLocation.uploaded ? oldLocation.updated + 1 : oldLocation.updated,
});

return executeTransaction('update locations set resources = ?, status = ?, uploaded = 0 where key = ?', [
await executeTransaction('UPDATE locations SET resources = ?, status = ?, uploaded = 0, updated = ? WHERE key = ?', [
resources,
status,
updated + 1,
key,
]);

return locationObject;
}

// fetches individual location
export async function fetchLocalLocation(locationKey) {
const rows = await executeTransaction('select * from locations where key = ?', [locationKey]);
const rows = await executeTransaction('SELECT * FROM locations WHERE key = ?', [locationKey]);
if (rows.length < 1) {
return false;
}
Expand All @@ -61,7 +104,7 @@ export async function fetchLocalLocation(locationKey) {

// fetch all locations in db
export async function fetchLocalLocations(offlineOnly = false) {
const query = offlineOnly ? 'select * from locations where uploaded = 0' : 'select * from locations';
const query = offlineOnly ? 'SELECT * FROM locations WHERE uploaded = 0' : 'SELECT * FROM locations';

const result = await executeTransaction(query);
// This is the shape of the data and cannot really be changed
Expand All @@ -80,9 +123,43 @@ export async function addLocalLocation(locationData) {

const createdAt = moment.utc(locationObject.created).toISOString();
await executeTransaction(
'insert into locations (key, coordinateKey, createdAt, resources, status, uploaded) values (?, ?, ?, ?, ?, ?)',
[key, key, createdAt, resourcesString, status, 0]
'INSERT INTO locations (key, coordinateKey, createdAt, resources, status, uploaded, updated) VALUES (?, ?, ?, ?, ?, ?, ?)',
[key, key, createdAt, resourcesString, status, 0, 0]
);

return locationObject;
}

export async function createOrUpdateDatabase() {
const version = (await SecureStore.getItemAsync(DATABASE_VERSION_KEY)) || 0;

await createDatabases();
smaclell marked this conversation as resolved.
Show resolved Hide resolved

if (version === DATABASE_VERSION) {
return Promise.resolve();
smaclell marked this conversation as resolved.
Show resolved Hide resolved
}

try {
const locations = await fetchLocalLocations();

await backupDatabase();
await clearDatabase();
await createDatabases();

const transactions = locations.map((location) => {
const { key, createdAt, resources, status, uploaded = 0, updated = 0 } = location;

return executeTransaction(
'INSERT INTO locations (key, coordinateKey, createdAt, resources, status, uploaded, updated) VALUES (?, ?, ?, ?, ?, ?, ?)',
[key, key, createdAt, JSON.stringify(resources), status, uploaded, updated]
);
});

return Promise.all(transactions).then(() => SecureStore.setItemAsync(DATABASE_VERSION_KEY, DATABASE_VERSION));
} catch (err) {
restoreBackup();

Sentry.captureException(err);
return Promise.reject(err);
}
}
Loading