Skip to content

Commit

Permalink
Backend downloads apk and frontend actually installs apk
Browse files Browse the repository at this point in the history
  • Loading branch information
staltz committed Oct 18, 2017
1 parent cddb09f commit 38d6f43
Show file tree
Hide file tree
Showing 14 changed files with 406 additions and 19 deletions.
17 changes: 8 additions & 9 deletions TODO.md
@@ -1,25 +1,24 @@
# Next

- Actually install apk
- https://www.npmjs.com/package/react-native-apk-installer
- https://stackoverflow.com/questions/26884956/how-to-install-update-remove-apk-using-packageinstaller-class-in-android-l
- Install button on each list row
- Extract from apk: app logo, name and version
- Fix install button marginRight
- Add GPL license and license comments

# Must

- Extract app logo from apk
- RELEASE
- Render circular progress bar around the logo
- Hide install button after it is installed

# Should

- Remove app
- Show install button also on list item
- Render banner at the top, to show errors
- Validate dat key before submitting it to backend
- Remove app

# Could

- Add GPL license and license comments
- RELEASE
- Validate dat key before submitting it to backend
- Use Material design components more consistently
- Fade-in the welcome text, instead of hard transitions
- Backend should go inactive sometimes
1 change: 1 addition & 0 deletions android/app/build.gradle
Expand Up @@ -137,6 +137,7 @@ android {
}

