Phase 4: history view with reverse geocoding
3-section layout (15/70/15) for browsing recorded events. Reverse-geocodes coordinates via Photon API with Haversine distance check and address caching. Also adds simulator GPS polling fallback (Position.getInfo) since the simulator does not fire location event callbacks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
025d3007db
commit
902121bd42
7 changed files with 368 additions and 11 deletions
|
|
@ -11,6 +11,7 @@
|
||||||
</iq:products>
|
</iq:products>
|
||||||
<iq:permissions>
|
<iq:permissions>
|
||||||
<iq:uses-permission id="Positioning"/>
|
<iq:uses-permission id="Positioning"/>
|
||||||
|
<iq:uses-permission id="Communications"/>
|
||||||
</iq:permissions>
|
</iq:permissions>
|
||||||
<iq:languages>
|
<iq:languages>
|
||||||
<iq:language>deu</iq:language>
|
<iq:language>deu</iq:language>
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@
|
||||||
<string id="error_generic">Fehler beim|Speichern</string>
|
<string id="error_generic">Fehler beim|Speichern</string>
|
||||||
|
|
||||||
<!-- History -->
|
<!-- History -->
|
||||||
<string id="history_empty">Keine Einträge</string>
|
<string id="history_empty">Keine|Einträge</string>
|
||||||
<string id="history_loading_address">Adresse wird geladen…</string>
|
<string id="history_loading_address">Adresse wird|geladen…</string>
|
||||||
<string id="history_no_phone">Verbinde Handy für Adresse</string>
|
<string id="history_no_phone">Verbinde Handy|für Adresse</string>
|
||||||
|
<string id="history_no_gps">Kein Standort</string>
|
||||||
|
|
||||||
<!-- Delete -->
|
<!-- Delete -->
|
||||||
<string id="delete_hold">Halten zum Löschen</string>
|
<string id="delete_hold">Halten zum Löschen</string>
|
||||||
|
|
|
||||||
83
source/GeocodingService.mc
Normal file
83
source/GeocodingService.mc
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
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.
|
||||||
|
class GeocodingService {
|
||||||
|
|
||||||
|
private var _callback as Method(address as String 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 = callback;
|
||||||
|
_lat = lat;
|
||||||
|
_lon = lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() as Void {
|
||||||
|
var url = Config.PHOTON_URL;
|
||||||
|
var params = { "lon" => _lon, "lat" => _lat, "limit" => 1 };
|
||||||
|
var options = {
|
||||||
|
:method => Communications.HTTP_REQUEST_METHOD_GET,
|
||||||
|
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
|
||||||
|
:headers => { "Accept" => "application/json" }
|
||||||
|
};
|
||||||
|
Communications.makeWebRequest(url, params, options, method(:_onResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _onResponse(responseCode as Number, data as Dictionary or String or Null) as Void {
|
||||||
|
if (responseCode != 200 || data == null || !(data instanceof Dictionary)) {
|
||||||
|
_callback.invoke(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var features = data["features"];
|
||||||
|
if (features == null || !(features instanceof Array) || features.size() == 0) {
|
||||||
|
_callback.invoke(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var feature = features[0] as Dictionary;
|
||||||
|
var props = feature["properties"] as Dictionary;
|
||||||
|
var geometry = feature["geometry"] as Dictionary;
|
||||||
|
|
||||||
|
// Haversine check: only use address if result is within threshold.
|
||||||
|
if (geometry != null) {
|
||||||
|
var coords = geometry["coordinates"] as Array;
|
||||||
|
if (coords != null && coords.size() >= 2) {
|
||||||
|
var rLon = (coords[0] as Double).toFloat();
|
||||||
|
var rLat = (coords[1] as Double).toFloat();
|
||||||
|
var dist = Haversine.distance(_lat, _lon, rLat, rLon);
|
||||||
|
if (dist > Config.ADDRESS_MAX_DISTANCE_M) {
|
||||||
|
_callback.invoke(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_callback.invoke(_formatAddress(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _formatAddress(props as Dictionary) as String {
|
||||||
|
var street = props["street"];
|
||||||
|
var number = props["housenumber"];
|
||||||
|
var city = props["city"];
|
||||||
|
|
||||||
|
var parts = "";
|
||||||
|
if (street != null) {
|
||||||
|
parts = street as String;
|
||||||
|
if (number != null) {
|
||||||
|
parts = parts + " " + number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (city != null) {
|
||||||
|
if (!parts.equals("")) {
|
||||||
|
parts = parts + ", ";
|
||||||
|
}
|
||||||
|
parts = parts + city;
|
||||||
|
}
|
||||||
|
return parts.equals("") ? "Unbekannt" : parts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,21 +2,22 @@ import Toybox.Lang;
|
||||||
import Toybox.Position;
|
import Toybox.Position;
|
||||||
import Toybox.Timer;
|
import Toybox.Timer;
|
||||||
|
|
||||||
// Single-shot GPS acquisition with a hard timeout. Caller supplies
|
// Single-shot GPS acquisition with a hard timeout. Uses both
|
||||||
// a callback that fires exactly once with either a position fix or
|
// enableLocationEvents (fires on real hardware) and a polling
|
||||||
// null (if nothing usable was received before the timeout). Accuracy
|
// fallback via Position.getInfo() (works in the simulator where
|
||||||
// shortcut: if the fix already hits Config.GPS_TARGET_ACCURACY_M we
|
// "Set Position" doesn't trigger location events).
|
||||||
// stop waiting even if the timeout hasn't expired.
|
|
||||||
class GpsService {
|
class GpsService {
|
||||||
|
|
||||||
private var _callback as Method(result as Dictionary or Null) as Void;
|
private var _callback as Method(result as Dictionary or Null) as Void;
|
||||||
private var _timer as Timer.Timer;
|
private var _timer as Timer.Timer;
|
||||||
|
private var _pollTimer as Timer.Timer;
|
||||||
private var _finished as Boolean = false;
|
private var _finished as Boolean = false;
|
||||||
private var _bestFix as Dictionary or Null = null;
|
private var _bestFix as Dictionary or Null = null;
|
||||||
|
|
||||||
function initialize(callback as Method(result as Dictionary or Null) as Void) {
|
function initialize(callback as Method(result as Dictionary or Null) as Void) {
|
||||||
_callback = callback;
|
_callback = callback;
|
||||||
_timer = new Timer.Timer();
|
_timer = new Timer.Timer();
|
||||||
|
_pollTimer = new Timer.Timer();
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() as Void {
|
function start() as Void {
|
||||||
|
|
@ -24,20 +25,34 @@ class GpsService {
|
||||||
Position.LOCATION_CONTINUOUS,
|
Position.LOCATION_CONTINUOUS,
|
||||||
method(:_onPosition)
|
method(:_onPosition)
|
||||||
);
|
);
|
||||||
|
// Poll Position.getInfo() every 500ms as fallback for simulator.
|
||||||
|
_pollTimer.start(method(:_poll), 500, true);
|
||||||
_timer.start(method(:_onTimeout), Config.GPS_TIMEOUT_MS, false);
|
_timer.start(method(:_onTimeout), Config.GPS_TIMEOUT_MS, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _poll() as Void {
|
||||||
|
if (_finished) { return; }
|
||||||
|
var info = Position.getInfo();
|
||||||
|
if (info != null && info.position != null) {
|
||||||
|
_processInfo(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _onPosition(info as Position.Info) as Void {
|
function _onPosition(info as Position.Info) as Void {
|
||||||
if (_finished) { return; }
|
if (_finished) { return; }
|
||||||
|
_processInfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _processInfo(info as Position.Info) as Void {
|
||||||
if (info == null || info.position == null) { return; }
|
if (info == null || info.position == null) { return; }
|
||||||
|
|
||||||
var degrees = info.position.toDegrees();
|
var degrees = info.position.toDegrees();
|
||||||
var acc = (info.accuracy != null) ? info.accuracy.toFloat() : null;
|
var acc = (info.accuracy != null) ? info.accuracy : null;
|
||||||
|
|
||||||
_bestFix = {
|
_bestFix = {
|
||||||
"lat" => degrees[0].toFloat(),
|
"lat" => degrees[0].toFloat(),
|
||||||
"lon" => degrees[1].toFloat(),
|
"lon" => degrees[1].toFloat(),
|
||||||
"acc" => acc
|
"acc" => (acc != null) ? acc.toFloat() : null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Good enough → stop early.
|
// Good enough → stop early.
|
||||||
|
|
@ -54,6 +69,7 @@ class GpsService {
|
||||||
if (_finished) { return; }
|
if (_finished) { return; }
|
||||||
_finished = true;
|
_finished = true;
|
||||||
_timer.stop();
|
_timer.stop();
|
||||||
|
_pollTimer.stop();
|
||||||
Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:_onPosition));
|
Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:_onPosition));
|
||||||
_callback.invoke(result);
|
_callback.invoke(result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
source/HistoryDelegate.mc
Normal file
29
source/HistoryDelegate.mc
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import Toybox.Lang;
|
||||||
|
import Toybox.WatchUi;
|
||||||
|
|
||||||
|
// Input handler for HistoryView. UP/DOWN scroll through events,
|
||||||
|
// BACK returns to the menu.
|
||||||
|
class HistoryDelegate extends WatchUi.BehaviorDelegate {
|
||||||
|
|
||||||
|
private var _view as HistoryView;
|
||||||
|
|
||||||
|
function initialize(view as HistoryView) {
|
||||||
|
BehaviorDelegate.initialize();
|
||||||
|
_view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNextPage() as Boolean {
|
||||||
|
_view.showNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPreviousPage() as Boolean {
|
||||||
|
_view.showPrev();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBack() as Boolean {
|
||||||
|
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
226
source/HistoryView.mc
Normal file
226
source/HistoryView.mc
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
import Toybox.Graphics;
|
||||||
|
import Toybox.Lang;
|
||||||
|
import Toybox.Time;
|
||||||
|
import Toybox.Time.Gregorian;
|
||||||
|
import Toybox.WatchUi;
|
||||||
|
|
||||||
|
// Scrollable history of recorded events. 3-section layout:
|
||||||
|
// top 15%: previous entry icon + up arrow
|
||||||
|
// mid 70%: current entry details (type, datetime, address/coords)
|
||||||
|
// bot 15%: next entry icon + down arrow
|
||||||
|
class HistoryView extends WatchUi.View {
|
||||||
|
|
||||||
|
private var _events as Array<Event>;
|
||||||
|
private var _index as Number = 0;
|
||||||
|
private var _bitmaps as Dictionary = {};
|
||||||
|
private var _addressCache as Dictionary = {}; // index → String or Null
|
||||||
|
private var _addressLoading as Boolean = false;
|
||||||
|
private var _geocoder as GeocodingService or Null = null;
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
View.initialize();
|
||||||
|
_events = EventStore.getAll();
|
||||||
|
// Start at newest entry.
|
||||||
|
if (_events.size() > 0) {
|
||||||
|
_index = _events.size() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLayout(dc as Dc) as Void {
|
||||||
|
var items = Config.menuItems();
|
||||||
|
for (var i = 0; i < items.size(); i++) {
|
||||||
|
var iconId = items[i][:icon];
|
||||||
|
_bitmaps[items[i][:key]] = WatchUi.loadResource(iconId) as BitmapResource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventCount() as Number {
|
||||||
|
return _events.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNext() as Void {
|
||||||
|
if (_events.size() == 0) { return; }
|
||||||
|
_index = (_index + 1) % _events.size();
|
||||||
|
_addressLoading = false;
|
||||||
|
_requestAddress();
|
||||||
|
WatchUi.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPrev() as Void {
|
||||||
|
if (_events.size() == 0) { return; }
|
||||||
|
_index = (_index - 1 + _events.size()) % _events.size();
|
||||||
|
_addressLoading = false;
|
||||||
|
_requestAddress();
|
||||||
|
WatchUi.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShow() as Void {
|
||||||
|
_requestAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _requestAddress() as Void {
|
||||||
|
if (_events.size() == 0) { return; }
|
||||||
|
if (_addressCache.hasKey(_index)) { return; }
|
||||||
|
var evt = _events[_index];
|
||||||
|
if (evt.lat == null || evt.lon == null) { return; }
|
||||||
|
_addressLoading = true;
|
||||||
|
_geocoder = new GeocodingService(
|
||||||
|
evt.lat as Float, evt.lon as Float,
|
||||||
|
method(:_onAddress)
|
||||||
|
);
|
||||||
|
_geocoder.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _onAddress(address as String or Null) as Void {
|
||||||
|
_addressCache[_index] = address;
|
||||||
|
_addressLoading = false;
|
||||||
|
WatchUi.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpdate(dc as Dc) as Void {
|
||||||
|
dc.setColor(Config.COLOR_FG, Config.COLOR_BG);
|
||||||
|
dc.clear();
|
||||||
|
|
||||||
|
if (_events.size() == 0) {
|
||||||
|
var cx = LayoutMetrics.centerX(dc);
|
||||||
|
var cy = LayoutMetrics.centerY(dc);
|
||||||
|
TextUtils.drawResourceCentered(dc, Rez.Strings.history_empty, cx, cy, Graphics.FONT_MEDIUM);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cx = LayoutMetrics.centerX(dc);
|
||||||
|
var topH = LayoutMetrics.topSectionHeight(dc);
|
||||||
|
var botY = LayoutMetrics.bottomSectionY(dc);
|
||||||
|
var screenH = dc.getHeight();
|
||||||
|
|
||||||
|
_drawTopSection(dc, cx, topH);
|
||||||
|
_drawMiddleSection(dc, cx, topH, botY);
|
||||||
|
_drawBottomSection(dc, cx, botY, screenH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _drawTopSection(dc as Dc, cx as Number, topH as Number) as Void {
|
||||||
|
if (_events.size() <= 1) { return; }
|
||||||
|
var midY = topH / 2;
|
||||||
|
var arrowSize = (topH * 0.2).toNumber();
|
||||||
|
dc.setColor(Config.COLOR_FG, Config.COLOR_BG);
|
||||||
|
dc.setPenWidth(2);
|
||||||
|
dc.drawLine(cx, midY - arrowSize, cx, midY + arrowSize);
|
||||||
|
dc.drawLine(cx - arrowSize, midY, cx, midY - arrowSize);
|
||||||
|
dc.drawLine(cx + arrowSize, midY, cx, midY - arrowSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _drawBottomSection(dc as Dc, cx as Number, botY as Number, screenH as Number) as Void {
|
||||||
|
if (_events.size() <= 1) { return; }
|
||||||
|
var sectionH = screenH - botY;
|
||||||
|
var midY = botY + sectionH / 2;
|
||||||
|
var arrowSize = (sectionH * 0.2).toNumber();
|
||||||
|
dc.setColor(Config.COLOR_FG, Config.COLOR_BG);
|
||||||
|
dc.setPenWidth(2);
|
||||||
|
dc.drawLine(cx, midY - arrowSize, cx, midY + arrowSize);
|
||||||
|
dc.drawLine(cx - arrowSize, midY, cx, midY + arrowSize);
|
||||||
|
dc.drawLine(cx + arrowSize, midY, cx, midY + arrowSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Address / coordinates / status.
|
||||||
|
var locStr = _locationString(evt);
|
||||||
|
dc.drawText(cx, midY + lineH / 2, fontDetail, locStr,
|
||||||
|
Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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];
|
||||||
|
if (cached != null) {
|
||||||
|
return cached as String;
|
||||||
|
}
|
||||||
|
return _formatCoords(evt.lat as Float, evt.lon as Float);
|
||||||
|
}
|
||||||
|
if (_addressLoading) {
|
||||||
|
return WatchUi.loadResource(Rez.Strings.history_loading_address) as String;
|
||||||
|
}
|
||||||
|
return _formatCoords(evt.lat as Float, evt.lon as Float);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _formatCoords(lat as Float, lon as Float) as String {
|
||||||
|
return lat.format("%.4f") + ", " + lon.format("%.4f");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _formatTimestamp(ts as Number) as String {
|
||||||
|
var moment = new Time.Moment(ts);
|
||||||
|
var info = Gregorian.info(moment, Time.FORMAT_SHORT);
|
||||||
|
return Lang.format("$1$.$2$.$3$ $4$:$5$", [
|
||||||
|
info.day.format("%02d"),
|
||||||
|
info.month.format("%02d"),
|
||||||
|
info.year,
|
||||||
|
info.hour.format("%02d"),
|
||||||
|
info.min.format("%02d")
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _eventLabel(key as String) as String {
|
||||||
|
var items = Config.menuItems();
|
||||||
|
for (var i = 0; i < items.size(); i++) {
|
||||||
|
if ((items[i][:key] as String).equals(key)) {
|
||||||
|
var lines = items[i][:lines] as Array<String>;
|
||||||
|
var result = "";
|
||||||
|
for (var j = 0; j < lines.size(); j++) {
|
||||||
|
if (j > 0) { result += ""; }
|
||||||
|
result += lines[j];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _eventColor(key as String) as Number {
|
||||||
|
var items = Config.menuItems();
|
||||||
|
for (var i = 0; i < items.size(); i++) {
|
||||||
|
if ((items[i][:key] as String).equals(key)) {
|
||||||
|
return items[i][:color] as Number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Config.COLOR_FG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _drawEventIcon(dc as Dc, key as String, cx as Number, cy as Number, size as Number) as Void {
|
||||||
|
var bmp = _bitmaps[key] as BitmapResource or Null;
|
||||||
|
if (bmp == null) { return; }
|
||||||
|
var bmpW = bmp.getWidth();
|
||||||
|
var scale = size.toFloat() / bmpW.toFloat();
|
||||||
|
dc.drawBitmap2(
|
||||||
|
cx - size / 2, cy - size / 2,
|
||||||
|
bmp,
|
||||||
|
{ :scaleX => scale, :scaleY => scale }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,8 @@ class MenuDelegate extends WatchUi.BehaviorDelegate {
|
||||||
var key = item[:key] as String;
|
var key = item[:key] as String;
|
||||||
|
|
||||||
if (key.equals(Config.ACTION_HISTORY)) {
|
if (key.equals(Config.ACTION_HISTORY)) {
|
||||||
// Phase 4
|
var view = new HistoryView();
|
||||||
|
WatchUi.pushView(view, new HistoryDelegate(view), WatchUi.SLIDE_LEFT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (key.equals(Config.ACTION_DELETE)) {
|
if (key.equals(Config.ACTION_DELETE)) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue