Skip to content

Commit

Permalink
Deep linking (closes #331) (#338)
Browse files Browse the repository at this point in the history
* Add incoming url detection logic, mapped to openItem

* Add custom protocol handling

* Add missing return statements

* Add .postArray() method to cope with JSON array posting

* Change redux architecture to fetch on demand
Change redux state architecture to fetch/resolve items on demand

Our data architecture was modelled around a single view that holds items found nearby. With the addtional of deep linking and the ability to render items that may not be nearby we needed to decouple 'nearby items' from 'items' in our app state and provide way to load item data when an item hasn't come from the scanner.

* Remove redundant metadata fetcher

* Show loading indicator when resolving item

* Protect against navigator not being defined yet

* Remove dead module

* Change to use proper async redux actions for data retrieval

* Fix loading spinner styling

* Listen for deep-links when app is running

* Add support for POSTing JSON arrays

* Fix incorrect props mapping

* Yarn lock not up-to-date for some reason

* Remove expiring logic as needs to be moved to native scanners

* Moved network status logic into redux store

* Fix refresh logic to be based on 'nearbyItems' not 'items'

* Add missing file for network checks

* Remove unused callback

* Fix tests

* Add native linking logic for ios

* yarn.lock not updated

* Fix broken unit-tests

* Rename files to match class names

* Use InteractionsManager to prevent render whilst animating

* Fix naming inconsistency

* Don't change state when distance hasn't changed

* Update yarn.lock

* Fix broken test

* Remove redundant code
  • Loading branch information
wilsonpage authored Nov 21, 2016
1 parent 3a2e9e6 commit 2d46b77
Show file tree
Hide file tree
Showing 37 changed files with 1,132 additions and 867 deletions.
15 changes: 13 additions & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,31 @@
<uses-sdk tools:overrideLibrary="org.mozilla.magnet.net.scanner" />

<application
android:name="org.mozilla.magnet.MainApplication"
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name="org.mozilla.magnet.MainActivity"
android:name=".MainActivity"
android:launchMode="singleTask"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- handles URIs that begin with "mozilla-magnet://item” -->
<data android:scheme="mozilla-magnet"
android:host="item" />
</intent-filter>

</activity>

<service
Expand Down
2 changes: 2 additions & 0 deletions android/app/src/main/java/org/mozilla/magnet/api/Api.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public void post(String path, Object data, Callback callback) {

if (match == null) {
callback.reject("no matching route");
return;
}

match.post(path, data, callback);
Expand All @@ -96,6 +97,7 @@ public void delete(String path, HashMap<String,Object> data, Callback callback)

if (match == null) {
callback.reject("no matching route");
return;
}

match.delete(path, data, callback);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableNativeArray;
import com.facebook.react.bridge.ReadableNativeMap;

import org.json.JSONArray;
Expand All @@ -15,6 +17,8 @@
import org.mozilla.magnet.api.Utils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ApiMagnetReact extends ReactContextBaseJavaModule {
private static final String TAG = "APIMagnetReact";
Expand Down Expand Up @@ -46,11 +50,39 @@ public void reject(String error) {
}

@ReactMethod
public void post(String path, ReadableMap data, final Promise promise) {
public void post(String path, ReadableMap map, final Promise promise) {
Log.d(TAG, "post");
HashMap<String,Object> map = ((ReadableNativeMap) data).toHashMap();
Object data = fromReactArgument(map);

if (data == null) {
promise.reject("invalid-data-type", "invalid data type");
return;
}

mApiMagnet.post(path, map, new Api.Callback() {
mApiMagnet.post(path, data, new Api.Callback() {
@Override
public void resolve(Object result) {
promise.resolve(toReactArgument(result));
}

@Override
public void reject(String error) {
promise.reject(error, error);
}
});
}

@ReactMethod
public void postArray(String path, ReadableArray array, final Promise promise) {
Log.d(TAG, "post");
Object data = fromReactArgument(array);

if (data == null) {
promise.reject("invalid-data-type", "invalid data type");
return;
}

mApiMagnet.post(path, data, new Api.Callback() {
@Override
public void resolve(Object result) {
promise.resolve(toReactArgument(result));
Expand Down Expand Up @@ -86,4 +118,10 @@ static private Object toReactArgument(Object object) {
else if (object instanceof JSONObject) return Utils.jsonToWritableMap((JSONObject) object);
else return null;
}

static private Object fromReactArgument(Object object) {
if (object instanceof ReadableNativeArray) return ((ReadableNativeArray) object).toArrayList();
else if (object instanceof ReadableNativeMap) return ((ReadableNativeMap) object).toHashMap();
else return null;
}
}
6 changes: 1 addition & 5 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ module.exports = {
title: 'Show distance',
},

'removeOldItems': {
value: false, // default
title: 'Remove old items',
},

'enableTelemetry': {
value: true, // default
title: 'Telemetry',
Expand All @@ -40,6 +35,7 @@ module.exports = {
'itemExpiring': 10000, // 10 secs

'metadataServiceUrl': 'https://tengam.org/api/v1/metadata',
'searchServiceUrl': 'https://tengam.org/content/v1/search/url',

'theme': {
'colorBackground': '#f2f2f2',
Expand Down
12 changes: 12 additions & 0 deletions ios/Api.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SwiftyJSON
protocol Api {
func get(path: String, callback: ApiCallback)
func post(path: String, data: NSDictionary, callback: ApiCallback)
func post(path: String, data: NSArray, callback: ApiCallback)
func put(path: String, data: NSDictionary, callback: ApiCallback)
func delete(path: String, data: NSDictionary, callback: ApiCallback)
func mount(path: String, api: Api)
Expand Down Expand Up @@ -60,6 +61,17 @@ class ApiBase: Api {
api!.post(path, data: data, callback: callback)
}

func post(path: String, data: NSArray, callback: ApiCallback) {
let api = find(path)

guard api != nil else {
callback.onError("Could not find route \(path)")
return
}

api!.post(path, data: data, callback: callback)
}

func put(path: String, data: NSDictionary, callback: ApiCallback) {
let api = find(path)

Expand Down
6 changes: 6 additions & 0 deletions ios/ApiMagnetReact.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ @interface RCT_EXTERN_MODULE(ApiMagnetReact, NSObject);
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(
postArray:(NSString *)path
data:(NSArray *)data
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(
put:(NSString *)path
data:(NSDictionary *)data
Expand Down
10 changes: 10 additions & 0 deletions ios/ApiMagnetReact.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ import Foundation
}))
}

@objc func postArray(path: String, data: NSArray, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
api.post(path, data: data, callback: ApiCallback(success: { result in
resolve(result.rawValue)
},
error: { (error) in
let err = NSError(coder: NSCoder())
reject("get_error", "Error resolving \(path) with \(data)", err)
}))
}

@objc func put(path: String, data: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
api.put(path, data: data, callback: ApiCallback(success: { result in
resolve(result.rawValue)
Expand Down
11 changes: 8 additions & 3 deletions ios/ApiMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,25 @@ class ApiMetadata: ApiBase {
//
// data["objects"] = elems

override func post(path: String, data: NSDictionary, callback: ApiCallback) {
override func post(path: String, data: NSArray, callback: ApiCallback) {
guard System.connectedToNetwork() else {
callback.onError("No internet connection")
return
}

let url = NSURL(string: ApiMetadata.SERVER_URL)
var urls = Array<Dictionary<String,String>>();

let parameters = ["objects": (data.valueForKey("objects"))!]
for url in data {
urls.append(["url": url as! String]);
}

let body = ["objects": urls]
let request = NSMutableURLRequest(URL: url!)

request.HTTPMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.HTTPBody = try! NSJSONSerialization.dataWithJSONObject(parameters, options: [])
request.HTTPBody = try! NSJSONSerialization.dataWithJSONObject(body, options: [])

Alamofire.request(request).responseJSON { response in
guard response.result.value != nil else {
Expand Down
4 changes: 4 additions & 0 deletions ios/Magnet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient",
"$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS",
);
INFOPLIST_FILE = Magnet/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
Expand Down Expand Up @@ -1027,6 +1028,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient",
"$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS",
);
INFOPLIST_FILE = Magnet/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
Expand Down Expand Up @@ -1086,6 +1088,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient",
"$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS/RCTLinkingManager.h",
);
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = YES;
Expand Down Expand Up @@ -1128,6 +1131,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient",
"$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS/RCTLinkingManager.h",
);
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = NO;
Expand Down
6 changes: 6 additions & 0 deletions ios/Magnet/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import "RCTBundleURLProvider.h"
#import "RCTRootView.h"
#import "RCTBridge.h"
#import "RCTLinkingManager.h"

// Include the project headers for swift code. (<project-name>-Swift.h)
#import "magnet-Swift.h"
Expand Down Expand Up @@ -70,4 +71,9 @@ -(void)application:(UIApplication *)application didReceiveLocalNotification:(UIL
[self.bridge.eventDispatcher sendDeviceEventWithName:@"notification:applaunch" body:nil];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}


@end
11 changes: 11 additions & 0 deletions ios/Magnet/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@
<string>1.2.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>org.mozilla.magnet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>mozilla-magnet</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>7</string>
<key>LSRequiresIPhoneOS</key>
Expand Down
6 changes: 2 additions & 4 deletions ios/NotificationsHelperIOS10.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,9 @@ class NotificationsHelperIOS10: NSObject, UNUserNotificationCenterDelegate {

private func fetchData(url: String, callback: ((JSON) -> Void)) {
let api = ApiMetadata()
let urls: NSArray = [url]

let item: Dictionary<String, String> = ["url": url]
let objects: Dictionary<String, NSArray> = ["objects": [item]]

api.post("metadata", data: objects, callback: ApiCallback(success: { json in
api.post("metadata", data: urls, callback: ApiCallback(success: { json in
callback(json)
}, error: { (err) in
debugPrint("Could not get metadata for \(url): \(err)")
Expand Down
50 changes: 50 additions & 0 deletions lib/api/fetch-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

/**
* Dependencies
*/

const { searchServiceUrl } = require('../../config');
const debug = require('../debug')('get-item');
const api = require('.');

/**
* Exports
*/

module.exports = function(url) {
return Promise.all([
fetchBeaconData(url),
fetchMetadata(url),
])

.then(results => {
var item = results[0][0];
var metadata = results[1][0];

return {
...item,
metadata,
};
});
};

function fetchMetadata(url) {
return api.postArray('metadata', [url]);
}

function fetchBeaconData(url) {
debug('fetch beacon data', url);
const request = new Request(searchServiceUrl, {
method: 'post',
headers: new Headers({ 'Content-Type': 'application/json;charset=utf-8' }),
body: JSON.stringify([url]),
});

return fetch(request)
.then(res => res.json())
.catch(() => {
debug('no beacon data', url);
return [];
});
}
File renamed without changes.
Loading

0 comments on commit 2d46b77

Please sign in to comment.