dependencies {
compile project(':react-native-apk-installer')
compile project(':react-native-node')
compile project(':react-native-navigation')
compile project(':react-native-fs')
Expand Down
Expand Up @@ -3,6 +3,7 @@
import android.app.Application;

import com.facebook.react.ReactApplication;
import com.cnull.apkinstaller.ApkInstallerPackage;
import com.staltz.reactnativenode.RNNodePackage;
import com.rnfs.RNFSPackage;
import com.facebook.react.ReactNativeHost;
Expand All @@ -26,7 +27,8 @@ protected List<ReactPackage> getPackages() {
// No need to add RnnPackage and MainReactPackage
return Arrays.<ReactPackage>asList(
new RNFSPackage(),
new RNNodePackage()
new RNNodePackage(),
new ApkInstallerPackage()
);
}

Expand Down
2 changes: 2 additions & 0 deletions android/settings.gradle
@@ -1,4 +1,6 @@
rootProject.name = 'DatInstaller'
include ':react-native-apk-installer'
project(':react-native-apk-installer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-apk-installer/android')
include ':react-native-node'
project(':react-native-node').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-node/android')
include ':react-native-navigation'
Expand Down
6 changes: 6 additions & 0 deletions index.android.js
Expand Up @@ -5,6 +5,7 @@ import { makeSingleScreenNavDrivers } from "cycle-native-navigation";
import onionify from "cycle-onionify";
import { navigatorStyle } from "./lib/styles";
import main from "./lib/main";
import ApkInstaller from "react-native-apk-installer";

const { screenVNodeDriver, commandDriver } = makeSingleScreenNavDrivers(
["DatInstaller.Central", "DatInstaller.Addition", "DatInstaller.Details"],
Expand All @@ -21,6 +22,11 @@ run(onionify(main), {
screen: screenVNodeDriver,
navCommand: commandDriver,
http: makeHTTPDriver(),
installApk: fullPath$ => {
fullPath$.addListener({
next: fullPath => ApkInstaller.install(fullPath),
});
},
});

RNNode.start();
3 changes: 3 additions & 0 deletions package.json
Expand Up @@ -3,6 +3,7 @@
"version": "0.0.1",
"private": true,
"scripts": {
"prepare": "patch-package",
"build-back": "./build-back.sh",
"build-front": "tsc -p ./tsconfig-frontend.json",
"prestart": "npm run build-back && npm run build-front",
Expand All @@ -22,9 +23,11 @@
"cycle-onionify": "^4.0.0",
"express": "^4.16.2",
"noderify": "^3.0.2",
"patch-package": "^3.5.1",
"react": "16.0.0-beta.5",
"react-native": "0.49.3",
"react-native-action-button": "^2.8.0",
"react-native-apk-installer": "^0.0.2",
"react-native-fs": "^2.8.1",
"react-native-navigation": "^1.1.236",
"react-native-node": "^2.1.1",
Expand Down
16 changes: 16 additions & 0 deletions patches/react-native-apk-installer+0.0.2.patch
@@ -0,0 +1,16 @@
diff --git a/node_modules/react-native-apk-installer/android/src/main/java/com/cnull/apkinstaller/ApkInstallerPackage.java b/node_modules/react-native-apk-installer/android/src/main/java/com/cnull/apkinstaller/ApkInstallerPackage.java
index f2e14ed..dd3996e 100644
--- a/node_modules/react-native-apk-installer/android/src/main/java/com/cnull/apkinstaller/ApkInstallerPackage.java
+++ b/node_modules/react-native-apk-installer/android/src/main/java/com/cnull/apkinstaller/ApkInstallerPackage.java
@@ -12,11 +12,6 @@ import java.util.List;

public class ApkInstallerPackage implements ReactPackage {

- @Override
- public List<Class<? extends JavaScriptModule>> createJSModules() {
- return Collections.emptyList();
- }
-
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
33 changes: 33 additions & 0 deletions src/backend/main.ts
Expand Up @@ -5,6 +5,7 @@ import {
joinNetwork,
trimProtocolPrefix,
looksLikeDatHash,
downloadFileFromDat,
readFileInDat,
} from "./utils";
import { Request, Response } from "express";
Expand Down Expand Up @@ -117,6 +118,12 @@ const readme$ = metadata$.switchMap(({ json, dat }) => {
: Rx.Observable.empty();
});

const apk$ = metadata$.switchMap(({ json, dat }) => {
const apkFilename: string = json.apk;
console.log("attempt to download apk file " + apkFilename);
return downloadFileFromDat(dat, json.apk).mapTo({ dat, apkFilename });
});

// Update global_state metadata for an app
metadata$.subscribe({
next: ({ json, dat }) => {
Expand Down Expand Up @@ -171,6 +178,32 @@ readme$.subscribe({
},
});

// Update global_state apk full path for an app
apk$.withLatestFrom(storagePath$).subscribe({
next: ([{ dat, apkFilename }, storagePath]) => {
const datHash = (dat.key as Buffer).toString("hex");
const apkFullPath = path.join(storagePath, datHash, apkFilename);
if (global_state[datHash]) {
global_state[datHash].apkFullPath = apkFullPath;
} else {
global_state[datHash] = {
key: datHash,
peers: 0,
readme: apkFullPath,
};
}
},
error: (e: Error) => {
if (e.message.endsWith("could not be found")) {
global_errors.push({
message: "The Dat for this app has a broken APK file",
});
} else {
global_errors.push(e);
}
},
});

// Read cold stored Dats and start syncing them
storagePath$
.take(1)
Expand Down
6 changes: 6 additions & 0 deletions src/backend/utils.ts
Expand Up @@ -13,6 +13,12 @@ export function readFileInDat(
)(file, encoding);
}

export function downloadFileFromDat(dat: any, file: string): Observable<null> {
return Observable.bindNodeCallback<string, null>(
dat.archive.download.bind(dat.archive),
)(file);
}

export function joinNetwork(dat: any): Observable<any> {
return Observable.bindNodeCallback<any>(dat.joinNetwork.bind(dat))();
}
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/main.ts
Expand Up @@ -24,6 +24,7 @@ type Sinks = {
navCommand: Stream<Command>;
onion: Stream<Reducer<State>>;
http: Stream<RequestOptions>;
installApk: Stream<string>;
};

type State = {
Expand Down Expand Up @@ -113,5 +114,6 @@ export default function main(sources: Sources): Sinks {
navCommand: navCommand$,
onion: reducer$,
http: request$,
installApk: detailsSinks.installApk,
};
}
56 changes: 54 additions & 2 deletions src/frontend/screens/details/index.ts
@@ -1,8 +1,15 @@
import xs, { Stream } from "xstream";
import sampleCombine from "xstream/extra/sampleCombine";
import { StateSource, Reducer } from "cycle-onionify";
import { HTTPSource, RequestOptions } from "@cycle/http";
import { ScreenVNode, ScreensSource, Command } from "cycle-native-navigation";
import { StyleSheet, Text, View, FlatList } from "react-native";
import {
StyleSheet,
Text,
View,
FlatList,
TouchableNativeFeedback,
} from "react-native";
import { createElement } from "react";
import { h } from "@cycle/native-screen";
import { AppMetadata } from "../../../typings/messages";
Expand All @@ -19,6 +26,7 @@ export type Sinks = {
navCommand: Stream<Command>;
onion: Stream<Reducer<State>>;
http: Stream<RequestOptions>;
installApk: Stream<string>;
};

export type State = {
Expand Down Expand Up @@ -63,6 +71,29 @@ const styles = StyleSheet.create({
readmeContainer: {
marginTop: 15,
},

installContainer: {
width: 120,
alignSelf: "flex-end",
marginLeft: 20,
marginRight: 20,
paddingLeft: 20,
paddingRight: 20,
paddingTop: 10,
paddingBottom: 10,
borderTopLeftRadius: 3,
borderTopRightRadius: 3,
borderBottomLeftRadius: 3,
borderBottomRightRadius: 3,
backgroundColor: "#199E33",
},

installText: {
textAlign: "center",
fontSize: 16,
fontWeight: "bold",
color: "#ffffff",
},
});

export const mdStyles = StyleSheet.create({
Expand Down Expand Up @@ -282,7 +313,15 @@ const rules = {
};

export default function details(sources: Sources): Sinks {
const vdom$ = sources.onion.state$.map(state => ({
const state$ = sources.onion.state$;

const installApk$ = sources.screen
.select("install")
.events("press")
.compose(sampleCombine(state$))
.map(([_, state]) => state.app.apkFullPath as string);

const vdom$ = state$.map(state => ({
screen: "DatInstaller.Details",
vdom: h(View, { style: styles.container }, [
h(View, { style: styles.header }, [
Expand All @@ -304,6 +343,18 @@ export default function details(sources: Sources): Sinks {
),
]),
]),
h(
TouchableNativeFeedback,
{
selector: "install",
background: TouchableNativeFeedback.SelectableBackground(),
},
[
h(View, { style: styles.installContainer }, [
h(Text, { style: styles.installText }, "Install"),
]),
],
),
h(View, { style: styles.readmeContainer }, [
h(Markdown, { styles: mdStyles, rules }, state.app.readme),
]),
Expand All @@ -315,5 +366,6 @@ export default function details(sources: Sources): Sinks {
navCommand: xs.never(),
onion: xs.never(),
http: xs.never(),
installApk: installApk$,
};
}
1 change: 1 addition & 0 deletions src/typings/messages.d.ts
Expand Up @@ -3,5 +3,6 @@ export type AppMetadata = {
name?: string | undefined;
version?: string | undefined;
readme?: string | undefined;
apkFullPath?: string | undefined;
peers: number;
};
2 changes: 1 addition & 1 deletion tsconfig-backend.json
Expand Up @@ -12,7 +12,7 @@
"target": "es2015",
"types": ["node"],
"outDir": "./rnnodeapp",
"lib": ["es5"]
"lib": ["es5", "es2015"]
},
"files": ["src/backend/main.ts"],
"exclude": ["node_modules", "src/typings/messages.d.ts"]
Expand Down

0 comments on commit 38d6f43

Please sign in to comment.