Merge branch 'issue/274-wkwebview' into develop

This commit is contained in:
Chris Garrity 2020-08-17 11:20:29 -04:00
commit eb21e8dc36
17 changed files with 7948 additions and 469 deletions

View file

@ -13,6 +13,9 @@
204D80CC18A4140600ECBB8B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 204D80CA18A4140600ECBB8B /* InfoPlist.strings */; };
204D80CE18A4140600ECBB8B /* ScratchJrTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 204D80CD18A4140600ECBB8B /* ScratchJrTests.m */; };
7465F19843DCA9DDC62CC852 /* libPods-ScratchJr Free.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ACBA0233542F96AC72D4D7E /* libPods-ScratchJr Free.a */; };
8F8FEAA724E700FF00571717 /* JsBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F8FEAA624E700FF00571717 /* JsBridge.m */; };
8F8FEAA924E703B000571717 /* JsRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F8FEAA824E703B000571717 /* JsRequest.m */; };
8F8FEAAB24E7065E00571717 /* View.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8F8FEAAA24E7065E00571717 /* View.xib */; };
A7FDEB52BFEDCD9E4520FF89 /* libPods-ScratchJrTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 512C88D0E9938AB5972ACF38 /* libPods-ScratchJrTests.a */; };
D92D0F951C33381B00C573AD /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2001BEB918E237AE008E563F /* MessageUI.framework */; };
D92D0F961C33381B00C573AD /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20B2227818A53688003BDE44 /* AVFoundation.framework */; };
@ -22,7 +25,6 @@
D92D0F9A1C33381B00C573AD /* libsqlite3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 20B2226E18A46327003BDE44 /* libsqlite3.0.dylib */; };
D92D0F9D1C33381B00C573AD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 20B2226818A4404B003BDE44 /* Settings.bundle */; };
D92D0F9E1C33381B00C573AD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 204D80B118A4140600ECBB8B /* InfoPlist.strings */; };
D92D0F9F1C33381B00C573AD /* View.xib in Resources */ = {isa = PBXBuildFile; fileRef = 204D80DC18A4195E00ECBB8B /* View.xib */; };
D92D0FCA1C3346D200C573AD /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D92D0FB31C33469A00C573AD /* AppDelegate.m */; };
D92D0FCB1C3346D200C573AD /* CameraMask.m in Sources */ = {isa = PBXBuildFile; fileRef = D92D0FB41C33469A00C573AD /* CameraMask.m */; };
D92D0FCC1C3346D200C573AD /* CameraView.m in Sources */ = {isa = PBXBuildFile; fileRef = D92D0FB51C33469A00C573AD /* CameraView.m */; };
@ -62,7 +64,6 @@
204D80C918A4140600ECBB8B /* ScratchJrTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ScratchJrTests-Info.plist"; sourceTree = "<group>"; };
204D80CB18A4140600ECBB8B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
204D80CD18A4140600ECBB8B /* ScratchJrTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScratchJrTests.m; sourceTree = "<group>"; };
204D80DC18A4195E00ECBB8B /* View.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = View.xib; sourceTree = "<group>"; };
20B2226818A4404B003BDE44 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = SOURCE_ROOT; };
20B2226E18A46327003BDE44 /* libsqlite3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.0.dylib; path = usr/lib/libsqlite3.0.dylib; sourceTree = SDKROOT; };
20B2227818A53688003BDE44 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
@ -70,6 +71,9 @@
5A3230A01AEC02BC09345F16 /* Pods-ScratchJrTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScratchJrTests.debug.xcconfig"; path = "Target Support Files/Pods-ScratchJrTests/Pods-ScratchJrTests.debug.xcconfig"; sourceTree = "<group>"; };
72E2C9BF3BE9F67099F0180F /* Pods-ScratchJrTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScratchJrTests.release.xcconfig"; path = "Target Support Files/Pods-ScratchJrTests/Pods-ScratchJrTests.release.xcconfig"; sourceTree = "<group>"; };
7ACBA0233542F96AC72D4D7E /* libPods-ScratchJr Free.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ScratchJr Free.a"; sourceTree = BUILT_PRODUCTS_DIR; };
8F8FEAA624E700FF00571717 /* JsBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = JsBridge.m; path = src/JsBridge.m; sourceTree = "<group>"; };
8F8FEAA824E703B000571717 /* JsRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = JsRequest.m; path = src/JsRequest.m; sourceTree = "<group>"; };
8F8FEAAA24E7065E00571717 /* View.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = View.xib; path = src/View.xib; sourceTree = "<group>"; };
97A526F2B60190BFC0BD3081 /* Pods-ScratchJr Free.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScratchJr Free.debug.xcconfig"; path = "Target Support Files/Pods-ScratchJr Free/Pods-ScratchJr Free.debug.xcconfig"; sourceTree = "<group>"; };
D905DF721C220BA6003DA34A /* free */ = {isa = PBXFileReference; lastKnownFileType = folder; name = free; path = ../editions/free; sourceTree = "<group>"; };
D92D0FA61C33381B00C573AD /* ScratchJr Free.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ScratchJr Free.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -166,7 +170,6 @@
children = (
D92D0FC91C3346AC00C573AD /* src */,
204D80AF18A4140600ECBB8B /* Supporting Files */,
204D80DC18A4195E00ECBB8B /* View.xib */,
);
path = ScratchJr;
sourceTree = "<group>";
@ -249,6 +252,9 @@
D92D0FBC1C33469A00C573AD /* ViewController.h */,
D92D0FBD1C33469A00C573AD /* ViewController.m */,
D92D0FB81C33469A00C573AD /* main.m */,
8F8FEAA624E700FF00571717 /* JsBridge.m */,
8F8FEAA824E703B000571717 /* JsRequest.m */,
8F8FEAAA24E7065E00571717 /* View.xib */,
);
name = App;
sourceTree = "<group>";
@ -310,7 +316,7 @@
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1110;
ORGANIZATIONNAME = "Playful Invention Company";
ORGANIZATIONNAME = "Scratch Foundation";
TargetAttributes = {
204D80BF18A4140600ECBB8B = {
DevelopmentTeam = TTT4R28WVF;
@ -354,9 +360,9 @@
buildActionMask = 2147483647;
files = (
D92D0F9D1C33381B00C573AD /* Settings.bundle in Resources */,
8F8FEAAB24E7065E00571717 /* View.xib in Resources */,
D965E3971C3EC63A005D792F /* Free-Images.xcassets in Resources */,
D92D0F9E1C33381B00C573AD /* InfoPlist.strings in Resources */,
D92D0F9F1C33381B00C573AD /* View.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -438,7 +444,9 @@
files = (
D92D0FCD1C3346DC00C573AD /* Database.m in Sources */,
D92D0FCE1C3346DC00C573AD /* IO.m in Sources */,
8F8FEAA724E700FF00571717 /* JsBridge.m in Sources */,
D92D0FCF1C3346DC00C573AD /* main.m in Sources */,
8F8FEAA924E703B000571717 /* JsRequest.m in Sources */,
D92D0FD01C3346DC00C573AD /* RecordSound.m in Sources */,
D92D0FD11C3346DC00C573AD /* ScratchJr.m in Sources */,
D92D0FD21C3346DC00C573AD /* ViewController.m in Sources */,
@ -520,7 +528,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
ONLY_ACTIVE_ARCH = YES;
PROVISIONING_PROFILE = "8f58b6d2-97d0-4e4a-ac2e-3c4ee99770bf";
SDKROOT = iphoneos;
@ -570,7 +578,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
PROVISIONING_PROFILE = "8f58b6d2-97d0-4e4a-ac2e-3c4ee99770bf";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = 2;
@ -644,7 +652,7 @@
"COCOAPODS=1",
);
INFOPLIST_FILE = "$(SRCROOT)/../editions/free/Free-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
PRODUCT_BUNDLE_IDENTIFIER = edu.mitmedialab.scratchjrfree;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
@ -669,7 +677,7 @@
"COCOAPODS=1",
);
INFOPLIST_FILE = "$(SRCROOT)/../editions/free/Free-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
PRODUCT_BUNDLE_IDENTIFIER = edu.mitmedialab.scratchjrfree;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="4514" systemVersion="12F45" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3747"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ViewController">
<connections>
<outlet property="view" destination="1" id="NLv-sC-BRg"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="1" customClass="UIWebView">
<rect key="frame" x="0.0" y="0.0" width="768" height="1004"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
</view>
</objects>
</document>

View file

@ -283,9 +283,9 @@ NSMutableDictionary *soundtimers;
NSString *soundName = [[timer userInfo] objectForKey:@"soundName"];
if (sounds[soundName] == nil) return;
NSString *callback = [NSString stringWithFormat:@"OS.soundDone('%@');", soundName];
UIWebView *webview = [ViewController webview];
WKWebView *webview = [ViewController webview];
dispatch_async(dispatch_get_main_queue(), ^{
[webview stringByEvaluatingJavaScriptFromString:callback];
[webview evaluateJavaScript:callback completionHandler:nil];
});
}

View file

@ -0,0 +1,196 @@
//
// JsBridge.m
// ScratchJr Free
//
// Created by Yueyu on 2020/7/31.
//
#import "ScratchJr.h"
#import <WebKit/WebKit.h>
@import Firebase;
@implementation JsBridge
-(void) userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
NSDictionary *dict = (NSDictionary *)message.body;
JsRequest *request = [[JsRequest alloc] initWithDictionary:dict];
NSString *method = [request.method stringByAppendingString:@":"];
SEL selector = NSSelectorFromString(method);
if (![self respondsToSelector:selector]) {
NSLog(@"method %@ not exists", request.method);
return;
}
// [self performSelector:selector withObject:request];
// to disable warning: PerformSelector may cause a leak because its selector is unknown
// thanks wbyoung for https://stackoverflow.com/a/20058585
IMP imp = [self methodForSelector:selector];
void (*func)(id, SEL, JsRequest *) = (void *)imp;
func(self, selector, request);
}
-(void) askForPermission: (JsRequest *) request
{
[RecordSound setPermission];
[request callback:@"ok"];
}
-(void) database_stmt: (JsRequest *) request {
[request callback:[Database stmt:request.params[0]]];
}
-(void) database_query: (JsRequest *) request {
[request callback:[Database query:request.params[0]]];
}
-(void) io_getmd5: (JsRequest *) request {
[request callback:[IO getMD5:request.params[0]]];
}
-(void) io_getsettings: (JsRequest *) request {
[request callback:[IO getsettings]];
}
-(void) io_cleanassets: (JsRequest *) request {
[IO cleanassets: request.params[0]];
[request callback:@"ok"];
}
-(void) io_setfile: (JsRequest *) request {
[request callback:[IO setfile:request.params[0]:request.params[1]]];
}
-(void) io_getfile: (JsRequest *) request {
[request callback:[IO getfile:request.params[0]]];
}
-(void) io_setmedia: (JsRequest *) request {
[request callback:[IO setmedia:request.params[0] :request.params[1]]];
}
-(void) io_setmedianame: (JsRequest *) request {
[request callback:[IO setmedianame:request.params[0] :request.params[1] :request.params[2]]];
}
-(void) io_getmedia: (JsRequest *) request {
[request callback:[IO getmedia:request.params[0]]];
}
-(void) io_getmediadata: (JsRequest *) request {
int offset = [request.params[1] intValue];
int length = [request.params[2] intValue];
NSString *key = [NSString stringWithFormat:@"%@", request.params[0]];
[request callback:[IO getmediadata:key :offset :length]];
}
-(void) io_getmedialen: (JsRequest *) request {
NSString *key = [NSString stringWithFormat:@"%@", request.params[1]];
[request callback:[IO getmedialen:request.params[0] :key]];
}
-(void) io_getmediadone: (JsRequest *) request {
[request callback:[IO getmediadone:request.params[0]]];
}
-(void) io_remove: (JsRequest *) request {
[request callback:[IO remove:request.params[0]]];
}
-(void) io_registersound: (JsRequest *) request {
[request callback:[IO registerSound:request.params[0] :request.params[1]]];
}
-(void) io_playsound: (JsRequest *) request {
[request callback:[IO playSound:request.params[0]]];
}
-(void) io_stopsound: (JsRequest *) request {
[request callback:[IO stopSound:request.params[0]]];
}
-(void) recordsound_recordstart: (JsRequest *) request {
[request callback:[RecordSound startRecord]];
}
-(void) recordsound_recordstop: (JsRequest *) request {
[request callback:[RecordSound stopRecording]];
}
-(void) recordsound_volume: (JsRequest *) request {
[request callback:[NSString stringWithFormat:@"%f", [RecordSound getVolume]]];
}
-(void) recordsound_startplay: (JsRequest *) request {
[request callback:[RecordSound startPlay]];
}
-(void) recordsound_stopplay: (JsRequest *) request {
[request callback:[RecordSound stopPlay]];
}
-(void) recordsound_recordclose: (JsRequest *) request {
[request callback:[RecordSound recordclose:request.params[0]]];
}
-(void) scratchjr_cameracheck: (JsRequest *) request {
[request callback:[ScratchJr cameracheck]];
}
-(void) scratchjr_has_multiple_cameras: (JsRequest *) request {
[request callback:@"YES"];
}
-(void) scratchjr_startfeed: (JsRequest *) request {
[request callback:[ScratchJr startfeed: request.params[0]]];
}
-(void) scratchjr_stopfeed: (JsRequest *) request {
[request callback:[ScratchJr stopfeed]];
}
-(void) scratchjr_choosecamera: (JsRequest *) request {
[request callback:[ScratchJr choosecamera:request.params[0]]];
}
-(void) scratchjr_captureimage: (JsRequest *) request {
[request callback:[ScratchJr captureimage:request.params[0]]];
}
-(void) sendSjrUsingShareDialog: (JsRequest *) request {
int shareType = [request.params[3] intValue];
NSString *res = [IO sendSjrUsingShareDialog:request.params[0] :request.params[1] :request.params[2] :shareType : request.params[4]];
[request callback:res];
}
- (void) hideSplash: (JsRequest *) request {
[request callback:[ScratchJr hideSplash:nil]];
}
-(void) analyticsEvent: (JsRequest *) request {
[FIRAnalytics logEventWithName:request.params[1] // action
parameters:@{
kFIRParameterItemName:request.params[2], // label
kFIRParameterItemCategory:request.params[0] // category
}];
}
-(void) setAnalyticsPlacePref: (JsRequest *) request {
[FIRAnalytics setUserPropertyString:request.params[0] forName:@"place_preference"];
[request callback:@"ok"];
}
// @param request like [name, propertyString]
-(void) setAnalyticsPref: (JsRequest *) request {
NSString *name = [NSString stringWithFormat:@"%@", request.params[0]];
NSString *propertyString = [NSString stringWithFormat:@"%@", request.params[1]];
[FIRAnalytics setUserPropertyString:propertyString forName:name];
}
// iPad name (used for information in the name/sharing dialog to help people using Airdrop)
- (void) deviceName: (JsRequest *) request {
[request callback:[[UIDevice currentDevice] name]];
}
@end

