mirror of
https://github.com/scratchfoundation/scratchjr.git
synced 2024-11-25 08:38:30 -05:00
Android: import project in native
This commit is contained in:
parent
e88b25dea0
commit
d77fc04f2d
4 changed files with 229 additions and 24 deletions
|
@ -1,6 +1,8 @@
|
||||||
package org.scratchjr.android;
|
package org.scratchjr.android;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
@ -14,6 +16,7 @@ import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteDatabase.CursorFactory;
|
import android.database.sqlite.SQLiteDatabase.CursorFactory;
|
||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,6 +203,27 @@ public class DatabaseManager {
|
||||||
return Long.toString(id);
|
return Long.toString(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String insert(String table, JSONObject data) throws DatabaseException {
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
List<String> keys = new ArrayList<>();
|
||||||
|
List<String> placeholders = new ArrayList<>();
|
||||||
|
JSONArray names = data.names();
|
||||||
|
for (int i = 0; i < names.length(); i++) {
|
||||||
|
String key = names.optString(i);
|
||||||
|
keys.add(key);
|
||||||
|
placeholders.add("?");
|
||||||
|
values.add(data.optString(key));
|
||||||
|
}
|
||||||
|
String statement = String.format(
|
||||||
|
"INSERT INTO %s (%s) VALUES (%s)",
|
||||||
|
table,
|
||||||
|
TextUtils.join(",", keys),
|
||||||
|
TextUtils.join(",", placeholders)
|
||||||
|
);
|
||||||
|
|
||||||
|
return stmt(statement, values.toArray(new String[0]));
|
||||||
|
}
|
||||||
|
|
||||||
private JSONObject getRowDataAsJsonObject(Cursor cursor)
|
private JSONObject getRowDataAsJsonObject(Cursor cursor)
|
||||||
throws SQLiteException, JSONException
|
throws SQLiteException, JSONException
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,12 +8,16 @@ import java.io.InputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
@ -272,4 +276,98 @@ public class IOManager {
|
||||||
}
|
}
|
||||||
return new String(hexChars);
|
return new String(hexChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void receiveProject(ScratchJrActivity activity, Uri uri) throws JSONException, IOException, DatabaseException {
|
||||||
|
File tempDir = new File(activity.getCacheDir() + File.separator + UUID.randomUUID().toString());
|
||||||
|
tempDir.mkdir();
|
||||||
|
List<String> entries = ScratchJrUtil.unzip(uri.getPath(), tempDir.getPath());
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
Log.e(LOG_TAG, "no entries found");
|
||||||
|
// no files
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// read project json
|
||||||
|
JSONObject json = ScratchJrUtil.readJson(tempDir + "/project/data.json");
|
||||||
|
JSONObject projectData = json.optJSONObject("json");
|
||||||
|
JSONObject projectJson = new JSONObject();
|
||||||
|
projectJson.put("isgift", "1");
|
||||||
|
projectJson.put("deleted", "NO");
|
||||||
|
projectJson.put("json", projectData.toString());
|
||||||
|
JSONObject thumbnail = json.optJSONObject("thumbnail");
|
||||||
|
projectJson.put("thumbnail", thumbnail.toString());
|
||||||
|
projectJson.put("version", "iOSv01");
|
||||||
|
projectJson.put("name", projectData.optString("name"));
|
||||||
|
_databaseManager.insert("projects", projectJson);
|
||||||
|
|
||||||
|
HashMap<String, JSONObject> spriteMap = new HashMap<>();
|
||||||
|
JSONArray pages = projectData.optJSONArray("pages");
|
||||||
|
for (int i = 0; i < pages.length(); i++) {
|
||||||
|
String pageName = pages.optString(i);
|
||||||
|
if (pageName == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
JSONObject page = projectData.optJSONObject(pageName);
|
||||||
|
JSONArray spriteNames = page.optJSONArray("sprites");
|
||||||
|
for (int j = 0; j < spriteNames.length(); j++) {
|
||||||
|
String spriteName = spriteNames.optString(j);
|
||||||
|
JSONObject sprite = page.optJSONObject(spriteName);
|
||||||
|
spriteMap.put(sprite.optString("md5"), sprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < entries.size(); i++) {
|
||||||
|
String entry = entries.get(i);
|
||||||
|
if (entry == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(entry.endsWith(".png") || entry.endsWith(".wav") || entry.endsWith(".svg"))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// copy file to target file
|
||||||
|
File sourceFile = new File(tempDir + File.separator + entry);
|
||||||
|
|
||||||
|
String fileName = sourceFile.getName();
|
||||||
|
File targetFile = new File(activity.getFilesDir().getPath() + File.separator + fileName);
|
||||||
|
if (!targetFile.exists()) {
|
||||||
|
ScratchJrUtil.copyFile(sourceFile, targetFile);
|
||||||
|
}
|
||||||
|
String folderName = sourceFile.getParentFile().getName();
|
||||||
|
if ("thumbnails".equals(folderName) || "sounds".equals(folderName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String table = "characters".equals(folderName) ? "usershapes" : "userbkgs";
|
||||||
|
String statement = String.format("SELECT id FROM %s WHERE md5 = ?", table);
|
||||||
|
JSONArray rows = _databaseManager.query(statement, new String[]{fileName});
|
||||||
|
if (rows.length() > 0) {
|
||||||
|
Log.e(LOG_TAG, "asset for " + fileName + "exists");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String pngName = fileName.replace(".svg", ".png");
|
||||||
|
JSONObject asset = new JSONObject();
|
||||||
|
asset.put("version", "iOSv01");
|
||||||
|
asset.put("md5", fileName);
|
||||||
|
asset.put("altmd5", pngName);
|
||||||
|
asset.put("width", "480");
|
||||||
|
asset.put("height", "360");
|
||||||
|
asset.put("ext", fileName.split("\\.")[1]);
|
||||||
|
if ("characters".equals(folderName)) {
|
||||||
|
JSONObject sprite = spriteMap.get(fileName);
|
||||||
|
if (sprite == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
asset.put("width", sprite.optString("w"));
|
||||||
|
asset.put("height", sprite.optString("h"));
|
||||||
|
asset.put("scale", sprite.optString("scale"));
|
||||||
|
asset.put("name", sprite.optString("name"));
|
||||||
|
}
|
||||||
|
File png = new File(activity.getFilesDir().getPath() + File.separator + pngName);
|
||||||
|
if (!png.exists()) {
|
||||||
|
String js = String.format("ScratchJr.makeThumb('%s', %s, %s)", fileName, asset.optString("width"), asset.optString("height"));
|
||||||
|
Log.d(LOG_TAG, js);
|
||||||
|
activity.runJavaScript(js);
|
||||||
|
}
|
||||||
|
_databaseManager.insert(table, asset);
|
||||||
|
}
|
||||||
|
// refresh lobby
|
||||||
|
activity.runJavaScript("Lobby.refresh();");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import android.os.Handler;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -36,10 +35,7 @@ import android.widget.RelativeLayout;
|
||||||
|
|
||||||
import com.google.firebase.analytics.FirebaseAnalytics;
|
import com.google.firebase.analytics.FirebaseAnalytics;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.util.ArrayList;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
|
@ -110,6 +106,11 @@ public class ScratchJrActivity
|
||||||
/* Firebase analytics tracking */
|
/* Firebase analytics tracking */
|
||||||
private FirebaseAnalytics _FirebaseAnalytics;
|
private FirebaseAnalytics _FirebaseAnalytics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project uri that need to be imported.
|
||||||
|
*/
|
||||||
|
private ArrayList<Uri> projectUris = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -310,7 +311,15 @@ public class ScratchJrActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveProject(Uri projectUri) {
|
private void receiveProject(final Uri projectUri) {
|
||||||
|
if (!isSplashDone()) {
|
||||||
|
projectUris.add(projectUri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
importProject(projectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importProject(final Uri projectUri) {
|
||||||
String PROJECT_EXTENSION = getApplicationContext().getString(R.string.share_extension_filter);
|
String PROJECT_EXTENSION = getApplicationContext().getString(R.string.share_extension_filter);
|
||||||
String scheme = projectUri.getScheme();
|
String scheme = projectUri.getScheme();
|
||||||
Log.i(LOG_TAG, "receiveProject(scheme): " + scheme);
|
Log.i(LOG_TAG, "receiveProject(scheme): " + scheme);
|
||||||
|
@ -324,25 +333,16 @@ public class ScratchJrActivity
|
||||||
if (scheme.equals(ContentResolver.SCHEME_FILE) && !projectUri.getPath().matches(PROJECT_EXTENSION)) {
|
if (scheme.equals(ContentResolver.SCHEME_FILE) && !projectUri.getPath().matches(PROJECT_EXTENSION)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Read the project one byte at a time into a buffer
|
runOnUiThread(new Runnable() {
|
||||||
ByteArrayOutputStream projectData = new ByteArrayOutputStream();
|
@Override
|
||||||
try {
|
public void run() {
|
||||||
InputStream is = getContentResolver().openInputStream(projectUri);
|
try {
|
||||||
|
_ioManager.receiveProject(ScratchJrActivity.this, projectUri);
|
||||||
byte[] readByte = new byte[1];
|
} catch (Exception e) {
|
||||||
while ((is.read(readByte)) == 1) {
|
e.printStackTrace();
|
||||||
projectData.write(readByte[0]);
|
}
|
||||||
}
|
}
|
||||||
} catch (FileNotFoundException e) {
|
});
|
||||||
Log.i(LOG_TAG, "File not found in project load");
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.i(LOG_TAG, "IOException in project load");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// We send the project Base64-encoded to JavaScript where it's processed and unpacked
|
|
||||||
String base64Project = Base64.encodeToString(projectData.toByteArray(), Base64.DEFAULT);
|
|
||||||
runJavaScript("OS.loadProjectFromSjr('" + base64Project + "');");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RelativeLayout getContainer() {
|
public RelativeLayout getContainer() {
|
||||||
|
@ -483,6 +483,10 @@ public class ScratchJrActivity
|
||||||
|
|
||||||
public void setSplashDone(boolean done) {
|
public void setSplashDone(boolean done) {
|
||||||
_splashDone = done;
|
_splashDone = done;
|
||||||
|
while (projectUris.size() > 0) {
|
||||||
|
Uri uri = projectUris.remove(0);
|
||||||
|
importProject(uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,16 +2,22 @@ package org.scratchjr.android;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,4 +192,77 @@ public class ScratchJrUtil {
|
||||||
}
|
}
|
||||||
return segments[segments.length - 1];
|
return segments[segments.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<String> unzip(String zipPath, String toPath) {
|
||||||
|
List<String> entries = new ArrayList<>();
|
||||||
|
File zipFile = new File(zipPath);
|
||||||
|
ZipInputStream zin;
|
||||||
|
try {
|
||||||
|
zin = new ZipInputStream(new FileInputStream(zipFile));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ZipEntry ze;
|
||||||
|
while ((ze = zin.getNextEntry()) != null) {
|
||||||
|
String path = toPath + File.separator + ze.getName();
|
||||||
|
File unzipFile = new File(path);
|
||||||
|
if (ze.isDirectory()) {
|
||||||
|
if(!unzipFile.isDirectory()) {
|
||||||
|
unzipFile.mkdirs();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File folder = unzipFile.getParentFile();
|
||||||
|
if (!folder.isDirectory()) {
|
||||||
|
folder.mkdirs();
|
||||||
|
}
|
||||||
|
FileOutputStream fout = new FileOutputStream(path, false);
|
||||||
|
BufferedOutputStream bout = new BufferedOutputStream(fout);
|
||||||
|
try {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int read;
|
||||||
|
while ((read = zin.read(buffer)) != -1) {
|
||||||
|
bout.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
bout.flush();
|
||||||
|
zin.closeEntry();
|
||||||
|
entries.add(ze.getName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
fout.close();
|
||||||
|
bout.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
zin.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONObject readJson(String path) throws IOException, JSONException {
|
||||||
|
byte[] data;
|
||||||
|
InputStream in = new FileInputStream(new File(path));
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
int len;
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
while ((len = in.read(buffer)) != -1) {
|
||||||
|
bos.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
bos.close();
|
||||||
|
data = bos.toByteArray();
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
return new JSONObject(new String(data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue