From 608dc5dba33f4911ad1130faa36524480273195f Mon Sep 17 00:00:00 2001 From: Christophe Geuzaine <cgeuzaine@ulg.ac.be> Date: Fri, 7 Oct 2016 16:32:49 +0000 Subject: [PATCH] bring Android up to par with iOS: - share model files by email - edit model files - clear results - remove screenshot menu - new help menu - visible about button --- contrib/mobile/Android/AndroidManifest.xml | 4 +- contrib/mobile/Android/res/values/strings.xml | 2 - .../src/org/geuz/onelab/AboutActivity.java | 113 ++++++++++++---- .../src/org/geuz/onelab/MainActivity.java | 27 ---- .../src/org/geuz/onelab/ModelList.java | 124 +++++++++++++++--- 5 files changed, 196 insertions(+), 74 deletions(-) diff --git a/contrib/mobile/Android/AndroidManifest.xml b/contrib/mobile/Android/AndroidManifest.xml index 42ec5c5f5f..661cd4a7e3 100644 --- a/contrib/mobile/Android/AndroidManifest.xml +++ b/contrib/mobile/Android/AndroidManifest.xml @@ -1,7 +1,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.geuz.onelab" - android:versionCode="22" - android:versionName="1.3.1" + android:versionCode="23" + android:versionName="1.4.0" android:installLocation="auto" > <uses-sdk android:minSdkVersion="14" diff --git a/contrib/mobile/Android/res/values/strings.xml b/contrib/mobile/Android/res/values/strings.xml index 119c913106..86b0d470c5 100644 --- a/contrib/mobile/Android/res/values/strings.xml +++ b/contrib/mobile/Android/res/values/strings.xml @@ -3,8 +3,6 @@ <string name="title_activity_main">Onelab</string> <string name="title_activity_about">About</string> <string name="title_activity_options">Parameters</string> - <string name="title_share">Share screenshot with …</string> - <string name="menu_share">Share screenshot</string> <string name="menu_parameters">Parameters</string> <string name="menu_run">Run</string> <string name="menu_stop">Stop</string> diff --git a/contrib/mobile/Android/src/org/geuz/onelab/AboutActivity.java b/contrib/mobile/Android/src/org/geuz/onelab/AboutActivity.java index 29c7d8d431..9755bd3ee8 100644 --- a/contrib/mobile/Android/src/org/geuz/onelab/AboutActivity.java +++ b/contrib/mobile/Android/src/org/geuz/onelab/AboutActivity.java @@ -3,52 +3,117 @@ package org.geuz.onelab; import org.geuz.onelab.Gmsh; import java.lang.String; +import java.io.File; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; import android.app.Activity; import android.content.Intent; import android.view.Menu; import android.view.MenuItem; import android.webkit.WebView; +import android.webkit.JavascriptInterface; +import android.os.Bundle; + +class MyJavaScriptInterface { + private String _filename; + public MyJavaScriptInterface(String filename){ + _filename = filename; + } + @JavascriptInterface + public void myJsCallback(String jsResult) { + File outputFile = new File(_filename); + try{ + BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile)); + writer.write(jsResult); + writer.close(); + } + catch (IOException e) { + } + } +} public class AboutActivity extends Activity{ private MenuItem _saveMenuItem; + private boolean _editingMode; + private WebView _webview; protected void onCreate(android.os.Bundle savedInstanceState) { super.onCreate(savedInstanceState); getActionBar().setDisplayHomeAsUpEnabled(true); - //getActionBar().setTitle("Editing"); + _webview = new WebView(this); + _webview.getSettings().setJavaScriptEnabled(true); - WebView webview = new WebView(this); - String aboutGmsh = Gmsh.getAboutGmsh(); - String aboutGetDP = Gmsh.getAboutGetDP(); - String aboutOnelab = "<p> </p><center><h3>Onelab/Mobile</h3>"; - try { - aboutOnelab += "Version " + this.getPackageManager(). - getPackageInfo(this.getPackageName(), 0).versionName; + String css = "body { background-color: #FFFFFF; color: #252525; margin: 35px 10px 35px 10px; padding: 0; font-family: helvetica,sans-serif; font-size: 1em; }"; + + Intent intent = getIntent(); + Bundle extras = intent.getExtras(); + if(extras != null) { + String name = extras.getString("name"); + getActionBar().setTitle(name); + if(name.equals("Help")){ + _editingMode = false; + _webview.loadDataWithBaseURL("", "<html><head><style type=\"text/css\">" + css + " h3 { text-align: center; } </style></head><h3>Onelab/Mobile</h3> <h4>Running an existing model</h4> <p> The list of available models appears when you launch the app. Selecting a model will load it. You can then press <b>Run</b> to launch a simulation with the default set of parameters. When available, additional information about a model can be obtained by long-pressing on the model description and selecting <b>Visit model website</b>.</p> <h4>Modifying a model</h4> <p>To run a model with different parameters, press <b>Parameters</b> and modify any of the presets. Then press <b>Run</b> again: all the simulation steps will be performed with the new parameter values. To restore the preset parameters values, press <b>Reset</b>. </p> <p> Advanced users can also directly edit the model input files: long-press on the model description and select <b>Edit model files</b>. </p> <p> To free up space, temporary model files (meshes, solution files) can be removed by long-pressing on the model description and selecting <b>Clear results</b>. </p> <p> To completey remove a model, long-press on the model description and select <b>Remove</b>. </p> <h4>Sharing a model</h4> <p> To share a model by email, long-press on the model description and select <b>Email model files</b>. </p> <h4>Installing a new model</h4> <p> To install a new model: <ol> <li>Put all the model files (.pro, .geo) in a directory, which should also contain a file named <code>infos.xml</code> with the model information: <pre>\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<models>\n <model>\n <title>Model title</title>\n <summary>Model summary</summary>\n <file type=\"pro\">main_pro_file.pro</file>\n <preview type=\"png\">128x128_pixel_screenshot.png</preview>\n <url>http://model_website.com</url>\n </model>\n</models>\n</pre><li>Zip the directory. <li>Open the .zip file on your device, e.g. by copying it on an SD card, through Google Drive or by emailing it to yourself and opening the attachment; or by putting it on a web server and downloading the file on the device with Chrome. <li>Refresh the list of models: the new model will be extracted alongside Onelab/Mobile\'s built-in models.</ol> <p style=\"padding-top: 2em;\">Visit <a href=\"http://onelab.info/\">http://onelab.info/</a> for more information.</p> </body></html>", "text/html", "UTF-8", ""); + } + else{ + _editingMode = true; + String filename = extras.getString("file"); + MyJavaScriptInterface javaInterface = new MyJavaScriptInterface(filename); + _webview.addJavascriptInterface(javaInterface, "HTMLOUT"); + File file = new File(filename); + StringBuilder text = new StringBuilder(); + try { + BufferedReader br = new BufferedReader(new FileReader(file)); + String line; + while ((line = br.readLine()) != null) { + text.append(line); + text.append('\n'); + } + br.close(); + } + catch (IOException e) { + } + String code = text.toString().replace("<", "<"); + code = code.toString().replace(">", ">"); + String js = "!function(a,b){\"function\"==typeof define&&define.amd?define([\"exports\"],b):b(\"undefined\"!=typeof exports?exports:a.microlight={})}(this,function(a){var k,l,m,b=window,c=document,d=\"appendChild\",e=\"test\",g=\"opacity:.\",n=function(a){for(l=c.getElementsByClassName(a||\"microlight\"),k=0;m=l[k++];){var n,o,r,s,t,f=m.textContent,h=0,i=f[0],j=1,p=m.innerHTML=\"\",q=0,u=/(\\d*\\, \\d*\\, \\d*)(, ([.\\d]*))?/g.exec(b.getComputedStyle(m).color);for(\"px rgba(\"+u[1]+\",\",u[3]||1;o=n,n=q<7&&\"\\\\\"==n?1:j;){if(j=i,i=f[++h],s=p.length>1,!j||q>8&&\"\\n\"==j||[/\\S/[e](j),1,1,!/[$\\w]/[e](j),(\"/\"==n||\"\\n\"==n)&&s,\'\"\'==n&&s,\"\'\"==n&&s,f[h-4]+o+n==\"-->\",o+n==\"*/\"][q])for(p&&(m[d](t=c.createElement(\"span\")).setAttribute(\"style\",[\"\",\"font-weight:bold;color:#00c;\",g+6,\"color: #a08;\"+g+8,\"font-style:italic;color: #b00;\"+g+8][q?q<3?2:q>6?4:q>3?3:+/^(If|Else|ElseIf|EndIf|Include|For|EndFor|Include|Macro|Return)$/[e](p):0]),t[d](c.createTextNode(p))),r=q&&q<7?q:r,p=\"\",q=11;![1,/[\\/{}[(\\-+*=<>:;|\\\\.,?!&@~]/[e](j),/[\\])]/[e](j),/[$\\w]/[e](j),\"/\"==j&&r<2&&\"<\"!=n,\'\"\'==j,\"\'\"==j,j+i+f[h+1]+f[h+2]==\"<!--\",j+i==\"/*\",j+i==\"//\",j+i==\"//\"][--q];);p+=j}}};a.reset=n,\"complete\"==c.readyState?n():b.addEventListener(\"load\",function(){n()},0)});"; + _webview.loadDataWithBaseURL("", "<html><head><script>" + js + + "</script></head><body><pre contenteditable=\"true\" class=microlight>" + + code + "</pre></body></html>", "text/html", "UTF-8", ""); + } } - catch (android.content.pm.PackageManager.NameNotFoundException e) { - aboutOnelab += "Version ?.?.?"; + else{ + _editingMode = false; + String aboutOnelab = "<center><h3>Onelab/Mobile</h3>"; + try { + aboutOnelab += "Version " + this.getPackageManager(). + getPackageInfo(this.getPackageName(), 0).versionName; + } + catch (android.content.pm.PackageManager.NameNotFoundException e) { + } + aboutOnelab += "<p>Copyright (C) 2014-2016 Christophe Geuzaine and Maxime Graulich, "; + aboutOnelab += "University of Liège</p>"; + aboutOnelab += "<p>Visit <a href=\"http://onelab.info/\">http://onelab.info/</a> "; + aboutOnelab += "for more information</p>"; + aboutOnelab += "<p> </p><p>This version of Onelab/Mobile contains:</p></center>"; + _webview.loadDataWithBaseURL("", "<html><head><style type=\"text/css\">" + css + "</style></head><body>" + + aboutOnelab + Gmsh.getAboutGmsh() + Gmsh.getAboutGetDP() + "</body></html>", + "text/html", "UTF-8", ""); } - - //aboutOnelab += "<pre contenteditable=\"true\"> THIS IS EDITABLE </pre>"; - - aboutOnelab += "<p>Copyright (C) 2014-2016 Christophe Geuzaine and Maxime Graulich, "; - aboutOnelab += "University of Liège</p>"; - aboutOnelab += "<p>Visit <a href=\"http://onelab.info/\">http://onelab.info/</a> "; - aboutOnelab += "for more information</p>"; - aboutOnelab += "<p> </p><p>This version of Onelab/Mobile contains:</p>"; - webview.loadDataWithBaseURL("", aboutOnelab + aboutGmsh + aboutGetDP, - "text/html", "UTF-8", ""); - setContentView(webview); + setContentView(_webview); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - //_saveMenuItem = menu.add(R.string.menu_save); - //_saveMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + if(_editingMode){ + _saveMenuItem = menu.add(R.string.menu_save); + _saveMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } return true; } @Override @@ -60,7 +125,7 @@ public class AboutActivity extends Activity{ this.finish(); } else if(item.getTitle().equals(getString(R.string.menu_save))){ - + _webview.loadUrl("javascript:( function () { window.HTMLOUT.myJsCallback(document.body.innerText); } ) ()"); } return super.onMenuItemSelected(featureId, item); } diff --git a/contrib/mobile/Android/src/org/geuz/onelab/MainActivity.java b/contrib/mobile/Android/src/org/geuz/onelab/MainActivity.java index 135d288475..dce8e2004d 100644 --- a/contrib/mobile/Android/src/org/geuz/onelab/MainActivity.java +++ b/contrib/mobile/Android/src/org/geuz/onelab/MainActivity.java @@ -105,8 +105,6 @@ public class MainActivity extends Activity{ } _runStopMenuItem = menu.add((_compute)?R.string.menu_stop:R.string.menu_run); _runStopMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - MenuItem shareMenuItem = menu.add(R.string.menu_share); - shareMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); return true; } @@ -135,31 +133,6 @@ public class MainActivity extends Activity{ _runStopMenuItem.setEnabled(false); _gmsh.onelabCB("stop"); } - else if(item.getTitle().equals(getString(R.string.menu_share))) { - if(this._compute) { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - _errorDialog = dialogBuilder.setTitle("Cannot show the model list") - .setMessage("The computation has to complete before you can take a screenshot") - .setPositiveButton("OK", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .show(); - } - else { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss"); - File file = new File(this.getExternalFilesDir(null), - "onelab-screenshot-"+dateFormat.format(new Date())+".png"); - file.setReadable(true, false); - _modelFragment.takeScreenshot(file); - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)); - shareIntent.setType("image/jpeg"); - startActivity(Intent.createChooser(shareIntent, getString(R.string.title_share))); - } - } else if(item.getItemId() == android.R.id.home) { if(this._compute) { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); diff --git a/contrib/mobile/Android/src/org/geuz/onelab/ModelList.java b/contrib/mobile/Android/src/org/geuz/onelab/ModelList.java index 8dbfc3b09f..b5254f18d2 100644 --- a/contrib/mobile/Android/src/org/geuz/onelab/ModelList.java +++ b/contrib/mobile/Android/src/org/geuz/onelab/ModelList.java @@ -2,8 +2,10 @@ package org.geuz.onelab; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -27,6 +29,7 @@ import android.widget.LinearLayout; import android.widget.ListView; import android.widget.Toast; import android.util.Log; +import android.os.Environment; public class ModelList extends Activity { private ModelArrayAdapter _modelArrayAdapter; @@ -40,6 +43,18 @@ public class ModelList extends Activity { fileOrDirectory.delete(); } + private void copyFile(File src, File dst) throws IOException { + FileInputStream in = new FileInputStream(src); + FileOutputStream out = new FileOutputStream(dst); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + in.close(); + out.close(); + } + @Override protected void onCreate(Bundle savedInstanceState) { @@ -74,17 +89,21 @@ public class ModelList extends Activity { final Model m = _modelArrayAdapter.getModel(position); CharSequence[] actions; if(m.getUrl() != null) { - actions = new CharSequence[4]; + actions = new CharSequence[6]; actions[0] = "Open"; actions[1] = "Remove"; - actions[2] = "Edit model files"; - actions[3] = "Visit model website"; + actions[2] = "Clear results"; + actions[3] = "Edit model files"; + actions[4] = "Email model files"; + actions[5] = "Visit model website"; } else { - actions = new CharSequence[3]; + actions = new CharSequence[5]; actions[0] = "Open"; actions[1] = "Remove"; - actions[2] = "Edit model files"; + actions[2] = "Clear results"; + actions[3] = "Edit model files"; + actions[4] = "Email model files"; } final AdapterView<?> p = parent; AlertDialog.Builder builder = new AlertDialog.Builder(p.getContext()); @@ -112,26 +131,86 @@ public class ModelList extends Activity { } break; case 2: - {/* + { + File folder = m.getFile().getParentFile(); + File[] files = folder.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isFile()) { + String filenameArray[] = files[i].getName().split("\\."); + String extension = filenameArray[filenameArray.length-1]; + if(extension.equalsIgnoreCase("msh") || + extension.equalsIgnoreCase("pre") || + extension.equalsIgnoreCase("res") || + extension.equalsIgnoreCase("pos")){ + deleteRecursive(files[i]); + } + } + } + } + break; + case 3: + { AlertDialog.Builder builder = new AlertDialog.Builder(p.getContext()); builder.setTitle("Edit model files"); - CharSequence[] act; - act = new CharSequence[1]; - act[0] = "Test"; - builder.setItems(act, new DialogInterface.OnClickListener() { + File folder = m.getFile().getParentFile(); + File[] files = folder.listFiles(); + ArrayList<String> selection = new ArrayList<String>(); + for (int i = 0; i < files.length; i++) { + if (files[i].isFile()) { + String filenameArray[] = files[i].getName().split("\\."); + String extension = filenameArray[filenameArray.length-1]; + if(extension.equalsIgnoreCase("txt") || + extension.equalsIgnoreCase("geo") || + extension.equalsIgnoreCase("pro") || + extension.equalsIgnoreCase("dat")){ + selection.add(files[i].getName()); + } + } + } + final String[] names = new String[selection.size()]; + for (int i = 0; i < selection.size(); i++) + names[i] = selection.get(i); + builder.setItems(names, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int position) { - // Intent intent = new Intent(ModelList.this, AboutActivity.class); - // intent.putExtra("file", m.getFile().toString()); - // intent.putExtra("name", m.getName()); - // startActivity(intent); + Intent intent = new Intent(ModelList.this, AboutActivity.class); + File folder = m.getFile().getParentFile(); + String file = folder + "/" + names[position]; + intent.putExtra("file", file); + intent.putExtra("name", names[position]); + startActivity(intent); } }); AlertDialog alert = builder.create(); alert.show(); - */ } break; - case 3: + case 4: + { + Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); + emailIntent.setType("vnd.android.cursor.dir/email"); + File folder = m.getFile().getParentFile(); + File[] files = folder.listFiles(); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, folder+"/"+files[0].getName()); + ArrayList<Uri> copies = new ArrayList<Uri>(); + for (int i = 0; i < files.length; i++) { + if (files[i].isFile()) { + // need to copy as we cannot attach files directly from + // the app data dir + File copy = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), + files[i].getName()); + try{ + copyFile(files[i], copy); + } + catch (IOException e) { + } + copies.add(Uri.fromFile(copy)); + } + } + emailIntent.putExtra(Intent.EXTRA_STREAM, copies); + startActivity(Intent.createChooser(emailIntent , "Share model files...")); + } + break; + case 5: { Intent browserIntent = new Intent(Intent.ACTION_VIEW, m.getUrl()); startActivity(browserIntent); @@ -154,9 +233,10 @@ public class ModelList extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { + MenuItem help = menu.add("Help"); + help.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); MenuItem about = menu.add("About"); - about.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - + about.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); return super.onCreateOptionsMenu(menu); } @@ -167,6 +247,12 @@ public class ModelList extends Activity { Intent intent = new Intent(ModelList.this, AboutActivity.class); startActivity(intent); } + else if(item.getTitle().equals("Help")) { + Intent intent = new Intent(ModelList.this, AboutActivity.class); + intent.putExtra("file", "Help"); + intent.putExtra("name", "Help"); + startActivity(intent); + } return super.onMenuItemSelected(featureId, item); } @@ -194,7 +280,7 @@ public class ModelList extends Activity { { File document = this.getFilesDir(); File files[] = document.listFiles(); - for(int i=0; i<files.length; i++) { + for(int i = 0; i < files.length; i++) { if(files[i].isDirectory()) { // models are in directory File xmlInfos = new File(files[i], "infos.xml"); if(!xmlInfos.isFile()) continue; -- GitLab