View file

@ -0,0 +1,32 @@
//
// JsRequest.m
// ScratchJr Free
//
// Created by Yueyu on 2020/7/31.
//
#import "ScratchJr.h"
@implementation JsRequest
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
self.method = dictionary[@"method"];
self.callId = dictionary[@"id"];
self.params = dictionary[@"params"];
return self;
}
- (void) callback:(NSString *)res {
NSString *js = nil;
if ([res hasPrefix:@"["] || [res hasPrefix:@"{"]) {
js = [NSString stringWithFormat:@"iOS.resolve('%@', %@);", self.callId, res];
} else {
js = [NSString stringWithFormat:@"iOS.resolve('%@', '%@');", self.callId, res];
}
dispatch_async(dispatch_get_main_queue(), ^{
[ViewController.webview evaluateJavaScript:js completionHandler:nil];
});
}
@end

View file

@ -3,6 +3,7 @@
#import <AVFoundation/AVFoundation.h>
#import <MessageUI/MessageUI.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import <WebKit/WebKit.h>
@interface Database : NSObject
@ -84,56 +85,8 @@
+ (NSString *)stopPlay;
+ (NSString *)recordclose:(NSString *)keep;
@end
@protocol JSExports <JSExport>
/* Functions exported to JavaScript */
- (NSString *)hideSplash:(NSString *)body;
- (void) askForPermission;
- (NSString *)database_stmt:(NSString *) json;
- (NSString *)database_query:(NSString *) json;
- (NSString *)io_getmd5:(NSString *) str;
- (NSString *)io_getsettings;
- (void)io_cleanassets:(NSString *)fileType;
- (NSString *)io_setfile:(NSString *)filename :(NSString *)base64ContentStr;
- (NSString *)io_getfile:(NSString *)filename;
- (NSString *)io_setmedia:(NSString *)base64ContentStr :(NSString *)extension;
- (NSString *)io_setmedianame:(NSString *)contents :(NSString *)key :(NSString *)ext;
- (NSString *)io_getmedia:(NSString *)filename;
- (NSString *)io_getmediadata:(NSString *)filename :(int)offset :(int)length;
- (NSString *)io_getmedialen:(NSString *)file :(NSString *)key;
- (NSString *)io_getmediadone:(NSString *)filename;
- (NSString *)io_remove:(NSString *)filename;
- (NSString *)io_registersound:(NSString *)dir :(NSString *)name;
- (NSString *)io_playsound:(NSString *)name;
- (NSString *)io_stopsound:(NSString *)name;
- (NSString *)recordsound_recordstart;
- (NSString *)recordsound_recordstop;
- (NSString *)recordsound_volume;
- (NSString *)recordsound_startplay;
- (NSString *)recordsound_stopplay;
- (NSString *)recordsound_recordclose:(NSString *)keep;
- (NSString *)scratchjr_cameracheck;
- (bool) scratchjr_has_multiple_cameras;
- (NSString *)scratchjr_startfeed:(NSString *)str;
- (NSString *)scratchjr_stopfeed;
- (NSString *)scratchjr_choosecamera:(NSString *)body;
- (NSString *)scratchjr_captureimage:(NSString *)onCameraCaptureComplete;
- (NSString *)sendSjrUsingShareDialog:(NSString *)fileName
:(NSString *)emailSubject
:(NSString *)emailBody
:(int)shareType
:(NSString *)b64data;
- (NSString *) deviceName;
- (NSString *) analyticsEvent:(NSString *)category :(NSString *)action :(NSString *)label;
- (void) setAnalyticsPlacePref:(NSString *)place;
- (void) setAnalyticsPref:(NSString *)prefObjStr;
@end
@interface ViewController : UIViewController <JSExports,UIWebViewDelegate,MFMailComposeViewControllerDelegate>
@property (nonatomic, readwrite, strong) JSContext *js;
+ (UIWebView *)webview;
@interface ViewController : UIViewController <MFMailComposeViewControllerDelegate, WKNavigationDelegate>
+ (WKWebView *)webview;
+ (UIImageView *)splashScreen;
- (void)receiveProject:(NSString *)project;
- (void)registerDefaultsFromSettingsBundle;
@ -148,6 +101,23 @@
- (void)showShareAirdrop:(NSURL *)projectURL;
@end
@interface JsBridge: NSObject <WKScriptMessageHandler>
@property(weak, nonatomic) ViewController *controller;
@end
@interface JsRequest : NSObject
@property(nonatomic, readwrite) NSString* callId;
@property(nonatomic, readwrite) NSString* method;
@property(nonatomic, readwrite) NSArray* params;
- (instancetype) initWithDictionary: (NSDictionary *)dictionary;
- (void) callback: (NSString *) res;
@end
@interface IO : NSObject

View file

@ -113,10 +113,10 @@ NSString *oncomplete;
+ (void)reportImageError {
NSString *callback = [NSString stringWithFormat: @"%@('error getting a still');",oncomplete];
UIWebView *webview = [ViewController webview];
WKWebView *webview = [ViewController webview];
dispatch_async(dispatch_get_main_queue(), ^{
[webview stringByEvaluatingJavaScriptFromString: callback];
[webview evaluateJavaScript:callback completionHandler:nil];
});
}
@ -124,9 +124,9 @@ NSString *oncomplete;
NSString *base64img = [cameraView getImageBase64:imagedata];
[self closefeed];
NSString *callback = [NSString stringWithFormat: @"%@('%@');",oncomplete, base64img];
UIWebView *webview = [ViewController webview];
WKWebView *webview = [ViewController webview];
dispatch_async(dispatch_get_main_queue(), ^{
[webview stringByEvaluatingJavaScriptFromString: callback];
[webview evaluateJavaScript:callback completionHandler:nil];
});
}

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ViewController">
<connections>
<outlet property="view" destination="iN0-l3-epB" id="sUQ-dS-ukS"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<point key="canvasLocation" x="139" y="103"/>
</view>
</objects>
</document>

View file

@ -1,12 +1,12 @@
#import "ScratchJr.h"
#import <WebKit/WebKit.h>
// @import MessageUI;
@import Firebase;
UIWebView *webview;
WKWebView *webview;
NSDate* startDate;
UIImageView *splashScreen;
NSDate *startDate;
JSContext *js;
@interface ViewController ()
@ -26,14 +26,17 @@ JSContext *js;
- (void)viewDidLoad
{
[super viewDidLoad];
webview = [[WKWebView alloc] initWithFrame:CGRectZero configuration:[self webViewConfig]];
webview.backgroundColor = UIColor.blackColor;
webview.navigationDelegate = self;
self.view = webview;
[self registerDefaultsFromSettingsBundle];
webview = (UIWebView*)[self view] ;
// disable webview scroll
// to fix https://github.com/LLK/scratchjr/issues/243
webview.scrollView.scrollEnabled = false;
[webview setDelegate:self];
[Database open:@"ScratchJr"];
[ScratchJr cameraInit];
[self reload];
@ -44,6 +47,18 @@ JSContext *js;
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
}
- (WKWebViewConfiguration*) webViewConfig {
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.allowsInlineMediaPlayback = true;
config.allowsAirPlayForMediaPlayback = true;
[config.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];
WKUserContentController *controller = [[WKUserContentController alloc] init];
[controller addScriptMessageHandler:[[JsBridge alloc] init] name:@"jsBridge"];
config.userContentController = controller;
return config;
}
- (void) showSplash {
UIImage *loadingImage = [UIImage imageNamed:@"Default-Landscape~ipad.png"];
splashScreen = [[UIImageView alloc] initWithImage:loadingImage];
@ -59,7 +74,7 @@ JSContext *js;
// Dispose of any resources that can be recreated.
}
+ (UIWebView*) webview {return webview;}
+ (WKWebView*) webview {return webview;}
+ (UIImageView*) splashScreen {return splashScreen;}
- (void)registerDefaultsFromSettingsBundle {
@ -86,11 +101,11 @@ JSContext *js;
}
- (void)reload {
UIWebView *webview = (UIWebView*)[self view];
WKWebView *webview = (WKWebView*)[self view];
NSString *location = [[NSUserDefaults standardUserDefaults] stringForKey:@"html"];
if ([location length] > 3) location = [location substringFromIndex:3];
NSString *path = [[NSBundle mainBundle] pathForResource: @"HTML5/index" ofType:@"html"];
NSURL *url = [NSURL URLWithString: [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURL *url = [NSURL fileURLWithPath:path];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[[NSURLCache sharedURLCache] removeAllCachedResponses];
[webview loadRequest:request];
@ -103,46 +118,10 @@ JSContext *js;
startDate = [NSDate date];
}
// UIWebView delegate methods
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
//read your request here
//before the webview will load your request
return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView{
//access your request
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
// Inject a reference for the dispatch method into the UIWebView
// This happens after the page is loaded and the page's onLoad method is called
js = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
js[@"tablet"] = self;
[self disableWebViewLongPressGestures:webView];
NSString *debugChoice =[[NSUserDefaults standardUserDefaults] stringForKey:@"debugstate"];
// Patch through app "advanced"->debug to allow users to display long-form errors
if (![debugChoice isEqualToString:@""] && ![debugChoice isEqualToString:@"0"]) {
[webView stringByEvaluatingJavaScriptFromString:@"window.reloadDebug = true;"];
}
NSURL* screenName = webView.request.URL.filePathURL;
NSString* screenString =[screenName absoluteString];
NSArray<NSString*>* parts = [screenString componentsSeparatedByString:@"/"];
NSString* page = [[[parts lastObject] componentsSeparatedByString:@"?"] firstObject];
// Track pageview in Firebase?
[FIRAnalytics setScreenName:page screenClass:NULL];
}
// Disables iOS 9 webview touch tooltip by disabling the long-press gesture recognizer in subviews
// Thanks to Rye:
// http://stackoverflow.com/questions/32687368/how-to-completely-disable-magnifying-glass-for-uiwebview-ios9
- (void) disableWebViewLongPressGestures:(UIWebView *)webview {
- (void) disableWebViewLongPressGestures:(WKWebView *)webview {
for(id subView in webview.subviews) {
if([subView isKindOfClass:[UIScrollView class]]) {
UIScrollView *scrollView = (UIScrollView *)subView;
@ -160,33 +139,27 @@ JSContext *js;
}
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
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: @"OS.pageError('%@');",desc];
UIWebView *webview = [ViewController webview];
dispatch_async(dispatch_get_main_queue(), ^{
[webview stringByEvaluatingJavaScriptFromString: callback];
});
}
- (void) receiveProject:(NSString *)project{
NSString *callback = [NSString stringWithFormat:@"OS.loadProjectFromSjr('%@');", project];
UIWebView *webview = [ViewController webview];
WKWebView *webview = [ViewController webview];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *res = [webview stringByEvaluatingJavaScriptFromString:callback];
if ([res isEqualToString:@"1"]) {
// Success
return;
} else if ([res isEqualToString:@"0"]) {
// Processing error
return;
} else {
// Loading the project failed - reschedule for a time when the WebView has hopefully loaded
// A little bit roundabout, but simpler than queueing projects to be loaded
[self performSelector:@selector(receiveProject:) withObject:project afterDelay:2.0];
}
[webview evaluateJavaScript:callback completionHandler:^(id result, NSError * _Nullable error) {
if (error != nil) {
return;
}
NSString *res = [NSString stringWithFormat:@"%@", result];
if ([res isEqualToString:@"1"]) {
// Success
return;
} else if ([res isEqualToString:@"0"]) {
// Processing error
return;
} else {
// Loading the project failed - reschedule for a time when the WebView has hopefully loaded
// A little bit roundabout, but simpler than queueing projects to be loaded
[self performSelector:@selector(receiveProject:) withObject:project afterDelay:2.0];
}
}];
});
}
@ -194,161 +167,39 @@ JSContext *js;
return YES;
}
/*
* JavaScript Interface Exports
*/
// WKNavigationDelegate
-(void) askForPermission {
[RecordSound setPermission];
- (void) webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[webview evaluateJavaScript:@"window.tablet = window.webkit.messageHandlers.jsBridge" completionHandler:nil];
[self disableWebViewLongPressGestures:webView];
NSString *debugChoice =[[NSUserDefaults standardUserDefaults] stringForKey:@"debugstate"];
// Patch through app "advanced"->debug to allow users to display long-form errors when projects fail to load
if (![debugChoice isEqualToString:@""] && ![debugChoice isEqualToString:@"0"]) {
[webView evaluateJavaScript:@"window.reloadDebug = true;" completionHandler:nil];
}
NSURL* screenName = webView.URL.filePathURL;
NSString* screenString =[screenName absoluteString];
NSArray<NSString*>* parts = [screenString componentsSeparatedByString:@"/"];
NSString* page = [[[parts lastObject] componentsSeparatedByString:@"?"] firstObject];
// Track pageview in Firebase?
[FIRAnalytics setScreenName:page screenClass:NULL];
}
-(NSString*) database_stmt: (NSString*) json {
return [Database stmt:json];
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
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];
WKWebView *webview = [ViewController webview];
dispatch_async(dispatch_get_main_queue(), ^{
[webview evaluateJavaScript:callback completionHandler:nil];
});
}
-(NSString*) database_query: (NSString*) json {
return [Database query:json];
}
-(NSString*) io_getmd5: (NSString*) str {
return [IO getMD5:str];
}
-(NSString*) io_getsettings {
return [IO getsettings];
}
-(void) io_cleanassets:(NSString*) fileType {
[IO cleanassets:fileType];
}
-(NSString*) io_setfile:(NSString*)filename :(NSString*)base64ContentStr {
return [IO setfile:filename:base64ContentStr];
}
-(NSString*) io_getfile:(NSString*)filename {
return [IO getfile:filename];
}
-(NSString*) io_setmedia:(NSString*) base64ContentStr :(NSString*) extension {
return [IO setmedia:base64ContentStr:extension];
}
-(NSString*) io_setmedianame:(NSString*) contents :(NSString*) key :(NSString*) ext {
return [IO setmedianame:contents:key:ext];
}
-(NSString*) io_getmedia:(NSString*) filename {
return [IO getmedia:filename];
}
-(NSString*) io_getmediadata:(NSString*)filename :(int) offset :(int) length {
return [IO getmediadata:filename:offset:length];
}
-(NSString*) io_getmedialen:(NSString*)file :(NSString*)key {
return [IO getmedialen:file:key];
}
-(NSString*) io_getmediadone:(NSString*)filename {
return [IO getmediadone:filename];
}
-(NSString*) io_remove:(NSString*)filename {
return [IO remove:filename];
}
-(NSString*) io_registersound:(NSString*)dir :(NSString*)name {
return [IO registerSound:dir:name];
}
-(NSString*) io_playsound:(NSString*) name {
return [IO playSound:name];
}
-(NSString*) io_stopsound:(NSString*) name {
return [IO stopSound:name];
}
-(NSString*) recordsound_recordstart {
return [RecordSound startRecord];
}
-(NSString*) recordsound_recordstop {
return [RecordSound stopRecording];
}
-(NSString*) recordsound_volume {
return [NSString stringWithFormat:@"%f", [RecordSound getVolume]];
}
-(NSString*) recordsound_startplay {
return [RecordSound startPlay];
}
-(NSString*) recordsound_stopplay {
return [RecordSound stopPlay];
}
-(NSString*) recordsound_recordclose:(NSString*) keep {
return [RecordSound recordclose:keep];
}
-(NSString*) scratchjr_cameracheck {
return [ScratchJr cameracheck];
}
-(bool) scratchjr_has_multiple_cameras {
return YES;
}
-(NSString*) scratchjr_startfeed:(NSString*)str {
return [ScratchJr startfeed:str];
}
-(NSString*) scratchjr_stopfeed {
return [ScratchJr stopfeed];
}
-(NSString*) scratchjr_choosecamera:(NSString *)body {
return [ScratchJr choosecamera:body];
}
-(NSString*) scratchjr_captureimage: (NSString*)onCameraCaptureComplete {
return [ScratchJr captureimage:onCameraCaptureComplete];
}
//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];
}
- (NSString *) hideSplash :(NSString *)body {
return [ScratchJr hideSplash:body];
}
-(NSString*) analyticsEvent:(NSString*) category :(NSString*) action :(NSString*) label {
[FIRAnalytics logEventWithName:action
parameters:@{
kFIRParameterItemName:label,
kFIRParameterItemCategory:category
}];
return @"1";
}
-(void) setAnalyticsPlacePref:(NSString*)place {
[FIRAnalytics setUserPropertyString:place forName:@"place_preference"];
}
// @param prefObjStr like "{\"place_preference\": \"School\"}"
-(void) setAnalyticsPref:(NSString*)prefObjStr {
NSData* data = [prefObjStr dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* dict = [NSJSONSerialization JSONObjectWithData:data options:0 error: nil];
NSString *key = [[dict allKeys] firstObject];
NSString *value = [dict objectForKey: key];
[FIRAnalytics setUserPropertyString:value forName:key];
}
// iPad name (used for information in the name/sharing dialog to help people using Airdrop)
- (NSString*) deviceName {
return [[UIDevice currentDevice] name];
}
// Sharing controllers - if we later decide to unify, use UIActivityViewController
// Email sharing

7331
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -15,10 +15,12 @@
},
"homepage": "https://github.com/llk/scratchjr",
"devDependencies": {
"@babel/polyfill": "^7.10.4",
"babel-core": "^6.4.0",
"babel-eslint": "^4.1.6",
"babel-loader": "^7.1.4",
"babel-preset-es2015": "^6.3.13",
"babel-preset-stage-3": "^6.24.1",
"esformatter": "^0.8.1",
"esformatter-braces": "^1.2.1",
"esformatter-dot-notation": "^1.3.1",

View file

@ -261,8 +261,8 @@ export default class Android {
AndroidInterface.setAnalyticsPlacePref(preferredPlace);
}
static setAnalyticsPref (obj) {
AndroidInterface.setAnalyticsPref(JSON.stringify(obj));
static setAnalyticsPref (key, value) {
AndroidInterface.setAnalyticsPref(key, value);
}
// // Web Wiew delegate call backs

View file

@ -107,16 +107,12 @@ export default class IO {
} // base64 dataurl
}
function nextStep (dataurl) { // iOS 7 requires to read the internal base64 images before returning contents
function nextStep (dataurl) {
var str = atob(dataurl);
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 () {
fcn(base64);
}); // base64 dataurl
}
var base64 = IO.getImageDataURL(md5, dataurl);
IO.getImagesInSVG(str, function () {
fcn(base64);
});
}
}

View file

@ -29,6 +29,10 @@ export default class OS {
return camera;
}
static set camera (newCamera) {
camera = newCamera;
}
static get database () {
return database;
}
@ -254,7 +258,7 @@ export default class OS {
}
static setAnalyticsPref (key, value) {
tabletInterface.setAnalyticsPref({[key]: value});
tabletInterface.setAnalyticsPref(key, value);
}
// Web Wiew delegate call backs

View file

@ -4,52 +4,105 @@
// javascript. It will be initialized prior to calling any functions in this class
//////////////////////////////////////////////////
import OS from './OS';
import '@babel/polyfill';
let mediacounter = 0;
let callbacks = {};
export default class iOS {
static getId () {
do { //eslint-disable-line no-constant-condition
var id = 'jr' + ((new Date()).getTime()) + Math.floor(Math.random() * 10000);
if (!callbacks[id]) {
return id;
}
} while (true);
}
static call (method) {
return new Promise((resolve) => {
var id = iOS.getId();
callbacks[id] = resolve;
var args = [].slice.call(arguments);
args.shift();
window.tablet.postMessage({
id: id,
method: method,
params: args
});
});
}
static resolve (id, res) {
if (!id) {
return;
}
const callbackFn = callbacks[id];
if (!callbackFn) {
return;
}
if (typeof callbackFn === 'function') {
callbackFn(res);
}
delete callbacks[id];
}
// Database functions
static stmt (json, fcn) {
var result = window.tablet.database_stmt(JSON.stringify(json));
if (typeof (fcn) !== 'undefined') {
fcn(result);
}
(async () => {
var result = await iOS.call('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);
}
(async () => {
var result = await iOS.call('database_query', JSON.stringify(json));
if (typeof result == 'object') {
result = JSON.stringify(result);
}
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) {
window.tablet.io_cleanassets(ft); fcn();
}
static getsettings (fcn) {
var result = window.tablet.io_getsettings();
if (fcn) {
fcn(result);
}
iOS.call('io_cleanassets', ft);
fcn();
}
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);
(async () => {
var result = await iOS.call('io_getmedialen', file, key);
iOS.processdata(key, 0, result * 1, '', whenDone);
})();
};
nextStep(file, mediacounter, fcn);
}
static getmediadata (key, offset, len, fcn) {
var result = window.tablet.io_getmediadata(key, offset, len);
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('io_getmediadata', key, offset, len);
if (fcn) {
fcn(result);
}
})();
}
static processdata (key, off, len, oldstr, fcn) {
@ -64,164 +117,230 @@ export default class iOS {
});
}
static getsettings (fcn) {
(async () => {
var result = await iOS.call('io_getsettings');
if (fcn) {
fcn(result);
}
})();
}
static getmediadone (file, fcn) {
var result = window.tablet.io_getmediadone(file);
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('io_getmediadone', file);
if (fcn) {
fcn(result);
}
})();
}
static setmedia (str, ext, fcn) {
var result = window.tablet.io_setmedia(str, ext);
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('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);
}
(async () => {
var result = await iOS.call('io_setmedianame', str, name, ext);
if (fcn) {
fcn(result);
}
})();
}
static getmd5 (str, fcn) {
var result = window.tablet.io_getmd5(str);
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('io_getmd5', str);
if (fcn) {
fcn(result);
}
})();
}
static remove (str, fcn) {
var result = window.tablet.io_remove(str);
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('io_remove', str);
if (fcn) {
fcn(result);
}
})();
}
static getfile (str, fcn) {
var result = window.tablet.io_getfile(str);
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('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);
}
(async () => {
var result = await iOS.call('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);
}
(async () => {
var result = await iOS.call('io_registersound', dir, name);
if (fcn) {
fcn(result);
}
})();
}
static playSound (name, fcn) {
var result = window.tablet.io_playsound(name);
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('io_playsound', name);
if (fcn) {
fcn(result);
}
})();
}
static stopSound (name, fcn) {
var result = window.tablet.io_stopsound(name);
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('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);
}
(async () => {
var result = await iOS.call('recordsound_recordstart');
if (fcn) {
fcn(result);
}
})();
}
static recordstop (fcn) {
var result = window.tablet.recordsound_recordstop();
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('recordsound_recordstop');
if (fcn) {
fcn(result);
}
})();
}
static volume (fcn) {
var result = window.tablet.recordsound_volume();
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('recordsound_volume');
if (fcn) {
fcn(result);
}
})();
}
static startplay (fcn) {
var result = window.tablet.recordsound_startplay();
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('recordsound_startplay');
if (fcn) {
fcn(result);
}
})();
}
static stopplay (fcn) {
var result = window.tablet.recordsound_stopplay();
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('recordsound_stopplay');
if (fcn) {
fcn(result);
}
})();
}
static recorddisappear (b, fcn) {
var result = window.tablet.recordsound_recordclose(b);
if (fcn) {
fcn(result);
}
(async () => {
var result = iOS.call('recordsound_recordclose', b);
if (fcn) {
fcn(result);
}
})();
}
// Record state
static askpermission () {
window.tablet.askForPermission();
iOS.call('askForPermission');
}
// camera functions
static hascamera () {
return window.tablet.scratchjr_cameracheck();
(async () => {
OS.camera = await iOS.call('scratchjr_cameracheck');
})();
}
static startfeed (data, fcn) {
var str = JSON.stringify(data);
var result = window.tablet.scratchjr_startfeed(str);
if (fcn) {
fcn(result);
}
(async () => {
var str = JSON.stringify(data);
var result = await iOS.call('scratchjr_startfeed', str);
if (fcn) {
fcn(result);
}
})();
}
static stopfeed (fcn) {
var result = window.tablet.scratchjr_stopfeed();
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('scratchjr_stopfeed');
if (fcn) {
fcn(result);
}
})();
}
static choosecamera (mode, fcn) {
var result = window.tablet.scratchjr_choosecamera(mode);
if (fcn) {
fcn(result);
}
(async () => {
var result = await iOS.call('scratchjr_choosecamera', mode);
if (fcn) {
fcn(result);
}
})();
}
static captureimage (fcn) {
window.tablet.scratchjr_captureimage(fcn);
iOS.call('scratchjr_captureimage', fcn);
}
static hidesplash (fcn) {
window.tablet.hideSplash();
if (fcn) {
fcn();
}
(async () => {
iOS.call('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 () {
}
///////////////
@ -237,52 +356,29 @@ export default class iOS {
// b64data: base-64 encoded .SJR file to share
static sendSjrToShareDialog (fileName, emailSubject, emailBody, shareType, b64data) {
window.tablet.sendSjrUsingShareDialog(fileName, emailSubject, emailBody, shareType, b64data);
iOS.call('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());
(async () => {
fcn(await iOS.call('deviceName'));
})();
}
static analyticsEvent (category, action, label) {
window.tablet.analyticsEvent(category, action, label);
iOS.call('analyticsEvent', category, action, label);
}
static setAnalyticsPlacePref (preferredPlace) {
window.tablet.setAnalyticsPlacePref(preferredPlace);
iOS.call('setAnalyticsPlacePref', preferredPlace);
}
static setAnalyticsPref (obj) {
window.tablet.setAnalyticsPref(JSON.stringify(obj));
static setAnalyticsPref (key, value) {
iOS.call('setAnalyticsPref', key, value);
}
// // 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;
window.iOS = iOS;

View file

@ -8,21 +8,9 @@ export const WINDOW_INNER_WIDTH = window.innerWidth;
export const scaleMultiplier = WINDOW_INNER_HEIGHT / 768.0;
export const fullscreenScaleMultiplier = 136;
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');
console.log('setting OS flags');
export const isiOS = (typeof AndroidInterface == 'undefined');
export const isAndroid = (typeof AndroidInterface != 'undefined');
export function libInit () {
frame = document.getElementById('frame');

View file

@ -27,7 +27,7 @@ module.exports = {
exclude: /node_modules/,
test: /\.jsx?$/,
query: {
presets: ['es2015']
presets: ['es2015', 'stage-3']
}
}
]