Merge pull request #284 from LLK/issue/274-refactor-os-interface

Split iOS and Android interfaces
This commit is contained in:
chrisgarrity 2020-08-10 16:08:12 -04:00 committed by GitHub
commit d6e1a77327
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1129 additions and 557 deletions

View file

@ -0,0 +1,116 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -5,7 +5,7 @@
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="10">
<list size="12">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
@ -16,12 +16,14 @@
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
<item index="10" class="java.lang.String" itemvalue="android.annotation.Nullable" />
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="9">
<list size="11">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
@ -31,6 +33,8 @@
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="9" class="java.lang.String" itemvalue="android.annotation.NonNull" />
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
</list>
</value>
</option>

View file

@ -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() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -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 */

View file

@ -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];

View file

@ -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];

View file

@ -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.

View file

@ -1,5 +1,5 @@
import Localization from '../../utils/Localization';
import IO from '../../iPad/IO';
import IO from '../../tablet/IO';
let loadCount = 0;

View file

@ -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*</g, '><');
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));

View file

@ -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*</g, '><');
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();
};

View file

@ -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);
}

View file

@ -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);

View file

@ -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) {

View file

@ -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 () {

View file

@ -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'));
}

View file

@ -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) {

View file

@ -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

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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';
};
}

View file

@ -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');

View file

@ -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;

View file

@ -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);
}
}

277
src/tablet/Android.js Normal file
View file

@ -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;

View file

@ -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 {

269
src/tablet/OS.js Normal file
View file

@ -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;

284
src/tablet/iOS.js Normal file
View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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');