diff --git a/resources/drawables/drawables.xml b/resources/drawables/drawables.xml
index d0febbd..4923670 100644
--- a/resources/drawables/drawables.xml
+++ b/resources/drawables/drawables.xml
@@ -1,13 +1,13 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/drawables/icon_arrest.png b/resources/drawables/icon_arrest.png
new file mode 100644
index 0000000..1a6f58c
Binary files /dev/null and b/resources/drawables/icon_arrest.png differ
diff --git a/resources/drawables/icon_arrival.png b/resources/drawables/icon_arrival.png
new file mode 100644
index 0000000..72bcea2
Binary files /dev/null and b/resources/drawables/icon_arrival.png differ
diff --git a/resources/drawables/icon_delete.png b/resources/drawables/icon_delete.png
new file mode 100644
index 0000000..639faf0
Binary files /dev/null and b/resources/drawables/icon_delete.png differ
diff --git a/resources/drawables/icon_end.png b/resources/drawables/icon_end.png
new file mode 100644
index 0000000..643147d
Binary files /dev/null and b/resources/drawables/icon_end.png differ
diff --git a/resources/drawables/icon_event.png b/resources/drawables/icon_event.png
new file mode 100644
index 0000000..b4788bf
Binary files /dev/null and b/resources/drawables/icon_event.png differ
diff --git a/resources/drawables/icon_evidence.png b/resources/drawables/icon_evidence.png
new file mode 100644
index 0000000..f9e13a9
Binary files /dev/null and b/resources/drawables/icon_evidence.png differ
diff --git a/resources/drawables/icon_force.png b/resources/drawables/icon_force.png
new file mode 100644
index 0000000..beefd85
Binary files /dev/null and b/resources/drawables/icon_force.png differ
diff --git a/resources/drawables/icon_history.png b/resources/drawables/icon_history.png
new file mode 100644
index 0000000..1302a8b
Binary files /dev/null and b/resources/drawables/icon_history.png differ
diff --git a/resources/drawables/icon_sighting.png b/resources/drawables/icon_sighting.png
new file mode 100644
index 0000000..b51c0de
Binary files /dev/null and b/resources/drawables/icon_sighting.png differ
diff --git a/resources/drawables/icon_start.png b/resources/drawables/icon_start.png
new file mode 100644
index 0000000..cce6665
Binary files /dev/null and b/resources/drawables/icon_start.png differ
diff --git a/resources/drawables/launcher_icon.png b/resources/drawables/launcher_icon.png
new file mode 100644
index 0000000..6f8b276
Binary files /dev/null and b/resources/drawables/launcher_icon.png differ
diff --git a/source/Config.mc b/source/Config.mc
index 0eea8a6..12d8bbb 100644
--- a/source/Config.mc
+++ b/source/Config.mc
@@ -10,8 +10,7 @@ module Config {
const RETENTION_SEC = 7 * 24 * 60 * 60;
// --- GPS ------------------------------------------------------------
- const GPS_TIMEOUT_MS = 10000;
- const GPS_TARGET_ACCURACY_M = 5;
+ const GPS_TIMEOUT_MS = 30000;
// --- Menu ring -----------------------------------------------------
// Angle (degrees) at which the selected icon sits relative to the
diff --git a/source/Event.mc b/source/Event.mc
index 73e3968..44eda88 100644
--- a/source/Event.mc
+++ b/source/Event.mc
@@ -10,6 +10,7 @@ class Event {
public var lon as Float or Null;
public var accuracy as Float or Null;
public var address as String or Null;
+ public var zip as String or Null;
function initialize(type as String, timestamp as Number,
lat as Float or Null, lon as Float or Null,
@@ -20,6 +21,7 @@ class Event {
self.lon = lon;
self.accuracy = accuracy;
self.address = null;
+ self.zip = null;
}
function toDict() as Dictionary {
@@ -30,9 +32,8 @@ class Event {
"lon" => lon,
"acc" => accuracy
};
- if (address != null) {
- d["addr"] = address;
- }
+ if (address != null) { d["addr"] = address; }
+ if (zip != null) { d["zip"] = zip; }
return d;
}
@@ -44,9 +45,8 @@ class Event {
d["lon"] as Float or Null,
d["acc"] as Float or Null
);
- if (d.hasKey("addr")) {
- evt.address = d["addr"] as String or Null;
- }
+ if (d.hasKey("addr")) { evt.address = d["addr"] as String or Null; }
+ if (d.hasKey("zip")) { evt.zip = d["zip"] as String or Null; }
return evt;
}
}
diff --git a/source/GeocodingService.mc b/source/GeocodingService.mc
index 9ca0bb1..8afee70 100644
--- a/source/GeocodingService.mc
+++ b/source/GeocodingService.mc
@@ -2,15 +2,15 @@ import Toybox.Communications;
import Toybox.Lang;
// Reverse-geocodes a lat/lon pair via Photon API. Calls back with
-// a formatted address string or null on failure.
+// a Dictionary { "addr" => String, "zip" => String } or null.
class GeocodingService {
- private var _callback as Method(address as String or Null) as Void;
+ private var _callback as Method(result as Dictionary or Null) as Void;
private var _lat as Float;
private var _lon as Float;
function initialize(lat as Float, lon as Float,
- callback as Method(address as String or Null) as Void) {
+ callback as Method(result as Dictionary or Null) as Void) {
_callback = callback;
_lat = lat;
_lon = lon;
@@ -28,16 +28,13 @@ class GeocodingService {
}
function _onResponse(responseCode as Number, data as Dictionary or String or Null) as Void {
- System.println("GEO: responseCode=" + responseCode);
if (responseCode != 200 || data == null || !(data instanceof Dictionary)) {
- System.println("GEO: bad response, data=" + data);
_callback.invoke(null);
return;
}
var features = data["features"];
if (features == null || !(features instanceof Array) || features.size() == 0) {
- System.println("GEO: no features");
_callback.invoke(null);
return;
}
@@ -45,7 +42,6 @@ class GeocodingService {
var feature = features[0] as Dictionary;
var props = feature["properties"] as Dictionary;
var geometry = feature["geometry"] as Dictionary;
- System.println("GEO: props=" + props);
// Haversine check: only use address if result is within threshold.
if (geometry != null) {
@@ -54,31 +50,38 @@ class GeocodingService {
var rLon = (coords[0] as Double).toFloat();
var rLat = (coords[1] as Double).toFloat();
var dist = Haversine.distance(_lat, _lon, rLat, rLon);
- System.println("GEO: dist=" + dist + " max=" + Config.ADDRESS_MAX_DISTANCE_M);
if (dist > Config.ADDRESS_MAX_DISTANCE_M) {
- System.println("GEO: too far, rejecting");
_callback.invoke(null);
return;
}
}
}
- var addr = _formatAddress(props);
- System.println("GEO: address=" + addr);
- _callback.invoke(addr);
- }
-
- private function _formatAddress(props as Dictionary) as String {
var street = props["street"];
- var number = props["housenumber"];
-
- var parts = "";
- if (street != null) {
- parts = street as String;
- if (number != null) {
- parts = parts + " " + number;
- }
+ if (street == null) { street = props["name"]; }
+ if (street == null) {
+ _callback.invoke(null);
+ return;
}
- return parts.equals("") ? "Unbekannt" : parts;
+
+ var addr = street as String;
+ var number = props["housenumber"];
+ if (number != null) {
+ addr = addr + " " + number;
+ }
+
+ var result = { "addr" => addr } as Dictionary;
+ var zip = props["postcode"];
+ var city = props["city"];
+ if (zip != null || city != null) {
+ var zipCity = "";
+ if (zip != null) { zipCity = zip as String; }
+ if (city != null) {
+ if (!zipCity.equals("")) { zipCity = zipCity + " "; }
+ zipCity = zipCity + city;
+ }
+ result["zip"] = zipCity;
+ }
+ _callback.invoke(result);
}
}
diff --git a/source/GpsService.mc b/source/GpsService.mc
index 6631f70..7101556 100644
--- a/source/GpsService.mc
+++ b/source/GpsService.mc
@@ -47,17 +47,16 @@ class GpsService {
if (info == null || info.position == null) { return; }
var degrees = info.position.toDegrees();
- var acc = (info.accuracy != null) ? info.accuracy : null;
+ var quality = (info.accuracy != null) ? info.accuracy : 0;
- _bestFix = {
- "lat" => degrees[0].toFloat(),
- "lon" => degrees[1].toFloat(),
- "acc" => (acc != null) ? acc.toFloat() : null
- };
-
- // Good enough → stop early.
- if (acc != null && acc <= Config.GPS_TARGET_ACCURACY_M) {
- _finish(_bestFix);
+ // Always keep the best quality fix.
+ if (_bestFix == null || quality >= (_bestFix["q"] as Number)) {
+ _bestFix = {
+ "lat" => degrees[0].toFloat(),
+ "lon" => degrees[1].toFloat(),
+ "acc" => quality.toFloat(),
+ "q" => quality
+ };
}
}
diff --git a/source/HistoryView.mc b/source/HistoryView.mc
index 881c8d3..ea39d05 100644
--- a/source/HistoryView.mc
+++ b/source/HistoryView.mc
@@ -64,7 +64,7 @@ class HistoryView extends WatchUi.View {
var evt = _events[_index];
// Use cached address from event if available.
if (evt.address != null) {
- _addressCache[_index] = evt.address;
+ _addressCache[_index] = { "addr" => evt.address, "zip" => evt.zip };
return;
}
if (evt.lat == null || evt.lon == null) { return; }
@@ -76,13 +76,14 @@ class HistoryView extends WatchUi.View {
_geocoder.start();
}
- function _onAddress(address as String or Null) as Void {
- _addressCache[_index] = address;
+ function _onAddress(result as Dictionary or Null) as Void {
+ _addressCache[_index] = result;
_addressLoading = false;
- // Persist address in the event so the glance can show it.
- if (address != null && _index < _events.size()) {
+ // Persist in the event so the glance can show it.
+ if (result != null && _index < _events.size()) {
var evt = _events[_index];
- evt.address = address;
+ evt.address = result["addr"] as String or Null;
+ evt.zip = result["zip"] as String or Null;
EventStore.updateAt(_index, evt);
}
WatchUi.requestUpdate();
@@ -134,51 +135,51 @@ class HistoryView extends WatchUi.View {
private function _drawMiddleSection(dc as Dc, cx as Number, topH as Number, botY as Number) as Void {
var evt = _events[_index];
- var midY = topH + (botY - topH) / 2;
+ var fontDetail = Graphics.FONT_XTINY;
+ var lineH = dc.getFontHeight(fontDetail);
+ var sectionMid = topH + (botY - topH) / 2;
- // Event type label.
- var label = _eventLabel(evt.type);
- var color = _eventColor(evt.type);
- dc.setColor(color, Config.COLOR_BG);
- var fontLabel = Graphics.FONT_SMALL;
- var lineH = dc.getFontHeight(fontLabel);
- dc.drawText(cx, midY - lineH * 2, fontLabel, label,
- Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
+ // Collect lines to draw.
+ var lines = [] as Array; // [text, color]
- // Date + time.
- dc.setColor(Config.COLOR_FG, Config.COLOR_BG);
- var fontDetail = Graphics.FONT_TINY;
- var dateStr = _formatTimestamp(evt.timestamp);
- dc.drawText(cx, midY - lineH / 2, fontDetail, dateStr,
- Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
+ // 1: Event type (colored).
+ lines.add([_eventLabel(evt.type), _eventColor(evt.type)]);
- // Address / coordinates / status.
- var locStr = _locationString(evt);
- dc.drawText(cx, midY + lineH / 2, fontDetail, locStr,
- Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
+ // 2: Date + time.
+ lines.add([_formatTimestamp(evt.timestamp), Config.COLOR_FG]);
- // Counter (e.g. "3/7").
- dc.setColor(0x888888, Config.COLOR_BG);
- dc.drawText(cx, midY + lineH * 3 / 2, Graphics.FONT_XTINY,
- (_index + 1) + "/" + _events.size(),
- Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
- }
-
- private function _locationString(evt as Event) as String {
+ // 3: Address or status.
if (evt.lat == null || evt.lon == null) {
- return WatchUi.loadResource(Rez.Strings.history_no_gps) as String;
- }
- if (_addressCache.hasKey(_index)) {
- var cached = _addressCache[_index];
+ lines.add([WatchUi.loadResource(Rez.Strings.history_no_gps) as String, Config.COLOR_FG]);
+ } else if (_addressCache.hasKey(_index)) {
+ var cached = _addressCache[_index] as Dictionary or Null;
if (cached != null) {
- return cached as String;
+ lines.add([cached["addr"] as String, Config.COLOR_FG]);
+ var zip = cached["zip"] as String or Null;
+ if (zip != null) {
+ lines.add([zip, 0x888888]);
+ }
}
- return _formatCoords(evt.lat as Float, evt.lon as Float);
+ } else if (_addressLoading) {
+ lines.add([WatchUi.loadResource(Rez.Strings.history_loading_address) as String, 0x888888]);
}
- if (_addressLoading) {
- return WatchUi.loadResource(Rez.Strings.history_loading_address) as String;
+
+ // 4: Coordinates (always, if available).
+ if (evt.lat != null && evt.lon != null) {
+ lines.add([_formatCoords(evt.lat as Float, evt.lon as Float), 0x888888]);
+ }
+
+ // 5: Counter.
+ lines.add([(_index + 1) + "/" + _events.size(), 0x666666]);
+
+ // Draw centered vertically.
+ var totalH = lines.size() * lineH;
+ var startY = sectionMid - totalH / 2 + lineH / 2;
+ for (var i = 0; i < lines.size(); i++) {
+ dc.setColor(lines[i][1] as Number, Config.COLOR_BG);
+ dc.drawText(cx, startY + i * lineH, fontDetail, lines[i][0] as String,
+ Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
}
- return _formatCoords(evt.lat as Float, evt.lon as Float);
}
private function _formatCoords(lat as Float, lon as Float) as String {
diff --git a/source/MenuView.mc b/source/MenuView.mc
index adbb1fd..a11bc09 100644
--- a/source/MenuView.mc
+++ b/source/MenuView.mc
@@ -67,6 +67,11 @@ class MenuView extends WatchUi.View {
var isSelected = (i == _selectedIndex);
var targetSize = isSelected ? selSize : baseSize;
+ // Colored circle behind icon.
+ var color = _items[i][:color] as Number;
+ dc.setColor(color, Config.COLOR_BG);
+ dc.fillCircle(x, y, targetSize / 2);
+
var iconId = _items[i][:icon];
var bmp = _bitmaps[iconId] as BitmapResource;
if (bmp != null) {
@@ -91,12 +96,12 @@ class MenuView extends WatchUi.View {
cx as Number, cy as Number,
targetSize as Number) as Void {
var bmpW = bmp.getWidth();
+ var bmpH = bmp.getHeight();
var scale = targetSize.toFloat() / bmpW.toFloat();
- dc.drawBitmap2(
- cx - targetSize / 2,
- cy - targetSize / 2,
- bmp,
- { :scaleX => scale, :scaleY => scale }
- );
+ var tf = new Graphics.AffineTransform();
+ tf.translate(cx.toFloat(), cy.toFloat());
+ tf.scale(scale, scale);
+ tf.translate(-bmpW.toFloat() / 2.0, -bmpH.toFloat() / 2.0);
+ dc.drawBitmap2(0, 0, bmp, { :transform => tf });
}
}