Skip to content
Permalink
Browse files
implemented choosing a station from the list
  • Loading branch information
mackuba committed Jun 14, 2020
1 parent 8602a58 commit 053e867234f4dc35e0a0cd166f254ac2a41a8bb0
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 12 deletions.
@@ -47,20 +47,68 @@
<items>
<imageView height="65" alignment="left" contentMode="left" id="bQu-Nl-bsS"/>
</items>
<edgeInsets key="margins" left="0.0" right="0.0" top="10" bottom="10"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="15" bottom="15"/>
</group>
<button alignment="left" id="pKa-Cr-HPG">
<group key="contentGroup" width="1" alignment="left" layout="vertical" id="XFM-mB-b7b">
<items>
<label alignment="left" text="Choose Station" id="19e-hf-Hcc"/>
<label height="17" alignment="left" text="Kraków, os. Swoszowice" textAlignment="left" minimumScaleFactor="0.90000000000000002" id="gaz-4U-Tj6">
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="font" style="UICTFontTextStyleFootnote"/>
</label>
</items>
<color key="backgroundColor" red="0.13336184620857239" green="0.13326132297515869" blue="0.13725566864013672" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
<edgeInsets key="margins" left="10" right="8" top="8" bottom="8"/>
</group>
<connections>
<segue destination="tQh-fT-tIc" kind="push" identifier="ChooseStation" id="NlC-dn-GDH"/>
</connections>
</button>
</items>
<connections>
<outlet property="chartView" destination="bQu-Nl-bsS" id="jQt-Hs-bWh"/>
<outlet property="gradeLabel" destination="fk1-AV-eHG" id="lhV-1z-dsp"/>
<outlet property="stationNameLabel" destination="gaz-4U-Tj6" id="aqh-ge-oLt"/>
<outlet property="updatedAtLabel" destination="aqO-eg-dhr" id="hNu-jD-mZz"/>
<outlet property="updatedAtRow" destination="9Z0-zI-ern" id="Dja-de-cL0"/>
<outlet property="valueCircle" destination="d9b-ve-QA0" id="bzd-xg-DFl"/>
<outlet property="valueLabel" destination="PDF-fB-VzR" id="dGE-oS-Wgn"/>
</connections>
</controller>
</objects>
<point key="canvasLocation" x="220" y="345"/>
<point key="canvasLocation" x="219.69230769230771" y="344.79487179487182"/>
</scene>
<!--Station-->
<scene sceneID="bJN-ig-kV3">
<objects>
<controller title="Station" id="tQh-fT-tIc" customClass="SelectionListController" customModule="SmogWatch_WatchKit_Extension">
<items>
<table alignment="left" id="kaT-KH-jqX">
<items>
<tableRow identifier="SelectionListRow" id="tDM-gg-ZG9" customClass="SelectionListRow" customModule="SmogWatch_WatchKit_Extension">
<group key="rootItem" width="1" alignment="left" id="62w-0c-mpC">
<items>
<label alignment="left" verticalAlignment="center" text="Kraków, os. Swoszowice" minimumScaleFactor="0.80000000000000004" id="eF5-tx-Nex"/>
<label width="15" alignment="right" verticalAlignment="center" hidden="YES" text="" textAlignment="right" id="g5D-7R-GxR">
<color key="textColor" red="0.39291518926620483" green="0.85138219594955444" blue="0.48621195554733276" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
</label>
</items>
</group>
<connections>
<outlet property="checkmark" destination="g5D-7R-GxR" id="gD1-CC-12d"/>
<outlet property="titleLabel" destination="eF5-tx-Nex" id="cpC-PO-bS9"/>
</connections>
</tableRow>
</items>
</table>
</items>
<connections>
<outlet property="table" destination="kaT-KH-jqX" id="AlH-bx-VSt"/>
</connections>
</controller>
</objects>
<point key="canvasLocation" x="534" y="345"/>
</scene>
</scenes>
</document>
@@ -10,6 +10,7 @@ import Foundation

