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; 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]; // Use cached address from event if available. if (evt.address != null) { _addressCache[_index] = { "addr" => evt.address, "zip" => evt.zip }; return; } 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(result as Dictionary or Null) as Void { _addressCache[_index] = result; _addressLoading = false; // Persist in the event so the glance can show it. if (result != null && _index < _events.size()) { var evt = _events[_index]; evt.address = result["addr"] as String or Null; evt.zip = result["zip"] as String or Null; EventStore.updateAt(_index, evt); } 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 fontDetail = Graphics.FONT_XTINY; var lineH = dc.getFontHeight(fontDetail); var sectionMid = topH + (botY - topH) / 2; // Collect lines to draw. var lines = [] as Array; // [text, color] // 1: Event type (colored). lines.add([_eventLabel(evt.type), _eventColor(evt.type)]); // 2: Date + time. lines.add([_formatTimestamp(evt.timestamp), Config.COLOR_FG]); // 3: Address or status. if (evt.lat == null || evt.lon == null) { 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) { lines.add([cached["addr"] as String, Config.COLOR_FG]); var zip = cached["zip"] as String or Null; if (zip != null) { lines.add([zip, Config.COLOR_FG]); } } } else if (_addressLoading) { lines.add([WatchUi.loadResource(Rez.Strings.history_loading_address) as String, 0x888888]); } // 4: Coordinates (always, if available). if (evt.lat != null && evt.lon != null) { lines.add([_formatCoords(evt.lat as Float, evt.lon as Float), Config.COLOR_FG]); } // 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); } } private function _formatCoords(lat as Float, lon as Float) as String { return lat.format("%.5f") + ", " + lon.format("%.5f"); } 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; 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 } ); } }