From 7a48c5201aa35d191450d9614f61b551beec9ed9 Mon Sep 17 00:00:00 2001 From: Donovan Preston Date: Thu, 16 Aug 2018 12:07:12 -0400 Subject: [PATCH] Fix #896 Port Send Android to choo --- android/android.js | 216 +----------------- android/app/src/main/assets/index.css | 12 + .../mozilla/send/sendandroid/MainActivity.kt | 1 + android/pages/.eslintrc.yaml | 6 + android/pages/home.js | 33 +++ android/pages/share.js | 47 ++++ android/pages/upload.js | 24 ++ android/stores/.eslintrc.yaml | 6 + android/stores/intents.js | 17 ++ android/stores/state.js | 39 ++++ 10 files changed, 195 insertions(+), 206 deletions(-) create mode 100644 android/pages/.eslintrc.yaml create mode 100644 android/pages/home.js create mode 100644 android/pages/share.js create mode 100644 android/pages/upload.js create mode 100644 android/stores/.eslintrc.yaml create mode 100644 android/stores/intents.js create mode 100644 android/stores/state.js diff --git a/android/android.js b/android/android.js index 68a677f9..ac645287 100644 --- a/android/android.js +++ b/android/android.js @@ -1,210 +1,14 @@ -/* global window, document, fetch */ +/* global window */ window.MAXFILESIZE = 1024 * 1024 * 1024 * 2; -const EventEmitter = require('events'); -const emitter = new EventEmitter(); +const choo = require('choo'); +const app = choo(); -function dom(tagName, attributes, children = []) { - const node = document.createElement(tagName); - for (const name in attributes) { - if (name.indexOf('on') === 0) { - node[name] = attributes[name]; - } else if (name === 'htmlFor') { - node.htmlFor = attributes.htmlFor; - } else if (name === 'className') { - node.className = attributes.className; - } else { - node.setAttribute(name, attributes[name]); - } - } - if (!(children instanceof Array)) { - children = [children]; - } - for (let child of children) { - if (typeof child === 'string') { - child = document.createTextNode(child); - } - node.appendChild(child); - } - return node; -} - -function uploadComplete(file) { - document.body.innerHTML = ''; - const input = dom('input', { id: 'url', value: file.url, readonly: 'true' }); - const copyText = dom('span', {}, 'Copy link'); - const copyImage = dom('img', { id: 'copy-image', src: 'copy-link.png' }); - const node = dom( - 'div', - { id: 'white' }, - dom('div', { className: 'card' }, [ - dom('div', {}, 'The card contents will be here.'), - dom('div', {}, [ - 'Expires after: ', - dom('span', { className: 'expiresAfter' }, 'exp') - ]), - input, - dom( - 'div', - { - id: 'copy-link', - onclick: e => { - e.preventDefault(); - input.select(); - document.execCommand('copy'); - input.selectionEnd = input.selectionStart; - copyText.textContent = 'Copied!'; - setTimeout(function() { - copyText.textContent = 'Copy link'; - }, 2000); - } - }, - [copyImage, copyText] - ), - dom('img', { - id: 'send-another', - src: 'cloud-upload.png', - onclick: () => { - render(); - document.getElementById('label').click(); - } - }) - ]) - ); - document.body.appendChild(node); -} - -const state = { - translate: (...toTranslate) => { - return toTranslate.map(o => JSON.stringify(o)).toString(); - }, - raven: { - captureException: e => { - console.error('ERROR ' + e + ' ' + e.stack); - } - }, - storage: { - files: [], - remove: function(fileId) { - console.log('REMOVE FILEID', fileId); - }, - writeFile: function(file) { - console.log('WRITEFILE', file); - }, - addFile: uploadComplete, - totalUploads: 0 - }, - transfer: null, - uploading: false, - settingPassword: false, - passwordSetError: null, - route: '/' -}; - -function upload(event) { - event.preventDefault(); - const target = event.target; - const file = target.files[0]; - if (file.size === 0) { - return; - } - - emitter.emit('addFiles', { files: [file] }); - emitter.emit('upload', {}); -} - -function render() { - document.body.innerHTML = ''; - const node = dom( - 'div', - { id: 'white' }, - dom('div', { id: 'centering' }, [ - dom('img', { src: 'encrypted-envelope.png' }), - dom('h4', {}, 'Private, Encrypted File Sharing'), - dom( - 'div', - {}, - 'Send files through a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever.' - ), - dom('div', { id: 'spacer' }), - dom( - 'label', - { id: 'label', htmlFor: 'input' }, - dom('img', { src: 'cloud-upload.png' }, []) - ), - dom('input', { - id: 'input', - type: 'file', - name: 'input', - onchange: upload - }) - ]) - ); - document.body.appendChild(node); -} - -emitter.on('render', function() { - if (!state.transfer || !state.transfer.progress) { - return; - } - document.body.innerHTML = ''; - const percent = Math.floor(state.transfer.progressRatio * 100); - const node = dom( - 'div', - { id: 'white', style: 'width: 90%' }, - dom('div', { className: 'card' }, [ - dom('div', {}, `${percent}%`), - dom( - 'span', - { - style: `display: inline-block; height: 4px; border-radius: 2px; width: ${percent}%; background-color: #1b96ef; color: white` - }, - '.' - ), - dom( - 'div', - { - style: 'text-align: right', - onclick: e => { - e.preventDefault(); - if (state.uploading) { - emitter.emit('cancel'); - render(); - } - } - }, - 'CANCEL' - ) - ]) - ); - document.body.appendChild(node); -}); - -emitter.on('pushState', function(path) { - console.log('pushState ' + path + ' ' + JSON.stringify(state)); -}); - -const fileManager = require('../app/fileManager').default; -try { - fileManager(state, emitter); -} catch (e) { - console.error('error' + e); - console.error(e); -} - -window.addEventListener( - 'message', - event => { - fetch(event.data) - .then(res => res.blob()) - .then(blob => { - emitter.emit('addFiles', { files: [blob] }); - emitter.emit('upload', {}); - }) - .catch(e => console.error('ERROR ' + e + ' ' + e.stack)); - }, - false -); - -render(); +app.use(require('./stores/state').default); +app.use(require('../app/fileManager').default); +app.use(require('./stores/intents').default); +app.route('/', require('./pages/home').default); +app.route('/upload', require('./pages/upload').default); +app.route('/share/:id', require('./pages/share').default); +app.mount('body'); diff --git a/android/app/src/main/assets/index.css b/android/app/src/main/assets/index.css index 68bcb164..34bb9c40 100644 --- a/android/app/src/main/assets/index.css +++ b/android/app/src/main/assets/index.css @@ -90,3 +90,15 @@ body { box-shadow: 5px 5px 5px 5px #d5d5d5; text-align: left; } + +.progress { + display: inline-block; + height: 4px; + border-radius: 2px; + background-color: #1b96ef; + color: white; +} + +.cancel { + text-align: right; +} diff --git a/android/app/src/main/java/com/mozilla/send/sendandroid/MainActivity.kt b/android/app/src/main/java/com/mozilla/send/sendandroid/MainActivity.kt index 2354fc7c..2a98afc4 100644 --- a/android/app/src/main/java/com/mozilla/send/sendandroid/MainActivity.kt +++ b/android/app/src/main/java/com/mozilla/send/sendandroid/MainActivity.kt @@ -36,6 +36,7 @@ class MainActivity : AppCompatActivity(), AdvancedWebView.Listener { mWebView!!.setWebChromeClient(LoggingWebChromeClient()) val webSettings = mWebView!!.getSettings() + webSettings.setAllowUniversalAccessFromFileURLs(true) webSettings.setJavaScriptEnabled(true) val intent = getIntent() diff --git a/android/pages/.eslintrc.yaml b/android/pages/.eslintrc.yaml new file mode 100644 index 00000000..73942709 --- /dev/null +++ b/android/pages/.eslintrc.yaml @@ -0,0 +1,6 @@ +env: + browser: true + +parserOptions: + sourceType: module + diff --git a/android/pages/home.js b/android/pages/home.js new file mode 100644 index 00000000..9585430f --- /dev/null +++ b/android/pages/home.js @@ -0,0 +1,33 @@ +const html = require('choo/html'); + +export default function mainPage(state, emit) { + function uploadFile(event) { + event.preventDefault(); + const target = event.target; + const file = target.files[0]; + if (file.size === 0) { + return; + } + + emit('pushState', '/upload'); + emit('addFiles', { files: [file] }); + emit('upload', {}); + } + return html` +
+
+ +

Private, Encrypted File Sharing

+
+ Send files through a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever. +
+
+
+ + +
+
+`; +} diff --git a/android/pages/share.js b/android/pages/share.js new file mode 100644 index 00000000..fe4d5caa --- /dev/null +++ b/android/pages/share.js @@ -0,0 +1,47 @@ +const html = require('choo/html'); + +export default function uploadComplete(state, emit) { + const file = state.storage.files[state.storage.files.length - 1]; + function onclick(e) { + e.preventDefault(); + input.select(); + document.execCommand('copy'); + input.selectionEnd = input.selectionStart; + copyText.textContent = 'Copied!'; + setTimeout(function() { + copyText.textContent = 'Copy link'; + }, 2000); + } + + function uploadFile(event) { + event.preventDefault(); + const target = event.target; + const file = target.files[0]; + if (file.size === 0) { + return; + } + + emit('pushState', '/upload'); + emit('addFiles', { files: [file] }); + emit('upload', {}); + } + + const input = html``; + const copyText = html`Copy link`; + return html` +
+
+
The card contents will be here.
+
Expires after: exp
+ ${input} + + + +
+`; +} diff --git a/android/pages/upload.js b/android/pages/upload.js new file mode 100644 index 00000000..8bf1191b --- /dev/null +++ b/android/pages/upload.js @@ -0,0 +1,24 @@ +const html = require('choo/html'); + +export default function progressBar(state, emit) { + let percent = 0; + if (state.transfer && state.transfer.progress) { + percent = Math.floor(state.transfer.progressRatio * 100); + } + function onclick(e) { + e.preventDefault(); + if (state.uploading) { + emit('cancel'); + } + emit('pushState', '/'); + } + return html` +
+
+
${percent}%
+ . +
CANCEL
+
+
+`; +} diff --git a/android/stores/.eslintrc.yaml b/android/stores/.eslintrc.yaml new file mode 100644 index 00000000..73942709 --- /dev/null +++ b/android/stores/.eslintrc.yaml @@ -0,0 +1,6 @@ +env: + browser: true + +parserOptions: + sourceType: module + diff --git a/android/stores/intents.js b/android/stores/intents.js new file mode 100644 index 00000000..3b8f4781 --- /dev/null +++ b/android/stores/intents.js @@ -0,0 +1,17 @@ +/* eslint-disable no-console */ + +export default function intentHandler(state, emitter) { + window.addEventListener( + 'message', + event => { + fetch(event.data) + .then(res => res.blob()) + .then(blob => { + emitter.emit('addFiles', { files: [blob] }); + emitter.emit('upload', {}); + }) + .catch(e => console.error('ERROR ' + e + ' ' + e.stack)); + }, + false + ); +} diff --git a/android/stores/state.js b/android/stores/state.js new file mode 100644 index 00000000..e22291e0 --- /dev/null +++ b/android/stores/state.js @@ -0,0 +1,39 @@ +/* eslint-disable no-console */ + +export default function initialState(state, emitter) { + const files = []; + + Object.assign(state, { + getAsset(name) { + return `/android_asset/${name}`; + }, + translate: (...toTranslate) => { + return toTranslate.map(o => JSON.stringify(o)).toString(); + }, + raven: { + captureException: e => { + console.error('ERROR ' + e + ' ' + e.stack); + } + }, + storage: { + files, + remove: function(fileId) { + console.log('REMOVE FILEID', fileId); + }, + writeFile: function(file) { + console.log('WRITEFILE', file); + }, + addFile: function(file) { + console.log('addfile' + JSON.stringify(file)); + files.push(file); + emitter.emit('pushState', `/share/${file.id}`); + }, + totalUploads: 0 + }, + transfer: null, + uploading: false, + settingPassword: false, + passwordSetError: null, + route: '/' + }); +}