private let savedPointsKey = "SavedPoints"
private let lastUpdateDateKey = "LastUpdateDate"
private let selectedChannelIdKey = "SelectedChannelId"

private let pointsCount = 8

@@ -18,11 +19,37 @@ struct DataPoint {
let value: Double
}

struct Station: Codable {
let channelId: Int
let name: String
let lat: Double
let lng: Double
}

class DataStore {
let defaults = UserDefaults.standard

static let dataLoadedNotification = Notification.Name("DataLoadedNotification")

lazy var stations: [Station] = loadStations()

var selectedChannelId: Int? {
get {
return defaults.object(forKey: selectedChannelIdKey) as? Int
}
set {
if newValue != selectedChannelId {
defaults.set(newValue, forKey: selectedChannelIdKey)
invalidateData()
}
}
}

func invalidateData() {
defaults.removeObject(forKey: savedPointsKey)
defaults.removeObject(forKey: lastUpdateDateKey)
}

var currentLevel: Double? {
get {
return points.last?.value
@@ -72,4 +99,11 @@ class DataStore {

defaults.set(encodedData, forKey: savedPointsKey)
}

func loadStations() -> [Station] {
let stationsFile = Bundle.main.url(forResource: "Stations", withExtension: "plist")!
let data = try! Data(contentsOf: stationsFile)

return try! PropertyListDecoder().decode([Station].self, from: data)
}
}
@@ -18,6 +18,7 @@ class InterfaceController: WKInterfaceController {
@IBOutlet var updatedAtLabel: WKInterfaceLabel!
@IBOutlet var updatedAtRow: WKInterfaceGroup!
@IBOutlet var chartView: WKInterfaceImage!
@IBOutlet var stationNameLabel: WKInterfaceLabel!

enum TextAlignment {
case left, right, center
@@ -31,17 +32,16 @@ class InterfaceController: WKInterfaceController {

let leftChartMargin: CGFloat = 17
let bottomChartMargin: CGFloat = 10
let rightMargin: CGFloat = 4
let rightMargin: CGFloat = 10

let chartFontAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.lightGray,
.font: UIFont.systemFont(ofSize: 8.0)
]

override func awake(withContext context: Any?) {
super.awake(withContext: context)

updateDisplayedData()
updateStationInfo()

NotificationCenter.default.addObserver(forName: DataStore.dataLoadedNotification, object: nil, queue: nil) { _ in
self.updateDisplayedData()
@@ -80,6 +80,16 @@ class InterfaceController: WKInterfaceController {
}
}

func updateStationInfo() {
let channelId = dataStore.selectedChannelId

if channelId != nil, let station = dataStore.stations.first(where: { $0.channelId == channelId }) {
stationNameLabel.setText(station.name)
} else {
stationNameLabel.setText("not selected")
}
}

func isSameDay(_ date: Date) -> Bool {
let calendar = Calendar.current

@@ -100,7 +110,7 @@ class InterfaceController: WKInterfaceController {
context.setStrokeColor(UIColor.lightGray.cgColor)
context.move(to: CGPoint(x: leftChartMargin, y: 0))
context.addLine(to: CGPoint(x: leftChartMargin, y: height - bottomChartMargin))
context.addLine(to: CGPoint(x: width, y: height - bottomChartMargin))
context.addLine(to: CGPoint(x: width - rightMargin + 2, y: height - bottomChartMargin))
context.drawPath(using: .stroke)

let values = points.map { $0.value }
@@ -184,4 +194,29 @@ class InterfaceController: WKInterfaceController {
func hour(for point: DataPoint) -> Int {
return Calendar.current.component(.hour, from: point.date)
}

func setSelectedStation(_ station: Station) {
dataStore.selectedChannelId = station.channelId
stationNameLabel.setText(station.name)

updateDisplayedData()

KrakowPiosDataLoader().fetchData { success in
self.updateDisplayedData()
}
}

override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
if segueIdentifier == "ChooseStation" {
return SelectionListContext(
items: dataStore.stations.sorted(by: { $0.name < $1.name }),
selectedId: dataStore.selectedChannelId,
onSelect: { station in
self.setSelectedStation(station)
}
)
}

return nil
}
}
@@ -9,7 +9,7 @@
import Foundation
import WatchKit

private let DataURL = "http://monitoring.krakow.pios.gov.pl/dane-pomiarowe/pobierz"
private let dataURL = "http://monitoring.krakow.pios.gov.pl/dane-pomiarowe/pobierz"

private struct Response: Decodable {
let data: ResponseData
@@ -68,7 +68,7 @@ class KrakowPiosDataLoader {

let dataStore = DataStore()

func queryString() -> String {
func queryString(channelId: Int) -> String {
// data is usually around one hour behind, so at midnight we need to ask for the previous day
let oneHourAgo = Calendar(identifier: .gregorian).date(byAdding: .hour, value: -1, to: Date())!

@@ -78,7 +78,7 @@ class KrakowPiosDataLoader {
"dateRange": "Day",
"date": dateFormatter.string(from: oneHourAgo),
"viewTypeEntityId": "pm10",
"channels": [148]
"channels": [channelId]
]

let jsonData = try! JSONSerialization.data(withJSONObject: query, options: [])
@@ -88,12 +88,19 @@ class KrakowPiosDataLoader {
}

func fetchData(_ completion: @escaping (Bool) -> ()) {
var request = URLRequest(url: URL(string: DataURL)!)
request.httpBody = queryString().data(using: .utf8)!
guard let channelId = dataStore.selectedChannelId else {
NSLog("KrakowPiosDataLoader: no channel selected")
completion(false)
return
}

let query = queryString(channelId: channelId)
var request = URLRequest(url: URL(string: dataURL)!)
request.httpBody = query.data(using: .utf8)!
request.httpMethod = "POST"

NSLog("KrakowPiosDataLoader: sending request [state: %@] to %@ with %@ ...",
WKExtension.shared().applicationState.description, DataURL, queryString())
WKExtension.shared().applicationState.description, dataURL, query)

let task = session.dataTask(with: request) { (data, response, error) in
var success = false
@@ -0,0 +1,59 @@
//
// SelectionListController.swift
// SmogWatch WatchKit Extension
//
// Created by Kuba Suder on 13.06.2020.
// Copyright © 2020 Kuba Suder. Licensed under WTFPL license.
//
import WatchKit

struct SelectionListContext {
let items: [Station]
let selectedId: Int?
let onSelect: ((Station) -> ())
}

class SelectionListController: WKInterfaceController {
@IBOutlet weak var table: WKInterfaceTable!

var selectedRowIndex: Int? = nil
var items: [Station] = []
var selectionHandler: ((Station) -> ())?

override func awake(withContext context: Any?) {
let context = context as! SelectionListContext

items = context.items
selectionHandler = context.onSelect

table.setNumberOfRows(items.count, withRowType: "SelectionListRow")

for i in 0..<items.count {
let row = table.rowController(at: i) as! SelectionListRow
row.setTitle(items[i].name)
}

if context.selectedId != nil {
if let index = items.firstIndex(where: { $0.channelId == context.selectedId }) {
selectedRowIndex = index
listRowController(at: index).setCheckmarkVisible(true)
}
}
}

override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
if let previous = selectedRowIndex, previous != rowIndex {
listRowController(at: previous).setCheckmarkVisible(false)
}

listRowController(at: rowIndex).setCheckmarkVisible(true)
selectedRowIndex = rowIndex

selectionHandler?(items[rowIndex])
}

func listRowController(at index: Int) -> SelectionListRow {
return table.rowController(at: index) as! SelectionListRow
}
}
@@ -0,0 +1,22 @@
//
// SelectionListRow.swift
// SmogWatch WatchKit Extension
//
// Created by Kuba Suder on 13.06.2020.
// Copyright © 2020 Kuba Suder. Licensed under WTFPL license.
//
import WatchKit

class SelectionListRow: NSObject {
@IBOutlet weak var titleLabel: WKInterfaceLabel!
@IBOutlet weak var checkmark: WKInterfaceLabel!

func setTitle(_ title: String) {
titleLabel.setText(title)
}

func setCheckmarkVisible(_ visible: Bool) {
checkmark.setHidden(!visible)
}
}
@@ -25,6 +25,8 @@
8C2A472621CEF41B0029E4BC /* KrakowPiosDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2A472521CEF41B0029E4BC /* KrakowPiosDataLoader.swift */; };
8C4FAC2922012BBE001D84C6 /* WatchKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4FAC2822012BBE001D84C6 /* WatchKitExtensions.swift */; };
8CD32618249291EB0090D0E1 /* SmogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD32617249291EA0090D0E1 /* SmogLevel.swift */; };
8CD32692249543450090D0E1 /* SelectionListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD32691249543450090D0E1 /* SelectionListRow.swift */; };
8CD32694249543DE0090D0E1 /* SelectionListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD32693249543DE0090D0E1 /* SelectionListController.swift */; };
8CD3269B249690500090D0E1 /* Stations.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8CD3269A249690500090D0E1 /* Stations.plist */; };
8CDC741D21D5681100D170D0 /* ComplicationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CDC741C21D5681100D170D0 /* ComplicationHandler.swift */; };
/* End PBXBuildFile section */
@@ -96,6 +98,8 @@
8C2A472521CEF41B0029E4BC /* KrakowPiosDataLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KrakowPiosDataLoader.swift; sourceTree = "<group>"; };
8C4FAC2822012BBE001D84C6 /* WatchKitExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchKitExtensions.swift; sourceTree = "<group>"; };
8CD32617249291EA0090D0E1 /* SmogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmogLevel.swift; sourceTree = "<group>"; };
8CD32691249543450090D0E1 /* SelectionListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionListRow.swift; sourceTree = "<group>"; };
8CD32693249543DE0090D0E1 /* SelectionListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionListController.swift; sourceTree = "<group>"; };
8CD3269824964B6A0090D0E1 /* import_channels.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = import_channels.rb; sourceTree = "<group>"; };
8CD3269A249690500090D0E1 /* Stations.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Stations.plist; sourceTree = "<group>"; };
8CDC741C21D5681100D170D0 /* ComplicationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplicationHandler.swift; sourceTree = "<group>"; };
@@ -177,6 +181,8 @@
8C2A472521CEF41B0029E4BC /* KrakowPiosDataLoader.swift */,
8C2A470421C5523C0029E4BC /* NotificationController.swift */,
8C2A470B21C5523C0029E4BC /* PushNotificationPayload.apns */,
8CD32693249543DE0090D0E1 /* SelectionListController.swift */,
8CD32691249543450090D0E1 /* SelectionListRow.swift */,
8CD32617249291EA0090D0E1 /* SmogLevel.swift */,
8CD3269A249690500090D0E1 /* Stations.plist */,
8C4FAC2822012BBE001D84C6 /* WatchKitExtensions.swift */,
@@ -340,7 +346,9 @@
8C2A470721C5523C0029E4BC /* ComplicationController.swift in Sources */,
8C4FAC2922012BBE001D84C6 /* WatchKitExtensions.swift in Sources */,
8C2A470321C5523C0029E4BC /* ExtensionDelegate.swift in Sources */,
8CD32694249543DE0090D0E1 /* SelectionListController.swift in Sources */,
8C2A472421CEEAAA0029E4BC /* DataStore.swift in Sources */,
8CD32692249543450090D0E1 /* SelectionListRow.swift in Sources */,
8CDC741D21D5681100D170D0 /* ComplicationHandler.swift in Sources */,
8C2A472621CEF41B0029E4BC /* KrakowPiosDataLoader.swift in Sources */,
8C2A470121C5523C0029E4BC /* InterfaceController.swift in Sources */,

0 comments on commit 053e867

Please sign in to comment.