diff --git a/android/ScratchJr/.idea/codeStyles/Project.xml b/android/ScratchJr/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/android/ScratchJr/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/android/ScratchJr/.idea/misc.xml b/android/ScratchJr/.idea/misc.xml index e8ee3d9..d5d4a5c 100644 --- a/android/ScratchJr/.idea/misc.xml +++ b/android/ScratchJr/.idea/misc.xml @@ -5,7 +5,7 @@ diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java index c47a469..a58b89f 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java @@ -337,7 +337,7 @@ public class ScratchJrActivity } // We send the project Base64-encoded to JavaScript where it's processed and unpacked String base64Project = Base64.encodeToString(projectData.toByteArray(), Base64.DEFAULT); - runJavaScript("iOS.loadProjectFromSjr('" + base64Project + "');"); + runJavaScript("OS.loadProjectFromSjr('" + base64Project + "');"); } public RelativeLayout getContainer() { diff --git a/editions/free/ios-resources/Default-Landscape@2x~ipad.png b/editions/free/ios-resources/Default-Landscape@2x~ipad.png index ed49202..34d2de1 100644 Binary files a/editions/free/ios-resources/Default-Landscape@2x~ipad.png and b/editions/free/ios-resources/Default-Landscape@2x~ipad.png differ diff --git a/editions/free/ios-resources/Default-Landscape~ipad.png b/editions/free/ios-resources/Default-Landscape~ipad.png index f6ee582..e946fe5 100644 Binary files a/editions/free/ios-resources/Default-Landscape~ipad.png and b/editions/free/ios-resources/Default-Landscape~ipad.png differ diff --git a/ios/ScratchJr.xcodeproj/project.pbxproj b/ios/ScratchJr.xcodeproj/project.pbxproj index 5c156f1..3c9959a 100644 --- a/ios/ScratchJr.xcodeproj/project.pbxproj +++ b/ios/ScratchJr.xcodeproj/project.pbxproj @@ -419,7 +419,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "EDITION=free;\n\n../bin/bundle-compile.sh;\n\nrsync -pvtrlL --cvs-exclude \\\n ../editions/$EDITION/ios-resources/* \\\n \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/\";\n\nrsync -pvtrlL --cvs-exclude \\\n ../editions/$EDITION/src/* \\\n \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/HTML5\";\n \nmkdir -p \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/HTML5/pnglibrary\";\n\n../bin/convert-svg-to-png.py -i \"../editions/$EDITION/src/svglibrary/\" -o \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/HTML5/pnglibrary\";"; + shellScript = "EDITION=free;\n\n../bin/bundle-compile.sh;\n\nrsync -pvtrlL --cvs-exclude \\\n ../editions/$EDITION/ios-resources/* \\\n \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/\";\n\nrsync -pvtrlL --cvs-exclude \\\n ../editions/$EDITION/src/* \\\n \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/HTML5\";\n \nmkdir -p \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/HTML5/pnglibrary\";\n\n../bin/convert-svg-to-png.py -i \"../editions/$EDITION/src/svglibrary/\" -o \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/HTML5/pnglibrary\";\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/ios/ScratchJr/src/IO.m b/ios/ScratchJr/src/IO.m index f2eea80..3324e53 100644 --- a/ios/ScratchJr/src/IO.m +++ b/ios/ScratchJr/src/IO.m @@ -282,7 +282,7 @@ NSMutableDictionary *soundtimers; + (void)soundEnded:(NSTimer*)timer { NSString *soundName = [[timer userInfo] objectForKey:@"soundName"]; if (sounds[soundName] == nil) return; - NSString *callback = [NSString stringWithFormat:@"iOS.soundDone('%@');", soundName]; + NSString *callback = [NSString stringWithFormat:@"OS.soundDone('%@');", soundName]; UIWebView *webview = [ViewController webview]; dispatch_async(dispatch_get_main_queue(), ^{ [webview stringByEvaluatingJavaScriptFromString:callback]; diff --git a/ios/ScratchJr/src/ViewController.m b/ios/ScratchJr/src/ViewController.m index 0827e9f..5571b85 100644 --- a/ios/ScratchJr/src/ViewController.m +++ b/ios/ScratchJr/src/ViewController.m @@ -164,7 +164,7 @@ JSContext *js; NSLog(@"could not load the website caused by error DESC: %@", error); NSDictionary *userInfo = [error userInfo]; NSString *desc = [NSString stringWithFormat:@"%@", ([userInfo objectForKey: @"NSLocalizedDescription"] == NULL)? [error localizedDescription]: [userInfo objectForKey: @"NSLocalizedDescription"]]; - NSString *callback = [NSString stringWithFormat: @"iOS.pageError('%@');",desc]; + NSString *callback = [NSString stringWithFormat: @"OS.pageError('%@');",desc]; UIWebView *webview = [ViewController webview]; dispatch_async(dispatch_get_main_queue(), ^{ [webview stringByEvaluatingJavaScriptFromString: callback]; @@ -172,7 +172,7 @@ JSContext *js; } - (void) receiveProject:(NSString *)project{ - NSString *callback = [NSString stringWithFormat:@"iOS.loadProjectFromSjr('%@');", project]; + NSString *callback = [NSString stringWithFormat:@"OS.loadProjectFromSjr('%@');", project]; UIWebView *webview = [ViewController webview]; dispatch_async(dispatch_get_main_queue(), ^{ NSString *res = [webview stringByEvaluatingJavaScriptFromString:callback]; @@ -310,7 +310,7 @@ JSContext *js; return [ScratchJr captureimage:onCameraCaptureComplete]; } -//iOS.sendSjrToShareDialog = function(fileName, emailSubject, emailBody, shareType, b64data) { +//OS.sendSjrToShareDialog = function(fileName, emailSubject, emailBody, shareType, b64data) { -(NSString*) sendSjrUsingShareDialog:(NSString*) fileName :(NSString*) emailSubject :(NSString*) emailBody :(int) shareType :(NSString*) b64data { return [IO sendSjrUsingShareDialog:fileName :emailSubject :emailBody :shareType : b64data]; diff --git a/src/editor/ScratchJr.js b/src/editor/ScratchJr.js index 52c2cf8..d6852cb 100644 --- a/src/editor/ScratchJr.js +++ b/src/editor/ScratchJr.js @@ -6,8 +6,8 @@ import Undo from './ui/Undo'; import Alert from './ui/Alert'; import Palette from './ui/Palette'; import Record from './ui/Record'; -import IO from '../iPad/IO'; -import iOS from '../iPad/iOS'; +import IO from '../tablet/IO'; +import OS from '../tablet/OS'; import UI from './ui/UI'; import Menu from './blocks/Menu'; import Library from './ui/Library'; @@ -183,7 +183,7 @@ export default class ScratchJr { document.body.scrollTop = 0; time = (new Date()) - 0; var urlvars = getUrlVars(); - iOS.hascamera(); + OS.hascamera(); ScratchJr.log('starting the app'); BlockSpecs.initBlocks(); Project.loadIcon = document.createElement('img'); @@ -345,7 +345,7 @@ export default class ScratchJr { static saveProject (e, onDone) { if (ScratchJr.isEditable() && editmode == 'storyStarter' && storyStarted && !Project.error) { - iOS.analyticsEvent('samples', 'story_starter_edited', Project.metadata.name); + OS.analyticsEvent('samples', 'story_starter_edited', Project.metadata.name); // Localize sample project names var sampleName = Localization.localize('SAMPLE_' + Project.metadata.name); // Get the new project name @@ -381,14 +381,14 @@ export default class ScratchJr { ScratchJr.stopStripsFromTop(e); ScratchJr.unfocus(e); ScratchJr.saveProject(e, ScratchJr.flippage); - iOS.analyticsEvent('editor', 'project_editor_close'); + OS.analyticsEvent('editor', 'project_editor_close'); } static flippage () { Alert.close(); - iOS.cleanassets('wav', doNext); + OS.cleanassets('wav', doNext); function doNext () { - iOS.cleanassets('svg', ScratchJr.switchPage); + OS.cleanassets('svg', ScratchJr.switchPage); } } @@ -525,7 +525,7 @@ export default class ScratchJr { ScratchJr.displayStatus('none'); inFullscreen = true; UI.enterFullScreen(); - iOS.analyticsEvent('editor', 'full_screen_entered'); + OS.analyticsEvent('editor', 'full_screen_entered'); document.body.style.background = 'black'; } @@ -537,7 +537,7 @@ export default class ScratchJr { inFullscreen = false; UI.quitFullScreen(); onBackButtonCallback.pop(); - iOS.analyticsEvent('editor', 'full_screen_exited'); + OS.analyticsEvent('editor', 'full_screen_exited'); document.body.style.background = 'white'; } @@ -905,26 +905,6 @@ export default class ScratchJr { ///////////////// //Application on the background - - // XXX: does this ever happen? - // I'm pretty sure this is dead code -TM - static saveProjectState () { - ScratchAudio.sndFX('tap.wav'); - if (frame.style.display == 'none') { - Paint.saveEditState(ScratchJr.stopServer); - } else { - ScratchJr.unfocus(); - ScratchJr.stopStrips(); - if (ScratchJr.isEditable() && currentProject && !Project.error && changed) { - Project.save(currentProject, ScratchJr.stopServer); - } - } - } - - static stopServer () { - iOS.stopserver(iOS.trace); - } - /** * The functions that are invokved when the Android back button is clicked. * Methods are called from the rear and popped off after each invocation. diff --git a/src/editor/blocks/BlockSpecs.js b/src/editor/blocks/BlockSpecs.js index 40f6f5a..14e4db6 100755 --- a/src/editor/blocks/BlockSpecs.js +++ b/src/editor/blocks/BlockSpecs.js @@ -1,5 +1,5 @@ import Localization from '../../utils/Localization'; -import IO from '../../iPad/IO'; +import IO from '../../tablet/IO'; let loadCount = 0; diff --git a/src/editor/engine/Page.js b/src/editor/engine/Page.js index e92e064..f1d9045 100644 --- a/src/editor/engine/Page.js +++ b/src/editor/engine/Page.js @@ -5,9 +5,9 @@ import UI from '../ui/UI'; import Sprite from './Sprite'; import Palette from '../ui/Palette'; import BlockSpecs from '../blocks/BlockSpecs'; -import iOS from '../../iPad/iOS'; -import IO from '../../iPad/IO'; -import MediaLib from '../../iPad/MediaLib'; +import OS from '../../tablet/OS'; +import IO from '../../tablet/IO'; +import MediaLib from '../../tablet/MediaLib'; import Undo from '../ui/Undo'; import Matrix from '../../geom/Matrix'; import Vector from '../../geom/Vector'; @@ -125,7 +125,9 @@ export default class Page { return; } var me = this; - var url = (MediaLib.keys[name]) ? MediaLib.path + name : (name.indexOf('/') < 0) ? iOS.path + name : name; + var url = (MediaLib.keys[name]) ? + MediaLib.path + name : + (name.indexOf('/') < 0) ? OS.path + name : name; var md5 = (MediaLib.keys[name]) ? MediaLib.path + name : name; if (md5.substr(md5.length - 3) == 'png') { @@ -137,7 +139,7 @@ export default class Page { if (md5.indexOf('/') > -1) { IO.requestFromServer(md5, doNext); } else { - iOS.getmedia(md5, nextStep); + OS.getmedia(md5, nextStep); } function nextStep (base64) { doNext(atob(base64)); @@ -145,7 +147,7 @@ export default class Page { function doNext (str) { str = str.replace(/>\s*<'); me.setSVG(str); - if ((str.indexOf('xlink:href') < 0) && iOS.path) { + if ((str.indexOf('xlink:href') < 0) && OS.path) { me.setBackgroundImage(url, fcn); // does not have embedded images } else { var base64 = IO.getImageDataURL(me.md5, btoa(str)); diff --git a/src/editor/engine/Sprite.js b/src/editor/engine/Sprite.js index c0949d3..75b2312 100755 --- a/src/editor/engine/Sprite.js +++ b/src/editor/engine/Sprite.js @@ -12,9 +12,9 @@ import Project from '../ui/Project'; import Thumbs from '../ui/Thumbs'; import UI from '../ui/UI'; import BlockSpecs from '../blocks/BlockSpecs'; -import iOS from '../../iPad/iOS'; -import IO from '../../iPad/IO'; -import MediaLib from '../../iPad/MediaLib'; +import IO from '../../tablet/IO'; +import OS from '../../tablet/OS'; +import MediaLib from '../../tablet/MediaLib'; import Undo from '../ui/Undo'; import ScriptsPane from '../ui/ScriptsPane'; import SVG2Canvas from '../../utils/SVG2Canvas'; @@ -77,12 +77,14 @@ export default class Sprite { getAsset (whenDone) { var md5 = this.md5; var spr = this; - var url = (MediaLib.keys[md5]) ? MediaLib.path + md5 : (md5.indexOf('/') < 0) ? iOS.path + md5 : md5; + var url = (MediaLib.keys[md5]) ? + MediaLib.path + md5 : + (md5.indexOf('/') < 0) ? OS.path + md5 : md5; md5 = (MediaLib.keys[md5]) ? MediaLib.path + md5 : md5; if (md5.indexOf('/') > -1) { IO.requestFromServer(md5, doNext); } else { - iOS.getmedia(md5, nextStep); + OS.getmedia(md5, nextStep); } function nextStep (base64) { doNext(atob(base64)); @@ -90,7 +92,7 @@ export default class Sprite { function doNext (str) { str = str.replace(/>\s*<'); spr.setSVG(str); - if ((str.indexOf('xlink:href') < 0) && iOS.path) { + if ((str.indexOf('xlink:href') < 0) && OS.path) { whenDone(url); // does not have embedded images } else { var base64 = IO.getImageDataURL(spr.md5, btoa(str)); @@ -716,7 +718,7 @@ export default class Sprite { var sprites = JSON.parse(page.sprites); sprites.push(this.id); page.sprites = JSON.stringify(sprites); - iOS.analyticsEvent('editor', 'text_sprite_create'); + OS.analyticsEvent('editor', 'text_sprite_create'); if ((this.str == '') && !whenDone) { this.setTextBox(); this.activateInput(); @@ -806,7 +808,7 @@ export default class Sprite { document.body.scrollLeft = 0; var form = document.forms.activetextbox; var changed = (this.oldvalue != form.typing.value); - iOS.analyticsEvent('editor', 'text_sprite_close'); + OS.analyticsEvent('editor', 'text_sprite_close'); if (this.noChars(form.typing.value)) { this.deleteText(this.oldvalue != ''); } else { @@ -891,7 +893,7 @@ export default class Sprite { var ti = document.forms.activetextbox.typing; gn('textbox').style.visibility = 'visible'; var me = this; - iOS.analyticsEvent('editor', 'text_sprite_open'); + OS.analyticsEvent('editor', 'text_sprite_open'); ti.onblur = function () { me.unfocusText(); }; diff --git a/src/editor/ui/Library.js b/src/editor/ui/Library.js index 96ce963..76cde19 100644 --- a/src/editor/ui/Library.js +++ b/src/editor/ui/Library.js @@ -1,8 +1,8 @@ import ScratchJr from '../ScratchJr'; -import iOS from '../../iPad/iOS'; -import IO from '../../iPad/IO'; -import MediaLib from '../../iPad/MediaLib'; +import OS from '../../tablet/OS'; +import IO from '../../tablet/IO'; +import MediaLib from '../../tablet/MediaLib'; import Paint from '../../painteditor/Paint'; import Events from '../../utils/Events'; import Localization from '../../utils/Localization'; @@ -406,12 +406,12 @@ export default class Library { // (this is possible if we receive a duplicate project, for example) Library.assetThumbnailUnique(data.altmd5, type, function (isUnique) { if (isUnique) { - iOS.remove(data.altmd5, iOS.trace); + OS.remove(data.altmd5, OS.trace); } }); } - IO.deleteobject(key, data.id, iOS.trace); + IO.deleteobject(key, data.id, OS.trace); } static parseAssetData (data) { @@ -525,7 +525,7 @@ export default class Library { if (!(selectedOne in MediaLib.keys)) { analyticsName = 'user_asset'; } - iOS.analyticsEvent('editor', 'new_character', analyticsName); + OS.analyticsEvent('editor', 'new_character', analyticsName); } Library.close(e); } @@ -542,7 +542,7 @@ export default class Library { if (!(selectedOne in MediaLib.keys)) { analyticsName = 'user_background'; } - iOS.analyticsEvent('editor', 'choose_background', analyticsName); + OS.analyticsEvent('editor', 'choose_background', analyticsName); } Library.close(e); } diff --git a/src/editor/ui/Palette.js b/src/editor/ui/Palette.js index a22dc3b..6f0ec7a 100644 --- a/src/editor/ui/Palette.js +++ b/src/editor/ui/Palette.js @@ -7,8 +7,8 @@ import Block from '../blocks/Block'; import BlockSpecs from '../blocks/BlockSpecs'; import ScriptsPane from './ScriptsPane'; import Undo from './Undo'; -import iOS from '../../iPad/iOS'; -import MediaLib from '../../iPad/MediaLib'; +import OS from '../../tablet/OS'; +import MediaLib from '../../tablet/MediaLib'; import Events from '../../utils/Events'; import Rectangle from '../../geom/Rectangle'; import DrawPath from '../../utils/DrawPath'; @@ -582,7 +582,7 @@ export default class Palette { e.preventDefault(); switch (Palette.getLandingPlace(element, e)) { case 'scripts': - iOS.analyticsEvent('editor', 'new_block_' + element.owner.blocktype); + OS.analyticsEvent('editor', 'new_block_' + element.owner.blocktype); var sc = ScratchJr.getActiveScript(); var dx = localx(sc, element.left); var dy = localy(sc, element.top); diff --git a/src/editor/ui/Project.js b/src/editor/ui/Project.js index 8229dc5..3ad7a9a 100644 --- a/src/editor/ui/Project.js +++ b/src/editor/ui/Project.js @@ -5,8 +5,8 @@ import Palette from './Palette'; import UI from './UI'; import Page from '../engine/Page'; import Sprite from '../engine/Sprite'; -import iOS from '../../iPad/iOS'; -import IO from '../../iPad/IO'; +import OS from '../../tablet/OS'; +import IO from '../../tablet/IO'; import Paint from '../../painteditor/Paint'; import SVG2Canvas from '../../utils/SVG2Canvas'; import {frame, gn, newHTML, scaleMultiplier, getIdFor, @@ -417,7 +417,7 @@ export default class Project { json.cond = 'deleted = ? AND id != ? AND gallery IS NULL'; json.items = ['name', 'thumbnail', 'id']; json.values = ['NO', projectID]; - IO.query(iOS.database, json, function (result) { + IO.query(OS.database, json, function (result) { var pdata = JSON.parse(result); var isUnique = true; for (var p = 0; p < pdata.length; p++) { @@ -444,7 +444,7 @@ export default class Project { if (thumb.md5.indexOf('samples/') < 0) { // In case we've exited story-starter mode Project.thumbnailUnique(thumb.md5, id, function (isUnique) { if (isUnique) { - iOS.remove(thumb.md5, iOS.trace); // remove thumb; + OS.remove(thumb.md5, OS.trace); // remove thumb; } }); } @@ -454,14 +454,14 @@ export default class Project { Project.getThumbnailPNG(ScratchJr.stage.pages[0], 192, 144, getMD5); function getMD5 (dataurl) { var pngBase64 = dataurl.split(',')[1]; - iOS.getmd5(pngBase64, function (str) { + OS.getmd5(pngBase64, function (str) { savePNG(str, pngBase64); }); } function savePNG (md5, pngBase64) { var filename = ScratchJr.currentProject + '_' + md5; - iOS.setmedianame(pngBase64, filename, 'png', doNext); + OS.setmedianame(pngBase64, filename, 'png', doNext); } function doNext (md5) { diff --git a/src/editor/ui/Record.js b/src/editor/ui/Record.js index 47bb8e6..a9dc43f 100644 --- a/src/editor/ui/Record.js +++ b/src/editor/ui/Record.js @@ -1,7 +1,7 @@ import ScratchJr from '../ScratchJr'; import Palette from './Palette'; import Undo from './Undo'; -import iOS from '../../iPad/iOS'; +import OS from '../../tablet/OS'; import ScratchAudio from '../../utils/ScratchAudio'; import {frame, gn, newHTML, isTablet, isAndroid, setProps} from '../../utils/lib'; @@ -60,7 +60,7 @@ export default class Record { // Dialog box hide/show static appear () { - iOS.analyticsEvent('editor', 'record_dialog_open'); + OS.analyticsEvent('editor', 'record_dialog_open'); gn('backdrop').setAttribute('class', 'modal-backdrop fade in'); setProps(gn('backdrop').style, { display: 'block' @@ -72,7 +72,7 @@ export default class Record { } static disappear () { - iOS.analyticsEvent('editor', 'record_dialog_close'); + OS.analyticsEvent('editor', 'record_dialog_close'); setTimeout(function () { gn('backdrop').setAttribute('class', 'modal-backdrop fade'); setProps(gn('backdrop').style, { @@ -148,13 +148,13 @@ export default class Record { if (isRecording) { Record.stopRecording(); // Stop if we're already recording } else { - iOS.sndrecord(Record.startRecording); // Start a recording + OS.sndrecord(Record.startRecording); // Start a recording } } } static startRecording (filename) { - iOS.analyticsEvent('editor', 'start_recording'); + OS.analyticsEvent('editor', 'start_recording'); if (parseInt(filename) < 0) { // Error in getting record filename - go back to editor recordedSound = undefined; @@ -169,7 +169,7 @@ export default class Record { Record.soundname = filename; Record.toggleButtonUI('record', true); var poll = function () { - iOS.volume(Record.updateVolume, Record.recordError); + OS.volume(Record.updateVolume, Record.recordError); }; interval = setInterval(poll, 33); timeLimit = setTimeout(function () { @@ -202,7 +202,7 @@ export default class Record { // Start playing the sound and switch UI appropriately static startPlaying () { - iOS.startplay(Record.timeOutPlay); + OS.startplay(Record.timeOutPlay); Record.toggleButtonUI('play', true); isPlaying = true; } @@ -244,7 +244,7 @@ export default class Record { // Stop playing the sound and switch UI appropriately static stopPlayingSound (fcn) { - iOS.stopplay(fcn); + OS.stopplay(fcn); Record.toggleButtonUI('play', false); isPlaying = false; window.clearTimeout(playTimeLimit); @@ -253,7 +253,7 @@ export default class Record { // Stop the volume monitor and recording static stopRecording (fcn) { - iOS.analyticsEvent('editor', 'stop_recording'); + OS.analyticsEvent('editor', 'stop_recording'); if (timeLimit != null) { clearTimeout(timeLimit); timeLimit = null; @@ -272,7 +272,7 @@ export default class Record { static volumeCheckStopped (fcn) { isRecording = false; Record.recordUIoff(); - iOS.recordstop(fcn); + OS.recordstop(fcn); } // Press OK (check) @@ -293,12 +293,12 @@ export default class Record { } static closeContinueSave () { - iOS.recorddisappear('YES', Record.registerProjectSound); + OS.recorddisappear('YES', Record.registerProjectSound); } static closeContinueRemove () { // don't get the sound - proceed right to tearDown - iOS.recorddisappear('NO', Record.tearDownRecorder); + OS.recorddisappear('NO', Record.tearDownRecorder); } static registerProjectSound () { diff --git a/src/editor/ui/Thumbs.js b/src/editor/ui/Thumbs.js index 8f07bc9..1e6d1d2 100644 --- a/src/editor/ui/Thumbs.js +++ b/src/editor/ui/Thumbs.js @@ -8,7 +8,7 @@ import Page from '../engine/Page'; import ScriptsPane from './ScriptsPane'; import Undo from './Undo'; import UI from './UI'; -import iOS from '../../iPad/iOS'; +import OS from '../../tablet/OS'; import Events from '../../utils/Events'; import ScratchAudio from '../../utils/ScratchAudio'; import {frame, gn, localx, newHTML, scaleMultiplier, getIdFor, @@ -83,7 +83,7 @@ export default class Thumbs { var tb = Thumbs.getType(Thumbs.t, 'pagethumb'); if (ScratchJr.shaking && (e.target.className == 'deletethumb')) { ScratchJr.clearSelection(); - iOS.analyticsEvent('editor', 'delete_scene'); + OS.analyticsEvent('editor', 'delete_scene'); ScratchJr.stage.deletePage(tb.owner); return; } @@ -378,7 +378,7 @@ export default class Thumbs { sc.owner.deactivate(); } ScratchJr.unfocus(e); - iOS.analyticsEvent('editor', 'add_scene'); + OS.analyticsEvent('editor', 'add_scene'); new Page(getIdFor('page')); } diff --git a/src/editor/ui/UI.js b/src/editor/ui/UI.js index 8bd8f1e..f9a7761 100644 --- a/src/editor/ui/UI.js +++ b/src/editor/ui/UI.js @@ -13,15 +13,16 @@ import Stage from '../engine/Stage'; import ScriptsPane from './ScriptsPane'; import Undo from './Undo'; import Library from './Library'; -import iOS from '../../iPad/iOS'; -import IO from '../../iPad/IO'; -import MediaLib from '../../iPad/MediaLib'; +import OS from '../../tablet/OS'; +import IO from '../../tablet/IO'; +import MediaLib from '../../tablet/MediaLib'; import Paint from '../../painteditor/Paint'; import Events from '../../utils/Events'; import Localization from '../../utils/Localization'; import ScratchAudio from '../../utils/ScratchAudio'; -import {frame, gn, CSSTransition, localx, newHTML, scaleMultiplier, fullscreenScaleMultiplier, getIdFor, isTablet, newDiv, - newTextInput, isAndroid, getDocumentWidth, getDocumentHeight, setProps, globalx} from '../../utils/lib'; +import {frame, gn, CSSTransition, localx, newHTML, scaleMultiplier, fullscreenScaleMultiplier, + getIdFor, isTablet, newDiv, newTextInput, isAndroid, getDocumentWidth, getDocumentHeight, + setProps, globalx} from '../../utils/lib'; let projectNameTextInput = null; let info = null; @@ -156,7 +157,7 @@ export default class UI { }; } - iOS.deviceName(function (name) { + OS.deviceName(function (name) { gn('deviceName').textContent = name; }); @@ -257,7 +258,7 @@ export default class UI { setTimeout(saveAndShare, 500); // 500ms delay to wait for loading GIF to show and keyboard to hide - iOS.analyticsEvent('editor', 'share_button', (shareType == EMAILSHARE) ? 'email' : 'airdrop'); + OS.analyticsEvent('editor', 'share_button', (shareType == EMAILSHARE) ? 'email' : 'airdrop'); function saveAndShare () { // Save the project's new name @@ -276,7 +277,7 @@ export default class UI { var emailSubject = Localization.localize('SHARING_EMAIL_SUBJECT', { PROJECT_NAME: IO.shareName }); - iOS.sendSjrToShareDialog(IO.zipFileName, emailSubject, Localization.localize('SHARING_EMAIL_TEXT'), + OS.sendSjrToShareDialog(IO.zipFileName, emailSubject, Localization.localize('SHARING_EMAIL_TEXT'), shareType, contents); shareLoadingGif.style.visibility = 'hidden'; @@ -332,7 +333,7 @@ export default class UI { static handleTextFieldSave (dontHide) { // Handle story-starter mode project if (ScratchJr.isEditable() && ScratchJr.editmode == 'storyStarter' && !Project.error) { - iOS.analyticsEvent('samples', 'story_starter_edited', Project.metadata.name); + OS.analyticsEvent('samples', 'story_starter_edited', Project.metadata.name); // Get the new project name var sampleName = Localization.localize('SAMPLE_' + Project.metadata.name); IO.uniqueProjectName({ @@ -366,7 +367,7 @@ export default class UI { } Project.metadata.name = pname; ScratchJr.changed = true; - iOS.setfield(iOS.database, Project.metadata.id, 'name', pname); + OS.setfield(OS.database, Project.metadata.id, 'name', pname); if (!dontHide) { ScratchAudio.sndFX('exittap.wav'); gn('infobox').className = 'infobox fade'; @@ -757,7 +758,7 @@ export default class UI { static switchGrid () { ScratchAudio.sndFX('tap.wav'); UI.setShowGrid(Grid.hidden); - iOS.analyticsEvent('editor', Grid.hidden ? 'hide_grid' : 'show_grid'); + OS.analyticsEvent('editor', Grid.hidden ? 'hide_grid' : 'show_grid'); } static setShowGrid (b) { diff --git a/src/entry/app.js b/src/entry/app.js index bcd31bf..136b7d6 100644 --- a/src/entry/app.js +++ b/src/entry/app.js @@ -1,9 +1,9 @@ import {preprocessAndLoadCss} from '../utils/lib'; import Localization from '../utils/Localization'; import AppUsage from '../utils/AppUsage'; -import iOS from '../iPad/iOS'; -import IO from '../iPad/IO'; -import MediaLib from '../iPad/MediaLib'; +import OS from '../tablet/OS'; +import IO from '../tablet/IO'; +import MediaLib from '../tablet/MediaLib'; import {indexMain} from './index'; import {homeMain} from './home'; @@ -11,7 +11,6 @@ import {editorMain} from './editor'; import {gettingStartedMain} from './gettingstarted'; import {inappInterfaceGuide, inappAbout, inappBlocksGuide, inappPaintEditorGuide} from './inapp'; - function loadSettings (settingsRoot, whenDone) { IO.requestFromServer(settingsRoot + 'settings.json', (result) => { window.Settings = JSON.parse(result); @@ -42,7 +41,7 @@ window.onload = () => { preprocessAndLoadCss('css', 'css/thumbs.css'); /* For parental gate. These CSS properties should be refactored */ preprocessAndLoadCss('css', 'css/editor.css'); - entryFunction = () => iOS.waitForInterface(indexMain); + entryFunction = () => OS.waitForInterface(indexMain); break; case 'home': // Lobby pages @@ -50,7 +49,7 @@ window.onload = () => { preprocessAndLoadCss('css', 'css/base.css'); preprocessAndLoadCss('css', 'css/lobby.css'); preprocessAndLoadCss('css', 'css/thumbs.css'); - entryFunction = () => iOS.waitForInterface(homeMain); + entryFunction = () => OS.waitForInterface(homeMain); break; case 'editor': // Editor pages @@ -62,14 +61,14 @@ window.onload = () => { preprocessAndLoadCss('css', 'css/editormodal.css'); preprocessAndLoadCss('css', 'css/librarymodal.css'); preprocessAndLoadCss('css', 'css/paintlook.css'); - entryFunction = () => iOS.waitForInterface(editorMain); + entryFunction = () => OS.waitForInterface(editorMain); break; case 'gettingStarted': // Getting started video page preprocessAndLoadCss('css', 'css/font.css'); preprocessAndLoadCss('css', 'css/base.css'); preprocessAndLoadCss('css', 'css/gs.css'); - entryFunction = () => iOS.waitForInterface(gettingStartedMain); + entryFunction = () => OS.waitForInterface(gettingStartedMain); break; case 'inappAbout': // About ScratchJr in-app help frame diff --git a/src/entry/editor.js b/src/entry/editor.js index 74d6925..76434b1 100644 --- a/src/entry/editor.js +++ b/src/entry/editor.js @@ -1,14 +1,14 @@ import ScratchJr from '../editor/ScratchJr'; -import iOS from '../iPad/iOS'; +import OS from '../tablet/OS'; import Camera from '../painteditor/Camera'; import Record from '../editor/ui/Record'; export function editorMain () { - iOS.getsettings(doNext); - iOS.analyticsEvent('editor', 'project_editor_open'); + OS.getsettings(doNext); + OS.analyticsEvent('editor', 'project_editor_open'); function doNext (str) { var list = str.split(','); - iOS.path = list[1] == '0' ? list[0] + '/' : undefined; + OS.path = list[1] == '0' ? list[0] + '/' : undefined; if (list.length > 2) { Record.available = list[2] == 'YES' ? true : false; } diff --git a/src/entry/home.js b/src/entry/home.js index aac133c..3f49588 100644 --- a/src/entry/home.js +++ b/src/entry/home.js @@ -1,15 +1,15 @@ import {gn} from '../utils/lib'; import Localization from '../utils/Localization'; -import iOS from '../iPad/iOS'; +import OS from '../tablet/OS'; import Lobby from '../lobby/Lobby'; export function homeMain () { gn('logotab').ontouchend = homeGoBack; homeStrings(); - iOS.getsettings(doNext); + OS.getsettings(doNext); function doNext (str) { var list = str.split(','); - iOS.path = list[1] == '0' ? list[0] + '/' : undefined; + OS.path = list[1] == '0' ? list[0] + '/' : undefined; Lobby.appinit(window.Settings.scratchJrVersion); } } diff --git a/src/entry/index.js b/src/entry/index.js index 27415d5..baa849c 100644 --- a/src/entry/index.js +++ b/src/entry/index.js @@ -1,6 +1,6 @@ import ScratchAudio from '../utils/ScratchAudio'; import {gn, getUrlVars, isAndroid, isiOS} from '../utils/lib'; -import iOS from '../iPad/iOS'; +import OS from '../tablet/OS'; import UI from '../editor/ui/UI'; import Localization from '../utils/Localization'; import AppUsage from '../utils/AppUsage'; @@ -49,9 +49,9 @@ function indexFirstTime () { gn('blueguy').className = 'blue show'; gn('redguy').className = 'red show'; } - iOS.askpermission(); // ask for sound recording + OS.askpermission(); // ask for sound recording setTimeout(function () { - iOS.hidesplash(doit); + OS.hidesplash(doit); }, 500); function doit () { window.ontouchend = function () { @@ -94,7 +94,7 @@ function indexLoadStart (afterUsage) { gn('usageOther').className = 'usageOther hide'; gn('usageNoanswer').className = 'usageNoanswer hide'; } - iOS.setAnalyticsPlacePref(AppUsage.currentUsage); + OS.setAnalyticsPlacePref(AppUsage.currentUsage); } gn('gettings').className = 'gettings show'; gn('startcode').className = 'startcode show'; @@ -133,7 +133,7 @@ function indexLoadUsage () { } function indexGohome () { - iOS.setfile('homescroll.sjr', 0, function () { + OS.setfile('homescroll.sjr', 0, function () { doNext(); }); function doNext () { @@ -171,7 +171,7 @@ function indexSetUsage (e) { break; } // Send one-time analytics event about usage - iOS.analyticsEvent('lobby', 'scratchjr_usage', usageText); + OS.analyticsEvent('lobby', 'scratchjr_usage', usageText); AppUsage.setUsage(usageText); ScratchAudio.sndFX('tap.wav'); indexLoadStart(true); diff --git a/src/iPad/iOS.js b/src/iPad/iOS.js deleted file mode 100644 index 8062bfe..0000000 --- a/src/iPad/iOS.js +++ /dev/null @@ -1,375 +0,0 @@ -import {isiOS, gn} from '../utils/lib'; -import IO from './IO'; -import Lobby from '../lobby/Lobby'; -import Alert from '../editor/ui/Alert'; -import ScratchAudio from '../utils/ScratchAudio'; - -////////////////////////////////////////////////// -// Tablet interface functions -////////////////////////////////////////////////// - -// This file and object are named "iOS" for legacy reasons. -// But, it is also used for the AndroidInterface. All function calls here -// are mapped to Android/iOS native calls. - -let path; -let camera; -let database = 'projects'; -let mediacounter = 0; -let tabletInterface = null; - -export default class iOS { - // Getters/setters for properties used in other classes - static get path () { - return path; - } - - static set path (newPath) { - path = newPath; - } - - static get camera () { - return camera; - } - - static get database () { - return database; - } - - // Wait for the tablet interface to be injected into the webview - static waitForInterface (fcn) { - // Already loaded the interface - if (tabletInterface != null) { - fcn(); - return; - } - - // Android device - if (typeof AndroidInterface !== 'undefined') { - tabletInterface = AndroidInterface; - if (fcn) { - fcn(); - } - return; - } - - // iOS device - might not be loaded yet - if (typeof (window.tablet) != 'object') { - // Come back in 100ms - setTimeout(function () { - iOS.waitForInterface(fcn); - }, 100); - } else { - // All set to run commands - tabletInterface = window.tablet; - if (fcn) { - fcn(); - } - } - } - - // Database functions - static stmt (json, fcn) { - var result = tabletInterface.database_stmt(JSON.stringify(json)); - if (typeof (fcn) !== 'undefined') { - fcn(result); - } - } - - static query (json, fcn) { - var result = tabletInterface.database_query(JSON.stringify(json)); - if (typeof (fcn) !== 'undefined') { - fcn(result); - } - } - - static setfield (db, id, fieldname, val, fcn) { - var json = {}; - var keylist = [fieldname + ' = ?', 'mtime = ?']; - json.values = [val, (new Date()).getTime().toString()]; - json.stmt = 'update ' + db + ' set ' + keylist.toString() + ' where id = ' + id; - iOS.stmt(json, fcn); - } - - // IO functions - - static cleanassets (ft, fcn) { - tabletInterface.io_cleanassets(ft); fcn(); - } - - static getmedia (file, fcn) { - mediacounter++; - var nextStep = function (file, key, whenDone) { - var result = tabletInterface.io_getmedialen(file, key); - iOS.processdata(key, 0, result, '', whenDone); - }; - nextStep(file, mediacounter, fcn); - } - - static getmediadata (key, offset, len, fcn) { - var result = tabletInterface.io_getmediadata(key, offset, len); - if (fcn) { - fcn(result); - } - } - - static processdata (key, off, len, oldstr, fcn) { - if (len == 0) { - iOS.getmediadone(key); - fcn(oldstr); - return; - } - var newlen = (len < 100000) ? len : 100000; - iOS.getmediadata(key, off, newlen, function (str) { - iOS.processdata(key, off + newlen, len - newlen, oldstr + str, fcn); - }); - } - - static getsettings (fcn) { - var result = tabletInterface.io_getsettings(); - if (fcn) { - fcn(result); - } - } - - static getmediadone (file, fcn) { - var result = tabletInterface.io_getmediadone(file); - if (fcn) { - fcn(result); - } - } - - static setmedia (str, ext, fcn) { - var result = tabletInterface.io_setmedia(str, ext); - if (fcn) { - fcn(result); - } - } - - static setmedianame (str, name, ext, fcn) { - var result = tabletInterface.io_setmedianame(str, name, ext); - if (fcn) { - fcn(result); - } - } - - static getmd5 (str, fcn) { - var result = tabletInterface.io_getmd5(str); - if (fcn) { - fcn(result); - } - } - - static remove (str, fcn) { - var result = tabletInterface.io_remove(str); - if (fcn) { - fcn(result); - } - } - - static getfile (str, fcn) { - var result = tabletInterface.io_getfile(str); - if (fcn) { - fcn(result); - } - } - - static setfile (name, str, fcn) { - var result = tabletInterface.io_setfile(name, btoa(str)); - if (fcn) { - fcn(result); - } - } - - // Sound functions - - static registerSound (dir, name, fcn) { - var result = tabletInterface.io_registersound(dir, name); - if (fcn) { - fcn(result); - } - } - - static playSound (name, fcn) { - var result = tabletInterface.io_playsound(name); - if (fcn) { - fcn(result); - } - } - - static stopSound (name, fcn) { - var result = tabletInterface.io_stopsound(name); - if (fcn) { - fcn(result); - } - } - - // Web Wiew delegate call backs - - static soundDone (name) { - ScratchAudio.soundDone(name); - } - - static sndrecord (fcn) { - var result = tabletInterface.recordsound_recordstart(); - if (fcn) { - fcn(result); - } - } - - static recordstop (fcn) { - var result = tabletInterface.recordsound_recordstop(); - if (fcn) { - fcn(result); - } - } - - static volume (fcn) { - var result = tabletInterface.recordsound_volume(); - if (fcn) { - fcn(result); - } - } - - static startplay (fcn) { - var result = tabletInterface.recordsound_startplay(); - if (fcn) { - fcn(result); - } - } - - static stopplay (fcn) { - var result = tabletInterface.recordsound_stopplay(); - if (fcn) { - fcn(result); - } - } - - static recorddisappear (b, fcn) { - var result = tabletInterface.recordsound_recordclose(b); - if (fcn) { - fcn(result); - } - } - - // Record state - static askpermission () { - if (isiOS) { - tabletInterface.askForPermission(); - } - } - - // camera functions - - static hascamera () { - camera = tabletInterface.scratchjr_cameracheck(); - } - - static startfeed (data, fcn) { - var str = JSON.stringify(data); - var result = tabletInterface.scratchjr_startfeed(str); - if (fcn) { - fcn(result); - } - } - - static stopfeed (fcn) { - var result = tabletInterface.scratchjr_stopfeed(); - if (fcn) { - fcn(result); - } - } - - static choosecamera (mode, fcn) { - var result = tabletInterface.scratchjr_choosecamera(mode); - if (fcn) { - fcn(result); - } - } - - static captureimage (fcn) { - tabletInterface.scratchjr_captureimage(fcn); - } - - static hidesplash (fcn) { - if (isiOS) { - tabletInterface.hideSplash(); - } - if (fcn) { - fcn(); - } - } - - static trace (str) { - console.log(str); // eslint-disable-line no-console - } - - static parse (str) { - console.log(JSON.parse(str)); // eslint-disable-line no-console - } - - static tracemedia (str) { - console.log(atob(str)); // eslint-disable-line no-console - } - - ignore () { - } - - /////////////// - // Sharing - /////////////// - - - // Called on the JS side to trigger native UI for project sharing. - // fileName: name for the file to share - // emailSubject: subject text to use for an email - // emailBody: body HTML to use for an email - // shareType: 0 for Email; 1 for Airdrop - // b64data: base-64 encoded .SJR file to share - - static sendSjrToShareDialog (fileName, emailSubject, emailBody, shareType, b64data) { - tabletInterface.sendSjrUsingShareDialog(fileName, emailSubject, emailBody, shareType, b64data); - } - - // Called on the Objective-C side. The argument is a base64-encoded .SJR file, - // to be unzipped, processed, and stored. - static loadProjectFromSjr (b64data) { - try { - IO.loadProjectFromSjr(b64data); - } catch (err) { - var errorMessage = 'Couldn\'t load share -- project data corrupted. ' + err.message; - Alert.open(gn('frame'), gn('frame'), errorMessage, '#ff0000'); - console.log(err); // eslint-disable-line no-console - return 0; - } - return 1; - } - - // Name of the device/iPad to display on the sharing dialog page - // fcn is called with the device name as an arg - static deviceName (fcn) { - fcn(tabletInterface.deviceName()); - } - - static analyticsEvent (category, action, label) { - tabletInterface.analyticsEvent(category, action, label); - } - - static setAnalyticsPlacePref (preferredPlace) { - tabletInterface.setAnalyticsPlacePref(preferredPlace); - } - - // Web Wiew delegate call backs - - static pageError (desc) { - console.log('XCODE ERROR:', desc); // eslint-disable-line no-console - if (window.location.href.indexOf('home.html') > -1) { - if (Lobby.errorTimer) { - Lobby.errorLoading(desc); - } - } - } -} - -// Expose iOS methods for ScratchJr tablet sharing callbacks -window.iOS = iOS; diff --git a/src/lobby/Home.js b/src/lobby/Home.js index 161bb2b..6a78204 100755 --- a/src/lobby/Home.js +++ b/src/lobby/Home.js @@ -3,8 +3,8 @@ ////////////////////////////////////////////////// import Lobby from './Lobby'; -import iOS from '../iPad/iOS'; -import IO from '../iPad/IO'; +import OS from '../tablet/OS'; +import IO from '../tablet/IO'; import Project from '../editor/ui/Project'; import Localization from '../utils/Localization'; import ScratchAudio from '../utils/ScratchAudio'; @@ -135,7 +135,7 @@ export default class Home { if (md5 && (md5 == 'newproject')) { Home.createNewProject(); } else if (md5) { - iOS.setfile('homescroll.sjr', gn('wrapc').scrollTop, function () { + OS.setfile('homescroll.sjr', gn('wrapc').scrollTop, function () { doNext(md5); }); } @@ -144,10 +144,10 @@ export default class Home { ScratchAudio.sndFX('cut.wav'); Project.thumbnailUnique(Home.actionTarget.thumb, Home.actionTarget.id, function (isUnique) { if (isUnique) { - iOS.remove(Home.actionTarget.thumb, iOS.trace); + OS.remove(Home.actionTarget.thumb, OS.trace); } }); - iOS.setfield(iOS.database, Home.actionTarget.id, 'deleted', 'YES', Home.removeProjThumb); + OS.setfield(OS.database, Home.actionTarget.id, 'deleted', 'YES', Home.removeProjThumb); break; default: if (Home.actionTarget && (Home.actionTarget.childElementCount > 2)) { @@ -156,13 +156,13 @@ export default class Home { break; } function doNext () { - iOS.analyticsEvent('lobby', 'existing_project_edited'); + OS.analyticsEvent('lobby', 'existing_project_edited'); window.location.href = 'editor.html?pmd5=' + md5 + '&mode=edit'; } } static createNewProject () { - iOS.analyticsEvent('lobby', 'project_created'); + OS.analyticsEvent('lobby', 'project_created'); var obj = {}; // XXX: for localization, the new project name should likely be refactored obj.name = Home.getNextName(Localization.localize('NEW_PROJECT_PREFIX')); @@ -172,7 +172,7 @@ export default class Home { } static gotoEditor (md5) { - iOS.setfile('homescroll.sjr', gn('wrapc').scrollTop, function () { + OS.setfile('homescroll.sjr', gn('wrapc').scrollTop, function () { doNext(md5); }); function doNext (md5) { @@ -230,7 +230,7 @@ export default class Home { ////////////////////////// static displayYourProjects () { - iOS.getfile('homescroll.sjr', gotScrollsState); + OS.getfile('homescroll.sjr', gotScrollsState); function gotScrollsState (str) { var num = Number(atob(str)); scrollvalue = (num.toString() == 'NaN') ? 0 : num; @@ -239,7 +239,7 @@ export default class Home { json.items = ['name', 'thumbnail', 'id', 'isgift']; json.values = ['NO', version]; json.order = 'ctime desc'; - IO.query(iOS.database, json, Home.displayProjects); + IO.query(OS.database, json, Home.displayProjects); } } diff --git a/src/lobby/Lobby.js b/src/lobby/Lobby.js index 680a7c3..eb416e2 100644 --- a/src/lobby/Lobby.js +++ b/src/lobby/Lobby.js @@ -4,7 +4,7 @@ import {libInit, getUrlVars, gn, isAndroid, newHTML} from '../utils/lib'; import ScratchAudio from '../utils/ScratchAudio'; -import iOS from '../iPad/iOS'; +import OS from '../tablet/OS'; import Localization from '../utils/Localization'; import Cookie from '../utils/Cookie'; @@ -98,7 +98,7 @@ export default class Lobby { var doNext = function (page) { Lobby.changePage(page); }; - iOS.setfile('homescroll.sjr', gn('wrapc').scrollTop, function () { + OS.setfile('homescroll.sjr', gn('wrapc').scrollTop, function () { doNext(page); }); } else { @@ -203,7 +203,7 @@ export default class Lobby { ScratchAudio.sndFX('tap.wav'); let newLocale = window.Settings.supportedLocales[e.target.textContent]; Cookie.set('localization', newLocale); - iOS.analyticsEvent('lobby', 'language_changed', newLocale); + OS.analyticsEvent('lobby', 'language_changed', newLocale); window.location = '?place=gear'; }; } diff --git a/src/lobby/Samples.js b/src/lobby/Samples.js index 240032a..82ae417 100644 --- a/src/lobby/Samples.js +++ b/src/lobby/Samples.js @@ -3,9 +3,9 @@ ////////////////////////////////////////////////// import Lobby from './Lobby'; -import IO from '../iPad/IO'; -import iOS from '../iPad/iOS'; -import MediaLib from '../iPad/MediaLib'; +import OS from '../tablet/OS'; +import IO from '../tablet/IO'; +import MediaLib from '../tablet/MediaLib'; import ScratchAudio from '../utils/ScratchAudio'; import Localization from '../utils/Localization'; import {gn, newHTML} from '../utils/lib'; @@ -75,7 +75,7 @@ export default class Samples { e.preventDefault(); e.stopPropagation(); ScratchAudio.sndFX('tap.wav'); - iOS.analyticsEvent('samples', 'sample_opened', mt.textContent); + OS.analyticsEvent('samples', 'sample_opened', mt.textContent); var md5 = mt.md5; window.location.href = 'editor.html?pmd5=' + md5 + '&mode=' + ((window.Settings.useStoryStarters) ? 'storyStarter' : 'look'); diff --git a/src/painteditor/Camera.js b/src/painteditor/Camera.js index 2ca1720..0d4dfbd 100644 --- a/src/painteditor/Camera.js +++ b/src/painteditor/Camera.js @@ -1,5 +1,5 @@ import ScratchJr from '../editor/ScratchJr'; -import iOS from '../iPad/iOS'; +import OS from '../tablet/OS'; import ScratchAudio from '../utils/ScratchAudio'; import Paint from './Paint'; import PaintUndo from './PaintUndo'; @@ -53,7 +53,7 @@ export default class Camera { data.mw = Paint.workspaceWidth; data.mh = Paint.workspaceHeight; data.image = mask.toDataURL('image/png'); - iOS.startfeed(data, iOS.trace); + OS.startfeed(data, OS.trace); Paint.cameraToolsOn(); } @@ -80,7 +80,7 @@ export default class Camera { case 'cameraflip': ScratchAudio.sndFX('tap.wav'); view = (view == 'front') ? 'back' : 'front'; - iOS.choosecamera(view, Camera.flip); + OS.choosecamera(view, Camera.flip); break; case 'camerasnap': Camera.snapShot(); @@ -101,7 +101,7 @@ export default class Camera { target = undefined; view = 'front'; Camera.active = false; - iOS.stopfeed(); + OS.stopfeed(); Paint.cameraToolsOff(); if (isAndroid) { ScratchJr.onBackButtonCallback.pop(); @@ -109,7 +109,7 @@ export default class Camera { } static snapShot () { - iOS.captureimage('Camera.processimage'); // javascript call back; + OS.captureimage('Camera.processimage'); // javascript call back; } static getLayerMask (elem) { @@ -197,5 +197,5 @@ export default class Camera { } } -// Exposing the camera for the tablet callback in iOS.snapShot +// Exposing the camera for the tablet callback in OS.snapShot window.Camera = Camera; diff --git a/src/painteditor/Paint.js b/src/painteditor/Paint.js index 1329bc7..407bf13 100644 --- a/src/painteditor/Paint.js +++ b/src/painteditor/Paint.js @@ -3,9 +3,9 @@ import BlockSpecs from '../editor/blocks/BlockSpecs'; import SVGTools from './SVGTools'; import SVG2Canvas from '../utils/SVG2Canvas'; import Ghost from './Ghost'; -import iOS from '../iPad/iOS'; -import IO from '../iPad/IO'; -import MediaLib from '../iPad/MediaLib'; +import OS from '../tablet/OS'; +import IO from '../tablet/IO'; +import MediaLib from '../tablet/MediaLib'; import Localization from '../utils/Localization'; import Alert from '../editor/ui/Alert'; import PaintAction from './PaintAction'; @@ -169,7 +169,7 @@ export default class Paint { // log two events: // * paint editor is opened // * type of edit (edit_background, edit_character, new_character) - iOS.analyticsEvent('paint_editor', 'paint_editor_open'); + OS.analyticsEvent('paint_editor', 'paint_editor_open'); if (bkg) { action = 'edit_background'; label = (md5 in MediaLib.keys) ? md5 : 'user_background'; @@ -177,7 +177,7 @@ export default class Paint { action = sname ? 'edit_character' : 'new_character'; label = (md5 in MediaLib.keys) ? md5 : 'user_character'; } - iOS.analyticsEvent('paint_editor', action, label); + OS.analyticsEvent('paint_editor', action, label); PaintUndo.buffer = []; PaintUndo.index = 0; maxZoom = 5; @@ -356,7 +356,7 @@ export default class Paint { } static close () { - iOS.analyticsEvent('paint_editor', 'paint_editor_close'); + OS.analyticsEvent('paint_editor', 'paint_editor_close'); saving = true; paintFrame.className = 'paintframe disappear'; frame.style.display = 'block'; @@ -774,7 +774,7 @@ export default class Paint { Paint.addSidePalette(rightpal, 'selectortools', ['select', 'rotate']); Paint.addSidePalette(rightpal, 'edittools', ['stamper', 'scissors']); Paint.addSidePalette(rightpal, 'filltools', - (iOS.camera == '1' && Camera.available) ? ['camera', 'paintbucket'] : ['paintbucket']); + (OS.camera == '1' && Camera.available) ? ['camera', 'paintbucket'] : ['paintbucket']); } static addSidePalette (p, id, list) { @@ -1098,7 +1098,7 @@ export default class Paint { Paint.loadChar(md5); } else if (!MediaLib.keys[md5]) { // Load user asset - iOS.getmedia(md5, nextStep); + OS.getmedia(md5, nextStep); } else { // Load library asset Paint.getBkg(MediaLib.path + md5); @@ -1185,7 +1185,7 @@ export default class Paint { Paint.loadChar(md5); } else if (!MediaLib.keys[md5]) { // Load user asset - iOS.getmedia(md5, nextStep); + OS.getmedia(md5, nextStep); } else { // Load library asset Paint.loadChar(MediaLib.path + md5); @@ -1277,14 +1277,14 @@ export default class Paint { static addToBkgLib (fcn) { var dataurl = IO.getThumbnail(svgdata, 480, 360, 120, 90); var pngBase64 = dataurl.split(',')[1]; - iOS.setmedia(pngBase64, 'png', setBkgRecord); + OS.setmedia(pngBase64, 'png', setBkgRecord); function setBkgRecord (pngmd5) { var json = {}; var keylist = ['md5', 'altmd5', 'version', 'width', 'height', 'ext']; var values = '?,?,?,?,?,?'; json.values = [saveMD5, pngmd5, ScratchJr.version, '480', '360', 'svg']; json.stmt = 'insert into userbkgs (' + keylist.toString() + ') values (' + values + ')'; - iOS.stmt(json, fcn); + OS.stmt(json, fcn); } } @@ -1364,14 +1364,14 @@ export default class Paint { var h = box.height.toString(); var dataurl = IO.getThumbnail(svgdata, w, h, 120, 90); var pngBase64 = dataurl.split(',')[1]; - iOS.setmedia(pngBase64, 'png', setCostumeRecord); + OS.setmedia(pngBase64, 'png', setCostumeRecord); function setCostumeRecord (pngmd5) { var json = {}; var keylist = ['scale', 'md5', 'altmd5', 'version', 'width', 'height', 'ext', 'name']; var values = '?,?,?,?,?,?,?,?'; json.values = [scale, saveMD5, pngmd5, ScratchJr.version, w, h, 'svg', cname]; json.stmt = 'insert into usershapes (' + keylist.toString() + ') values (' + values + ')'; - iOS.stmt(json, fcn); + OS.stmt(json, fcn); } } diff --git a/src/tablet/Android.js b/src/tablet/Android.js new file mode 100644 index 0000000..90d0fee --- /dev/null +++ b/src/tablet/Android.js @@ -0,0 +1,277 @@ +////////////////////////////////////////////////// +// Android interface functions +// AndroidInterface will be the class defined for all the native function calls +////////////////////////////////////////////////// + +let mediacounter = 0; + +export default class Android { + // Database functions + static stmt (json, fcn) { + var result = AndroidInterface.database_stmt(JSON.stringify(json)); + if (typeof (fcn) !== 'undefined') { + fcn(result); + } + } + + static query (json, fcn) { + var result = AndroidInterface.database_query(JSON.stringify(json)); + if (typeof (fcn) !== 'undefined') { + fcn(result); + } + } + + // IO functions + + static cleanassets (ft, fcn) { + AndroidInterface.io_cleanassets(ft); fcn(); + } + + static getmedia (file, fcn) { + mediacounter++; + var nextStep = function (file, key, whenDone) { + var result = AndroidInterface.io_getmedialen(file, key); + Android.processdata(key, 0, result, '', whenDone); + }; + nextStep(file, mediacounter, fcn); + } + + static getmediadata (key, offset, len, fcn) { + var result = AndroidInterface.io_getmediadata(key, offset, len); + if (fcn) { + fcn(result); + } + } + + static processdata (key, off, len, oldstr, fcn) { + if (len == 0) { + Android.getmediadone(key); + fcn(oldstr); + return; + } + var newlen = (len < 100000) ? len : 100000; + Android.getmediadata(key, off, newlen, function (str) { + Android.processdata(key, off + newlen, len - newlen, oldstr + str, fcn); + }); + } + + static getsettings (fcn) { + var result = AndroidInterface.io_getsettings(); + if (fcn) { + fcn(result); + } + } + + static getmediadone (file, fcn) { + var result = AndroidInterface.io_getmediadone(file); + if (fcn) { + fcn(result); + } + } + + static setmedia (str, ext, fcn) { + var result = AndroidInterface.io_setmedia(str, ext); + if (fcn) { + fcn(result); + } + } + + static setmedianame (str, name, ext, fcn) { + var result = AndroidInterface.io_setmedianame(str, name, ext); + if (fcn) { + fcn(result); + } + } + + static getmd5 (str, fcn) { + var result = AndroidInterface.io_getmd5(str); + if (fcn) { + fcn(result); + } + } + + static remove (str, fcn) { + var result = AndroidInterface.io_remove(str); + if (fcn) { + fcn(result); + } + } + + static getfile (str, fcn) { + var result = AndroidInterface.io_getfile(str); + if (fcn) { + fcn(result); + } + } + + static setfile (name, str, fcn) { + var result = AndroidInterface.io_setfile(name, btoa(str)); + if (fcn) { + fcn(result); + } + } + + // Sound functions + + static registerSound (dir, name, fcn) { + var result = AndroidInterface.io_registersound(dir, name); + if (fcn) { + fcn(result); + } + } + + static playSound (name, fcn) { + var result = AndroidInterface.io_playsound(name); + if (fcn) { + fcn(result); + } + } + + static stopSound (name, fcn) { + var result = AndroidInterface.io_stopsound(name); + if (fcn) { + fcn(result); + } + } + + // Web Wiew delegate call backs + + static sndrecord (fcn) { + var result = AndroidInterface.recordsound_recordstart(); + if (fcn) { + fcn(result); + } + } + + static recordstop (fcn) { + var result = AndroidInterface.recordsound_recordstop(); + if (fcn) { + fcn(result); + } + } + + static volume (fcn) { + var result = AndroidInterface.recordsound_volume(); + if (fcn) { + fcn(result); + } + } + + static startplay (fcn) { + var result = AndroidInterface.recordsound_startplay(); + if (fcn) { + fcn(result); + } + } + + static stopplay (fcn) { + var result = AndroidInterface.recordsound_stopplay(); + if (fcn) { + fcn(result); + } + } + + static recorddisappear (b, fcn) { + var result = AndroidInterface.recordsound_recordclose(b); + if (fcn) { + fcn(result); + } + } + + // camera functions + + static hascamera () { + return AndroidInterface.scratchjr_cameracheck(); + } + + static startfeed (data, fcn) { + var str = JSON.stringify(data); + var result = AndroidInterface.scratchjr_startfeed(str); + if (fcn) { + fcn(result); + } + } + + static stopfeed (fcn) { + var result = AndroidInterface.scratchjr_stopfeed(); + if (fcn) { + fcn(result); + } + } + + static choosecamera (mode, fcn) { + var result = AndroidInterface.scratchjr_choosecamera(mode); + if (fcn) { + fcn(result); + } + } + + static captureimage (fcn) { + AndroidInterface.scratchjr_captureimage(fcn); + } + + static hidesplash (fcn) { + // just call funct, splash is hidden in native code + if (fcn) { + fcn(); + } + } + + /////////////// + // Sharing + /////////////// + + + // Called on the JS side to trigger native UI for project sharing. + // fileName: name for the file to share + // emailSubject: subject text to use for an email + // emailBody: body HTML to use for an email + // shareType: 0 for Email; 1 for Airdrop + // b64data: base-64 encoded .SJR file to share + + static sendSjrToShareDialog (fileName, emailSubject, emailBody, shareType, b64data) { + AndroidInterface.sendSjrUsingShareDialog(fileName, emailSubject, emailBody, shareType, b64data); + } + + // // Called on the Objective-C side. The argument is a base64-encoded .SJR file, + // // to be unzipped, processed, and stored. + // static loadProjectFromSjr (b64data) { + // try { + // IO.loadProjectFromSjr(b64data); + // } catch (err) { + // var errorMessage = 'Couldn\'t load share -- project data corrupted. ' + err.message; + // Alert.open(gn('frame'), gn('frame'), errorMessage, '#ff0000'); + // console.log(err); // eslint-disable-line no-console + // return 0; + // } + // return 1; + // } + + // Name of the device/iPad to display on the sharing dialog page + // fcn is called with the device name as an arg + static deviceName (fcn) { + fcn(AndroidInterface.deviceName()); + } + + static analyticsEvent (category, action, label) { + AndroidInterface.analyticsEvent(category, action, label); + } + + static setAnalyticsPlacePref (preferredPlace) { + AndroidInterface.setAnalyticsPlacePref(preferredPlace); + } + + // // Web Wiew delegate call backs + // + // static pageError (desc) { + // console.log('XCODE ERROR:', desc); // eslint-disable-line no-console + // if (window.location.href.indexOf('home.html') > -1) { + // if (Lobby.errorTimer) { + // Lobby.errorLoading(desc); + // } + // } + // } +} + +// // Expose Android methods for ScratchJr tablet sharing callbacks +// window.Android = Android; diff --git a/src/iPad/IO.js b/src/tablet/IO.js similarity index 95% rename from src/iPad/IO.js rename to src/tablet/IO.js index db9727e..e378faf 100644 --- a/src/iPad/IO.js +++ b/src/tablet/IO.js @@ -1,4 +1,4 @@ -import iOS from './iOS'; +import OS from './OS'; import MediaLib from './MediaLib'; import JSZip from 'jszip'; import {setCanvasSize, drawThumbnail, gn} from '../utils/lib'; @@ -90,10 +90,10 @@ export default class IO { IO.requestFromServer(md5, gotit); // get url contents return; } - if ((IO.getExtension(md5) == 'png') && iOS.path) { - fcn(iOS.path + md5); // only if it is not in debug mode + if ((IO.getExtension(md5) == 'png') && OS.path) { + fcn(OS.path + md5); // only if it is not in debug mode } else { - iOS.getmedia(md5, nextStep); + OS.getmedia(md5, nextStep); } // get url contents function gotit (str) { @@ -109,8 +109,8 @@ export default class IO { function nextStep (dataurl) { // iOS 7 requires to read the internal base64 images before returning contents var str = atob(dataurl); - if ((str.indexOf('xlink:href') < 0) && iOS.path) { - fcn(iOS.path + md5); // does not have embedded images + if ((str.indexOf('xlink:href') < 0) && OS.path) { + fcn(OS.path + md5); // does not have embedded images } else { var base64 = IO.getImageDataURL(md5, dataurl); IO.getImagesInSVG(str, function () { @@ -210,11 +210,11 @@ export default class IO { var json = {}; json.stmt = 'select * from ' + db + ' where id = ?'; json.values = [md5]; - iOS.query(json, fcn); + OS.query(json, fcn); } static setMedia (data, type, fcn) { - iOS.setmedia(btoa(data), type, fcn); + OS.setmedia(btoa(data), type, fcn); } static query (type, obj, fcn) { @@ -222,14 +222,14 @@ export default class IO { json.stmt = 'select ' + obj.items + ' from ' + type + ' where ' + obj.cond + (obj.order ? ' order by ' + obj.order : ''); json.values = obj.values; - iOS.query(json, fcn); + OS.query(json, fcn); } static deleteobject (type, id, fcn) { var json = {}; json.stmt = 'delete from ' + type + ' where id = ?'; json.values = [id]; - iOS.stmt(json, fcn); + OS.stmt(json, fcn); } //////////////////////// @@ -258,7 +258,7 @@ export default class IO { addValue('thumbnail', JSON.stringify(obj.thumbnail)); } json.stmt = 'insert into ' + database + ' (' + keylist.toString() + ') values (' + values + ')'; - iOS.stmt(json, fcn); + OS.stmt(json, fcn); function addValue (key, str) { keylist.push(key); values += ',?'; @@ -272,7 +272,7 @@ export default class IO { json.values = [obj.version, obj.deleted, obj.name, JSON.stringify(obj.json), JSON.stringify(obj.thumbnail), (new Date()).getTime().toString()]; json.stmt = 'update ' + database + ' set ' + keylist.toString() + ' where id = ' + obj.id; - iOS.stmt(json, fcn); + OS.stmt(json, fcn); } // Since saveProject is changing the modified time of the project, @@ -282,7 +282,7 @@ export default class IO { var keylist = ['isgift = ?']; json.values = [obj.isgift]; json.stmt = 'update ' + database + ' set ' + keylist.toString() + ' where id = ' + obj.id; - iOS.stmt(json, fcn); + OS.stmt(json, fcn); } static getExtension (str) { @@ -407,7 +407,7 @@ export default class IO { }); } else { // User file - iOS.getmedia(md5, addB64ToZip); + OS.getmedia(md5, addB64ToZip); } }; @@ -496,7 +496,7 @@ export default class IO { json.cond = 'deleted = ? AND gallery IS NULL'; json.items = ['name']; json.values = ['NO']; - IO.query(iOS.database, json, function (existingProjects) { + IO.query(OS.database, json, function (existingProjects) { var newNumber = null; existingProjects = JSON.parse(existingProjects); @@ -613,14 +613,14 @@ export default class IO { if (subFolder == 'thumbnails' || subFolder == 'sounds') { // Save these immediately to the filesystem - no additional processing necessary - iOS.setmedianame(b2data, name, ext, function () { + OS.setmedianame(b2data, name, ext, function () { saveActual++; }); } else if (subFolder == 'characters') { // This code is messy - needs a refactor sometime for all the database calls/duplication for bkgs... // Save the character, generate its thumbnail, and add entry to the database - iOS.setmedianame(b2data, name, ext, function () { // Saves the SVG + OS.setmedianame(b2data, name, ext, function () { // Saves the SVG // Parse SVG to determine width/height var svgParser = new DOMParser().parseFromString(data, 'text/xml'); var width = svgParser.getElementsByTagName('svg')[0].width.baseVal.value; @@ -636,7 +636,7 @@ export default class IO { var charName = characterNames[fullName]; - iOS.setmedia(thumbnailPngBase64, 'png', function (thumbnailMD5) { + OS.setmedia(thumbnailPngBase64, 'png', function (thumbnailMD5) { // Sprite thumbnail is saved - save character to the DB // First ensure that this character doesn't already exist in the exact form @@ -660,7 +660,7 @@ export default class IO { json.stmt = 'insert into usershapes (' + keylist.toString() + ') values (' + values + ')'; - iOS.stmt(json, function () { + OS.stmt(json, function () { saveActual++; }); } else { @@ -672,13 +672,13 @@ export default class IO { }); } else if (subFolder == 'backgrounds') { // Same idea as characters, but the dimensions are fixed - iOS.setmedianame(b2data, name, ext, function () { + OS.setmedianame(b2data, name, ext, function () { IO.getImagesInSVG(data, gotSVGImages); function gotSVGImages () { var thumbnailDataURL = IO.getThumbnail(data, 480, 360, 120, 90); var thumbnailPngBase64 = thumbnailDataURL.split(',')[1]; - iOS.setmedia(thumbnailPngBase64, 'png', function (thumbnailMD5) { + OS.setmedia(thumbnailPngBase64, 'png', function (thumbnailMD5) { // First ensure that this bg doesn't already exist in the exact form var json = {}; @@ -696,7 +696,7 @@ export default class IO { json.values = [fullName, thumbnailMD5, 'iOSv01', '480', '360', 'svg']; json.stmt = 'insert into userbkgs (' + keylist.toString() + ') values (' + values + ')'; - iOS.stmt(json, function () { + OS.stmt(json, function () { saveActual++; }); } else { diff --git a/src/iPad/MediaLib.js b/src/tablet/MediaLib.js similarity index 100% rename from src/iPad/MediaLib.js rename to src/tablet/MediaLib.js diff --git a/src/tablet/OS.js b/src/tablet/OS.js new file mode 100644 index 0000000..794b7ef --- /dev/null +++ b/src/tablet/OS.js @@ -0,0 +1,269 @@ +import {isiOS, isAndroid, gn} from '../utils/lib'; +import IO from './IO'; +import iOS from './iOS'; +import Android from './Android'; +import Lobby from '../lobby/Lobby'; +import Alert from '../editor/ui/Alert'; +import ScratchAudio from '../utils/ScratchAudio'; + +////////////////////////////////////////////////// +// Tablet interface functions +////////////////////////////////////////////////// + +let path; +let camera; +let database = 'projects'; +let tabletInterface = null; + +export default class OS { + // Getters/setters for properties used in other classes + static get path () { + return path; + } + + static set path (newPath) { + path = newPath; + } + + static get camera () { + return camera; + } + + static get database () { + return database; + } + + // Wait for the tablet interface to be injected into the webview + static waitForInterface (fcn) { + // Already loaded the interface + if (tabletInterface != null) { + fcn(); + return; + } + if ((isAndroid && typeof AndroidInterface === 'undefined') || (isiOS && typeof (window.tablet) !== 'object')) { + // interface not loaded - come back in 100ms + setTimeout(function () { + OS.waitForInterface(fcn); + }, 100); + } + + tabletInterface = isiOS ? iOS : Android; + if (fcn) { + fcn(); + } + return; + } + + // Database functions + static stmt (json, fcn) { + tabletInterface.stmt(json, fcn); + } + + static query (json, fcn) { + tabletInterface.query(json, fcn); + } + + // DB helper - shared by both + static setfield (db, id, fieldname, val, fcn) { + var json = {}; + var keylist = [fieldname + ' = ?', 'mtime = ?']; + json.values = [val, (new Date()).getTime().toString()]; + json.stmt = 'update ' + db + ' set ' + keylist.toString() + ' where id = ' + id; + OS.stmt(json, fcn); + } + + // IO functions + + static cleanassets (ft, fcn) { + tabletInterface.cleanassets(ft, fcn); + } + + static getsettings (fcn) { + tabletInterface.getsettings(fcn); + } + + // note the interfaces (iOS and Android) are responsible for deciding how + // to manage getting media (e.g. whether it needs to be done in chunks etc) + static getmedia (file, fcn) { + tabletInterface.getmedia(file, fcn); + } + + static setmedia (str, ext, fcn) { + tabletInterface.setmedia(str, ext, fcn); + } + + static setmedianame (str, name, ext, fcn) { + tabletInterface.setmedianame(str, name, ext, fcn); + } + + static getmd5 (str, fcn) { + tabletInterface.getmd5(str, fcn); + } + + static remove (str, fcn) { + tabletInterface.remove(str, fcn); + } + + static getfile (str, fcn) { + tabletInterface.getfile(str, fcn); + } + + static setfile (name, str, fcn) { + tabletInterface.setfile(name, str, fcn); + } + + // Sound functions + + static registerSound (dir, name, fcn) { + tabletInterface.registerSound(dir, name, fcn); + } + + static playSound (name, fcn) { + tabletInterface.playSound(name, fcn); + } + + static stopSound (name, fcn) { + tabletInterface.stopSound(name, fcn); + } + + // Web Wiew delegate call backs + + static soundDone (name) { + ScratchAudio.soundDone(name); + } + + static sndrecord (fcn) { + tabletInterface.sndrecord(fcn); + } + + static recordstop (fcn) { + tabletInterface.recordstop(fcn); + } + + static volume (fcn) { + tabletInterface.volume(fcn); + } + + static startplay (fcn) { + tabletInterface.startplay(fcn); + } + + static stopplay (fcn) { + tabletInterface.stopplay(fcn); + } + + static recorddisappear (b, fcn) { + tabletInterface.recorddisappear(b, fcn); + } + + // Record state + static askpermission () { + if (isiOS) { + iOS.askpermission(); + } + } + + // camera functions + + static hascamera () { + camera = tabletInterface.hascamera(); + } + + static startfeed (data, fcn) { + tabletInterface.startfeed(data, fcn); + } + + static stopfeed (fcn) { + tabletInterface.stopfeed(fcn); + } + + static choosecamera (mode, fcn) { + tabletInterface.choosecamera(mode, fcn); + } + + static captureimage (fcn) { + tabletInterface.captureimage(fcn); + } + + static hidesplash (fcn) { + if (isiOS) { + iOS.hidesplash(); + } + if (fcn) { + fcn(); + } + } + + static trace (str) { + console.log(str); // eslint-disable-line no-console + } + + static parse (str) { + console.log(JSON.parse(str)); // eslint-disable-line no-console + } + + static tracemedia (str) { + console.log(atob(str)); // eslint-disable-line no-console + } + + ignore () { + } + + /////////////// + // Sharing + /////////////// + + + // Called on the JS side to trigger native UI for project sharing. + // fileName: name for the file to share + // emailSubject: subject text to use for an email + // emailBody: body HTML to use for an email + // shareType: 0 for Email; 1 for Airdrop + // b64data: base-64 encoded .SJR file to share + + static sendSjrToShareDialog (fileName, emailSubject, emailBody, shareType, b64data) { + tabletInterface.sendSjrToShareDialog(fileName, emailSubject, emailBody, shareType, b64data); + } + + // Called on the Objective-C side. The argument is a base64-encoded .SJR file, + // to be unzipped, processed, and stored. + static loadProjectFromSjr (b64data) { + try { + IO.loadProjectFromSjr(b64data); + } catch (err) { + var errorMessage = 'Couldn\'t load share -- project data corrupted. ' + err.message; + Alert.open(gn('frame'), gn('frame'), errorMessage, '#ff0000'); + console.log(err); // eslint-disable-line no-console + return 0; + } + return 1; + } + + // Name of the device/iPad to display on the sharing dialog page + // fcn is called with the device name as an arg + static deviceName (fcn) { + tabletInterface.deviceName(fcn); + } + + static analyticsEvent (category, action, label) { + tabletInterface.analyticsEvent(category, action, label); + } + + static setAnalyticsPlacePref (preferredPlace) { + tabletInterface.setAnalyticsPlacePref(preferredPlace); + } + + // Web Wiew delegate call backs + + static pageError (desc) { + console.log('XCODE ERROR:', desc); // eslint-disable-line no-console + if (window.location.href.indexOf('home.html') > -1) { + if (Lobby.errorTimer) { + Lobby.errorLoading(desc); + } + } + } +} + +// Expose OS methods for ScratchJr tablet sharing callbacks +window.OS = OS; diff --git a/src/tablet/iOS.js b/src/tablet/iOS.js new file mode 100644 index 0000000..747751e --- /dev/null +++ b/src/tablet/iOS.js @@ -0,0 +1,284 @@ +////////////////////////////////////////////////// +// iOS interface functions +// window.tablet is the class where native functions are injected for calling in +// javascript. It will be initialized prior to calling any functions in this class +////////////////////////////////////////////////// + +let mediacounter = 0; + +export default class iOS { + + // Database functions + static stmt (json, fcn) { + var result = window.tablet.database_stmt(JSON.stringify(json)); + if (typeof (fcn) !== 'undefined') { + fcn(result); + } + } + + static query (json, fcn) { + var result = window.tablet.database_query(JSON.stringify(json)); + if (typeof (fcn) !== 'undefined') { + fcn(result); + } + } + + // IO functions + + static cleanassets (ft, fcn) { + window.tablet.io_cleanassets(ft); fcn(); + } + + static getsettings (fcn) { + var result = window.tablet.io_getsettings(); + if (fcn) { + fcn(result); + } + } + + static getmedia (file, fcn) { + mediacounter++; + var nextStep = function (file, key, whenDone) { + var result = window.tablet.io_getmedialen(file, key); + iOS.processdata(key, 0, result, '', whenDone); + }; + nextStep(file, mediacounter, fcn); + } + + static getmediadata (key, offset, len, fcn) { + var result = window.tablet.io_getmediadata(key, offset, len); + if (fcn) { + fcn(result); + } + } + + static processdata (key, off, len, oldstr, fcn) { + if (len == 0) { + iOS.getmediadone(key); + fcn(oldstr); + return; + } + var newlen = (len < 100000) ? len : 100000; + iOS.getmediadata(key, off, newlen, function (str) { + iOS.processdata(key, off + newlen, len - newlen, oldstr + str, fcn); + }); + } + + static getmediadone (file, fcn) { + var result = window.tablet.io_getmediadone(file); + if (fcn) { + fcn(result); + } + } + + static setmedia (str, ext, fcn) { + var result = window.tablet.io_setmedia(str, ext); + if (fcn) { + fcn(result); + } + } + + static setmedianame (str, name, ext, fcn) { + var result = window.tablet.io_setmedianame(str, name, ext); + if (fcn) { + fcn(result); + } + } + + static getmd5 (str, fcn) { + var result = window.tablet.io_getmd5(str); + if (fcn) { + fcn(result); + } + } + + static remove (str, fcn) { + var result = window.tablet.io_remove(str); + if (fcn) { + fcn(result); + } + } + + static getfile (str, fcn) { + var result = window.tablet.io_getfile(str); + if (fcn) { + fcn(result); + } + } + + static setfile (name, str, fcn) { + var result = window.tablet.io_setfile(name, btoa(str)); + if (fcn) { + fcn(result); + } + } + + // Sound functions + + static registerSound (dir, name, fcn) { + var result = window.tablet.io_registersound(dir, name); + if (fcn) { + fcn(result); + } + } + + static playSound (name, fcn) { + var result = window.tablet.io_playsound(name); + if (fcn) { + fcn(result); + } + } + + static stopSound (name, fcn) { + var result = window.tablet.io_stopsound(name); + if (fcn) { + fcn(result); + } + } + + // Web Wiew delegate call backs + + static sndrecord (fcn) { + var result = window.tablet.recordsound_recordstart(); + if (fcn) { + fcn(result); + } + } + + static recordstop (fcn) { + var result = window.tablet.recordsound_recordstop(); + if (fcn) { + fcn(result); + } + } + + static volume (fcn) { + var result = window.tablet.recordsound_volume(); + if (fcn) { + fcn(result); + } + } + + static startplay (fcn) { + var result = window.tablet.recordsound_startplay(); + if (fcn) { + fcn(result); + } + } + + static stopplay (fcn) { + var result = window.tablet.recordsound_stopplay(); + if (fcn) { + fcn(result); + } + } + + static recorddisappear (b, fcn) { + var result = window.tablet.recordsound_recordclose(b); + if (fcn) { + fcn(result); + } + } + + // Record state + static askpermission () { + window.tablet.askForPermission(); + } + + // camera functions + + static hascamera () { + return window.tablet.scratchjr_cameracheck(); + } + + static startfeed (data, fcn) { + var str = JSON.stringify(data); + var result = window.tablet.scratchjr_startfeed(str); + if (fcn) { + fcn(result); + } + } + + static stopfeed (fcn) { + var result = window.tablet.scratchjr_stopfeed(); + if (fcn) { + fcn(result); + } + } + + static choosecamera (mode, fcn) { + var result = window.tablet.scratchjr_choosecamera(mode); + if (fcn) { + fcn(result); + } + } + + static captureimage (fcn) { + window.tablet.scratchjr_captureimage(fcn); + } + + static hidesplash (fcn) { + window.tablet.hideSplash(); + if (fcn) { + fcn(); + } + } + + /////////////// + // Sharing + /////////////// + + + // Called on the JS side to trigger native UI for project sharing. + // fileName: name for the file to share + // emailSubject: subject text to use for an email + // emailBody: body HTML to use for an email + // shareType: 0 for Email; 1 for Airdrop + // b64data: base-64 encoded .SJR file to share + + static sendSjrToShareDialog (fileName, emailSubject, emailBody, shareType, b64data) { + window.tablet.sendSjrUsingShareDialog(fileName, emailSubject, emailBody, shareType, b64data); + } + + // // Called on the Objective-C side. The argument is a base64-encoded .SJR file, + // // to be unzipped, processed, and stored. + // static loadProjectFromSjr (b64data) { + // try { + // IO.loadProjectFromSjr(b64data); + // } catch (err) { + // var errorMessage = 'Couldn\'t load share -- project data corrupted. ' + err.message; + // Alert.open(gn('frame'), gn('frame'), errorMessage, '#ff0000'); + // console.log(err); // eslint-disable-line no-console + // return 0; + // } + // return 1; + // } + + // Name of the device/iPad to display on the sharing dialog page + // fcn is called with the device name as an arg + static deviceName (fcn) { + fcn(window.tablet.deviceName()); + } + + static analyticsEvent (category, action, label) { + window.tablet.analyticsEvent(category, action, label); + } + + static setAnalyticsPlacePref (preferredPlace) { + window.tablet.setAnalyticsPlacePref(preferredPlace); + } + + // // Web Wiew delegate call backs + // + // static pageError (desc) { + // console.log('XCODE ERROR:', desc); // eslint-disable-line no-console + // if (window.location.href.indexOf('home.html') > -1) { + // if (Lobby.errorTimer) { + // Lobby.errorLoading(desc); + // } + // } + // } +} + +// Expose iOS methods for ScratchJr tablet sharing callbacks +// window.iOS = iOS; diff --git a/src/utils/Localization.js b/src/utils/Localization.js index c009a22..a11b00c 100644 --- a/src/utils/Localization.js +++ b/src/utils/Localization.js @@ -1,6 +1,6 @@ import Cookie from './Cookie'; import Intl from 'intl'; -import IO from '../iPad/IO'; +import IO from '../tablet/IO'; if (!window.Intl) { window.Intl = Intl; diff --git a/src/utils/ScratchAudio.js b/src/utils/ScratchAudio.js index 7add306..1330654 100755 --- a/src/utils/ScratchAudio.js +++ b/src/utils/ScratchAudio.js @@ -1,6 +1,6 @@ import {isAndroid} from './lib'; import Sound from './Sound'; -import iOS from '../iPad/iOS'; +import OS from '../tablet/OS'; //////////////////////////////////////////////////// /// Sound Playing @@ -64,7 +64,7 @@ export default class ScratchAudio { fcn(name); } }; - iOS.registerSound(url, snd, whenDone); + OS.registerSound(url, snd, whenDone); } else { // In Android, this is handled outside of JavaScript, so just place a stub here. dict[snd] = new Sound(url + snd); diff --git a/src/utils/Sound.js b/src/utils/Sound.js index 1d7aba9..66ede8f 100644 --- a/src/utils/Sound.js +++ b/src/utils/Sound.js @@ -1,5 +1,5 @@ import {isAndroid} from './lib'; -import iOS from '../iPad/iOS'; +import OS from '../tablet/OS'; export default class Sound { constructor (name, time) { @@ -23,7 +23,7 @@ export default class Sound { if (this.playing) { this.stop(); } - iOS.playSound(this.name); + OS.playSound(this.name); this.playing = true; } } @@ -51,7 +51,7 @@ export default class Sound { } this.soundPlayId = null; } else { - iOS.stopSound(this.name); + OS.stopSound(this.name); this.playing = false; } } diff --git a/src/utils/lib.js b/src/utils/lib.js index 99d5673..a2664a2 100755 --- a/src/utils/lib.js +++ b/src/utils/lib.js @@ -8,8 +8,21 @@ export const WINDOW_INNER_WIDTH = window.innerWidth; export const scaleMultiplier = WINDOW_INNER_HEIGHT / 768.0; export const fullscreenScaleMultiplier = 136; -export const isiOS = (typeof AndroidInterface == 'undefined'); -export const isAndroid = (typeof AndroidInterface != 'undefined'); +export function detectOS () { + var userAgent = window.navigator.userAgent.toLowerCase(); + const ios = /iphone|ipod|ipad/.test( userAgent ); + // safari = /safari/.test( userAgent ), // currently do not need to detect browser vs webview + // android = /android/.text.(userAgent); + + if( ios ) { + return 'iOS'; + } else { + // for now assume Android, this could be further refined to detect Chromium etc. + return 'android'; + } +} +export const isiOS = (detectOS() == 'iOS'); +export const isAndroid = (detectOS() == 'android'); export function libInit () { frame = document.getElementById('frame');