mirror of
https://github.com/scratchfoundation/scratchjr.git
synced 2024-11-25 00:28:20 -05:00
Merge branch 'issue/274-wkwebview' into develop
This commit is contained in:
commit
eb21e8dc36
17 changed files with 7948 additions and 469 deletions
|
@ -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 = "";
|
||||
|
|
|
@ -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>
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
196
ios/ScratchJr/src/JsBridge.m
Normal file
196
ios/ScratchJr/src/JsBridge.m
Normal 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
|
32
ios/ScratchJr/src/JsRequest.m
Normal file
32
ios/ScratchJr/src/JsRequest.m
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
25
ios/ScratchJr/src/View.xib
Normal file
25
ios/ScratchJr/src/View.xib
Normal 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>
|
|
@ -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
7331
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -27,7 +27,7 @@ module.exports = {
|
|||
exclude: /node_modules/,
|
||||
test: /\.jsx?$/,
|
||||
query: {
|
||||
presets: ['es2015']
|
||||
presets: ['es2015', 'stage-3']
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue