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] = evt.address; 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(address as String or Null) as Void { _addressCache[_index] = address; _addressLoading = false; // Persist address in the event so the glance can show it. if (address != null && _index < _events.size()) { var evt = _events[_index]; evt.address = address; 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 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; 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 } ); } }