Various improvements to the configuration page:

- localstorage is now cleared at every launch: this prevents some clay configuration pages to send back to the watch a number of keys that were set by other configuration pages
- only execute JS on document ready: this prevents some race conditions
- added dummy getTimelineToken function to Pebble JS object
- corrected (hopefully!) a few logic errors in the JS code (this referenced where it wasn't)
- refactored the steps visualization in JS
- lifecycle changes to the java activity: now the configuration page gets closed as soon as the settings have been sent, and there is only one instance of it
master
Daniele Gobbetti 2016-06-18 18:40:57 +02:00
parent fffeb87607
commit 966c3d4811
4 changed files with 91 additions and 46 deletions

View File

@ -261,6 +261,7 @@
</receiver> </receiver>
<activity <activity
android:launchMode="singleInstance"
android:name=".activities.ExternalPebbleJSActivity" android:name=".activities.ExternalPebbleJSActivity"
android:label="@string/app_configure" android:label="@string/app_configure"
android:parentActivityName=".activities.AppManagerActivity"> android:parentActivityName=".activities.AppManagerActivity">

View File

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8"/>
<meta name='viewport' content='initial-scale=1.0, maximum-scale=1.0'> <meta name='viewport' content='initial-scale=1.0, maximum-scale=1.0'>
<script type="text/javascript" src="js/Uri.js"> <script type="text/javascript" src="js/Uri.js">
</script> </script>
@ -52,14 +52,16 @@
<!-- TODO --> <!-- TODO -->
</style> </style>
</head> </head>
<body onload="" style="width: 100%;"> <body>
<div id="step1"> <div id="step1" class="step">
<h2>Url of the configuration:</h2> <h2>Url of the configuration:</h2>
<div id="config_url"></div> <div id="config_url"></div>
<!--<button class="btn" name="show config" value="show config" onclick="Pebble.showConfiguration()" >Show config / URL</button>--> <!--<button class="btn" name="show config" value="show config" onclick="Pebble.showConfiguration()" >Show config / URL</button>-->
<button class="btn" name="open config" value="open config" onclick="Pebble.actuallyOpenURL()" >Open configuration website</button> <button class="btn" name="open config" value="open config" onclick="Pebble.actuallyOpenURL()">
Open configuration website
</button>
</div> </div>
<div id="step1compat"> <div id="step1compat" class="step">
<p>In case of "network error" after saving settings in the wathchapp, copy the "network error" <p>In case of "network error" after saving settings in the wathchapp, copy the "network error"
URL and paste it here:</p> URL and paste it here:</p>
<textarea id="pastereturn"></textarea><br/> <textarea id="pastereturn"></textarea><br/>
@ -67,9 +69,11 @@
configuration configuration
</button> </button>
</div> </div>
<div id="step2"> <div id="step2" class="step">
<h2>Incoming configuration data:</h2> <h2>Incoming configuration data:</h2>
<div id="jsondata"></div> <div id="jsondata"></div>
<button class="btn" name="send config" value="send config" onclick="Pebble.actuallySendData()" >Send data to pebble</button> <button class="btn" name="send config" value="send config" onclick="Pebble.actuallySendData()">
Send data to pebble
</button>
</div> </div>
</body> </body>

View File

@ -1,3 +1,6 @@
//clay stores the values in the localStorage
localStorage.clear();
function loadScript(url, callback) { function loadScript(url, callback) {
// Adding the script tag to the head as suggested before // Adding the script tag to the head as suggested before
var head = document.getElementsByTagName('head')[0]; var head = document.getElementsByTagName('head')[0];
@ -29,46 +32,63 @@ function getURLVariable(variable, defaultValue) {
return defaultValue || false; return defaultValue || false;
} }
function showStep(desiredStep) {
var steps = document.getElementsByClassName("step");
var testStep = null;
for (var i = 0; i < steps.length; i ++) {
if (steps[i].id == desiredStep)
testStep = steps[i].id;
}
if (testStep !== null) {
for (var i = 0; i < steps.length; i ++) {
steps[i].style.display = 'none';
}
document.getElementById(desiredStep).style.display="block";
}
}
function gbPebble() { function gbPebble() {
this.configurationURL = null; this.configurationURL = null;
this.configurationValues = null; this.configurationValues = null;
var self = this;
this.addEventListener = function(e, f) { this.addEventListener = function(e, f) {
if(e == 'ready') { if(e == 'ready') {
this.ready = f; self.ready = f;
} }
if(e == 'showConfiguration') { if(e == 'showConfiguration') {
this.showConfiguration = f; self.showConfiguration = f;
} }
if(e == 'webviewclosed') { if(e == 'webviewclosed') {
this.parseconfig = f; self.parseconfig = f;
} }
if(e == 'appmessage') { if(e == 'appmessage') {
this.appmessage = f; self.appmessage = f;
} }
} }
this.removeEventListener = function(e, f) { this.removeEventListener = function(e, f) {
if(e == 'ready') { if(e == 'ready') {
this.ready = null; self.ready = null;
} }
if(e == 'showConfiguration') { if(e == 'showConfiguration') {
this.showConfiguration = null; self.showConfiguration = null;
} }
if(e == 'webviewclosed') { if(e == 'webviewclosed') {
this.parseconfig = null; self.parseconfig = null;
} }
if(e == 'appmessage') { if(e == 'appmessage') {
this.appmessage = null; self.appmessage = null;
} }
} }
this.actuallyOpenURL = function() { this.actuallyOpenURL = function() {
document.getElementById('step1compat').style.display="block"; showStep("step1compat");
window.open(this.configurationURL.toString(), "config"); window.open(self.configurationURL.toString(), "config");
} }
this.actuallySendData = function() { this.actuallySendData = function() {
GBjs.sendAppMessage(this.configurationValues); GBjs.sendAppMessage(self.configurationValues);
GBjs.closeActivity();
} }
//needs to be called like this because of original Pebble function name //needs to be called like this because of original Pebble function name
@ -76,7 +96,7 @@ function gbPebble() {
if (url.lastIndexOf("http", 0) === 0) { if (url.lastIndexOf("http", 0) === 0) {
document.getElementById("config_url").innerHTML=url; document.getElementById("config_url").innerHTML=url;
var UUID = GBjs.getAppUUID(); var UUID = GBjs.getAppUUID();
this.configurationURL = new Uri(url).addQueryParam("return_to", "gadgetbridge://"+UUID+"?config=true&json="); self.configurationURL = new Uri(url).addQueryParam("return_to", "gadgetbridge://"+UUID+"?config=true&json=");
} else { } else {
//TODO: add custom return_to //TODO: add custom return_to
location.href = url; location.href = url;
@ -90,7 +110,7 @@ function gbPebble() {
this.sendAppMessage = function (dict, callbackAck, callbackNack){ this.sendAppMessage = function (dict, callbackAck, callbackNack){
try { try {
this.configurationValues = JSON.stringify(dict); self.configurationValues = JSON.stringify(dict);
document.getElementById("jsondata").innerHTML=this.configurationValues; document.getElementById("jsondata").innerHTML=this.configurationValues;
return callbackAck; return callbackAck;
} }
@ -108,6 +128,10 @@ function gbPebble() {
return GBjs.getWatchToken(); return GBjs.getWatchToken();
} }
this.getTimelineToken = function() {
return '';
}
this.showSimpleNotificationOnPebble = function(title, body) { this.showSimpleNotificationOnPebble = function(title, body) {
GBjs.gbLog("app wanted to show: " + title + " body: "+ body); GBjs.gbLog("app wanted to show: " + title + " body: "+ body);
} }
@ -116,17 +140,14 @@ function gbPebble() {
} }
this.parseReturnedPebbleJS = function() { this.parseReturnedPebbleJS = function() {
var str = document.getElementById('pastereturn').value; var str = document.getElementById('pastereturn').value;
var needle = "pebblejs://close#"; var needle = "pebblejs://close#";
if (str.split(needle)[1] !== undefined) { if (str.split(needle)[1] !== undefined) {
var t = new Object(); var t = new Object();
t.response = unescape(str.split(needle)[1]); t.response = unescape(str.split(needle)[1]);
this.parseconfig(t); self.parseconfig(t);
document.getElementById('step1').style.display="none"; showStep("step2");
document.getElementById('step1compat').style.display="none";
document.getElementById('step2').style.display="block";
} else { } else {
console.error("No valid configuration found in the entered string."); console.error("No valid configuration found in the entered string.");
} }
@ -136,13 +157,11 @@ function gbPebble() {
var Pebble = new gbPebble(); var Pebble = new gbPebble();
var jsConfigFile = GBjs.getAppConfigurationFile(); var jsConfigFile = GBjs.getAppConfigurationFile();
document.addEventListener('DOMContentLoaded', function(){
if (jsConfigFile != null) { if (jsConfigFile != null) {
loadScript(jsConfigFile, function() { loadScript(jsConfigFile, function() {
if (getURLVariable('config') == 'true') { if (getURLVariable('config') == 'true') {
document.getElementById('step1').style.display="none"; showStep("step2");
document.getElementById('step1compat').style.display="none";
document.getElementById('step2').style.display="block";
var json_string = unescape(getURLVariable('json')); var json_string = unescape(getURLVariable('json'));
var t = new Object(); var t = new Object();
t.response = json_string; t.response = json_string;
@ -154,3 +173,4 @@ if (jsConfigFile != null) {
} }
}); });
} }
}, false);

View File

@ -28,6 +28,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
@ -37,7 +38,9 @@ public class ExternalPebbleJSActivity extends GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class); private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class);
private UUID appUuid; private UUID appUuid;
private Uri confUri;
private GBDevice mGBDevice = null; private GBDevice mGBDevice = null;
private WebView myWebView;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -46,27 +49,15 @@ public class ExternalPebbleJSActivity extends GBActivity {
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
if (extras != null) { if (extras != null) {
mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
appUuid = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID);
} else { } else {
throw new IllegalArgumentException("Must provide a device when invoking this activity"); throw new IllegalArgumentException("Must provide a device when invoking this activity");
} }
String queryString = "";
Uri uri = getIntent().getData();
if (uri != null) {
//getting back with configuration data
try {
appUuid = UUID.fromString(uri.getHost());
queryString = uri.getEncodedQuery();
} catch (IllegalArgumentException e) {
Log.d("returned uri: ", uri.toString());
}
} else {
appUuid = (UUID) getIntent().getSerializableExtra("app_uuid");
}
setContentView(R.layout.activity_external_pebble_js); setContentView(R.layout.activity_external_pebble_js);
WebView myWebView = (WebView) findViewById(R.id.configureWebview); myWebView = (WebView) findViewById(R.id.configureWebview);
myWebView.clearCache(true); myWebView.clearCache(true);
myWebView.setWebViewClient(new GBWebClient()); myWebView.setWebViewClient(new GBWebClient());
myWebView.setWebChromeClient(new GBChromeClient()); myWebView.setWebChromeClient(new GBChromeClient());
@ -78,7 +69,32 @@ public class ExternalPebbleJSActivity extends GBActivity {
JSInterface gbJSInterface = new JSInterface(); JSInterface gbJSInterface = new JSInterface();
myWebView.addJavascriptInterface(gbJSInterface, "GBjs"); myWebView.addJavascriptInterface(gbJSInterface, "GBjs");
myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString); myWebView.loadUrl("file:///android_asset/app_config/configure.html");
}
@Override
protected void onNewIntent(Intent incoming) {
super.onNewIntent(incoming);
confUri = incoming.getData();
}
@Override
protected void onResume() {
super.onResume();
String queryString = "";
if (confUri != null) {
//getting back with configuration data
try {
appUuid = UUID.fromString(confUri.getHost());
queryString = confUri.getEncodedQuery();
} catch (IllegalArgumentException e) {
GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR);
}
myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString);
}
} }
private JSONObject getAppConfigurationKeys() { private JSONObject getAppConfigurationKeys() {
@ -112,8 +128,7 @@ public class ExternalPebbleJSActivity extends GBActivity {
@Override @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) { public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("http://") || url.startsWith("https://")) { if (url.startsWith("http://") || url.startsWith("https://")) {
Intent i = new Intent(Intent.ACTION_VIEW, Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
Uri.parse(url));
startActivity(i); startActivity(i);
} else { } else {
url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json=");
@ -222,6 +237,11 @@ public class ExternalPebbleJSActivity extends GBActivity {
//specification says: A string that is is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/ //specification says: A string that is is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/
return "gb" + appUuid.toString(); return "gb" + appUuid.toString();
} }
@JavascriptInterface
public void closeActivity() {
finish();
}
} }
@Override @Override