firstPage = (MainPage)PageFactory.GetInstance(Pages.StandBy);
MainPage = new NavigationPage(firstPage);
mainPageModel = (MainPageModel)firstPage.BindingContext;
-
- //firstPageCS = (MainPageCS)PageFactory.GetInstance(Pages.StandByCS);
- //MainPage = new NavigationPage(firstPageCS);
-
- //mainPageModel = (MainPageModel)firstPageCS.BindingContext;
}
// database for voice records
case RemainingTimeType.TimeText:
int minutes = remains / 60000;
int seconds = (remains - minutes * 60000) / 1000;
- //Console.WriteLine("[DurationToRemainingTimeConverter - TimeText] remaining time : " + remains);
- // return the remaining time, formatted as {minutes}:{seconds}
return String.Format("{0:00}:{1:00}", minutes, seconds);
default:
return null;
{
int RecordsCnt = System.Convert.ToInt32(value);
ViewType viewType = (ViewType)parameter;
- //Console.WriteLine("[RecordsCountToViewVisibilityConverter] RecordsCount = " + RecordsCnt + ", viewType :" + viewType);
switch (viewType)
{
{
bool isSttOn = (bool)System.Convert.ToBoolean(value);
SttPropertyType type = (SttPropertyType)parameter;
- //Console.WriteLine("[SttToPropertyConverter] isSttOn: " + isSttOn + ", type:" + type);
switch (type)
{
case SttPropertyType.TextString:
bool changed = SetProperty(ref _Checked, value, "Checked");
if (changed)
{
- //Console.WriteLine("Record.Checked : " + Checked + " --> CheckedNamesCount changed..");
((App)App.Current).mainPageModel.CheckedNamesCount += Checked ? 1 : -1;
}
}
public GraphicPopUpRenderer()
{
- Console.WriteLine("GraphicPopUpRenderer() GetHashCode:" + this.GetHashCode());
_popUp = new Popup(TForms.NativeParent)
{
Style = "toast/circle",
private void _popUp_Dismissed(object sender, EventArgs e)
{
- Console.WriteLine("[GraphicPopUpRenderer._popUp_Dismissed] ");
}
private void TimedOutHandler(object sender, EventArgs e)
{
- Console.WriteLine("[GraphicPopUpRenderer.TimedOutHandler] ");
TimedOut?.Invoke(this, EventArgs.Empty);
_popUp.Dismiss();
}
private void BackButtonPressedHandler(object sender, EventArgs e)
{
- Console.WriteLine("[GraphicPopUpRenderer.BackButtonPressedHandler] ");
BackButtonPressed?.Invoke(this, EventArgs.Empty);
_popUp.Dismiss();
}
if (_Text != value)
{
_Text = value;
- Console.WriteLine("[GraphicPopUpRenderer--Update] Text (" + Text + ")");
_popUp.SetPartText("elm.text", Text);
}
}
if (_Duration != value)
{
_Duration = value;
- Console.WriteLine("[GraphicPopUpRenderer--Update] Duration (" + Duration + ")");
_popUp.Timeout = Duration;
}
}
return;
}
- Console.WriteLine("[GraphicPopUpRenderer.Dispose] ");
if (disposing)
{
if (_popUp != null)
public void Show()
{
- Console.WriteLine("[GraphicPopUpRenderer.Show] ");
_popUp.Show();
}
}
private void _popUp_ShowAnimationFinished(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer._popUp_ShowAnimationFinished] ");
}
private void _popUp_Resized(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer._popUp_Resized] ");
}
private void _popUp_RenderPost(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer._popUp_RenderPost] ");
}
private void _popUp_Moved(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer._popUp_Moved] ");
}
private void _popUp2_Dismissed(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer._popUp2_Dismissed] ");
}
private void _popUp_Dismissed(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer._popUp_Dismissed] ");
}
/// <summary>
/// <param name="e">EventArgs</param>
private void _progressbar_Deleted(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer._progressbar_Deleted] ");
_popUp2.Show();
}
private void ProgressbarPopup_TimedOutHandler(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer.ProgressbarPopup_TimedOutHandler] ");
// Remove Progressbar from Popup when doing progressbar is done
_popUp.SetPartContent("elm.swallow.progress", null);
}
private void TimedOutHandler(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer.TimedOutHandler] 0");
TimedOut?.Invoke(this, EventArgs.Empty);
_popUp.Dismiss();
}
private void BackButtonPressedHandler(object sender, EventArgs e)
{
- Console.WriteLine("[ProgressbarPopupRenderer.BackButtonPressedHandler] ");
BackButtonPressed?.Invoke(this, EventArgs.Empty);
_popUp.Dismiss();
}
if (_Text != value)
{
_Text = value;
- Console.WriteLine("[Update] Text (" + _Text + ")");
_popUp2.SetPartText("elm.text", _Text);
}
}
public MainPage()
{
InitializeComponent();
- Color stopIconColor = ColorConverter.OneUIColorConverter(new HSB(0, 98, 88, 100), null);
- ImageAttributes.SetBlendColor(VoiceRecorderIconImage, stopIconColor);
+ // Add tapGesture recognizer
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += OnImageButtonClicked;
VoiceRecorderIconImage.GestureRecognizers.Add(tapGestureRecognizer);
+ // TODO(vincent): blinking when red dot appearing.
+ Color stopIconColor = ColorConverter.OneUIColorConverter(new HSB(0, 98, 88, 100), null);
+ ImageAttributes.SetBlendColor(VoiceRecorderIconImage, stopIconColor);
}
/// <summary>
--- /dev/null
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2017 Samsung
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
--- /dev/null
+# Weather
+
+This is a wearable port of [Weather](../../Mobile/Weather/) mobile app.
+
+Weather is a sample application that demonstrates how to obtain data provided by the RESTful API using the [OpenWeatherMap](<https://openweathermap.org/>) API.
+
+![MainPage1](./Screenshots/weatherApp_screen01.png)
+![MainPage2](./Screenshots/weatherApp_screen02.png)
+![MainPage3](./Screenshots/weatherApp_screen03.png)
+![CurrentWeatherPage](./Screenshots/weatherApp_screen04.png)
+![ForecastPage](./Screenshots/weatherApp_screen05.png)
+
+### Features
+* Checking current weather (sample includes some US cities, check included [json files](./Weather/Data/) for details).
+* Checking forecast for next 5 days.
+
+### Prerequisites
+* [Visual Studio](https://www.visualstudio.com/) - Buildtool, IDE
+* [Visual Studio Tools for Tizen](https://developer.tizen.org/development/tizen-.net-preview/visual-studio-tools-tizen) - Visual Studio plugin for Tizen .NET application development
+* [Tizen CircularUI](https://samsung.github.io/Tizen.CircularUI/guide/Quickstart.html)
+
+### Author
+* Original mobile app was created by Michał Kołodziejski
+* Port to wearable by [@Piotr12](https://github.com/Piotr12)
--- /dev/null
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27703.2026
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Weather", "Weather\Weather.csproj", "{E6312AF9-328D-4A17-9677-1341036CB73A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E6312AF9-328D-4A17-9677-1341036CB73A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E6312AF9-328D-4A17-9677-1341036CB73A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E6312AF9-328D-4A17-9677-1341036CB73A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E6312AF9-328D-4A17-9677-1341036CB73A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {86318113-EF47-4428-AF81-F34D84489808}
+ EndGlobalSection
+EndGlobal
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Weather.Config;
+using Weather.Views;
+using Xamarin.Forms;
+
+namespace Weather
+{
+ /// <summary>
+ /// Main App class - derives Xamarin.Forms application functionalities
+ /// </summary>
+ public class App : Application
+ {
+ #region properties
+
+ /// <summary>
+ /// Gets or sets value that indicates if weather and forecast for selected city is initialized.
+ /// </summary>
+ public bool IsInitialized { get; set; }
+
+ #endregion
+
+
+ #region methods
+
+ /// <summary>
+ /// App constructor.
+ /// Makes sure that API Key is defined.
+ /// </summary>
+ public App()
+ {
+ // Its a NavigationPage based app that ...
+ MainPage = new NavigationPage();
+
+ if (!ApiConfig.IsApiKeyDefined())
+ {
+ // either complains about a missing API key ...
+ MainPage.Navigation.PushAsync(new MissingKeyErrorPage(), false);
+ }
+ else
+ {
+ // or gets you started with a MainPage UI.
+ MainPage.Navigation.PushAsync(new MainPage(), false);
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Collections.Generic;
+using System.Linq;
+using Weather.Models.Location;
+using Weather.Service;
+using Weather.Utils;
+using Xamarin.Forms;
+
+namespace Weather.Behaviors
+{
+ /// <summary>
+ /// Behavior class that validates user input in country entry.
+ /// </summary>
+ public class CountryCodeValidatorBehavior : Behavior<Entry>
+ {
+ #region fields
+
+ /// <summary>
+ /// Contains list of all supported countries.
+ /// </summary>
+ private CountryProvider _provider;
+
+ #endregion
+
+ #region properties
+
+ /// <summary>
+ /// Maximum length of user input.
+ /// </summary>
+ public int MaxLength { get; set; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Default class constructor.
+ /// </summary>
+ public CountryCodeValidatorBehavior()
+ {
+ LoadCountryList();
+ }
+
+ /// <summary>
+ /// Called when behavior is attached to entry.
+ /// </summary>
+ /// <param name="bindable">Object to attach behavior.</param>
+ protected override void OnAttachedTo(Entry bindable)
+ {
+ base.OnAttachedTo(bindable);
+ bindable.TextChanged += EntryTextChanged;
+ }
+
+ /// <summary>
+ /// Called when behavior is detached from entry.
+ /// </summary>
+ /// <param name="bindable">Object to detach behavior.</param>
+ protected override void OnDetachingFrom(Entry bindable)
+ {
+ base.OnDetachingFrom(bindable);
+ bindable.TextChanged -= EntryTextChanged;
+ }
+
+ /// <summary>
+ /// Callback method, invoked when entry text is changed.
+ /// Changes user input to uppercase and forbids to enter more than "MaxLength" number of characters.
+ /// </summary>
+ /// <param name="sender">Object that invoked event.</param>
+ /// <param name="textChangedEventArgs">Event arguments.</param>
+ private void EntryTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)
+ {
+ if (!(sender is Entry entry))
+ {
+ return;
+ }
+
+ entry.Text = entry.Text.Length > MaxLength
+ ? textChangedEventArgs.OldTextValue.ToUpper()
+ : textChangedEventArgs.NewTextValue.ToUpper();
+
+ entry.TextColor = _provider.Validate(entry.Text) ? Color.Gray : Color.Red;
+ }
+
+ /// <summary>
+ /// Loads supported city list from file.
+ /// </summary>
+ private void LoadCountryList()
+ {
+ var jsonFileReader = new JsonFileReader<IList<Country>>("Weather.Data.", "country.list.json");
+ jsonFileReader.Read();
+ _provider = new CountryProvider(jsonFileReader.Result.AsQueryable());
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using Xamarin.Forms;
+
+namespace Weather.Behaviors
+{
+ /// <summary>
+ /// Content page behavior that allows to bind command to "Appearing" event.
+ /// </summary>
+ public class CurrentWeatherPageBehavior : Behavior<ContentPage>
+ {
+ #region properties
+
+ /// <summary>
+ /// Bindable property that allows to set command that will be executed during page appearing.
+ /// </summary>
+ public static readonly BindableProperty AppearingCommandProperty =
+ BindableProperty.Create(nameof(AppearingCommand), typeof(Command), typeof(CurrentWeatherPageBehavior),
+ default(Command));
+
+ /// <summary>
+ /// Command that will be executed during page appearing.
+ /// </summary>
+ public Command AppearingCommand
+ {
+ get => (Command)GetValue(AppearingCommandProperty);
+ set => SetValue(AppearingCommandProperty, value);
+ }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Called when behavior is attached to content page.
+ /// </summary>
+ /// <param name="bindable">Object to attach behavior.</param>
+ protected override void OnAttachedTo(ContentPage bindable)
+ {
+ base.OnAttachedTo(bindable);
+ bindable.Appearing += PageOnAppearing;
+ }
+
+ /// <summary>
+ /// Called when behavior is detached from content page.
+ /// </summary>
+ /// <param name="bindable">Object to detach behavior.</param>
+ protected override void OnDetachingFrom(ContentPage bindable)
+ {
+ base.OnDetachingFrom(bindable);
+ bindable.Appearing -= PageOnAppearing;
+ }
+
+ /// <summary>
+ /// Called when content page is appearing.
+ /// </summary>
+ /// <param name="sender">Content page that sent event.</param>
+ /// <param name="eventArgs">Event arguments.</param>
+ private void PageOnAppearing(object sender, EventArgs eventArgs)
+ {
+ if (!((App)Application.Current).IsInitialized)
+ {
+ AppearingCommand?.Execute(sender);
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+namespace Weather.Config
+{
+ /// <summary>
+ /// Foreign API configuration.
+ /// </summary>
+ public static class ApiConfig
+ {
+ #region fields
+
+ /// <summary>
+ /// Open Weather Map API key.
+ /// </summary>
+ /// <remarks>
+ /// To get your API key please visit http://openweathermap.org/appid
+ /// </remarks>
+ public const string API_KEY = "7c0d9b7749d114f810fe9a84b0b8ffdc";
+
+ /// <summary>
+ /// "OpenWeatherMap" current weather resource.
+ /// </summary>
+ public const string WEATHER_URL = "https://api.openweathermap.org/data/2.5/weather";
+
+ /// <summary>
+ /// "OpenWeatherMap" forecast resource.
+ /// </summary>
+ public const string FORECAST_URL = "https://api.openweathermap.org/data/2.5/forecast";
+
+ /// <summary>
+ /// "OpenWeatherMap" icon location template.
+ /// </summary>
+ public const string WEATHER_ICON = "http://openweathermap.org/img/w/{0}.png";
+
+ /// <summary>
+ /// Google API URL for getting timezones information.
+ /// </summary>
+ public const string TIMEZONE_URL = "https://maps.googleapis.com/maps/api/timezone/json";
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Checks if API key is defined.
+ /// </summary>
+ /// <returns>True if API key is set, otherwise false.</returns>
+ public static bool IsApiKeyDefined()
+ {
+ return !string.IsNullOrEmpty(API_KEY);
+ }
+
+ #endregion
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ x:Class="Weather.Controls.DoubleLabel"
+ x:Name="Root"
+ Orientation="Vertical"
+ HorizontalOptions="CenterAndExpand">
+
+ <Label Text="{Binding Source={x:Reference Name=Root}, Path=MainText}"
+ FontSize="8"
+ HorizontalTextAlignment="Center" />
+ <Label Text="{Binding Source={x:Reference Name=Root}, Path=SubText}"
+ HorizontalTextAlignment="Center"
+ TextColor="LightSlateGray"
+ FontSize="6" />
+
+</StackLayout>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using Xamarin.Forms;
+
+namespace Weather.Controls
+{
+ /// <summary>
+ /// Interaction logic for DoubleLabel.xaml.
+ /// </summary>
+ public partial class DoubleLabel
+ {
+ #region properties
+
+ /// <summary>
+ /// Bindable property that allows to set main text of control.
+ /// </summary>
+ public static readonly BindableProperty MainTextProperty =
+ BindableProperty.Create(nameof(MainText), typeof(string), typeof(DoubleLabel), default(string));
+
+ /// <summary>
+ /// Bindable property that allows to set sub text of control.
+ /// </summary>
+ public static readonly BindableProperty SubTextProperty =
+ BindableProperty.Create(nameof(SubText), typeof(string), typeof(DoubleLabel), default(string));
+
+ /// <summary>
+ /// Gets or sets main text of control.
+ /// </summary>
+ public string MainText
+ {
+ get => (string)GetValue(MainTextProperty);
+ set => SetValue(MainTextProperty, value);
+ }
+
+ /// <summary>
+ /// Gets or sets sub text of control.
+ /// </summary>
+ public string SubText
+ {
+ get => (string)GetValue(SubTextProperty);
+ set => SetValue(SubTextProperty, value);
+ }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Default constructor.
+ /// </summary>
+ public DoubleLabel()
+ {
+ InitializeComponent();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace Weather.Converters
+{
+ /// <summary>
+ /// Class that converts double value to cardinal direction.
+ /// </summary>
+ public class DegreeToCardinalDirectionConverter : IValueConverter
+ {
+ #region methods
+
+ /// <summary>
+ /// Converts double to cardinal value.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Cardinal direction.</returns>
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var directions = new[]
+ {
+ "North", "North-East", "East", "South-East", "South", "South-West", "West", "North-West", "North"
+ };
+
+ return value is double degree ? directions[(int)Math.Round(degree % 360 / 45)] : string.Empty;
+ }
+
+ /// <summary>
+ /// Does nothing, but it must be defined, because it is in "IValueConverter" interface.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Converted value.</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace Weather.Converters
+{
+ /// <summary>
+ /// Class that converts possible null string to error message.
+ /// </summary>
+ public class ErrorMessageConverter : IValueConverter
+ {
+ #region methods
+
+ /// <summary>
+ /// Converts double to cardinal value.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Cardinal direction.</returns>
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var message = "None";
+
+ if (value is string msg)
+ {
+ message = msg;
+ }
+
+ return message;
+ }
+
+
+
+ /// <summary>
+ /// Does nothing, but it must be defined, because it is in "IValueConverter" interface.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Converted value.</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Globalization;
+using Weather.Config;
+using Xamarin.Forms;
+
+namespace Weather.Converters
+{
+ /// <summary>
+ /// Class that converts weather icon id to image source.
+ /// </summary>
+ public class ImageSourceConverter : IValueConverter
+ {
+ #region methods
+
+ /// <summary>
+ /// Converts weather icon id to image source.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>URI to image.</returns>
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var url = string.Empty;
+
+ if (value is string iconCode)
+ {
+ url = string.Format(ApiConfig.WEATHER_ICON, iconCode);
+ }
+
+ return url;
+ }
+
+ /// <summary>
+ /// Does nothing, but it must be defined, because it is in "IValueConverter" interface.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Converted value.</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Globalization;
+using Weather.Utils;
+using Xamarin.Forms;
+
+namespace Weather.Converters
+{
+ /// <summary>
+ /// Class that converts double value to value with unit.
+ /// </summary>
+ public class MeasurementSystemConverter : IValueConverter
+ {
+ #region methods
+
+ /// <summary>
+ /// Converts double value to value with unit.
+ /// Unit depends on converter parameter.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Cardinal direction.</returns>
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is double && parameter is string)
+ {
+ return string.Format(new UnitFormatter(), "{0:" + parameter + "}", value);
+ }
+
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Does nothing, but it must be defined, because it is in "IValueConverter" interface.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Converted value.</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Globalization;
+using Weather.Utils;
+using Xamarin.Forms;
+
+namespace Weather.Converters
+{
+ /// <summary>
+ /// Class that converts time stamp to date object.
+ /// </summary>
+ public class TimeStampToDateConverter : IValueConverter
+ {
+ #region methods
+
+ /// <summary>
+ /// Converts time stamp to date object.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Date and hour in local format.</returns>
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var date = string.Empty;
+
+ if (value is ulong time)
+ {
+ date = TimeStamp.Convert(time).ToLocalTime().ToString("g");
+ }
+
+ return date;
+ }
+
+ /// <summary>
+ /// Does nothing, but it must be defined, because it is in "IValueConverter" interface.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Converted value.</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Globalization;
+using Weather.Utils;
+using Xamarin.Forms;
+
+namespace Weather.Converters
+{
+ /// <summary>
+ /// Class that converts timestamp to time of the day.
+ /// </summary>
+ public class TimeStampToTimeConverter : IValueConverter
+ {
+ #region methods
+
+ /// <summary>
+ /// Converts timestamp to time of the day.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Time of the day.</returns>
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var date = string.Empty;
+
+ if (value is ulong time)
+ {
+ if (parameter is Models.Location.TimeZone timeZone)
+ {
+ date = TimeStamp.Convert(time).AddSeconds(timeZone.Offset)
+ .ToString(culture.DateTimeFormat.ShortTimePattern);
+ }
+ else
+ {
+ date = TimeStamp.Convert((ulong)value).ToString(culture.DateTimeFormat.ShortTimePattern);
+ }
+ }
+
+ return date;
+ }
+
+ /// <summary>
+ /// Does nothing, but it must be defined, because it is in "IValueConverter" interface.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Converted value.</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Globalization;
+using Weather.Utils;
+using Xamarin.Forms;
+
+namespace Weather.Converters
+{
+ /// <summary>
+ /// Class that converts timestamp to region date and time with timezone.
+ /// </summary>
+ public class TimeStampWithOffsetAndTimezoneConverter : IValueConverter
+ {
+ #region methods
+
+ /// <summary>
+ /// Converts timestamp to date and time with timezone.
+ /// Timezone depends on converter parameter.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Cardinal direction.</returns>
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is ulong timestamp && parameter is BindableString offsetString &&
+ long.TryParse(offsetString.Value, out long offset))
+ {
+ var time = TimeStamp.Convert(timestamp).AddSeconds(offset).ToString("g");
+ return $"{time} (GMT{offset / 60 / 60:+#;-#;+0})";
+ }
+
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Does nothing, but it must be defined, because it is in "IValueConverter" interface.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Converted value.</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Globalization;
+using Weather.Utils;
+using Xamarin.Forms;
+
+namespace Weather.Converters
+{
+ /// <summary>
+ /// Class that converts timestamp to region date and time.
+ /// </summary>
+ public class TimeStampWithOffsetConverter : IValueConverter
+ {
+ #region methods
+
+ /// <summary>
+ /// Converts timestamp to date and time with timezone.
+ /// Timezone depends on converter parameter.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Cardinal direction.</returns>
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is ulong timestamp && parameter is BindableString offsetString &&
+ long.TryParse(offsetString.Value, out long offset))
+ {
+ return TimeStamp.Convert(timestamp).AddSeconds(offset).ToShortTimeString();
+ }
+
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Does nothing, but it must be defined, because it is in "IValueConverter" interface.
+ /// </summary>
+ /// <param name="value">The value produced by the binding source.</param>
+ /// <param name="targetType">The type of the binding target property.</param>
+ /// <param name="parameter">The converter parameter to use.</param>
+ /// <param name="culture">The culture to use in the converter.</param>
+ /// <returns>Converted value.</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+[
+ {
+ "id": 5128638,
+ "name": "New York",
+ "country": "US",
+ "coord": {
+ "lon": -75.499901,
+ "lat": 43.000351
+ }
+ },
+ {
+ "id": 5368361,
+ "name": "Los Angeles",
+ "country": "US",
+ "coord": {
+ "lon": -118.243683,
+ "lat": 34.052231
+ }
+ },
+ {
+ "id": 4887398,
+ "name": "Chicago",
+ "country": "US",
+ "coord": {
+ "lon": -87.650047,
+ "lat": 41.850029
+ }
+ },
+ {
+ "id": 4699066,
+ "name": "Houston",
+ "country": "US",
+ "coord": {
+ "lon": -95.363274,
+ "lat": 29.763281
+ }
+ },
+ {
+ "id": 5308655,
+ "name": "Phoenix",
+ "country": "US",
+ "coord": {
+ "lon": -112.074043,
+ "lat": 33.44838
+ }
+ },
+ {
+ "id": 4560349,
+ "name": "Philadelphia",
+ "country": "US",
+ "coord": {
+ "lon": -75.163788,
+ "lat": 39.952339
+ }
+ },
+ {
+ "id": 4726206,
+ "name": "San Antonio",
+ "country": "US",
+ "coord": {
+ "lon": -98.493629,
+ "lat": 29.42412
+ }
+ },
+ {
+ "id": 5391811,
+ "name": "San Diego",
+ "country": "US",
+ "coord": {
+ "lon": -117.157257,
+ "lat": 32.715328
+ }
+ },
+ {
+ "id": 4684888,
+ "name": "Dallas",
+ "country": "US",
+ "coord": {
+ "lon": -96.806671,
+ "lat": 32.783058
+ }
+ },
+ {
+ "id": 5392171,
+ "name": "San Jose",
+ "country": "US",
+ "coord": {
+ "lon": -121.894958,
+ "lat": 37.33939
+ }
+ },
+ {
+ "id": 4254010,
+ "name": "Austin",
+ "country": "US",
+ "coord": {
+ "lon": -85.808029,
+ "lat": 38.758389
+ }
+ },
+ {
+ "id": 4241704,
+ "name": "Jacksonville",
+ "country": "US",
+ "coord": {
+ "lon": -90.229012,
+ "lat": 39.73394
+ }
+ },
+ {
+ "id": 5391959,
+ "name": "San Francisco",
+ "country": "US",
+ "coord": {
+ "lon": -122.419418,
+ "lat": 37.774929
+ }
+ },
+ {
+ "id": 4256038,
+ "name": "Columbus",
+ "country": "US",
+ "coord": {
+ "lon": -85.921379,
+ "lat": 39.201439
+ }
+ },
+ {
+ "id": 4861716,
+ "name": "Indianapolis",
+ "country": "US",
+ "coord": {
+ "lon": -92.433517,
+ "lat": 41.39695
+ }
+ },
+ {
+ "id": 4691930,
+ "name": "Fort Worth",
+ "country": "US",
+ "coord": {
+ "lon": -97.320847,
+ "lat": 32.72541
+ }
+ },
+ {
+ "id": 4460243,
+ "name": "Charlotte",
+ "country": "US",
+ "coord": {
+ "lon": -80.843132,
+ "lat": 35.227089
+ }
+ },
+ {
+ "id": 5809844,
+ "name": "Seattle",
+ "country": "US",
+ "coord": {
+ "lon": -122.332069,
+ "lat": 47.606209
+ }
+ },
+ {
+ "id": 4463523,
+ "name": "Denver",
+ "country": "US",
+ "coord": {
+ "lon": -81.0298,
+ "lat": 35.53125
+ }
+ },
+ {
+ "id": 5520993,
+ "name": "El Paso",
+ "country": "US",
+ "coord": {
+ "lon": -106.486931,
+ "lat": 31.75872
+ }
+ },
+ {
+ "id": 4880731,
+ "name": "Washington",
+ "country": "US",
+ "coord": {
+ "lon": -91.69294,
+ "lat": 41.299179
+ }
+ },
+ {
+ "id": 4317656,
+ "name": "Boston",
+ "country": "US",
+ "coord": {
+ "lon": -92.055679,
+ "lat": 29.883261
+ }
+ },
+ {
+ "id": 4990729,
+ "name": "Detroit",
+ "country": "US",
+ "coord": {
+ "lon": -83.045753,
+ "lat": 42.331429
+ }
+ },
+ {
+ "id": 5003243,
+ "name": "Nashville",
+ "country": "US",
+ "coord": {
+ "lon": -85.093048,
+ "lat": 42.60281
+ }
+ },
+ {
+ "id": 4641239,
+ "name": "Memphis",
+ "country": "US",
+ "coord": {
+ "lon": -90.048981,
+ "lat": 35.149529
+ }
+ },
+ {
+ "id": 4720131,
+ "name": "Portland",
+ "country": "US",
+ "coord": {
+ "lon": -97.323883,
+ "lat": 27.877251
+ }
+ },
+ {
+ "id": 4544349,
+ "name": "Oklahoma City",
+ "country": "US",
+ "coord": {
+ "lon": -97.516434,
+ "lat": 35.46756
+ }
+ },
+ {
+ "id": 5506956,
+ "name": "Las Vegas",
+ "country": "US",
+ "coord": {
+ "lon": -115.137222,
+ "lat": 36.174969
+ }
+ },
+ {
+ "id": 4299276,
+ "name": "Louisville",
+ "country": "US",
+ "coord": {
+ "lon": -85.759407,
+ "lat": 38.254238
+ }
+ },
+ {
+ "id": 4347778,
+ "name": "Baltimore",
+ "country": "US",
+ "coord": {
+ "lon": -76.61219,
+ "lat": 39.290379
+ }
+ },
+ {
+ "id": 5263045,
+ "name": "Milwaukee",
+ "country": "US",
+ "coord": {
+ "lon": -87.906471,
+ "lat": 43.038898
+ }
+ },
+ {
+ "id": 5454711,
+ "name": "Albuquerque",
+ "country": "US",
+ "coord": {
+ "lon": -106.651138,
+ "lat": 35.084492
+ }
+ },
+ {
+ "id": 5318313,
+ "name": "Tucson",
+ "country": "US",
+ "coord": {
+ "lon": -110.926483,
+ "lat": 32.221741
+ }
+ },
+ {
+ "id": 4692400,
+ "name": "Fresno",
+ "country": "US",
+ "coord": {
+ "lon": -95.447441,
+ "lat": 29.538851
+ }
+ },
+ {
+ "id": 5389489,
+ "name": "Sacramento",
+ "country": "US",
+ "coord": {
+ "lon": -121.4944,
+ "lat": 38.58157
+ }
+ },
+ {
+ "id": 5304391,
+ "name": "Mesa",
+ "country": "US",
+ "coord": {
+ "lon": -111.822639,
+ "lat": 33.422272
+ }
+ },
+ {
+ "id": 4273837,
+ "name": "Kansas City",
+ "country": "US",
+ "coord": {
+ "lon": -94.627457,
+ "lat": 39.11417
+ }
+ },
+ {
+ "id": 4180439,
+ "name": "Atlanta",
+ "country": "US",
+ "coord": {
+ "lon": -84.387978,
+ "lat": 33.749001
+ }
+ },
+ {
+ "id": 5125086,
+ "name": "Long Beach",
+ "country": "US",
+ "coord": {
+ "lon": -73.657913,
+ "lat": 40.58844
+ }
+ },
+ {
+ "id": 5417598,
+ "name": "Colorado Springs",
+ "country": "US",
+ "coord": {
+ "lon": -104.821358,
+ "lat": 38.833881
+ }
+ },
+ {
+ "id": 4247770,
+ "name": "Raleigh",
+ "country": "US",
+ "coord": {
+ "lon": -88.531998,
+ "lat": 37.826988
+ }
+ },
+ {
+ "id": 4164138,
+ "name": "Miami",
+ "country": "US",
+ "coord": {
+ "lon": -80.193657,
+ "lat": 25.774269
+ }
+ },
+ {
+ "id": 4791259,
+ "name": "Virginia Beach",
+ "country": "US",
+ "coord": {
+ "lon": -75.977982,
+ "lat": 36.852928
+ }
+ },
+ {
+ "id": 5074472,
+ "name": "Omaha",
+ "country": "US",
+ "coord": {
+ "lon": -95.93779,
+ "lat": 41.25861
+ }
+ },
+ {
+ "id": 4276543,
+ "name": "Oakland",
+ "country": "US",
+ "coord": {
+ "lon": -95.636368,
+ "lat": 39.066669
+ }
+ },
+ {
+ "id": 5037649,
+ "name": "Minneapolis",
+ "country": "US",
+ "coord": {
+ "lon": -93.26384,
+ "lat": 44.979969
+ }
+ },
+ {
+ "id": 4553433,
+ "name": "Tulsa",
+ "country": "US",
+ "coord": {
+ "lon": -95.992783,
+ "lat": 36.15398
+ }
+ },
+ {
+ "id": 4282658,
+ "name": "Arlington",
+ "country": "US",
+ "coord": {
+ "lon": -84.307709,
+ "lat": 37.760639
+ }
+ },
+ {
+ "id": 4335045,
+ "name": "New Orleans",
+ "country": "US",
+ "coord": {
+ "lon": -90.075073,
+ "lat": 29.954651
+ }
+ },
+ {
+ "id": 4281730,
+ "name": "Wichita",
+ "country": "US",
+ "coord": {
+ "lon": -97.33754,
+ "lat": 37.692242
+ }
+ },
+ {
+ "id": 4188377,
+ "name": "Cleveland",
+ "country": "US",
+ "coord": {
+ "lon": -83.763237,
+ "lat": 34.597038
+ }
+ },
+ {
+ "id": 4174757,
+ "name": "Tampa",
+ "country": "US",
+ "coord": {
+ "lon": -82.458427,
+ "lat": 27.947519
+ }
+ },
+ {
+ "id": 5325738,
+ "name": "Bakersfield",
+ "country": "US",
+ "coord": {
+ "lon": -119.018707,
+ "lat": 35.373291
+ }
+ },
+ {
+ "id": 4883817,
+ "name": "Aurora",
+ "country": "US",
+ "coord": {
+ "lon": -88.320068,
+ "lat": 41.760578
+ }
+ },
+ {
+ "id": 5856195,
+ "name": "Honolulu",
+ "country": "US",
+ "coord": {
+ "lon": -157.858337,
+ "lat": 21.30694
+ }
+ },
+ {
+ "id": 5323810,
+ "name": "Anaheim",
+ "country": "US",
+ "coord": {
+ "lon": -117.914497,
+ "lat": 33.835289
+ }
+ },
+ {
+ "id": 5392900,
+ "name": "Santa Ana",
+ "country": "US",
+ "coord": {
+ "lon": -117.867828,
+ "lat": 33.745571
+ }
+ },
+ {
+ "id": 4683416,
+ "name": "Corpus Christi",
+ "country": "US",
+ "coord": {
+ "lon": -97.396378,
+ "lat": 27.800579
+ }
+ },
+ {
+ "id": 4522586,
+ "name": "Riverside",
+ "country": "US",
+ "coord": {
+ "lon": -84.1241,
+ "lat": 39.779781
+ }
+ },
+ {
+ "id": 4297983,
+ "name": "Lexington",
+ "country": "US",
+ "coord": {
+ "lon": -84.477722,
+ "lat": 37.988689
+ }
+ },
+ {
+ "id": 4407066,
+ "name": "Saint Louis",
+ "country": "US",
+ "coord": {
+ "lon": -90.197891,
+ "lat": 38.62727
+ }
+ },
+ {
+ "id": 5399020,
+ "name": "Stockton",
+ "country": "US",
+ "coord": {
+ "lon": -121.290779,
+ "lat": 37.957699
+ }
+ },
+ {
+ "id": 5206379,
+ "name": "Pittsburgh",
+ "country": "US",
+ "coord": {
+ "lon": -79.995888,
+ "lat": 40.44062
+ }
+ },
+ {
+ "id": 5045360,
+ "name": "Saint Paul",
+ "country": "US",
+ "coord": {
+ "lon": -93.093269,
+ "lat": 44.944408
+ }
+ },
+ {
+ "id": 4508722,
+ "name": "Cincinnati",
+ "country": "US",
+ "coord": {
+ "lon": -84.456886,
+ "lat": 39.161999
+ }
+ },
+ {
+ "id": 5879400,
+ "name": "Anchorage",
+ "country": "US",
+ "coord": {
+ "lon": -149.900284,
+ "lat": 61.21806
+ }
+ },
+ {
+ "id": 4294494,
+ "name": "Henderson",
+ "country": "US",
+ "coord": {
+ "lon": -87.590012,
+ "lat": 37.836151
+ }
+ },
+ {
+ "id": 4469146,
+ "name": "Greensboro",
+ "country": "US",
+ "coord": {
+ "lon": -79.791977,
+ "lat": 36.072639
+ }
+ },
+ {
+ "id": 4719457,
+ "name": "Plano",
+ "country": "US",
+ "coord": {
+ "lon": -96.698891,
+ "lat": 33.01984
+ }
+ },
+ {
+ "id": 4833930,
+ "name": "Newark",
+ "country": "US",
+ "coord": {
+ "lon": -89.227608,
+ "lat": 42.542229
+ }
+ },
+ {
+ "id": 5072006,
+ "name": "Lincoln",
+ "country": "US",
+ "coord": {
+ "lon": -96.666962,
+ "lat": 40.799999
+ }
+ },
+ {
+ "id": 5174035,
+ "name": "Toledo",
+ "country": "US",
+ "coord": {
+ "lon": -83.555206,
+ "lat": 41.66394
+ }
+ },
+ {
+ "id": 4167147,
+ "name": "Orlando",
+ "country": "US",
+ "coord": {
+ "lon": -81.379242,
+ "lat": 28.53834
+ }
+ },
+ {
+ "id": 5336899,
+ "name": "Chula Vista",
+ "country": "US",
+ "coord": {
+ "lon": -117.084198,
+ "lat": 32.640049
+ }
+ },
+ {
+ "id": 5359777,
+ "name": "Irvine",
+ "country": "US",
+ "coord": {
+ "lon": -117.823112,
+ "lat": 33.66946
+ }
+ },
+ {
+ "id": 4920423,
+ "name": "Fort Wayne",
+ "country": "US",
+ "coord": {
+ "lon": -85.12886,
+ "lat": 41.1306
+ }
+ },
+ {
+ "id": 5099836,
+ "name": "Jersey City",
+ "country": "US",
+ "coord": {
+ "lon": -74.077637,
+ "lat": 40.728161
+ }
+ },
+ {
+ "id": 4464368,
+ "name": "Durham",
+ "country": "US",
+ "coord": {
+ "lon": -78.898621,
+ "lat": 35.99403
+ }
+ },
+ {
+ "id": 4171563,
+ "name": "Saint Petersburg",
+ "country": "US",
+ "coord": {
+ "lon": -82.679268,
+ "lat": 27.770861
+ }
+ },
+ {
+ "id": 4705349,
+ "name": "Laredo",
+ "country": "US",
+ "coord": {
+ "lon": -99.507538,
+ "lat": 27.506411
+ }
+ },
+ {
+ "id": 5019588,
+ "name": "Buffalo",
+ "country": "US",
+ "coord": {
+ "lon": -93.874687,
+ "lat": 45.171909
+ }
+ },
+ {
+ "id": 4434663,
+ "name": "Madison",
+ "country": "US",
+ "coord": {
+ "lon": -90.115356,
+ "lat": 32.461811
+ }
+ },
+ {
+ "id": 5525577,
+ "name": "Lubbock",
+ "country": "US",
+ "coord": {
+ "lon": -101.855171,
+ "lat": 33.577862
+ }
+ },
+ {
+ "id": 4049176,
+ "name": "Chandler",
+ "country": "US",
+ "coord": {
+ "lon": -94.382729,
+ "lat": 39.300282
+ }
+ },
+ {
+ "id": 5313457,
+ "name": "Scottsdale",
+ "country": "US",
+ "coord": {
+ "lon": -111.899033,
+ "lat": 33.509209
+ }
+ },
+ {
+ "id": 5577409,
+ "name": "Glendale",
+ "country": "US",
+ "coord": {
+ "lon": -105.366379,
+ "lat": 40.081928
+ }
+ },
+ {
+ "id": 5511077,
+ "name": "Reno",
+ "country": "US",
+ "coord": {
+ "lon": -119.813797,
+ "lat": 39.529629
+ }
+ },
+ {
+ "id": 4776222,
+ "name": "Norfolk",
+ "country": "US",
+ "coord": {
+ "lon": -76.285217,
+ "lat": 36.846809
+ }
+ },
+ {
+ "id": 4499612,
+ "name": "Winston-Salem",
+ "country": "US",
+ "coord": {
+ "lon": -80.244217,
+ "lat": 36.099861
+ }
+ },
+ {
+ "id": 5509403,
+ "name": "North Las Vegas",
+ "country": "US",
+ "coord": {
+ "lon": -115.1175,
+ "lat": 36.19886
+ }
+ },
+ {
+ "id": 4700168,
+ "name": "Irving",
+ "country": "US",
+ "coord": {
+ "lon": -96.948891,
+ "lat": 32.814018
+ }
+ },
+ {
+ "id": 4752186,
+ "name": "Chesapeake",
+ "country": "US",
+ "coord": {
+ "lon": -76.27494,
+ "lat": 36.819038
+ }
+ },
+ {
+ "id": 5295903,
+ "name": "Gilbert",
+ "country": "US",
+ "coord": {
+ "lon": -111.789032,
+ "lat": 33.352829
+ }
+ },
+ {
+ "id": 4158476,
+ "name": "Hialeah",
+ "country": "US",
+ "coord": {
+ "lon": -80.278107,
+ "lat": 25.857599
+ }
+ },
+ {
+ "id": 4693003,
+ "name": "Garland",
+ "country": "US",
+ "coord": {
+ "lon": -96.638878,
+ "lat": 32.912621
+ }
+ },
+ {
+ "id": 4920512,
+ "name": "Fremont",
+ "country": "US",
+ "coord": {
+ "lon": -84.932739,
+ "lat": 41.730881
+ }
+ },
+ {
+ "id": 4315588,
+ "name": "Baton Rouge",
+ "country": "US",
+ "coord": {
+ "lon": -91.154549,
+ "lat": 30.45075
+ }
+ },
+ {
+ "id": 4305974,
+ "name": "Richmond",
+ "country": "US",
+ "coord": {
+ "lon": -84.294647,
+ "lat": 37.74786
+ }
+ },
+ {
+ "id": 5586437,
+ "name": "Boise",
+ "country": "US",
+ "coord": {
+ "lon": -116.203453,
+ "lat": 43.613499
+ }
+ },
+ {
+ "id": 5391710,
+ "name": "San Bernardino",
+ "country": "US",
+ "coord": {
+ "lon": -117.289772,
+ "lat": 34.108341
+ }
+ },
+ {
+ "id": 5811696,
+ "name": "Spokane",
+ "country": "US",
+ "coord": {
+ "lon": -117.429077,
+ "lat": 47.65966
+ }
+ },
+ {
+ "id": 4853828,
+ "name": "Des Moines",
+ "country": "US",
+ "coord": {
+ "lon": -93.609108,
+ "lat": 41.60054
+ }
+ },
+ {
+ "id": 5373900,
+ "name": "Modesto",
+ "country": "US",
+ "coord": {
+ "lon": -120.99688,
+ "lat": 37.639099
+ }
+ },
+ {
+ "id": 4049979,
+ "name": "Birmingham",
+ "country": "US",
+ "coord": {
+ "lon": -86.80249,
+ "lat": 33.52066
+ }
+ },
+ {
+ "id": 5812944,
+ "name": "Tacoma",
+ "country": "US",
+ "coord": {
+ "lon": -122.44429,
+ "lat": 47.25288
+ }
+ },
+ {
+ "id": 5349755,
+ "name": "Fontana",
+ "country": "US",
+ "coord": {
+ "lon": -117.435051,
+ "lat": 34.092232
+ }
+ },
+ {
+ "id": 5043473,
+ "name": "Rochester",
+ "country": "US",
+ "coord": {
+ "lon": -92.469902,
+ "lat": 44.021629
+ }
+ },
+ {
+ "id": 5380184,
+ "name": "Oxnard",
+ "country": "US",
+ "coord": {
+ "lon": -119.177048,
+ "lat": 34.197498
+ }
+ },
+ {
+ "id": 5374732,
+ "name": "Moreno Valley",
+ "country": "US",
+ "coord": {
+ "lon": -117.230591,
+ "lat": 33.937519
+ }
+ },
+ {
+ "id": 4194474,
+ "name": "Fayetteville",
+ "country": "US",
+ "coord": {
+ "lon": -84.454933,
+ "lat": 33.44873
+ }
+ },
+ {
+ "id": 4883817,
+ "name": "Aurora",
+ "country": "US",
+ "coord": {
+ "lon": -88.320068,
+ "lat": 41.760578
+ }
+ },
+ {
+ "id": 5577409,
+ "name": "Glendale",
+ "country": "US",
+ "coord": {
+ "lon": -105.366379,
+ "lat": 40.081928
+ }
+ },
+ {
+ "id": 5145215,
+ "name": "Yonkers",
+ "country": "US",
+ "coord": {
+ "lon": -73.89875,
+ "lat": 40.93121
+ }
+ },
+ {
+ "id": 5358705,
+ "name": "Huntington Beach",
+ "country": "US",
+ "coord": {
+ "lon": -117.999229,
+ "lat": 33.660301
+ }
+ },
+ {
+ "id": 4076784,
+ "name": "Montgomery",
+ "country": "US",
+ "coord": {
+ "lon": -86.299973,
+ "lat": 32.36681
+ }
+ },
+ {
+ "id": 5516233,
+ "name": "Amarillo",
+ "country": "US",
+ "coord": {
+ "lon": -101.831299,
+ "lat": 35.222
+ }
+ },
+ {
+ "id": 4119403,
+ "name": "Little Rock",
+ "country": "US",
+ "coord": {
+ "lon": -92.289589,
+ "lat": 34.746479
+ }
+ },
+ {
+ "id": 4267427,
+ "name": "Akron",
+ "country": "US",
+ "coord": {
+ "lon": -97.015038,
+ "lat": 37.351688
+ }
+ },
+ {
+ "id": 4256038,
+ "name": "Columbus",
+ "country": "US",
+ "coord": {
+ "lon": -85.921379,
+ "lat": 39.201439
+ }
+ },
+ {
+ "id": 4180531,
+ "name": "Augusta",
+ "country": "US",
+ "coord": {
+ "lon": -81.974838,
+ "lat": 33.47097
+ }
+ },
+ {
+ "id": 4994358,
+ "name": "Grand Rapids",
+ "country": "US",
+ "coord": {
+ "lon": -85.668091,
+ "lat": 42.96336
+ }
+ },
+ {
+ "id": 4341513,
+ "name": "Shreveport",
+ "country": "US",
+ "coord": {
+ "lon": -93.750183,
+ "lat": 32.52515
+ }
+ },
+ {
+ "id": 5780993,
+ "name": "Salt Lake City",
+ "country": "US",
+ "coord": {
+ "lon": -111.891052,
+ "lat": 40.76078
+ }
+ },
+ {
+ "id": 4699540,
+ "name": "Huntsville",
+ "country": "US",
+ "coord": {
+ "lon": -95.550781,
+ "lat": 30.72353
+ }
+ },
+ {
+ "id": 4076598,
+ "name": "Mobile",
+ "country": "US",
+ "coord": {
+ "lon": -88.043053,
+ "lat": 30.694361
+ }
+ },
+ {
+ "id": 4174715,
+ "name": "Tallahassee",
+ "country": "US",
+ "coord": {
+ "lon": -84.280731,
+ "lat": 30.438259
+ }
+ },
+ {
+ "id": 4694482,
+ "name": "Grand Prairie",
+ "country": "US",
+ "coord": {
+ "lon": -96.99778,
+ "lat": 32.74596
+ }
+ },
+ {
+ "id": 4276873,
+ "name": "Overland Park",
+ "country": "US",
+ "coord": {
+ "lon": -94.670792,
+ "lat": 38.982231
+ }
+ },
+ {
+ "id": 4634946,
+ "name": "Knoxville",
+ "country": "US",
+ "coord": {
+ "lon": -83.920738,
+ "lat": 35.96064
+ }
+ },
+ {
+ "id": 4169171,
+ "name": "Port Saint Lucie",
+ "country": "US",
+ "coord": {
+ "lon": -80.350327,
+ "lat": 27.29393
+ }
+ },
+ {
+ "id": 4956184,
+ "name": "Worcester",
+ "country": "US",
+ "coord": {
+ "lon": -71.802292,
+ "lat": 42.262589
+ }
+ },
+ {
+ "id": 4149077,
+ "name": "Brownsville",
+ "country": "US",
+ "coord": {
+ "lon": -80.241158,
+ "lat": 25.82176
+ }
+ },
+ {
+ "id": 5317058,
+ "name": "Tempe",
+ "country": "US",
+ "coord": {
+ "lon": -111.909309,
+ "lat": 33.414768
+ }
+ },
+ {
+ "id": 5393049,
+ "name": "Santa Clarita",
+ "country": "US",
+ "coord": {
+ "lon": -118.542587,
+ "lat": 34.391659
+ }
+ },
+ {
+ "id": 4776024,
+ "name": "Newport News",
+ "country": "US",
+ "coord": {
+ "lon": -76.428001,
+ "lat": 36.97876
+ }
+ },
+ {
+ "id": 4149962,
+ "name": "Cape Coral",
+ "country": "US",
+ "coord": {
+ "lon": -81.949532,
+ "lat": 26.562851
+ }
+ },
+ {
+ "id": 4263408,
+ "name": "Providence",
+ "country": "US",
+ "coord": {
+ "lon": -86.176659,
+ "lat": 39.490879
+ }
+ },
+ {
+ "id": 4155966,
+ "name": "Fort Lauderdale",
+ "country": "US",
+ "coord": {
+ "lon": -80.143379,
+ "lat": 26.122311
+ }
+ },
+ {
+ "id": 4612862,
+ "name": "Chattanooga",
+ "country": "US",
+ "coord": {
+ "lon": -85.309677,
+ "lat": 35.045631
+ }
+ },
+ {
+ "id": 5385955,
+ "name": "Rancho Cucamonga",
+ "country": "US",
+ "coord": {
+ "lon": -117.593109,
+ "lat": 34.1064
+ }
+ },
+ {
+ "id": 5129603,
+ "name": "Oceanside",
+ "country": "US",
+ "coord": {
+ "lon": -73.640129,
+ "lat": 40.63871
+ }
+ },
+ {
+ "id": 5393287,
+ "name": "Santa Rosa",
+ "country": "US",
+ "coord": {
+ "lon": -122.714432,
+ "lat": 38.440472
+ }
+ },
+ {
+ "id": 5351515,
+ "name": "Garden Grove",
+ "country": "US",
+ "coord": {
+ "lon": -117.941452,
+ "lat": 33.773911
+ }
+ },
+ {
+ "id": 5814616,
+ "name": "Vancouver",
+ "country": "US",
+ "coord": {
+ "lon": -122.661491,
+ "lat": 45.638729
+ }
+ },
+ {
+ "id": 5231851,
+ "name": "Sioux Falls",
+ "country": "US",
+ "coord": {
+ "lon": -96.700333,
+ "lat": 43.549969
+ }
+ },
+ {
+ "id": 4924493,
+ "name": "Ontario",
+ "country": "US",
+ "coord": {
+ "lon": -85.382477,
+ "lat": 41.702271
+ }
+ },
+ {
+ "id": 4710178,
+ "name": "McKinney",
+ "country": "US",
+ "coord": {
+ "lon": -96.615273,
+ "lat": 33.19762
+ }
+ },
+ {
+ "id": 5346111,
+ "name": "Elk Grove",
+ "country": "US",
+ "coord": {
+ "lon": -121.37162,
+ "lat": 38.408798
+ }
+ },
+ {
+ "id": 4241691,
+ "name": "Jackson",
+ "country": "US",
+ "coord": {
+ "lon": -87.650299,
+ "lat": 38.721161
+ }
+ },
+ {
+ "id": 4168139,
+ "name": "Pembroke Pines",
+ "country": "US",
+ "coord": {
+ "lon": -80.223938,
+ "lat": 26.003151
+ }
+ },
+ {
+ "id": 4950065,
+ "name": "Salem",
+ "country": "US",
+ "coord": {
+ "lon": -70.896721,
+ "lat": 42.519539
+ }
+ },
+ {
+ "id": 4525353,
+ "name": "Springfield",
+ "country": "US",
+ "coord": {
+ "lon": -83.808823,
+ "lat": 39.924229
+ }
+ },
+ {
+ "id": 5339631,
+ "name": "Corona",
+ "country": "US",
+ "coord": {
+ "lon": -117.566437,
+ "lat": 33.87529
+ }
+ },
+ {
+ "id": 4992263,
+ "name": "Eugene",
+ "country": "US",
+ "coord": {
+ "lon": -84.706947,
+ "lat": 43.292252
+ }
+ },
+ {
+ "id": 5577147,
+ "name": "Fort Collins",
+ "country": "US",
+ "coord": {
+ "lon": -105.084419,
+ "lat": 40.585258
+ }
+ },
+ {
+ "id": 4905687,
+ "name": "Peoria",
+ "country": "US",
+ "coord": {
+ "lon": -89.588989,
+ "lat": 40.693649
+ }
+ },
+ {
+ "id": 4692559,
+ "name": "Frisco",
+ "country": "US",
+ "coord": {
+ "lon": -96.823608,
+ "lat": 33.150669
+ }
+ },
+ {
+ "id": 4459467,
+ "name": "Cary",
+ "country": "US",
+ "coord": {
+ "lon": -78.78112,
+ "lat": 35.791538
+ }
+ },
+ {
+ "id": 4516233,
+ "name": "Lancaster",
+ "country": "US",
+ "coord": {
+ "lon": -82.599327,
+ "lat": 39.71368
+ }
+ },
+ {
+ "id": 5355933,
+ "name": "Hayward",
+ "country": "US",
+ "coord": {
+ "lon": -122.080803,
+ "lat": 37.668819
+ }
+ },
+ {
+ "id": 5380698,
+ "name": "Palmdale",
+ "country": "US",
+ "coord": {
+ "lon": -118.116463,
+ "lat": 34.57943
+ }
+ },
+ {
+ "id": 5391295,
+ "name": "Salinas",
+ "country": "US",
+ "coord": {
+ "lon": -121.655502,
+ "lat": 36.677738
+ }
+ },
+ {
+ "id": 4744091,
+ "name": "Alexandria",
+ "country": "US",
+ "coord": {
+ "lon": -77.046921,
+ "lat": 38.80484
+ }
+ },
+ {
+ "id": 5100280,
+ "name": "Lakewood",
+ "country": "US",
+ "coord": {
+ "lon": -74.217644,
+ "lat": 40.097889
+ }
+ },
+ {
+ "id": 4525353,
+ "name": "Springfield",
+ "country": "US",
+ "coord": {
+ "lon": -83.808823,
+ "lat": 39.924229
+ }
+ },
+ {
+ "id": 4364990,
+ "name": "Pasadena",
+ "country": "US",
+ "coord": {
+ "lon": -76.571083,
+ "lat": 39.10733
+ }
+ },
+ {
+ "id": 5400075,
+ "name": "Sunnyvale",
+ "country": "US",
+ "coord": {
+ "lon": -122.036346,
+ "lat": 37.368832
+ }
+ },
+ {
+ "id": 4163123,
+ "name": "Macon",
+ "country": "US",
+ "coord": {
+ "lon": -84.279068,
+ "lat": 30.49464
+ }
+ },
+ {
+ "id": 4503665,
+ "name": "Pomona",
+ "country": "US",
+ "coord": {
+ "lon": -74.575157,
+ "lat": 39.478451
+ }
+ },
+ {
+ "id": 4158928,
+ "name": "Hollywood",
+ "country": "US",
+ "coord": {
+ "lon": -80.14949,
+ "lat": 26.0112
+ }
+ },
+ {
+ "id": 4273837,
+ "name": "Kansas City",
+ "country": "US",
+ "coord": {
+ "lon": -94.627457,
+ "lat": 39.11417
+ }
+ },
+ {
+ "id": 5346827,
+ "name": "Escondido",
+ "country": "US",
+ "coord": {
+ "lon": -117.086418,
+ "lat": 33.119209
+ }
+ },
+ {
+ "id": 4381424,
+ "name": "Clarksville",
+ "country": "US",
+ "coord": {
+ "lon": -90.905128,
+ "lat": 39.370602
+ }
+ },
+ {
+ "id": 4898015,
+ "name": "Joliet",
+ "country": "US",
+ "coord": {
+ "lon": -88.081734,
+ "lat": 41.525028
+ }
+ },
+ {
+ "id": 5043556,
+ "name": "Rockford",
+ "country": "US",
+ "coord": {
+ "lon": -93.734413,
+ "lat": 45.088299
+ }
+ },
+ {
+ "id": 5403022,
+ "name": "Torrance",
+ "country": "US",
+ "coord": {
+ "lon": -118.34063,
+ "lat": 33.83585
+ }
+ },
+ {
+ "id": 4903279,
+ "name": "Naperville",
+ "country": "US",
+ "coord": {
+ "lon": -88.147293,
+ "lat": 41.785858
+ }
+ },
+ {
+ "id": 5102466,
+ "name": "Paterson",
+ "country": "US",
+ "coord": {
+ "lon": -74.171806,
+ "lat": 40.916771
+ }
+ },
+ {
+ "id": 4221552,
+ "name": "Savannah",
+ "country": "US",
+ "coord": {
+ "lon": -81.099831,
+ "lat": 32.083542
+ }
+ },
+ {
+ "id": 4254942,
+ "name": "Bridgeport",
+ "country": "US",
+ "coord": {
+ "lon": -86.317207,
+ "lat": 39.732269
+ }
+ },
+ {
+ "id": 5508180,
+ "name": "Mesquite",
+ "country": "US",
+ "coord": {
+ "lon": -114.067192,
+ "lat": 36.805531
+ }
+ },
+ {
+ "id": 4703223,
+ "name": "Killeen",
+ "country": "US",
+ "coord": {
+ "lon": -97.727798,
+ "lat": 31.117121
+ }
+ },
+ {
+ "id": 5140405,
+ "name": "Syracuse",
+ "country": "US",
+ "coord": {
+ "lon": -76.147423,
+ "lat": 43.048119
+ }
+ },
+ {
+ "id": 4709796,
+ "name": "McAllen",
+ "country": "US",
+ "coord": {
+ "lon": -98.230011,
+ "lat": 26.203409
+ }
+ },
+ {
+ "id": 4364990,
+ "name": "Pasadena",
+ "country": "US",
+ "coord": {
+ "lon": -76.571083,
+ "lat": 39.10733
+ }
+ },
+ {
+ "id": 4884416,
+ "name": "Bellevue",
+ "country": "US",
+ "coord": {
+ "lon": -89.680099,
+ "lat": 40.684479
+ }
+ },
+ {
+ "id": 5351247,
+ "name": "Fullerton",
+ "country": "US",
+ "coord": {
+ "lon": -117.925339,
+ "lat": 33.870289
+ }
+ },
+ {
+ "id": 4716805,
+ "name": "Orange",
+ "country": "US",
+ "coord": {
+ "lon": -93.736549,
+ "lat": 30.092991
+ }
+ },
+ {
+ "id": 4509884,
+ "name": "Dayton",
+ "country": "US",
+ "coord": {
+ "lon": -84.191612,
+ "lat": 39.758949
+ }
+ },
+ {
+ "id": 4164601,
+ "name": "Miramar",
+ "country": "US",
+ "coord": {
+ "lon": -80.232269,
+ "lat": 25.98731
+ }
+ },
+ {
+ "id": 5441492,
+ "name": "Thornton",
+ "country": "US",
+ "coord": {
+ "lon": -104.971916,
+ "lat": 39.868038
+ }
+ },
+ {
+ "id": 5784607,
+ "name": "West Valley City",
+ "country": "US",
+ "coord": {
+ "lon": -112.001053,
+ "lat": 40.691608
+ }
+ },
+ {
+ "id": 4276614,
+ "name": "Olathe",
+ "country": "US",
+ "coord": {
+ "lon": -94.81913,
+ "lat": 38.881401
+ }
+ },
+ {
+ "id": 4762894,
+ "name": "Hampton",
+ "country": "US",
+ "coord": {
+ "lon": -76.345222,
+ "lat": 37.029869
+ }
+ },
+ {
+ "id": 5094264,
+ "name": "Warren",
+ "country": "US",
+ "coord": {
+ "lon": -71.892029,
+ "lat": 43.923119
+ }
+ },
+ {
+ "id": 5001929,
+ "name": "Midland",
+ "country": "US",
+ "coord": {
+ "lon": -84.247208,
+ "lat": 43.615582
+ }
+ },
+ {
+ "id": 4739526,
+ "name": "Waco",
+ "country": "US",
+ "coord": {
+ "lon": -97.146667,
+ "lat": 31.54933
+ }
+ },
+ {
+ "id": 4574324,
+ "name": "Charleston",
+ "country": "US",
+ "coord": {
+ "lon": -79.930923,
+ "lat": 32.776569
+ }
+ },
+ {
+ "id": 4614867,
+ "name": "Columbia",
+ "country": "US",
+ "coord": {
+ "lon": -87.035278,
+ "lat": 35.61507
+ }
+ },
+ {
+ "id": 4990694,
+ "name": "Denton",
+ "country": "US",
+ "coord": {
+ "lon": -83.524101,
+ "lat": 42.25782
+ }
+ },
+ {
+ "id": 4186416,
+ "name": "Carrollton",
+ "country": "US",
+ "coord": {
+ "lon": -85.076607,
+ "lat": 33.580109
+ }
+ },
+ {
+ "id": 5316428,
+ "name": "Surprise",
+ "country": "US",
+ "coord": {
+ "lon": -112.333221,
+ "lat": 33.630589
+ }
+ },
+ {
+ "id": 5007655,
+ "name": "Roseville",
+ "country": "US",
+ "coord": {
+ "lon": -82.937141,
+ "lat": 42.497261
+ }
+ },
+ {
+ "id": 5011148,
+ "name": "Sterling Heights",
+ "country": "US",
+ "coord": {
+ "lon": -83.030197,
+ "lat": 42.580311
+ }
+ },
+ {
+ "id": 4644312,
+ "name": "Murfreesboro",
+ "country": "US",
+ "coord": {
+ "lon": -86.390266,
+ "lat": 35.845619
+ }
+ },
+ {
+ "id": 4692748,
+ "name": "Gainesville",
+ "country": "US",
+ "coord": {
+ "lon": -98.521423,
+ "lat": 30.677679
+ }
+ },
+ {
+ "id": 4850751,
+ "name": "Cedar Rapids",
+ "country": "US",
+ "coord": {
+ "lon": -91.644073,
+ "lat": 42.008331
+ }
+ },
+ {
+ "id": 5406567,
+ "name": "Visalia",
+ "country": "US",
+ "coord": {
+ "lon": -119.292061,
+ "lat": 36.330231
+ }
+ },
+ {
+ "id": 4151909,
+ "name": "Coral Springs",
+ "country": "US",
+ "coord": {
+ "lon": -80.270599,
+ "lat": 26.271191
+ }
+ },
+ {
+ "id": 4839366,
+ "name": "New Haven",
+ "country": "US",
+ "coord": {
+ "lon": -72.928162,
+ "lat": 41.308151
+ }
+ },
+ {
+ "id": 4843564,
+ "name": "Stamford",
+ "country": "US",
+ "coord": {
+ "lon": -73.538727,
+ "lat": 41.053429
+ }
+ },
+ {
+ "id": 5402405,
+ "name": "Thousand Oaks",
+ "country": "US",
+ "coord": {
+ "lon": -118.837593,
+ "lat": 34.170559
+ }
+ },
+ {
+ "id": 4382072,
+ "name": "Concord",
+ "country": "US",
+ "coord": {
+ "lon": -90.357338,
+ "lat": 38.524502
+ }
+ },
+ {
+ "id": 5097598,
+ "name": "Elizabeth",
+ "country": "US",
+ "coord": {
+ "lon": -74.210701,
+ "lat": 40.66399
+ }
+ },
+ {
+ "id": 4540465,
+ "name": "Lafayette",
+ "country": "US",
+ "coord": {
+ "lon": -95.103569,
+ "lat": 35.173988
+ }
+ },
+ {
+ "id": 5159537,
+ "name": "Kent",
+ "country": "US",
+ "coord": {
+ "lon": -81.357887,
+ "lat": 41.153671
+ }
+ },
+ {
+ "id": 4280539,
+ "name": "Topeka",
+ "country": "US",
+ "coord": {
+ "lon": -95.67804,
+ "lat": 39.048328
+ }
+ },
+ {
+ "id": 5396003,
+ "name": "Simi Valley",
+ "country": "US",
+ "coord": {
+ "lon": -118.781479,
+ "lat": 34.269451
+ }
+ },
+ {
+ "id": 5750516,
+ "name": "Santa Clara",
+ "country": "US",
+ "coord": {
+ "lon": -123.131203,
+ "lat": 44.103458
+ }
+ },
+ {
+ "id": 4505542,
+ "name": "Athens",
+ "country": "US",
+ "coord": {
+ "lon": -82.101257,
+ "lat": 39.329239
+ }
+ },
+ {
+ "id": 4835797,
+ "name": "Hartford",
+ "country": "US",
+ "coord": {
+ "lon": -72.685089,
+ "lat": 41.76371
+ }
+ },
+ {
+ "id": 5406222,
+ "name": "Victorville",
+ "country": "US",
+ "coord": {
+ "lon": -117.291161,
+ "lat": 34.53611
+ }
+ },
+ {
+ "id": 4669635,
+ "name": "Abilene",
+ "country": "US",
+ "coord": {
+ "lon": -99.733139,
+ "lat": 32.448738
+ }
+ },
+ {
+ "id": 4543762,
+ "name": "Norman",
+ "country": "US",
+ "coord": {
+ "lon": -97.439484,
+ "lat": 35.222569
+ }
+ },
+ {
+ "id": 5405380,
+ "name": "Vallejo",
+ "country": "US",
+ "coord": {
+ "lon": -122.256638,
+ "lat": 38.104092
+ }
+ },
+ {
+ "id": 5327684,
+ "name": "Berkeley",
+ "country": "US",
+ "coord": {
+ "lon": -122.272751,
+ "lat": 37.87159
+ }
+ },
+ {
+ "id": 4724129,
+ "name": "Round Rock",
+ "country": "US",
+ "coord": {
+ "lon": -97.678902,
+ "lat": 30.508261
+ }
+ },
+ {
+ "id": 4984247,
+ "name": "Ann Arbor",
+ "country": "US",
+ "coord": {
+ "lon": -83.740883,
+ "lat": 42.277561
+ }
+ },
+ {
+ "id": 5059163,
+ "name": "Fargo",
+ "country": "US",
+ "coord": {
+ "lon": -96.789803,
+ "lat": 46.87719
+ }
+ },
+ {
+ "id": 4614867,
+ "name": "Columbia",
+ "country": "US",
+ "coord": {
+ "lon": -87.035278,
+ "lat": 35.61507
+ }
+ },
+ {
+ "id": 5178127,
+ "name": "Allentown",
+ "country": "US",
+ "coord": {
+ "lon": -75.490181,
+ "lat": 40.608429
+ }
+ },
+ {
+ "id": 4257227,
+ "name": "Evansville",
+ "country": "US",
+ "coord": {
+ "lon": -87.555847,
+ "lat": 37.974758
+ }
+ },
+ {
+ "id": 4672989,
+ "name": "Beaumont",
+ "country": "US",
+ "coord": {
+ "lon": -94.101852,
+ "lat": 30.08605
+ }
+ },
+ {
+ "id": 5527554,
+ "name": "Odessa",
+ "country": "US",
+ "coord": {
+ "lon": -102.367638,
+ "lat": 31.84568
+ }
+ },
+ {
+ "id": 4145381,
+ "name": "Wilmington",
+ "country": "US",
+ "coord": {
+ "lon": -75.546593,
+ "lat": 39.745949
+ }
+ },
+ {
+ "id": 5412199,
+ "name": "Arvada",
+ "country": "US",
+ "coord": {
+ "lon": -105.087479,
+ "lat": 39.802761
+ }
+ },
+ {
+ "id": 5194748,
+ "name": "Independence",
+ "country": "US",
+ "coord": {
+ "lon": -80.309227,
+ "lat": 40.572289
+ }
+ },
+ {
+ "id": 5780026,
+ "name": "Provo",
+ "country": "US",
+ "coord": {
+ "lon": -111.658531,
+ "lat": 40.233841
+ }
+ },
+ {
+ "id": 5123993,
+ "name": "Lansing",
+ "country": "US",
+ "coord": {
+ "lon": -76.479942,
+ "lat": 42.484241
+ }
+ },
+ {
+ "id": 5345743,
+ "name": "El Monte",
+ "country": "US",
+ "coord": {
+ "lon": -118.027573,
+ "lat": 34.068619
+ }
+ },
+ {
+ "id": 4525353,
+ "name": "Springfield",
+ "country": "US",
+ "coord": {
+ "lon": -83.808823,
+ "lat": 39.924229
+ }
+ },
+ {
+ "id": 7257859,
+ "name": "Fairfield",
+ "country": "US",
+ "coord": {
+ "lon": -74.304024,
+ "lat": 40.882549
+ }
+ },
+ {
+ "id": 4151316,
+ "name": "Clearwater",
+ "country": "US",
+ "coord": {
+ "lon": -82.800102,
+ "lat": 27.965851
+ }
+ },
+ {
+ "id": 4905687,
+ "name": "Peoria",
+ "country": "US",
+ "coord": {
+ "lon": -89.588989,
+ "lat": 40.693649
+ }
+ },
+ {
+ "id": 5043473,
+ "name": "Rochester",
+ "country": "US",
+ "coord": {
+ "lon": -92.469902,
+ "lat": 44.021629
+ }
+ },
+ {
+ "id": 5334223,
+ "name": "Carlsbad",
+ "country": "US",
+ "coord": {
+ "lon": -117.350594,
+ "lat": 33.158089
+ }
+ },
+ {
+ "id": 5242810,
+ "name": "Westminster",
+ "country": "US",
+ "coord": {
+ "lon": -72.458702,
+ "lat": 43.06786
+ }
+ },
+ {
+ "id": 5784549,
+ "name": "West Jordan",
+ "country": "US",
+ "coord": {
+ "lon": -111.939102,
+ "lat": 40.609669
+ }
+ },
+ {
+ "id": 4718097,
+ "name": "Pearland",
+ "country": "US",
+ "coord": {
+ "lon": -95.286049,
+ "lat": 29.56357
+ }
+ },
+ {
+ "id": 4722625,
+ "name": "Richardson",
+ "country": "US",
+ "coord": {
+ "lon": -96.729721,
+ "lat": 32.948181
+ }
+ },
+ {
+ "id": 5343858,
+ "name": "Downey",
+ "country": "US",
+ "coord": {
+ "lon": -118.132568,
+ "lat": 33.94001
+ }
+ },
+ {
+ "id": 4164167,
+ "name": "Miami Gardens",
+ "country": "US",
+ "coord": {
+ "lon": -80.245598,
+ "lat": 25.942039
+ }
+ },
+ {
+ "id": 5401395,
+ "name": "Temecula",
+ "country": "US",
+ "coord": {
+ "lon": -117.148361,
+ "lat": 33.493641
+ }
+ },
+ {
+ "id": 5339840,
+ "name": "Costa Mesa",
+ "country": "US",
+ "coord": {
+ "lon": -117.918671,
+ "lat": 33.641129
+ }
+ },
+ {
+ "id": 4682464,
+ "name": "College Station",
+ "country": "US",
+ "coord": {
+ "lon": -96.334412,
+ "lat": 30.627979
+ }
+ },
+ {
+ "id": 4890864,
+ "name": "Elgin",
+ "country": "US",
+ "coord": {
+ "lon": -88.281189,
+ "lat": 42.037251
+ }
+ },
+ {
+ "id": 5375911,
+ "name": "Murrieta",
+ "country": "US",
+ "coord": {
+ "lon": -117.213921,
+ "lat": 33.553909
+ }
+ },
+ {
+ "id": 5729485,
+ "name": "Gresham",
+ "country": "US",
+ "coord": {
+ "lon": -122.43148,
+ "lat": 45.49818
+ }
+ },
+ {
+ "id": 4471025,
+ "name": "High Point",
+ "country": "US",
+ "coord": {
+ "lon": -80.005318,
+ "lat": 35.955688
+ }
+ },
+ {
+ "id": 4670957,
+ "name": "Antioch",
+ "country": "US",
+ "coord": {
+ "lon": -97.198067,
+ "lat": 32.31292
+ }
+ },
+ {
+ "id": 5359488,
+ "name": "Inglewood",
+ "country": "US",
+ "coord": {
+ "lon": -118.353127,
+ "lat": 33.961681
+ }
+ },
+ {
+ "id": 5587779,
+ "name": "Cambridge",
+ "country": "US",
+ "coord": {
+ "lon": -116.675987,
+ "lat": 44.572659
+ }
+ },
+ {
+ "id": 4942618,
+ "name": "Lowell",
+ "country": "US",
+ "coord": {
+ "lon": -71.31617,
+ "lat": 42.633419
+ }
+ },
+ {
+ "id": 5000598,
+ "name": "Manchester",
+ "country": "US",
+ "coord": {
+ "lon": -84.03772,
+ "lat": 42.150318
+ }
+ },
+ {
+ "id": 5640350,
+ "name": "Billings",
+ "country": "US",
+ "coord": {
+ "lon": -108.500687,
+ "lat": 45.783291
+ }
+ },
+ {
+ "id": 5435464,
+ "name": "Pueblo",
+ "country": "US",
+ "coord": {
+ "lon": -104.609138,
+ "lat": 38.254452
+ }
+ },
+ {
+ "id": 4167499,
+ "name": "Palm Bay",
+ "country": "US",
+ "coord": {
+ "lon": -80.588661,
+ "lat": 28.03446
+ }
+ },
+ {
+ "id": 4186974,
+ "name": "Centennial",
+ "country": "US",
+ "coord": {
+ "lon": -83.612389,
+ "lat": 33.58596
+ }
+ },
+ {
+ "id": 4305974,
+ "name": "Richmond",
+ "country": "US",
+ "coord": {
+ "lon": -84.294647,
+ "lat": 37.74786
+ }
+ },
+ {
+ "id": 5405878,
+ "name": "Ventura",
+ "country": "US",
+ "coord": {
+ "lon": -119.293167,
+ "lat": 34.278339
+ }
+ },
+ {
+ "id": 4169014,
+ "name": "Pompano Beach",
+ "country": "US",
+ "coord": {
+ "lon": -80.124771,
+ "lat": 26.23786
+ }
+ },
+ {
+ "id": 4589387,
+ "name": "North Charleston",
+ "country": "US",
+ "coord": {
+ "lon": -80.007507,
+ "lat": 32.888561
+ }
+ },
+ {
+ "id": 4936008,
+ "name": "Everett",
+ "country": "US",
+ "coord": {
+ "lon": -71.053658,
+ "lat": 42.408428
+ }
+ },
+ {
+ "id": 4845193,
+ "name": "Waterbury",
+ "country": "US",
+ "coord": {
+ "lon": -73.051498,
+ "lat": 41.558151
+ }
+ },
+ {
+ "id": 4177887,
+ "name": "West Palm Beach",
+ "country": "US",
+ "coord": {
+ "lon": -80.053368,
+ "lat": 26.71534
+ }
+ },
+ {
+ "id": 5574991,
+ "name": "Boulder",
+ "country": "US",
+ "coord": {
+ "lon": -105.270554,
+ "lat": 40.014992
+ }
+ },
+ {
+ "id": 5407933,
+ "name": "West Covina",
+ "country": "US",
+ "coord": {
+ "lon": -117.93895,
+ "lat": 34.068619
+ }
+ },
+ {
+ "id": 4531405,
+ "name": "Broken Arrow",
+ "country": "US",
+ "coord": {
+ "lon": -95.790817,
+ "lat": 36.052601
+ }
+ },
+ {
+ "id": 5338122,
+ "name": "Clovis",
+ "country": "US",
+ "coord": {
+ "lon": -119.702919,
+ "lat": 36.82523
+ }
+ },
+ {
+ "id": 5341430,
+ "name": "Daly City",
+ "country": "US",
+ "coord": {
+ "lon": -122.461922,
+ "lat": 37.705769
+ }
+ },
+ {
+ "id": 5123873,
+ "name": "Lakeland",
+ "country": "US",
+ "coord": {
+ "lon": -73.12011,
+ "lat": 40.80788
+ }
+ },
+ {
+ "id": 5393180,
+ "name": "Santa Maria",
+ "country": "US",
+ "coord": {
+ "lon": -120.435722,
+ "lat": 34.95303
+ }
+ },
+ {
+ "id": 4839822,
+ "name": "Norwalk",
+ "country": "US",
+ "coord": {
+ "lon": -73.407898,
+ "lat": 41.117599
+ }
+ },
+ {
+ "id": 4490315,
+ "name": "Sandy Springs",
+ "country": "US",
+ "coord": {
+ "lon": -77.944153,
+ "lat": 35.332661
+ }
+ },
+ {
+ "id": 5731371,
+ "name": "Hillsboro",
+ "country": "US",
+ "coord": {
+ "lon": -122.98983,
+ "lat": 45.522888
+ }
+ },
+ {
+ "id": 4580391,
+ "name": "Green Bay",
+ "country": "US",
+ "coord": {
+ "lon": -79.755363,
+ "lat": 33.014889
+ }
+ },
+ {
+ "id": 4738214,
+ "name": "Tyler",
+ "country": "US",
+ "coord": {
+ "lon": -95.301064,
+ "lat": 32.351261
+ }
+ },
+ {
+ "id": 4741752,
+ "name": "Wichita Falls",
+ "country": "US",
+ "coord": {
+ "lon": -98.493393,
+ "lat": 33.913712
+ }
+ },
+ {
+ "id": 4706057,
+ "name": "Lewisville",
+ "country": "US",
+ "coord": {
+ "lon": -96.994171,
+ "lat": 33.04623
+ }
+ },
+ {
+ "id": 4885983,
+ "name": "Burbank",
+ "country": "US",
+ "coord": {
+ "lon": -87.779503,
+ "lat": 41.733921
+ }
+ },
+ {
+ "id": 5577592,
+ "name": "Greeley",
+ "country": "US",
+ "coord": {
+ "lon": -104.709129,
+ "lat": 40.423309
+ }
+ },
+ {
+ "id": 5392423,
+ "name": "San Mateo",
+ "country": "US",
+ "coord": {
+ "lon": -122.325531,
+ "lat": 37.562988
+ }
+ },
+ {
+ "id": 5345529,
+ "name": "El Cajon",
+ "country": "US",
+ "coord": {
+ "lon": -116.962532,
+ "lat": 32.794769
+ }
+ },
+ {
+ "id": 5387288,
+ "name": "Rialto",
+ "country": "US",
+ "coord": {
+ "lon": -117.370323,
+ "lat": 34.1064
+ }
+ },
+ {
+ "id": 4853423,
+ "name": "Davenport",
+ "country": "US",
+ "coord": {
+ "lon": -90.577637,
+ "lat": 41.52364
+ }
+ },
+ {
+ "id": 4705692,
+ "name": "League City",
+ "country": "US",
+ "coord": {
+ "lon": -95.094933,
+ "lat": 29.50745
+ }
+ },
+ {
+ "id": 5097529,
+ "name": "Edison",
+ "country": "US",
+ "coord": {
+ "lon": -74.412102,
+ "lat": 40.518719
+ }
+ },
+ {
+ "id": 4152820,
+ "name": "Davie",
+ "country": "US",
+ "coord": {
+ "lon": -80.233101,
+ "lat": 26.06287
+ }
+ },
+ {
+ "id": 5475352,
+ "name": "Las Cruces",
+ "country": "US",
+ "coord": {
+ "lon": -106.778343,
+ "lat": 32.312321
+ }
+ },
+ {
+ "id": 4926563,
+ "name": "South Bend",
+ "country": "US",
+ "coord": {
+ "lon": -86.250008,
+ "lat": 41.68338
+ }
+ }
+]
\ No newline at end of file
--- /dev/null
+[
+ {
+ "Code": "AD"
+ },
+ {
+ "Code": "AE"
+ },
+ {
+ "Code": "AF"
+ },
+ {
+ "Code": "AG"
+ },
+ {
+ "Code": "AI"
+ },
+ {
+ "Code": "AL"
+ },
+ {
+ "Code": "AM"
+ },
+ {
+ "Code": "AO"
+ },
+ {
+ "Code": "AQ"
+ },
+ {
+ "Code": "AR"
+ },
+ {
+ "Code": "AS"
+ },
+ {
+ "Code": "AT"
+ },
+ {
+ "Code": "AU"
+ },
+ {
+ "Code": "AW"
+ },
+ {
+ "Code": "AX"
+ },
+ {
+ "Code": "AZ"
+ },
+ {
+ "Code": "BA"
+ },
+ {
+ "Code": "BB"
+ },
+ {
+ "Code": "BD"
+ },
+ {
+ "Code": "BE"
+ },
+ {
+ "Code": "BF"
+ },
+ {
+ "Code": "BG"
+ },
+ {
+ "Code": "BH"
+ },
+ {
+ "Code": "BI"
+ },
+ {
+ "Code": "BJ"
+ },
+ {
+ "Code": "BL"
+ },
+ {
+ "Code": "BM"
+ },
+ {
+ "Code": "BN"
+ },
+ {
+ "Code": "BO"
+ },
+ {
+ "Code": "BQ"
+ },
+ {
+ "Code": "BR"
+ },
+ {
+ "Code": "BS"
+ },
+ {
+ "Code": "BT"
+ },
+ {
+ "Code": "BV"
+ },
+ {
+ "Code": "BW"
+ },
+ {
+ "Code": "BY"
+ },
+ {
+ "Code": "BZ"
+ },
+ {
+ "Code": "CA"
+ },
+ {
+ "Code": "CC"
+ },
+ {
+ "Code": "CD"
+ },
+ {
+ "Code": "CF"
+ },
+ {
+ "Code": "CG"
+ },
+ {
+ "Code": "CH"
+ },
+ {
+ "Code": "CI"
+ },
+ {
+ "Code": "CK"
+ },
+ {
+ "Code": "CL"
+ },
+ {
+ "Code": "CM"
+ },
+ {
+ "Code": "CN"
+ },
+ {
+ "Code": "CO"
+ },
+ {
+ "Code": "CR"
+ },
+ {
+ "Code": "CU"
+ },
+ {
+ "Code": "CV"
+ },
+ {
+ "Code": "CW"
+ },
+ {
+ "Code": "CX"
+ },
+ {
+ "Code": "CY"
+ },
+ {
+ "Code": "CZ"
+ },
+ {
+ "Code": "DE"
+ },
+ {
+ "Code": "DJ"
+ },
+ {
+ "Code": "DK"
+ },
+ {
+ "Code": "DM"
+ },
+ {
+ "Code": "DO"
+ },
+ {
+ "Code": "DZ"
+ },
+ {
+ "Code": "EC"
+ },
+ {
+ "Code": "EE"
+ },
+ {
+ "Code": "EG"
+ },
+ {
+ "Code": "EH"
+ },
+ {
+ "Code": "ER"
+ },
+ {
+ "Code": "ES"
+ },
+ {
+ "Code": "ET"
+ },
+ {
+ "Code": "FI"
+ },
+ {
+ "Code": "FJ"
+ },
+ {
+ "Code": "FK"
+ },
+ {
+ "Code": "FM"
+ },
+ {
+ "Code": "FO"
+ },
+ {
+ "Code": "FR"
+ },
+ {
+ "Code": "GA"
+ },
+ {
+ "Code": "GB"
+ },
+ {
+ "Code": "GD"
+ },
+ {
+ "Code": "GE"
+ },
+ {
+ "Code": "GF"
+ },
+ {
+ "Code": "GG"
+ },
+ {
+ "Code": "GH"
+ },
+ {
+ "Code": "GI"
+ },
+ {
+ "Code": "GL"
+ },
+ {
+ "Code": "GM"
+ },
+ {
+ "Code": "GN"
+ },
+ {
+ "Code": "GP"
+ },
+ {
+ "Code": "GQ"
+ },
+ {
+ "Code": "GR"
+ },
+ {
+ "Code": "GS"
+ },
+ {
+ "Code": "GT"
+ },
+ {
+ "Code": "GU"
+ },
+ {
+ "Code": "GW"
+ },
+ {
+ "Code": "GY"
+ },
+ {
+ "Code": "HK"
+ },
+ {
+ "Code": "HM"
+ },
+ {
+ "Code": "HN"
+ },
+ {
+ "Code": "HR"
+ },
+ {
+ "Code": "HT"
+ },
+ {
+ "Code": "HU"
+ },
+ {
+ "Code": "ID"
+ },
+ {
+ "Code": "IE"
+ },
+ {
+ "Code": "IL"
+ },
+ {
+ "Code": "IM"
+ },
+ {
+ "Code": "IN"
+ },
+ {
+ "Code": "IO"
+ },
+ {
+ "Code": "IQ"
+ },
+ {
+ "Code": "IR"
+ },
+ {
+ "Code": "IS"
+ },
+ {
+ "Code": "IT"
+ },
+ {
+ "Code": "JE"
+ },
+ {
+ "Code": "JM"
+ },
+ {
+ "Code": "JO"
+ },
+ {
+ "Code": "JP"
+ },
+ {
+ "Code": "KE"
+ },
+ {
+ "Code": "KG"
+ },
+ {
+ "Code": "KH"
+ },
+ {
+ "Code": "KI"
+ },
+ {
+ "Code": "KM"
+ },
+ {
+ "Code": "KN"
+ },
+ {
+ "Code": "KP"
+ },
+ {
+ "Code": "KR"
+ },
+ {
+ "Code": "KW"
+ },
+ {
+ "Code": "KY"
+ },
+ {
+ "Code": "KZ"
+ },
+ {
+ "Code": "LA"
+ },
+ {
+ "Code": "LB"
+ },
+ {
+ "Code": "LC"
+ },
+ {
+ "Code": "LI"
+ },
+ {
+ "Code": "LK"
+ },
+ {
+ "Code": "LR"
+ },
+ {
+ "Code": "LS"
+ },
+ {
+ "Code": "LT"
+ },
+ {
+ "Code": "LU"
+ },
+ {
+ "Code": "LV"
+ },
+ {
+ "Code": "LY"
+ },
+ {
+ "Code": "MA"
+ },
+ {
+ "Code": "MC"
+ },
+ {
+ "Code": "MD"
+ },
+ {
+ "Code": "ME"
+ },
+ {
+ "Code": "MF"
+ },
+ {
+ "Code": "MG"
+ },
+ {
+ "Code": "MH"
+ },
+ {
+ "Code": "MK"
+ },
+ {
+ "Code": "ML"
+ },
+ {
+ "Code": "MM"
+ },
+ {
+ "Code": "MN"
+ },
+ {
+ "Code": "MO"
+ },
+ {
+ "Code": "MP"
+ },
+ {
+ "Code": "MQ"
+ },
+ {
+ "Code": "MR"
+ },
+ {
+ "Code": "MS"
+ },
+ {
+ "Code": "MT"
+ },
+ {
+ "Code": "MU"
+ },
+ {
+ "Code": "MV"
+ },
+ {
+ "Code": "MW"
+ },
+ {
+ "Code": "MX"
+ },
+ {
+ "Code": "MY"
+ },
+ {
+ "Code": "MZ"
+ },
+ {
+ "Code": "NA"
+ },
+ {
+ "Code": "NC"
+ },
+ {
+ "Code": "NE"
+ },
+ {
+ "Code": "NF"
+ },
+ {
+ "Code": "NG"
+ },
+ {
+ "Code": "NI"
+ },
+ {
+ "Code": "NL"
+ },
+ {
+ "Code": "NO"
+ },
+ {
+ "Code": "NP"
+ },
+ {
+ "Code": "NR"
+ },
+ {
+ "Code": "NU"
+ },
+ {
+ "Code": "NZ"
+ },
+ {
+ "Code": "OM"
+ },
+ {
+ "Code": "PA"
+ },
+ {
+ "Code": "PE"
+ },
+ {
+ "Code": "PF"
+ },
+ {
+ "Code": "PG"
+ },
+ {
+ "Code": "PH"
+ },
+ {
+ "Code": "PK"
+ },
+ {
+ "Code": "PL"
+ },
+ {
+ "Code": "PM"
+ },
+ {
+ "Code": "PN"
+ },
+ {
+ "Code": "PR"
+ },
+ {
+ "Code": "PS"
+ },
+ {
+ "Code": "PT"
+ },
+ {
+ "Code": "PW"
+ },
+ {
+ "Code": "PY"
+ },
+ {
+ "Code": "QA"
+ },
+ {
+ "Code": "RE"
+ },
+ {
+ "Code": "RO"
+ },
+ {
+ "Code": "RS"
+ },
+ {
+ "Code": "RU"
+ },
+ {
+ "Code": "RW"
+ },
+ {
+ "Code": "SA"
+ },
+ {
+ "Code": "SB"
+ },
+ {
+ "Code": "SC"
+ },
+ {
+ "Code": "SD"
+ },
+ {
+ "Code": "SE"
+ },
+ {
+ "Code": "SG"
+ },
+ {
+ "Code": "SH"
+ },
+ {
+ "Code": "SI"
+ },
+ {
+ "Code": "SJ"
+ },
+ {
+ "Code": "SK"
+ },
+ {
+ "Code": "SL"
+ },
+ {
+ "Code": "SM"
+ },
+ {
+ "Code": "SN"
+ },
+ {
+ "Code": "SO"
+ },
+ {
+ "Code": "SR"
+ },
+ {
+ "Code": "ST"
+ },
+ {
+ "Code": "SV"
+ },
+ {
+ "Code": "SX"
+ },
+ {
+ "Code": "SY"
+ },
+ {
+ "Code": "SZ"
+ },
+ {
+ "Code": "TC"
+ },
+ {
+ "Code": "TD"
+ },
+ {
+ "Code": "TF"
+ },
+ {
+ "Code": "TG"
+ },
+ {
+ "Code": "TH"
+ },
+ {
+ "Code": "TJ"
+ },
+ {
+ "Code": "TK"
+ },
+ {
+ "Code": "TL"
+ },
+ {
+ "Code": "TM"
+ },
+ {
+ "Code": "TN"
+ },
+ {
+ "Code": "TO"
+ },
+ {
+ "Code": "TR"
+ },
+ {
+ "Code": "TT"
+ },
+ {
+ "Code": "TV"
+ },
+ {
+ "Code": "TW"
+ },
+ {
+ "Code": "TZ"
+ },
+ {
+ "Code": "UA"
+ },
+ {
+ "Code": "UG"
+ },
+ {
+ "Code": "UM"
+ },
+ {
+ "Code": "US"
+ },
+ {
+ "Code": "UY"
+ },
+ {
+ "Code": "UZ"
+ },
+ {
+ "Code": "VA"
+ },
+ {
+ "Code": "VC"
+ },
+ {
+ "Code": "VE"
+ },
+ {
+ "Code": "VG"
+ },
+ {
+ "Code": "VI"
+ },
+ {
+ "Code": "VN"
+ },
+ {
+ "Code": "VU"
+ },
+ {
+ "Code": "WF"
+ },
+ {
+ "Code": "WS"
+ },
+ {
+ "Code": "YE"
+ },
+ {
+ "Code": "YT"
+ },
+ {
+ "Code": "ZA"
+ },
+ {
+ "Code": "ZM"
+ },
+ {
+ "Code": "ZW"
+ }
+]
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Newtonsoft.Json;
+
+namespace Weather.Models.Location
+{
+ /// <summary>
+ /// Single city model.
+ /// </summary>
+ public class City
+ {
+ #region properties
+
+ /// <summary>
+ /// Id of the city.
+ /// </summary>
+ [JsonProperty(PropertyName = "id")]
+ public int Id { get; set; }
+
+ /// <summary>
+ /// Name of the city.
+ /// </summary>
+ [JsonProperty(PropertyName = "name")]
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Country code in ISO 3166 format.
+ /// </summary>
+ [JsonProperty(PropertyName = "country")]
+ public string CountryCode { get; set; }
+
+ /// <summary>
+ /// Coordinates of the city.
+ /// </summary>
+ [JsonProperty(PropertyName = "coord")]
+ public Coordinates Coordinates { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Newtonsoft.Json;
+
+namespace Weather.Models.Location
+{
+ /// <summary>
+ /// Coordinates of a city.
+ /// </summary>
+ public class Coordinates
+ {
+ #region properties
+
+ /// <summary>
+ /// North-South position of a city on the Earth's surface.
+ /// </summary>
+ [JsonProperty(PropertyName = "lon")]
+ public double Longitude { get; set; }
+
+ /// <summary>
+ /// East-west position of a city on the Earth's surface.
+ /// </summary>
+ [JsonProperty(PropertyName = "lat")]
+ public double Latitude { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Newtonsoft.Json;
+
+namespace Weather.Models.Location
+{
+ /// <summary>
+ /// Single country model.
+ /// </summary>
+ public class Country
+ {
+ /// <summary>
+ /// Country code in ISO 3166 format.
+ /// </summary>
+ [JsonProperty(PropertyName = "Code")]
+ public string Code { get; set; }
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Newtonsoft.Json;
+using Xamarin.Forms;
+
+namespace Weather.Models.Location
+{
+ /// <summary>
+ /// Time zone model.
+ /// </summary>
+ public class TimeZone : BindableObject
+ {
+ #region properties
+
+ /// <summary>
+ /// Bindable property that allows to set timestamp offset of timezone.
+ /// </summary>
+ public static readonly BindableProperty OffsetProperty =
+ BindableProperty.Create(nameof(Offset), typeof(int), typeof(TimeZone), default(int));
+
+ /// <summary>
+ /// Gets or sets timestamp offset of timezone.
+ /// </summary>
+ [JsonProperty(PropertyName = "rawOffset")]
+ public int Offset
+ {
+ get => (int)GetValue(OffsetProperty);
+ set
+ {
+ SetValue(OffsetProperty, value);
+ OnPropertyChanged();
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+using Newtonsoft.Json;
+
+namespace Weather.Models.Weather
+{
+ /// <summary>
+ /// Class containing data about cloudiness.
+ /// </summary>
+ public class Clouds
+ {
+ #region properties
+
+ /// <summary>
+ /// Cloudiness in percent.
+ /// </summary>
+ [JsonProperty(PropertyName = "all")]
+ public double Percent { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Weather.Models.Location;
+
+namespace Weather.Models.Weather
+{
+ /// <summary>
+ /// Current weather model.
+ /// </summary>
+ public class CurrentWeather
+ {
+ #region properties
+
+ /// <summary>
+ /// List of base weather properties, e.g. "Clear sky" etc.
+ /// </summary>
+ [JsonProperty(PropertyName = "weather")]
+ public IList<WeatherBase> Weather { get; set; }
+
+ /// <summary>
+ /// Weather data, e.g. temperature, pressure, etc.
+ /// </summary>
+ [JsonProperty(PropertyName = "main")]
+ public WeatherData WeatherData { get; set; }
+
+ /// <summary>
+ /// Wind properties, e.g. speed.
+ /// </summary>
+ [JsonProperty(PropertyName = "wind")]
+ public Wind Wind { get; set; }
+
+ /// <summary>
+ /// Cloudiness data.
+ /// </summary>
+ [JsonProperty(PropertyName = "clouds")]
+ public Clouds Clouds { get; set; }
+
+ /// <summary>
+ /// Time stamp of the weather measurement.
+ /// </summary>
+ [JsonProperty(PropertyName = "dt")]
+ public ulong TimeStamp { get; set; }
+
+ /// <summary>
+ /// Sunrise and sunset times.
+ /// </summary>
+ [JsonProperty(PropertyName = "sys")]
+ public SunData SunData { get; set; }
+
+ /// <summary>
+ /// City name.
+ /// </summary>
+ [JsonProperty(PropertyName = "name")]
+ public string CityName { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Weather.Models.Weather
+{
+ /// <summary>
+ /// Forecast model.
+ /// </summary>
+ public class Forecast
+ {
+ /// <summary>
+ /// List of weather data for next days.
+ /// Obtains data from "OpenWeatherMap" API.
+ /// </summary>
+ [JsonProperty(PropertyName = "list")]
+ public IList<CurrentWeather> WeatherList { get; set; }
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Newtonsoft.Json;
+
+namespace Weather.Models.Weather
+{
+ /// <summary>
+ /// Class containing additional weather data.
+ /// </summary>
+ public class SunData
+ {
+ #region properties
+
+ /// <summary>
+ /// Time stamp of sunrise.
+ /// </summary>
+ [JsonProperty(PropertyName = "sunrise")]
+ public ulong SunriseTimeStamp { get; set; }
+
+ /// <summary>
+ /// Time stamp of sunset.
+ /// </summary>
+ [JsonProperty(PropertyName = "sunset")]
+ public ulong SunsetTimeStamp { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Newtonsoft.Json;
+
+namespace Weather.Models.Weather
+{
+ /// <summary>
+ /// Class containing basic weather information.
+ /// </summary>
+ public class WeatherBase
+ {
+ #region properties
+
+ /// <summary>
+ /// Weather condition id.
+ /// </summary>
+ [JsonProperty(PropertyName = "id")]
+ public int Id { get; set; }
+
+ /// <summary>
+ /// Weather condition (e.g. Rain, Snow, Clear).
+ /// </summary>
+ [JsonProperty(PropertyName = "main")]
+ public string Condition { get; set; }
+
+ /// <summary>
+ /// Weather condition within the group.
+ /// </summary>
+ [JsonProperty(PropertyName = "description")]
+ public string Description { get; set; }
+
+ /// <summary>
+ /// Weather icon id.
+ /// </summary>
+ [JsonProperty(PropertyName = "icon")]
+ public string Icon { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Newtonsoft.Json;
+
+namespace Weather.Models.Weather
+{
+ /// <summary>
+ /// Class containing weather data.
+ /// </summary>
+ public class WeatherData
+ {
+ #region properties
+
+ /// <summary>
+ /// Temperature in degrees Celsius.
+ /// </summary>
+ [JsonProperty(PropertyName = "temp")]
+ public double Temperature { get; set; }
+
+ /// <summary>
+ /// Minimum temperature at the moment. Significant for large cities.
+ /// </summary>
+ [JsonProperty(PropertyName = "temp_min")]
+ public double MinimumTemperature { get; set; }
+
+ /// <summary>
+ /// Maximum temperature at the moment. Significant for large cities.
+ /// </summary>
+ [JsonProperty(PropertyName = "temp_max")]
+ public double MaximumTemperature { get; set; }
+
+ /// <summary>
+ /// Atmospheric pressure in hPa.
+ /// </summary>
+ [JsonProperty(PropertyName = "pressure")]
+ public double Pressure { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Newtonsoft.Json;
+
+namespace Weather.Models.Weather
+{
+ /// <summary>
+ /// Class containing wind data.
+ /// </summary>
+ public class Wind
+ {
+ #region properties
+
+ /// <summary>
+ /// Wind speed in meters per second.
+ /// </summary>
+ [JsonProperty(PropertyName = "speed")]
+ public double Speed { get; set; }
+
+ /// <summary>
+ /// Wind direction in degrees.
+ /// </summary>
+ [JsonProperty(PropertyName = "deg")]
+ public double Degree { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Collections.Generic;
+using System.Linq;
+using Weather.Models.Location;
+
+namespace Weather.Service
+{
+ /// <summary>
+ /// Class that manages supported cities.
+ /// </summary>
+ public class CityProvider : ICityProvider
+ {
+ #region properties
+
+ /// <summary>
+ /// List of supported cities with their data.
+ /// </summary>
+ public IQueryable<City> CityList { get; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Class constructor which allows to set supported cities.
+ /// </summary>
+ /// <param name="cityList">Supported city list.</param>
+ public CityProvider(IQueryable<City> cityList)
+ {
+ CityList = cityList;
+ }
+
+ /// <summary>
+ /// Indicates if given cityName is in supported city list.
+ /// </summary>
+ /// <param name="cityName">Name of the city to check.</param>
+ /// <returns>True if city is found in the list, otherwise false is returned.</returns>
+ public bool Validate(string cityName)
+ {
+ return CityList.Any(x => x.Name.Equals(cityName));
+ }
+
+ /// <summary>
+ /// Finds first n cities that start with given text.
+ /// </summary>
+ /// <param name="text">Search condition.</param>
+ /// <param name="n">Maximum number of cities that will be returned.</param>
+ /// <returns>List of cities if found, otherwise null is returned.</returns>
+ public IList<City> FindCity(string text, int n)
+ {
+ return CityList.Where(cityItem => cityItem.Name.ToLower().StartsWith(text.ToLower())).Take(n).ToList();
+ }
+
+ /// <summary>
+ /// Finds first n cities that starts with given text.
+ /// Finds cities only in country specified with country code in ISO-3166 format.
+ /// </summary>
+ /// <param name="text">Search condition.</param>
+ /// <param name="countryCode">Country code in ISO-3166 format.</param>
+ /// <param name="n">Maximum number of cities that will be returned.</param>
+ /// <returns>List of cities if found otherwise returns null.</returns>
+ public IList<City> FindCity(string text, string countryCode, int n)
+ {
+ return CityList
+ .Where(cityItem => cityItem.Name.ToLower().StartsWith(text.ToLower()) && cityItem.CountryCode.Equals(countryCode)).Take(n)
+ .ToList();
+ }
+
+
+ /// <summary>
+ /// Gets first occurrence of city with given name.
+ /// </summary>
+ /// <param name="cityName">Name of the city.</param>
+ /// <returns>City with given name otherwise returns null.</returns>
+ public City GetCiy(string cityName) => CityList.FirstOrDefault(x => x.Name.Equals(cityName));
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Linq;
+using System.Text.RegularExpressions;
+using Weather.Models.Location;
+
+namespace Weather.Service
+{
+ /// <summary>
+ /// Class that manages supported countries.
+ /// </summary>
+ public class CountryProvider : ICountryProvider
+ {
+ #region fields
+
+ /// <summary>
+ /// Country code validation rule.
+ /// </summary>
+ private const string COUNTRY_REGEX = "^[A-Z]{2}$";
+
+ #endregion
+
+ #region properties
+
+ /// <summary>
+ /// Supported country list.
+ /// </summary>
+ public IQueryable<Country> CountryList { get; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Class constructor which allows to set supported country list.
+ /// </summary>
+ /// <param name="countryList">Supported country list.</param>
+ public CountryProvider(IQueryable<Country> countryList)
+ {
+ CountryList = countryList;
+ }
+
+ /// <summary>
+ /// Checks if given countryCode is in accordance with ISO 3166 standard.
+ /// </summary>
+ /// <param name="countryCode">Country Code.</param>
+ /// <returns>True if code is valid, otherwise false is returned.</returns>
+ public bool Validate(string countryCode)
+ {
+ return Regex.IsMatch(countryCode, COUNTRY_REGEX) && CountryList.Any(x => x.Code.Equals(countryCode));
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Collections.Generic;
+using System.Linq;
+using Weather.Models.Location;
+
+namespace Weather.Service
+{
+ /// <summary>
+ /// Interface that contains all necessary tools to manage supported cities.
+ /// </summary>
+ public interface ICityProvider
+ {
+ #region properties
+
+ /// <summary>
+ /// List of supported cities with their data.
+ /// </summary>
+ IQueryable<City> CityList { get; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Indicates if given cityName is in supported city list.
+ /// </summary>
+ /// <param name="cityName">Name of the city to check.</param>
+ /// <returns>True if city is found in the list otherwise returns false.</returns>
+ bool Validate(string cityName);
+
+ /// <summary>
+ /// Finds first n cities that starts with given text.
+ /// </summary>
+ /// <param name="text">Search condition.</param>
+ /// <param name="n">Maximum number of cities that will be returned.</param>
+ /// <returns>List of cities if found otherwise returns null.</returns>
+ IList<City> FindCity(string text, int n);
+
+ /// <summary>
+ /// Finds first n cities that starts with given text.
+ /// Finds cities only in country specified with country code in ISO-3166 format.
+ /// </summary>
+ /// <param name="text">Search condition.</param>
+ /// <param name="countryCode">Country code in ISO-3166 format.</param>
+ /// <param name="n">Maximum number of cities that will be returned.</param>
+ /// <returns>List of cities if found otherwise returns null.</returns>
+ IList<City> FindCity(string text, string countryCode, int n);
+
+ /// <summary>
+ /// Gets first occurrence of city with given name.
+ /// </summary>
+ /// <param name="cityName">Name of the city.</param>
+ /// <returns>City with given name otherwise returns null.</returns>
+ City GetCiy(string cityName);
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Linq;
+using Weather.Models.Location;
+
+namespace Weather.Service
+{
+ /// <summary>
+ /// Interface that contains all necessary tools to manage supported countries.
+ /// </summary>
+ public interface ICountryProvider
+ {
+ #region properties
+
+ /// <summary>
+ /// List of codes of all supported countries.
+ /// </summary>
+ IQueryable<Country> CountryList { get; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Checks if given country code is valid.
+ /// </summary>
+ /// <param name="countryCode">Country code.</param>
+ /// <returns>Returns true if code is valid, otherwise returns false.</returns>
+ bool Validate(string countryCode);
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Xamarin.Forms;
+
+namespace Weather.Utils
+{
+ /// <summary>
+ /// Helper class that allows to bind string value.
+ /// </summary>
+ public class BindableString : BindableObject
+ {
+ #region properties
+
+ /// <summary>
+ /// Bindable property that allows to set value of string.
+ /// </summary>
+ public static readonly BindableProperty ValueProperty =
+ BindableProperty.Create(nameof(Value), typeof(string), typeof(BindableString), string.Empty);
+
+ /// <summary>
+ /// Gets or sets value of string.
+ /// </summary>
+ public string Value
+ {
+ get => GetValue(ValueProperty).ToString();
+ set => SetValue(ValueProperty, value);
+ }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Returns string value instead of class string representation.
+ /// </summary>
+ /// <returns>String value.</returns>
+ public override string ToString()
+ {
+ return Value;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Linq;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+
+namespace Weather.Utils
+{
+ /// <summary>
+ /// Class responsible for handling HTTP errors and displaying information about them.
+ /// </summary>
+ public static class ErrorHandler
+ {
+ #region fields
+
+ /// <summary>
+ /// Navigation context.
+ /// </summary>
+ private static readonly INavigation _navigation;
+
+ /// <summary>
+ /// Indicates if any error was handled.
+ /// Avoids handling multiple error at once.
+ /// </summary>
+ private static bool _isHandled;
+
+ #endregion
+
+ #region properties
+
+ /// <summary>
+ /// HTTP status code.
+ /// </summary>
+ public static int Code { get; private set; }
+
+ /// <summary>
+ /// Message provided with exception.
+ /// </summary>
+ public static string Message { get; private set; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Default constructor.
+ /// </summary>
+ static ErrorHandler()
+ {
+ _navigation = Application.Current.MainPage.Navigation;
+ }
+
+ /// <summary>
+ /// Handles exception.
+ /// </summary>
+ /// <param name="code">HTTP status code.</param>
+ /// <param name="message">Message provided with exception.</param>
+ /// <returns>Async task.</returns>
+ public static async Task HandleException(int code, string message)
+ {
+ if (_isHandled)
+ {
+ return;
+ }
+
+ Code = code;
+ Message = message;
+
+ await _navigation.PushAsync(new Views.ApiErrorPage());
+
+ RemoveExistingPages();
+
+ _isHandled = true;
+ }
+
+ /// <summary>
+ /// Removes all pages from navigation stack except error page.
+ /// </summary>
+ private static void RemoveExistingPages()
+ {
+ var existingPages = _navigation.NavigationStack.ToList();
+
+ foreach (var page in existingPages)
+ {
+ if (page.GetType() != typeof(Views.ApiErrorPage))
+ {
+ _navigation.RemovePage(page);
+ }
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Net;
+using System.Net.Http;
+
+namespace Weather.Utils
+{
+ /// <summary>
+ /// Exception that is thrown on HTTP errors.
+ /// </summary>
+ public class HttpException : HttpRequestException
+ {
+ #region properties
+
+ /// <summary>
+ /// HTTP status code.
+ /// </summary>
+ public HttpStatusCode StatusCode { get; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Default constructor.
+ /// </summary>
+ /// <param name="code">HTTP status code of error.</param>
+ public HttpException(HttpStatusCode code)
+ {
+ StatusCode = code;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Threading.Tasks;
+
+namespace Weather.Utils
+{
+ /// <summary>
+ /// Provides functionality to get response from Web API.
+ /// </summary>
+ /// <typeparam name="T">The type of expected object from API.</typeparam>
+ public interface IRequest<T>
+ {
+ #region properties
+
+ /// <summary>
+ /// URI of the Web service.
+ /// </summary>
+ string RequestUri { get; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Adds parameter to the URI.
+ /// </summary>
+ /// <param name="name">Name of the parameter.</param>
+ /// <param name="value">Value of the parameter.</param>
+ void AddParameter(string name, string value);
+
+ /// <summary>
+ /// Sends HTTP request using GET method.
+ /// </summary>
+ /// <returns>Response from service.</returns>
+ Task<T> Get();
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.IO;
+using System.Reflection;
+using Newtonsoft.Json;
+
+namespace Weather.Utils
+{
+ /// <summary>
+ /// Class that reads JSON file and converts it to given type.
+ /// </summary>
+ /// <typeparam name="T">JSON object type.</typeparam>
+ public class JsonFileReader<T>
+ {
+ #region fields
+
+ /// <summary>
+ /// Namespace of the file.
+ /// </summary>
+ private readonly string _fileNameSpace;
+
+ /// <summary>
+ /// File name.
+ /// </summary>
+ private readonly string _fileName;
+
+ #endregion
+
+ #region properties
+
+ /// <summary>
+ /// File content in T format.
+ /// </summary>
+ /// <remarks>Before calling Read methods, it is always null.</remarks>
+ public T Result { get; set; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Class constructor, which allows to specify file location and name.
+ /// </summary>
+ /// <param name="fileNameSpace">Namespace of the file.</param>
+ /// <param name="fileName">Name of the file.</param>
+ public JsonFileReader(string fileNameSpace, string fileName)
+ {
+ _fileNameSpace = fileNameSpace;
+ _fileName = fileName;
+ }
+
+ /// <summary>
+ /// Reads the file.
+ /// </summary>
+ public virtual void Read()
+ {
+ var assembly = typeof(JsonFileReader<T>).GetTypeInfo().Assembly;
+
+ using (var stream = assembly.GetManifestResourceStream(_fileNameSpace + _fileName))
+ using (var streamReader = new StreamReader(stream))
+ using (var reader = new JsonTextReader(streamReader))
+ {
+ var serializer = JsonSerializer.Create();
+ Result = serializer.Deserialize<T>(reader);
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Weather.Utils
+{
+ /// <summary>
+ /// Class that allows binding to task.
+ /// After task is completed it notify view about it.
+ /// </summary>
+ /// <typeparam name="T">Type of the result of the task.</typeparam>
+ public class NotificationTask<T> : INotifyPropertyChanged
+ {
+ #region properties
+
+ /// <summary>
+ /// Task that will be executed.
+ /// </summary>
+ public Task<T> Task { get; }
+
+ /// <summary>
+ /// Gets result of the task.
+ /// </summary>
+ public T Result => Task.Status == TaskStatus.RanToCompletion ? Task.Result : default(T);
+
+ /// <summary>
+ /// Gets status of the task.
+ /// </summary>
+ public TaskStatus Status => Task.Status;
+
+ /// <summary>
+ /// Indicates if task is completed.
+ /// </summary>
+ public bool IsCompleted => Task.IsCompleted;
+
+ /// <summary>
+ /// Indicates if task is completed successfully.
+ /// </summary>
+ public bool IsSuccessfullyCompleted => Task.Status == TaskStatus.RanToCompletion;
+
+ /// <summary>
+ /// Indicates if task is not completed.
+ /// </summary>
+ public bool IsNotCompleted => !Task.IsCompleted;
+
+ /// <summary>
+ /// Indicates if task is cancelled.
+ /// </summary>
+ public bool IsCanceled => Task.IsCanceled;
+
+ /// <summary>
+ /// Indicates if task is faulted.
+ /// </summary>
+ public bool IsFaulted => Task.IsFaulted;
+
+ /// <summary>
+ /// Gets all exceptions from execution of the task.
+ /// </summary>
+ public AggregateException Exception => Task.Exception;
+
+ /// <summary>
+ /// Gets current exception from execution of the task.
+ /// </summary>
+ public Exception InnerException => Exception?.InnerException;
+
+ /// <summary>
+ /// Event that informs view about property change.
+ /// </summary>
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Default class constructor.
+ /// </summary>
+ /// <param name="task">Task to be executed.</param>
+ public NotificationTask(Task<T> task)
+ {
+ Task = task;
+
+ if (!task.IsCompleted)
+ {
+ var taskAsync = ExecuteTaskAsync(task);
+ }
+ }
+
+ /// <summary>
+ /// Executes task and notifies view about changes of its properties.
+ /// </summary>
+ /// <param name="task">Task to be executed.</param>
+ /// <returns>Task that was executed.</returns>
+ private async Task ExecuteTaskAsync(Task task)
+ {
+ try
+ {
+ await task;
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ }
+
+ var propertyChanged = PropertyChanged;
+ if (propertyChanged == null)
+ {
+ return;
+ }
+
+ propertyChanged(this, new PropertyChangedEventArgs(nameof(Status)));
+ propertyChanged(this, new PropertyChangedEventArgs(nameof(IsCompleted)));
+ propertyChanged(this, new PropertyChangedEventArgs(nameof(IsNotCompleted)));
+
+ if (Task.IsCanceled)
+ {
+ propertyChanged(this, new PropertyChangedEventArgs(nameof(IsCanceled)));
+ }
+ else if (Task.IsFaulted)
+ {
+ propertyChanged(this, new PropertyChangedEventArgs(nameof(IsFaulted)));
+ propertyChanged(this, new PropertyChangedEventArgs(nameof(Exception)));
+ propertyChanged(this, new PropertyChangedEventArgs(nameof(InnerException)));
+ }
+ else
+ {
+ propertyChanged(this, new PropertyChangedEventArgs(nameof(IsSuccessfullyCompleted)));
+ propertyChanged(this, new PropertyChangedEventArgs(nameof(Result)));
+ }
+ }
+
+ /// <summary>
+ /// PropertyChanged event invoker.
+ /// </summary>
+ /// <param name="propertyName">Property name.</param>
+ protected virtual void OnPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace Weather.Utils
+{
+ /// <summary>
+ /// Class responsible for sending HTTP requests.
+ /// </summary>
+ /// <typeparam name="T">The type of expected object from API.</typeparam>
+ public class Request<T> : IRequest<T>
+ {
+ #region fields
+
+ /// <summary>
+ /// HTTP client.
+ /// </summary>
+ private readonly HttpClient _httpClient;
+
+ private bool _isFirstParameter = true;
+
+ #endregion
+
+ #region properties
+
+ /// <summary>
+ /// URI of the Web service.
+ /// </summary>
+ public string RequestUri { get; protected set; }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Class constructor that allows to set API key and address of the server.
+ /// </summary>
+ /// <param name="address">Server address.</param>
+ public Request(string address)
+ {
+ _httpClient = new HttpClient();
+ RequestUri = address;
+ }
+
+ /// <summary>
+ /// Adds parameter to the URI.
+ /// </summary>
+ /// <param name="name">Name of the parameter.</param>
+ /// <param name="value">Value of the parameter.</param>
+ public void AddParameter(string name, string value)
+ {
+ if (_isFirstParameter)
+ {
+ RequestUri += $"?{name}={value}";
+ _isFirstParameter = false;
+ }
+ else
+ {
+ RequestUri += $"&{name}={value}";
+ }
+ }
+
+ /// <summary>
+ /// Sends HTTP request using GET method.
+ /// </summary>
+ /// <returns>Response from service.</returns>
+ public async Task<T> Get()
+ {
+ var response = await _httpClient.GetAsync(RequestUri);
+
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new HttpException(response.StatusCode);
+ }
+
+ return ReadStream(await response.Content.ReadAsStreamAsync());
+ }
+
+ /// <summary>
+ /// Gets stream from HTTP client and deserializes it to the object.
+ /// </summary>
+ /// <param name="stream">Stream from client.</param>
+ /// <returns>
+ /// Returns type T object with response.
+ /// If deserializing was not successful returns default value of type T.
+ /// </returns>
+ private static T ReadStream(Stream stream)
+ {
+ using (var streamReader = new StreamReader(stream))
+ using (var reader = new JsonTextReader(streamReader))
+ {
+ var serializer = JsonSerializer.Create();
+ return serializer.Deserialize<T>(reader);
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+
+namespace Weather.Utils
+{
+ /// <summary>
+ /// Class that is responsible for converting timestamp.
+ /// </summary>
+ public static class TimeStamp
+ {
+ #region methods
+
+ /// <summary>
+ /// Converts timestamp to "DateTime" object.
+ /// </summary>
+ /// <param name="utcTimeStamp">UTC timestamp.</param>
+ /// <returns>"DateTime" object.</returns>
+ public static DateTime Convert(ulong utcTimeStamp)
+ {
+ var dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+ return dateTime.AddSeconds(utcTimeStamp);
+ }
+
+ /// <summary>
+ /// Converts timestamp to "DateTime" object.
+ /// </summary>
+ /// <param name="utcTimeStamp">UTC timestamp.</param>
+ /// <returns>"DateTime" object.</returns>
+ public static DateTime Convert(long utcTimeStamp)
+ {
+ var dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+ return dateTime.AddSeconds(utcTimeStamp);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Globalization;
+
+namespace Weather.Utils
+{
+ /// <summary>
+ /// Class that provide custom formatter for string.
+ /// </summary>
+ public class UnitFormatter : IFormatProvider, ICustomFormatter
+ {
+ #region methods
+
+ /// <summary>
+ /// Gets an object that provides formatting services for the specified type.
+ /// </summary>
+ /// <param name="formatType">An object that specifies the type of format object to return.</param>
+ /// <returns>An instance of the object specified by formatType.</returns>
+ public object GetFormat(Type formatType)
+ {
+ return formatType == typeof(ICustomFormatter) ? this : null;
+ }
+
+ /// <summary>
+ /// Converts the value of a specified object to an equivalent string representation.
+ /// </summary>
+ /// <param name="fmt">A format string containing formatting specifications.</param>
+ /// <param name="arg">An object to format.</param>
+ /// <param name="formatProvider">An object that supplies format information about the current instance.</param>
+ /// <returns>
+ /// The string representation of the arg, formatted as specified by format and formatProvider.
+ /// </returns>
+ public string Format(string fmt, object arg, IFormatProvider formatProvider)
+ {
+ if (arg == null)
+ {
+ return string.Empty;
+ }
+
+ switch (fmt)
+ {
+ case "temp":
+ {
+ var sign = RegionInfo.CurrentRegion.IsMetric ? "°C" : "°F";
+ return $"{arg:0.0}{sign}";
+ }
+
+ case "speed":
+ {
+ var sign = RegionInfo.CurrentRegion.IsMetric ? " m/s" : " mph";
+ return $"{arg:0.00}{sign}";
+ }
+
+ default:
+ {
+ return arg.ToString();
+ }
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+
+using Weather.Utils;
+using Xamarin.Forms;
+
+namespace Weather.ViewModels
+{
+ /// <summary>
+ /// ViewModel class for ApiErrorPage.
+ /// </summary>
+ public class ApiErrorViewModel
+ {
+ #region properties
+
+ /// <summary>
+ /// Gets HTTP status code.
+ /// </summary>
+ public int Code => ErrorHandler.Code;
+
+ /// <summary>
+ /// Gets message provided with exception.
+ /// </summary>
+ public string Message => ErrorHandler.Message ?? "None";
+
+ /// <summary>
+ /// Gets command that exits application.
+ /// </summary>
+ public Command ExitAppCommand { get; } = new Command(() => { Xamarin.Forms.Forms.Context.Exit(); });
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Threading.Tasks;
+using Weather.Config;
+using Weather.Models.Location;
+using Weather.Models.Weather;
+using Weather.Utils;
+using Xamarin.Forms;
+
+namespace Weather.ViewModels
+{
+ /// <summary>
+ /// ViewModel class for CurrentWeatherPage.
+ /// </summary>
+ public class CurrentWeatherViewModel : ViewModelBase
+ {
+ #region fields
+
+ /// <summary>
+ /// Local storage of command that initializes weather data.
+ /// </summary>
+ private Command _initializeCommand;
+
+ /// <summary>
+ /// Local storage of task that obtains current weather.
+ /// </summary>
+ private NotificationTask<CurrentWeather> _currentWeather;
+
+ /// <summary>
+ /// Local storage of city time zone.
+ /// </summary>
+ private NotificationTask<Models.Location.TimeZone> _cityTimeZone;
+
+ /// <summary>
+ /// Local storage of command that shows screen with forecast.
+ /// </summary>
+ private Command _checkForecastCommand;
+
+ /// <summary>
+ /// Local storage of forecast data.
+ /// </summary>
+ private NotificationTask<Forecast> _forecast;
+
+ #endregion
+
+ #region properties
+
+ /// <summary>
+ /// Bindable property that allows to set city data.
+ /// </summary>
+ public static readonly BindableProperty CityDataProperty =
+ BindableProperty.Create(nameof(CityData), typeof(City), typeof(CurrentWeatherViewModel), default(City));
+
+ /// <summary>
+ /// Bindable property that allows to set navigation context.
+ /// </summary>
+ public static readonly BindableProperty NavigationProperty =
+ BindableProperty.Create(nameof(Navigation), typeof(INavigation), typeof(MainPageViewModel), default(Type));
+
+ /// <summary>
+ /// Gets or sets city data.
+ /// View model holds weather data for this city.
+ /// </summary>
+ public City CityData
+ {
+ get => (City)GetValue(CityDataProperty);
+ set => SetValue(CityDataProperty, value);
+ }
+
+ /// <summary>
+ /// Gets or sets task that obtains current weather.
+ /// </summary>
+ public NotificationTask<CurrentWeather> CurrentWeather
+ {
+ get => _currentWeather;
+ set => SetProperty(ref _currentWeather, value);
+ }
+
+ /// <summary>
+ /// Gets or sets city time zone.
+ /// </summary>
+ public NotificationTask<Models.Location.TimeZone> CityTimeZone
+ {
+ get => _cityTimeZone;
+ set => SetProperty(ref _cityTimeZone, value);
+ }
+
+ /// <summary>
+ /// Gets or sets command that initializes weather data.
+ /// </summary>
+ public Command InitializeCommand
+ {
+ get => _initializeCommand;
+ set => SetProperty(ref _initializeCommand, value);
+ }
+
+ /// <summary>
+ /// Gets or sets command that shows screen with forecast.
+ /// </summary>
+ public Command CheckForecastCommand
+ {
+ get => _checkForecastCommand;
+ set => SetProperty(ref _checkForecastCommand, value);
+ }
+
+ /// <summary>
+ /// Gets or sets navigation context of application.
+ /// </summary>
+ public INavigation Navigation
+ {
+ get => (INavigation)GetValue(NavigationProperty);
+ set => SetValue(NavigationProperty, value);
+ }
+
+ /// <summary>
+ /// Gets or sets forecast data.
+ /// </summary>
+ public NotificationTask<Forecast> Forecast
+ {
+ get => _forecast;
+ set => SetProperty(ref _forecast, value);
+ }
+
+ /// <summary>
+ /// Indicates if initialization was completed.
+ /// </summary>
+ public bool InitializationCompleted => ((App)Application.Current).IsInitialized =
+ Forecast != null && CurrentWeather != null && CityTimeZone != null &&
+ Forecast.IsSuccessfullyCompleted &&
+ CurrentWeather.IsSuccessfullyCompleted &&
+ CityTimeZone.IsSuccessfullyCompleted;
+
+ public bool InitializationInProgress => !InitializationCompleted;
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Default class constructor.
+ /// </summary>
+ public CurrentWeatherViewModel()
+ {
+ InitializeCommand = new Command(o =>
+ {
+ CurrentWeather = null;
+ Forecast = null;
+ OnPropertyChanged(nameof(InitializationCompleted));
+ OnPropertyChanged(nameof(InitializationInProgress));
+
+ CityTimeZone = new NotificationTask<Models.Location.TimeZone>(InitializeTimeZone());
+ CityTimeZone.PropertyChanged += CityTimeZoneOnPropertyChanged;
+ });
+
+ CheckForecastCommand = new Command(CheckForecast);
+ }
+
+ /// <summary>
+ /// Pushes page with forecast data to navigation stack.
+ /// </summary>
+ /// <param name="param">Page to push to navigation stack.</param>
+ private void CheckForecast(object param)
+ {
+ if (param is Page page)
+ {
+ Navigation.PushAsync(page);
+ }
+ }
+
+ /// <summary>
+ /// Initializes current weather class.
+ /// Sends GET request to server.
+ /// </summary>
+ /// <returns>Async task with current weather.</returns>
+ private async Task<CurrentWeather> InitializeWeather()
+ {
+ var request = new Request<CurrentWeather>(ApiConfig.WEATHER_URL);
+
+ request.AddParameter("appid", ApiConfig.API_KEY);
+ request.AddParameter("id", CityData.Id.ToString());
+ request.AddParameter("units", RegionInfo.CurrentRegion.IsMetric ? "metric" : "imperial");
+
+ return await request.Get();
+ }
+
+ /// <summary>
+ /// Initializes forecast class.
+ /// Sends GET request to server.
+ /// </summary>
+ /// <returns>Async task with forecast data.</returns>
+ private async Task<Forecast> InitializeForecast()
+ {
+ var request = new Request<Forecast>(ApiConfig.FORECAST_URL);
+
+ request.AddParameter("appid", ApiConfig.API_KEY);
+ request.AddParameter("id", CityData.Id.ToString());
+ request.AddParameter("units", RegionInfo.CurrentRegion.IsMetric ? "metric" : "imperial");
+
+ return await request.Get();
+ }
+
+ /// <summary>
+ /// Initializes time zone for city.
+ /// </summary>
+ /// <returns>Async task with time zone.</returns>
+ private async Task<Models.Location.TimeZone> InitializeTimeZone()
+ {
+ var request = new Request<Models.Location.TimeZone>(ApiConfig.TIMEZONE_URL);
+
+ request.AddParameter("location", $"{CityData.Coordinates.Latitude},{CityData.Coordinates.Longitude}");
+ request.AddParameter("timestamp",
+ DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds.ToString());
+ request.AddParameter("sensor", "false");
+
+ return await request.Get();
+ }
+
+ /// <summary>
+ /// Starts request to weather API after time zone request is completed.
+ /// </summary>
+ /// <param name="sender">Object that sent event.</param>
+ /// <param name="propertyChangedEventArgs">Arguments of the event.</param>
+ private void CityTimeZoneOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
+ {
+ if (propertyChangedEventArgs.PropertyName == "Result")
+ {
+ CurrentWeather = new NotificationTask<CurrentWeather>(InitializeWeather());
+ Forecast = new NotificationTask<Forecast>(InitializeForecast());
+
+ CurrentWeather.PropertyChanged += CurrentWeatherOnPropertyChanged;
+ Forecast.PropertyChanged += ForecastOnPropertyChanged;
+ }
+ }
+
+ /// <summary>
+ /// Callback method that is invoked on Forecast property change.
+ /// </summary>
+ /// <param name="sender">Object that sent event.</param>
+ /// <param name="propertyChangedEventArgs">Arguments of the event.</param>
+ private void ForecastOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
+ {
+ var task = ForecastOnPropertyChangedTask(propertyChangedEventArgs.PropertyName);
+ }
+
+ /// <summary>
+ /// Callback method that is invoked on Current Weather property change.
+ /// </summary>
+ /// <param name="sender">Object that sent event.</param>
+ /// <param name="propertyChangedEventArgs">Arguments of the event.</param>
+ private void CurrentWeatherOnPropertyChanged(object sender,
+ PropertyChangedEventArgs propertyChangedEventArgs)
+ {
+ var task = CurrentWeatherOnPropertyChangedTask(propertyChangedEventArgs.PropertyName);
+ }
+
+ /// <summary>
+ /// Method executed when one of Forecast property has changed.
+ /// </summary>
+ /// <param name="propertyName">Name of property that has changed.</param>
+ /// <returns>Task to be executed.</returns>
+ private async Task ForecastOnPropertyChangedTask(string propertyName)
+ {
+ if (propertyName == nameof(NotificationTask<Forecast>.IsFaulted))
+ {
+ if (Forecast.InnerException is HttpException exception)
+ {
+ await ErrorHandler.HandleException((int)exception.StatusCode, exception.StatusCode.ToString());
+ }
+ }
+
+ if (propertyName == nameof(NotificationTask<Forecast>.IsSuccessfullyCompleted))
+ {
+ foreach (var currentWeather in Forecast.Result.WeatherList)
+ {
+ if (currentWeather != null)
+ {
+ currentWeather.CityName = CityData.Name;
+ }
+ }
+
+ OnPropertyChanged(nameof(InitializationCompleted));
+ OnPropertyChanged(nameof(InitializationInProgress));
+ }
+ }
+
+ /// <summary>
+ /// Method executed when one of Current Weather property has changed.
+ /// </summary>
+ /// <param name="propertyName">Name of property that has changed.</param>
+ /// <returns>Task to be executed.</returns>
+ private async Task CurrentWeatherOnPropertyChangedTask(string propertyName)
+ {
+ if (propertyName == nameof(NotificationTask<CurrentWeather>.IsFaulted))
+ {
+ if (CurrentWeather.InnerException is HttpException exception)
+ {
+ await ErrorHandler.HandleException((int)exception.StatusCode, exception.StatusCode.ToString());
+ }
+ }
+
+ if (propertyName == nameof(NotificationTask<CurrentWeather>.IsSuccessfullyCompleted))
+ {
+ OnPropertyChanged(nameof(InitializationCompleted));
+ OnPropertyChanged(nameof(InitializationInProgress));
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using Weather.Models.Weather;
+using Weather.Utils;
+using Xamarin.Forms;
+using Tizen.System;
+using Tizen;
+
+namespace Weather.ViewModels
+{
+ /// <summary>
+ /// ViewModel class for forecast root page.
+ /// </summary>
+ public class ForecastViewModel : ViewModelBase
+ {
+ #region fields
+
+ /// <summary>
+ /// Local storage of all the forecasts (in CurrentWeather format)
+ /// </summary>
+ private ObservableCollection<CurrentWeather> _forecastsModels;
+
+ /// <summary>
+ /// Command to handle UI request for a previous forecast display
+ /// </summary>
+ private Command _previousForecastCommand;
+
+ /// <summary>
+ /// Command to handle UI request for next forecast display
+ /// </summary>
+ private Command _nextForecastCommand;
+
+ #endregion
+
+ #region properties
+
+ /// <summary>
+ /// Bindable property that allows to set forecast data.
+ /// </summary>
+ public static readonly BindableProperty ForecastProperty =
+ BindableProperty.Create(nameof(Forecast), typeof(Forecast), typeof(ForecastViewModel), default(Forecast),
+ propertyChanged: ForecastPropertyChanged);
+
+ /// <summary>
+ /// Bindable property that allows to set timezone offset property.
+ /// </summary>
+ public static readonly BindableProperty OffsetProperty =
+ BindableProperty.Create(nameof(Offset), typeof(int), typeof(ForecastViewModel), default(int));
+
+ /// <summary>
+ /// Bindable property that allows to set City Name property.
+ /// </summary>
+ public static readonly BindableProperty CityNameProperty =
+ BindableProperty.Create(nameof(CityName), typeof(string), typeof(ForecastViewModel), "");
+
+ /// <summary>
+ /// Gets or sets timezone offset property.
+ /// </summary>
+ public int Offset
+ {
+ get => (int)GetValue(OffsetProperty);
+ set => SetValue(OffsetProperty, value);
+ }
+
+ /// <summary>
+ /// Identifier of current forecast being presented in UI (id of an element in _forecastsModels table)
+ /// </summary>
+ public int CurrentForecastId { get; private set; } = 0;
+
+ /// <summary>
+ /// Readonly property referencing currently selected forecast
+ /// </summary>
+ public CurrentWeather SelectedForecast
+ {
+ get
+ {
+ if (ForecastsModels != null)
+ {
+ return ForecastsModels[CurrentForecastId];
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Get or set forecast data.
+ /// </summary>
+ public Forecast Forecast
+ {
+ get => (Forecast)GetValue(ForecastProperty);
+ set => SetValue(ForecastProperty, value);
+ }
+
+ /// <summary>
+ /// Get or set CityName property
+ /// </summary>
+ public string CityName
+ {
+ get => GetValue(CityNameProperty).ToString();
+ set => SetValue(CityNameProperty, value);
+ }
+
+ /// <summary>
+ /// Get or set _forecastsModels => Local storage of all the forecasts (in CurrentWeather format)
+ /// </summary>
+ public ObservableCollection<CurrentWeather> ForecastsModels
+ {
+ get => _forecastsModels;
+ set => SetProperty(ref _forecastsModels, value);
+ }
+
+ /// <summary>
+ /// Get or set _nextForecastCommand => Command to handle UI request for next forecast display
+ /// </summary>
+ public Command NextForecastCommand
+ {
+ get => _nextForecastCommand;
+ set => SetProperty(ref _nextForecastCommand, value);
+ }
+
+ /// <summary>
+ /// Get or set _previousForecastCommand => Command to handle UI request for a previous forecast display
+ /// </summary>
+ public Command PreviousForecastCommand
+ {
+ get => _previousForecastCommand;
+ set => SetProperty(ref _previousForecastCommand, value);
+ }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Callback method invoked on Forecast property change.
+ /// </summary>
+ /// <param name="bindable">Object that contains property.</param>
+ /// <param name="oldValue">Old value of the property.</param>
+ /// <param name="newValue">New value of the property.</param>
+ private static void ForecastPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (newValue != null)
+ {
+ var viewModel = (ForecastViewModel)bindable;
+ viewModel.PrepareViewModels();
+ }
+ }
+
+ /// <summary>
+ /// Prepares view-models for every day of forecast.
+ /// Assigns prev/next forecast handlers
+ /// </summary>
+ private void PrepareViewModels()
+ {
+ ForecastsModels = new ObservableCollection<CurrentWeather>();
+ var dictionary = SortViewModelsByDate();
+ foreach (var list in dictionary.Values)
+ {
+ foreach (CurrentWeather forecast in list)
+ {
+ ForecastsModels.Add(forecast);
+ }
+ }
+
+ // Previous forecast handler - either navigate down the forecast list or
+ // let the user know that the bottom (0) is reached via a simple vibration
+ PreviousForecastCommand = new Command(o =>
+ {
+ if (ForecastsModels != null)
+ {
+ if (CurrentForecastId > 0)
+ {
+ CurrentForecastId--;
+ OnPropertyChanged(nameof(SelectedForecast));
+ }
+ else
+ {
+ Vibrate();
+ }
+ }
+ });
+
+ // Next forecast handler - either navigate up the forecast list or
+ // let the user know that the top (most distant forecast) is reached via a simple vibration
+ NextForecastCommand = new Command(o =>
+ {
+ if (ForecastsModels != null)
+ {
+ if (CurrentForecastId < _forecastsModels.Count - 1)
+ {
+ CurrentForecastId++;
+ OnPropertyChanged(nameof(SelectedForecast));
+ }
+ else
+ {
+ Vibrate();
+ }
+ }
+ });
+ // Notify all parties interested that the Selected forecast (initially the first one) is
+ // ready and waiting to be presented in the relevant View.
+ OnPropertyChanged(nameof(SelectedForecast));
+ }
+
+ /// <summary>
+ /// Sort forecast data by date.
+ /// </summary>
+ /// <returns>List of sorted forecast data.</returns>
+ private Dictionary<int, List<CurrentWeather>> SortViewModelsByDate()
+ {
+ var dictionary = new Dictionary<int, List<CurrentWeather>>();
+
+ foreach (var weather in Forecast.WeatherList)
+ {
+ var day = TimeStamp.Convert(weather.TimeStamp).AddSeconds(Offset).DayOfYear;
+
+ if (!dictionary.ContainsKey(day))
+ {
+ dictionary.Add(day, new List<CurrentWeather>());
+ }
+
+ dictionary[day].Add(weather);
+ }
+
+ if (dictionary.Count > 5)
+ {
+ dictionary.Remove(dictionary.Last().Key);
+ }
+
+ return dictionary;
+ }
+
+
+ /// <summary>
+ /// Use Tizen.System.Feedback to let user know something went a bit wrong (vibrate)
+ /// </summary>
+ private void Vibrate()
+ {
+ try
+ {
+ Feedback feedback = new Feedback();
+ feedback.Play(FeedbackType.All, "General");
+ }
+ catch (System.Exception e)
+ {
+ Log.Debug("WeatherApp", e.Message);
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using Weather.Models.Location;
+using Weather.Service;
+using Weather.Utils;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+
+namespace Weather.ViewModels
+{
+ /// <summary>
+ /// ViewModel class for Main Page.
+ /// </summary>
+ public class MainPageViewModel : ViewModelBase
+ {
+ #region fields
+
+ /// <summary>
+ /// Bindable property that allows to set navigation context.
+ /// </summary>
+ public static readonly BindableProperty NavigationProperty =
+ BindableProperty.Create(nameof(Navigation), typeof(INavigation), typeof(MainPageViewModel), default(Type));
+
+
+ /// <summary>
+ /// Maximum number of items that will be displayed on the list.
+ /// </summary>
+ private const int MAX_ITEMS_ON_LIST = 10;
+
+ /// <summary>
+ /// Contains all supported cities.
+ /// </summary>
+ private CityProvider _provider;
+
+ /// <summary>
+ /// Local storage of collection of displayed cities.
+ /// </summary>
+ private ObservableCollection<City> _cities;
+
+ /// <summary>
+ /// Local storage of city name entered by user.
+ /// </summary>
+ private string _enteredCity = "";
+
+ /// <summary>
+ /// Local storage of city selected by user.
+ /// </summary>
+ private City _selectedCity;
+
+ /// <summary>
+ /// Local storage of command that opens page provided in command parameter.
+ /// </summary>
+ private Command _checkWeatherCommand;
+
+ /// <summary>
+ /// Local storage of country code.
+ /// </summary>
+ private string _enteredCountry;
+
+ /// <summary>
+ /// Local storage of CityEntry text color.
+ /// </summary>
+ private Color _cityEntryTextColor;
+
+ #endregion
+
+ #region properties
+
+ /// <summary>
+ /// Flag used for Watch UI visibility.
+ /// A "helper" UI element (list of cities matching criteria) should be hidden if the right city is entered
+ /// </summary>
+ public bool InvalidCityEntered => (SelectedCity == null) && (EnteredCity.Length != 0) ? true : false;
+
+ /// <summary>
+ /// Gets or sets collection of available cities.
+ /// </summary>
+ public ObservableCollection<City> Cities
+ {
+ get => _cities;
+ set => SetProperty(ref _cities, value);
+ }
+
+ /// <summary>
+ /// Gets or sets text color for CityEntry.
+ /// </summary>
+ public Color CityEntryTextColor
+ {
+ get => _cityEntryTextColor;
+ set => SetProperty(ref _cityEntryTextColor, value);
+ }
+
+ /// <summary>
+ /// Gets or sets city name entered by user.
+ /// </summary>
+ public string EnteredCity
+ {
+ get => _enteredCity;
+ set
+ {
+ SetProperty(ref _enteredCity, value);
+ OnPropertyChanged(nameof(InvalidCityEntered));
+ FilterCities();
+ ValidateInput();
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets city selected by user.
+ /// </summary>
+ public City SelectedCity
+ {
+ get => _selectedCity;
+ set
+ {
+ SetProperty(ref _selectedCity, value);
+ OnPropertyChanged(nameof(InvalidCityEntered));
+ if (value != null)
+ {
+ ((App)Application.Current).IsInitialized = false;
+ EnteredCity = value.Name;
+ CityEntryTextColor = Color.Gray;
+ CheckWeatherCommand.ChangeCanExecute();
+ }
+ else
+ {
+ CityEntryTextColor = Color.Red;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets command that opens page provided in command parameter.
+ /// </summary>
+ public Command CheckWeatherCommand
+ {
+ get => _checkWeatherCommand;
+ set => SetProperty(ref _checkWeatherCommand, value);
+ }
+
+ /// <summary>
+ /// Country code in ISO-3166 format.
+ /// </summary>
+ public string EnteredCountry
+ {
+ get => _enteredCountry;
+ set
+ {
+ SetProperty(ref _enteredCountry, value);
+ FilterCities();
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets navigation context.
+ /// </summary>
+ public INavigation Navigation
+ {
+ get => (INavigation)GetValue(NavigationProperty);
+ set => SetValue(NavigationProperty, value);
+ }
+
+ #endregion
+
+ #region methods
+
+ /// <summary>
+ /// Default class constructor.
+ /// </summary>
+ public MainPageViewModel()
+ {
+ LoadCityList();
+
+ Cities = new ObservableCollection<City>(_provider.FindCity("", MAX_ITEMS_ON_LIST));
+ CheckWeatherCommand = new Command<Page>(ExecuteCheckWeatherCommand, CanExecuteCheckWeatherCommand);
+ CityEntryTextColor = Color.FromRgb(128, 128, 128);
+
+ // Fill in the country code for better "first run experience"
+ SetProperty<string>(ref _enteredCountry, "US");
+ }
+
+ /// <summary>
+ /// Loads list of cities from JSON file.
+ /// </summary>
+ private void LoadCityList()
+ {
+ var jsonFileReader = new JsonFileReader<IList<City>>("Weather.Data.", "city.list.json");
+ jsonFileReader.Read();
+ _provider = new CityProvider(jsonFileReader.Result.AsQueryable());
+ }
+
+ /// <summary>
+ /// Filters city list using text entered by user.
+ /// </summary>
+ private void FilterCities()
+ {
+ Cities.Clear();
+ _provider.FindCity(_enteredCity, _enteredCountry, MAX_ITEMS_ON_LIST).ForEach(c => Cities.Add(c));
+ }
+
+ /// <summary>
+ /// Validates city name entered by user.
+ /// </summary>
+ private void ValidateInput()
+ {
+ if (!_provider.Validate(EnteredCity))
+ {
+ SelectedCity = null;
+ CityEntryTextColor = Color.Red;
+ CheckWeatherCommand.ChangeCanExecute();
+ }
+ }
+
+ /// <summary>
+ /// Checks if CheckWeather command could be executed.
+ /// Page parameter and selected city can't be null.
+ /// </summary>
+ /// <param name="page">Page that will be shown.</param>
+ /// <returns>
+ /// Returns true if city is selected.
+ /// If no city is selected, or it is not valid method returns false.
+ /// </returns>
+ private bool CanExecuteCheckWeatherCommand(Page page)
+ {
+ return page != null && SelectedCity != null;
+ }
+
+ /// <summary>
+ /// Executes CheckWeather command.
+ /// Pushes page given as command parameter to navigation stack.
+ /// </summary>
+ /// <param name="page">Page that will be opened.</param>
+ private void ExecuteCheckWeatherCommand(Page page)
+ {
+ Navigation.PushAsync(page);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Xamarin.Forms;
+
+namespace Weather.ViewModels
+{
+ /// <summary>
+ /// Base ViewModel class.
+ /// </summary>
+ public class ViewModelBase : BindableObject
+ {
+ /// <summary>
+ /// Generic set property method which also calls OnPropertyChanged() after value modification.
+ /// </summary>
+ /// <param name="storage">Value storage object</param>
+ /// <param name="value">Value to set</param>
+ /// <param name="propertyName">Automatically obtained property name</param>
+ /// <typeparam name="T">Property value type</typeparam>
+ /// <returns>True if value was changed, false if value is not different from current.</returns>
+ protected bool SetProperty<T>(ref T storage, T value,
+ [CallerMemberName] string propertyName = null)
+ {
+ if (Equals(storage, value))
+ {
+ return false;
+ }
+
+ storage = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<w:CirclePage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:viewModels="clr-namespace:Weather.ViewModels;assembly=Weather"
+ xmlns:converters="clr-namespace:Weather.Converters;assembly=Weather"
+ x:Class="Weather.Views.ApiErrorPage"
+ NavigationPage.HasBackButton="False"
+ NavigationPage.HasNavigationBar="False"
+ xmlns:w="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=XSF.CircularUI.Forms"
+ >
+
+ <ContentPage.BindingContext>
+ <viewModels:ApiErrorViewModel />
+ </ContentPage.BindingContext>
+
+ <ContentPage.Resources>
+ <ResourceDictionary>
+ <converters:TimeStampToDateConverter x:Key="TimeStampToDateConverter" />
+ </ResourceDictionary>
+ </ContentPage.Resources>
+ <w:CircleStackLayout Orientation="Vertical" Spacing="10">
+ <Label Text="Error"
+ FontSize="12"
+ TextColor="OrangeRed"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center"/>
+ <Label Text="Error occurred while trying to get response from API."
+ FontSize="8"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center" />
+ <Label Text="{Binding Code, StringFormat='HTTP status code: {0}'}"
+ FontSize="6"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center" />
+ <Label Text="{Binding Message}"
+ FontSize="6"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center" />
+ </w:CircleStackLayout>
+ <w:CirclePage.ActionButton>
+ <w:ActionButtonItem Command="{Binding ExitAppCommand}" Text="Exit" />
+ </w:CirclePage.ActionButton>
+</w:CirclePage>
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Xamarin.Forms;
+
+namespace Weather.Views
+{
+ /// <summary>
+ /// Interaction logic for ApiErrorPage.xaml.
+ /// </summary>
+ public partial class ApiErrorPage
+ {
+ #region methods
+
+ /// <summary>
+ /// Default class constructor.
+ /// </summary>
+ public ApiErrorPage()
+ {
+ InitializeComponent();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<w:CirclePage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:viewModels="clr-namespace:Weather.ViewModels;assembly=Weather"
+ xmlns:behaviors="clr-namespace:Weather.Behaviors;assembly=Weather"
+ xmlns:converters="clr-namespace:Weather.Converters;assembly=Weather"
+ xmlns:location="clr-namespace:Weather.Models.Location;assembly=Weather"
+ xmlns:controls="clr-namespace:Weather.Controls;assembly=Weather"
+ xmlns:views="clr-namespace:Weather.Views;assembly=Weather"
+ xmlns:w="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=XSF.CircularUI.Forms"
+ x:Class="Weather.Views.CurrentWeatherPage"
+ x:Name="Root"
+ NavigationPage.HasNavigationBar="False"
+ Title="Current Weather"
+ RotaryFocusObject="{x:Reference myscroller}">
+
+ <w:CirclePage.BindingContext>
+ <viewModels:CurrentWeatherViewModel x:Name="ViewModel"
+ CityData="{Binding Source={x:Reference Name=Root}, Path=CityData}"
+ Navigation="{Binding Source={x:Reference Name=Root}, Path=Navigation}" />
+ </w:CirclePage.BindingContext>
+
+ <w:CirclePage.Behaviors>
+ <behaviors:CurrentWeatherPageBehavior
+ AppearingCommand="{Binding Source={x:Reference Name=ViewModel}, Path=InitializeCommand}" />
+ </w:CirclePage.Behaviors>
+
+ <w:CirclePage.Resources>
+ <ResourceDictionary>
+ <converters:TimeStampToDateConverter x:Key="TimeStampToDateConverter" />
+ <converters:TimeStampToTimeConverter x:Key="TimeStampToTimeConverter" />
+ <converters:DegreeToCardinalDirectionConverter x:Key="CardinalDirectionConverter" />
+ <converters:ImageSourceConverter x:Key="ImageSourceConverter" />
+ <converters:MeasurementSystemConverter x:Key="MeasurementSystemConverter" />
+ <location:TimeZone x:Key="TimeZone"
+ Offset="{Binding Source={x:Reference Name=ViewModel}, Path=CityTimeZone.Result.Offset}" />
+ </ResourceDictionary>
+ </w:CirclePage.Resources>
+
+ <StackLayout Orientation="Vertical">
+ <ActivityIndicator
+ IsVisible="{Binding InitializationInProgress}"
+ IsRunning="{Binding InitializationInProgress}"
+ />
+
+ <w:CircleScrollView x:Name="myscroller">
+ <StackLayout
+ Spacing="20"
+ IsVisible="{Binding InitializationCompleted}">
+
+ <Label
+ Text="{Binding CurrentWeather.Result.CityName}"
+ HeightRequest="120"
+ MinimumHeightRequest="120"
+ FontSize="12"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="End"
+ />
+
+ <controls:DoubleLabel
+ MainText="{Binding CurrentWeather.Result.TimeStamp, Converter={StaticResource TimeStampToDateConverter}}"
+ SubText="Time of measurement." />
+ <StackLayout Orientation="Horizontal">
+ <controls:DoubleLabel
+ MainText="{Binding CurrentWeather.Result.SunData.SunriseTimeStamp,
+ Converter={StaticResource TimeStampToTimeConverter}, ConverterParameter={StaticResource TimeZone}}"
+ SubText="Sunrise" />
+ <controls:DoubleLabel
+ MainText="{Binding CurrentWeather.Result.SunData.SunsetTimeStamp,
+ Converter={StaticResource TimeStampToTimeConverter}, ConverterParameter={StaticResource TimeZone}}"
+ SubText="Sunset" />
+ </StackLayout>
+
+ <Image
+ Source="{Binding CurrentWeather.Result.Weather[0].Icon, Converter={StaticResource ImageSourceConverter}}"
+ HeightRequest="100"
+ WidthRequest="100"
+ Aspect="AspectFit" />
+ <Label
+ Text="{Binding CurrentWeather.Result.Weather[0].Description}"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center"
+ FontSize="8" />
+
+
+
+
+ <controls:DoubleLabel
+ MainText="{Binding CurrentWeather.Result.WeatherData.Temperature, Converter={StaticResource MeasurementSystemConverter}, ConverterParameter='temp'}"
+ SubText="Temperature" />
+
+
+ <StackLayout Orientation="Horizontal">
+ <controls:DoubleLabel
+ MainText="{Binding CurrentWeather.Result.Clouds.Percent, StringFormat=' {0}%'}"
+ SubText="Clouds percent" />
+ <controls:DoubleLabel
+ MainText="{Binding CurrentWeather.Result.WeatherData.Pressure, StringFormat=' {0} hPa'}"
+ SubText="Pressure" />
+ </StackLayout>
+
+ <StackLayout Orientation="Horizontal">
+ <controls:DoubleLabel
+ MainText="{Binding CurrentWeather.Result.Wind.Speed, Converter={StaticResource MeasurementSystemConverter}, ConverterParameter='speed'}"
+ SubText="Wind speed" />
+ <controls:DoubleLabel
+ MainText="{Binding CurrentWeather.Result.Wind.Degree, Converter={StaticResource CardinalDirectionConverter}}"
+ SubText="Wind direction" />
+ </StackLayout>
+
+ <Button Text="Check Forecast"
+ BackgroundColor="DarkGray"
+ TextColor="White"
+ HeightRequest="90"
+ MinimumHeightRequest="90"
+ Command="{Binding CheckForecastCommand}"
+ IsVisible="{Binding InitializationCompleted}">
+ <Button.CommandParameter>
+ <views:ForecastPage>
+ <views:ForecastPage.BindingContext>
+ <viewModels:ForecastViewModel
+ Forecast="{Binding Source={x:Reference Name=ViewModel}, Path=Forecast.Result}"
+ Offset="{Binding Source={x:Reference Name=ViewModel}, Path=CityTimeZone.Result.Offset}"
+ CityName="{Binding Source={x:Reference Name=ViewModel}, Path=CityData.Name}" />
+ </views:ForecastPage.BindingContext>
+ </views:ForecastPage>
+ </Button.CommandParameter>
+ </Button>
+ </StackLayout>
+ </w:CircleScrollView>
+
+
+ </StackLayout>
+</w:CirclePage>
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Weather.Models.Location;
+using Xamarin.Forms;
+
+namespace Weather.Views
+{
+ /// <summary>
+ /// Interaction logic for CurrentWeatherPage.xaml.
+ /// </summary>
+ public partial class CurrentWeatherPage
+ {
+ #region properties
+
+ /// <summary>
+ /// Bindable property that allows to set city data selected by user.
+ /// </summary>
+ public static readonly BindableProperty CityDataProperty =
+ BindableProperty.Create(nameof(CityData), typeof(City), typeof(CurrentWeatherPage), default(City));
+
+ /// <summary>
+ /// Gets or sets city data selected by user.
+ /// </summary>
+ public City CityData
+ {
+ get => (City)GetValue(CityDataProperty);
+ set => SetValue(CityDataProperty, value);
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Default class constructor.
+ /// </summary>
+ public CurrentWeatherPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<w:CirclePage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:viewModels="clr-namespace:Weather.ViewModels;assembly=Weather"
+ xmlns:behaviors="clr-namespace:Weather.Behaviors;assembly=Weather"
+ xmlns:converters="clr-namespace:Weather.Converters;assembly=Weather"
+ xmlns:location="clr-namespace:Weather.Models.Location;assembly=Weather"
+ xmlns:controls="clr-namespace:Weather.Controls;assembly=Weather"
+ xmlns:views="clr-namespace:Weather.Views;assembly=Weather"
+ xmlns:w="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=XSF.CircularUI.Forms"
+ x:Class="Weather.Views.ForecastPage"
+ x:Name="ForecastCirclePage"
+ NavigationPage.HasNavigationBar="False"
+ RotaryFocusObject="{x:Reference ForecastCirclePage}"
+ Title="Weather Forecast">
+
+ <w:CirclePage.Resources>
+ <ResourceDictionary>
+ <converters:TimeStampToDateConverter x:Key="TimeStampToDateConverter" />
+ <converters:TimeStampToTimeConverter x:Key="TimeStampToTimeConverter" />
+ <converters:DegreeToCardinalDirectionConverter x:Key="CardinalDirectionConverter" />
+ <converters:ImageSourceConverter x:Key="ImageSourceConverter" />
+ <converters:MeasurementSystemConverter x:Key="MeasurementSystemConverter" />
+ </ResourceDictionary>
+ </w:CirclePage.Resources>
+
+ <StackLayout Orientation="Vertical" HorizontalOptions="CenterAndExpand" >
+
+ <Label
+ Text="{Binding SelectedForecast.CityName}"
+ HeightRequest="60"
+ MinimumHeightRequest="60"
+ FontSize="10"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="End"/>
+ <Label
+ Text="{Binding SelectedForecast.TimeStamp, Converter={StaticResource TimeStampToDateConverter}}"
+ FontSize="8"
+ HorizontalTextAlignment="Center"/>
+ <StackLayout Orientation="Horizontal" HorizontalOptions="CenterAndExpand">
+ <StackLayout Orientation="Vertical">
+ <Image
+ Source="{Binding SelectedForecast.Weather[0].Icon, Converter={StaticResource ImageSourceConverter}}"
+ HeightRequest="100"
+ WidthRequest="100"
+ Aspect="AspectFit" />
+ <Label
+ Text="{Binding SelectedForecast.Weather[0].Description}"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center"
+ FontSize="5" />
+ </StackLayout>
+ <StackLayout Orientation="Vertical">
+ <controls:DoubleLabel
+ MainText="{Binding SelectedForecast.WeatherData.Temperature, Converter={StaticResource MeasurementSystemConverter}, ConverterParameter='temp'}"
+ SubText="Temperature" />
+ <controls:DoubleLabel
+ MainText="{Binding SelectedForecast.Clouds.Percent, StringFormat=' {0}%'}"
+ SubText="Clouds percent" />
+ </StackLayout>
+ </StackLayout>
+
+ <StackLayout Orientation="Horizontal">
+ <controls:DoubleLabel
+ MainText="{Binding SelectedForecast.Wind.Speed, Converter={StaticResource MeasurementSystemConverter}, ConverterParameter='speed'}"
+ SubText="Wind speed" />
+ <controls:DoubleLabel
+ MainText="{Binding SelectedForecast.Wind.Degree, Converter={StaticResource CardinalDirectionConverter}}"
+ SubText="Wind direction" />
+ </StackLayout>
+ </StackLayout>
+</w:CirclePage>
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System.Linq;
+using Weather.Utils;
+using Tizen.Wearable.CircularUI.Forms;
+
+namespace Weather.Views
+{
+ /// <summary>
+ /// Interaction logic for ForecastPage.xaml.
+ /// </summary>
+ public partial class ForecastPage : CirclePage, IRotaryEventReceiver
+ {
+ #region fields
+ private bool _rotating = false;
+
+ #endregion fields
+
+ #region methods
+
+ /// <summary>
+ /// Default class constructor.
+ /// </summary>
+ public ForecastPage()
+ {
+ InitializeComponent();
+ }
+
+ /// <summary>
+ /// Handle bezel rotation
+ /// </summary>
+ /// <param name="args">Rotary event arguments</param>
+ public void Rotate(RotaryEventArgs args)
+ {
+ var a = this.BindingContext;
+
+ if (_rotating)
+ {
+ return;
+ }
+
+ _rotating = true;
+ if (args.IsClockwise)
+ {
+ ((ViewModels.ForecastViewModel)BindingContext).NextForecastCommand.Execute(null);
+ _rotating = false;
+ }
+ else
+ {
+ ((ViewModels.ForecastViewModel)BindingContext).PreviousForecastCommand.Execute(null);
+ _rotating = false;
+ }
+ }
+
+ /// <summary>
+ /// Sets the binding context to page resources.
+ /// </summary>
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ if (Resources != null)
+ {
+ foreach (var bindableString in Resources.Values.OfType<BindableString>())
+ {
+ bindableString.BindingContext = BindingContext;
+ }
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<w:CirclePage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:viewModels="clr-namespace:Weather.ViewModels;assembly=Weather"
+ xmlns:behaviors="clr-namespace:Weather.Behaviors;assembly=Weather"
+ xmlns:w="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=XSF.CircularUI.Forms"
+ xmlns:views="clr-namespace:Weather.Views;assembly=Weather"
+ x:Class="Weather.Views.MainPage"
+ x:Name="Root"
+ NavigationPage.HasNavigationBar="False"
+ RotaryFocusObject="{x:Reference myscroller}">
+
+
+ <w:CirclePage.BindingContext>
+ <viewModels:MainPageViewModel
+ x:Name="ViewModel"
+ Navigation="{Binding Source={x:Reference Name=Root}, Path=Navigation}" />
+ </w:CirclePage.BindingContext>
+
+
+ <w:CircleScrollView x:Name="myscroller">
+ <StackLayout
+ Orientation="Vertical"
+ HorizontalOptions="FillAndExpand"
+ VerticalOptions="FillAndExpand"
+ Spacing="5">
+
+ <Label Text="Weather App"
+ FontSize="12"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="End"
+ HeightRequest="120"
+ MinimumHeightRequest="120"/>
+
+ <Label Text="Country code:"
+ FontSize="8"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center" />
+
+ <w:PopupEntry BackgroundColor="White"
+ HorizontalTextAlignment="Center"
+ HorizontalOptions="Center"
+ WidthRequest="280"
+ Text="{Binding EnteredCountry, Mode=TwoWay}">
+ <w:PopupEntry.Behaviors>
+ <behaviors:CountryCodeValidatorBehavior MaxLength="2" />
+ </w:PopupEntry.Behaviors>
+ </w:PopupEntry>
+
+ <Label Text="City:"
+ FontSize="8"
+ HorizontalOptions="Fill"
+ VerticalOptions="Fill"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center" />
+
+ <w:PopupEntry x:Name="CityEntry"
+ BackgroundColor="White"
+ HorizontalOptions="Center"
+ WidthRequest="280"
+ Text="{Binding EnteredCity, Mode=TwoWay}"
+ HorizontalTextAlignment="Center"
+ TextColor="{Binding CityEntryTextColor}">
+ </w:PopupEntry>
+
+
+ <Label Text="Matching Cities:"
+ FontSize="6"
+ HorizontalOptions="Fill"
+ VerticalOptions="Fill"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center"
+ IsVisible="{Binding Source={x:Reference Name=ViewModel}, Path=InvalidCityEntered}"/>
+
+ <w:CircleListView ItemsSource="{Binding Cities}"
+ SelectedItem="{Binding SelectedCity, Mode=TwoWay}"
+ IsVisible="{Binding Source={x:Reference Name=ViewModel}, Path=InvalidCityEntered}"
+ HeightRequest="150"
+ MinimumHeightRequest="150"
+ WidthRequest="260"
+ HorizontalOptions="Center"
+ BackgroundColor="Black">
+ <w:CircleListView.ItemTemplate>
+ <DataTemplate>
+ <ViewCell>
+ <Label Text="{Binding Name}"
+ FontSize="10"
+ HeightRequest="50"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center"
+ TextColor="LightGray"/>
+ </ViewCell>
+ </DataTemplate>
+ </w:CircleListView.ItemTemplate>
+ </w:CircleListView>
+
+ <StackLayout VerticalOptions="Fill" MinimumHeightRequest="20" HeightRequest="20"> </StackLayout>
+ <Button
+ Text="Check Weather"
+ BackgroundColor="DarkGray"
+ TextColor="White"
+ Command="{Binding CheckWeatherCommand}"
+ HeightRequest="90"
+ MinimumHeightRequest="90">
+ <Button.CommandParameter>
+ <views:CurrentWeatherPage
+ CityData="{Binding Source={x:Reference Name=ViewModel}, Path=SelectedCity}" />
+ </Button.CommandParameter>
+ </Button>
+
+ </StackLayout>
+ </w:CircleScrollView>
+</w:CirclePage>
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+namespace Weather.Views
+{
+ /// <summary>
+ /// Interaction logic for CurrentWeatherPage.xaml
+ /// </summary>
+ public partial class MainPage
+ {
+ #region methods
+
+ /// <summary>
+ /// Default class constructor.
+ /// </summary>
+ public MainPage()
+ {
+ InitializeComponent();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<w:CirclePage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:w="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=XSF.CircularUI.Forms"
+ xmlns:viewModels="clr-namespace:Weather.ViewModels;assembly=Weather"
+ x:Class="Weather.Views.MissingKeyErrorPage"
+ NavigationPage.HasNavigationBar="False">
+
+ <w:CirclePage.BindingContext>
+ <viewModels:ApiErrorViewModel />
+ </w:CirclePage.BindingContext>
+
+ <w:CirclePage.ActionButton>
+ <w:ActionButtonItem Command="{Binding ExitAppCommand}" Text="Exit app" />
+ </w:CirclePage.ActionButton>
+
+ <w:CirclePage.Content>
+ <StackLayout
+ VerticalOptions="CenterAndExpand"
+ HorizontalOptions="CenterAndExpand"
+ Orientation="Vertical">
+ <Label
+ Text="Configuration error"
+ FontSize="Medium"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center"
+ TextColor="OrangeRed"/>
+ <Label
+ Text="API key is not set. Please, define the key in "ApiConfig.cs" file and rebuild application."
+ FontSize="8"
+ HorizontalTextAlignment="Center"
+ VerticalTextAlignment="Center" />
+ </StackLayout>
+ </w:CirclePage.Content>
+
+</w:CirclePage>
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using Tizen.Wearable.CircularUI.Forms;
+
+namespace Weather.Views
+{
+ /// <summary>
+ /// Interaction logic for MissingKeyErrorPage.xaml
+ /// </summary>
+ public partial class MissingKeyErrorPage : CirclePage
+ {
+ #region methods
+
+ /// <summary>
+ /// Default class constructor.
+ /// </summary>
+ public MissingKeyErrorPage()
+ {
+ InitializeComponent();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
--- /dev/null
+//Copyright 2018 Samsung Electronics Co., Ltd
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+using System;
+using System.Collections.Generic;
+using Tizen.Wearable.CircularUI.Forms;
+using Tizen.Wearable.CircularUI.Forms.Renderer;
+using Xamarin.Forms;
+
+namespace Weather
+{
+ class Program : global::Xamarin.Forms.Platform.Tizen.ApplicationLifecycle
+ {
+ //protected override void OnCreate()
+ //{
+ // base.OnCreate();
+
+ // LoadApplication(new App());
+ //}
+
+ /// <summary>
+ /// Called when this application is launched.
+ /// </summary>
+ protected override void OnCreate()
+ {
+ var weather = new App();
+ FormsApplication.LoadApplication(weather);
+ }
+
+ static void Main(string[] args)
+ {
+ var app = new Program();
+ // define your custom handlers
+ var customRenderers = new Dictionary<Type, Func<IRegisterable>>()
+ {
+ { typeof(CirclePage), ()=> new CirclePageRenderer() },
+ { typeof(global:: Tizen.Wearable.CircularUI.Forms.CircleListView), () => new CircleListViewRenderer() },
+ { typeof(global:: Tizen.Wearable.CircularUI.Forms.CircleScrollView), ()=> new global::Tizen.Wearable.CircularUI.Forms.Renderer.CircleScrollViewRenderer() },
+ };
+ var option = new InitializationOptions(app)
+ {
+ UseMessagingCenter = false,
+ UseStyle = false,
+ UseShell = false,
+ UseVisual = false,
+ StaticRegistarStrategy = StaticRegistrarStrategy.StaticRegistrarOnly,
+ CustomHandlers = customRenderers,
+ Flags = InitializationFlags.DisableCss
+ };
+
+ Forms.Init(option);
+
+ // It's mandatory to initialize Circular UI for using Tizen Wearable Circular UI API
+ global::Tizen.Wearable.CircularUI.Forms.Renderer.FormsCircularUI.Init();
+ app.FormsApplication.Run(args);
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Tizen.NET.Sdk/1.0.8">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>tizen40</TargetFramework>
+ </PropertyGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugType>portable</DebugType>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>None</DebugType>
+ </PropertyGroup>
+ <ItemGroup>
+ <None Remove="Data\city.list.json" />
+ <None Remove="Data\country.list.json" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Data\city.list.json">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </EmbeddedResource>
+ <EmbeddedResource Include="Data\country.list.json">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </EmbeddedResource>
+ </ItemGroup>
+
+ <ItemGroup>
+ <Folder Include="lib\" />
+ <Folder Include="Config\" />
+ <Folder Include="Views\" />
+ <Folder Include="res\" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\Tizen.CircularUI\Tizen.Wearable.CircularUI.Forms.Renderer\XSF.CircularUI.Forms.Renderer.csproj" />
+ <!--<ProjectReference Include="..\..\..\Tizen.CircularUI\Tizen.Wearable.CircularUI.Forms\XSF.CircularUI.Forms.csproj" />
+ <ProjectReference Include="..\..\..\Xamarin.Forms\Xamarin.Forms.Core\XSF.Core.csproj" />
+ <ProjectReference Include="..\..\..\Xamarin.Forms\Xamarin.Forms.Platform.Tizen\XSF.Platform.Tizen.csproj" />
+ <ProjectReference Include="..\..\..\Xamarin.Forms\Xamarin.Forms.Platform\XSF.Platform.csproj" />
+ <ProjectReference Include="..\..\..\Xamarin.Forms\Xamarin.Forms.Xaml\XSF.Xaml.csproj" />-->
+ </ItemGroup>
+
+</Project>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="org.tizen.example.Weather" version="1.0.0" api-version="4" xmlns="http://tizen.org/ns/packages">
+ <author href="https://github.com/Samsung/Tizen-CSharp-Samples">Michał Kołodziejski & @Piotr12</author>
+ <description>
+ Mobile sample app made by Michał Kołodziejski.
+ Wearable port by @Piotr12.
+
+ Icon sourced from https://openclipart.org/detail/30073/tango-weather-few-clouds
+ </description>
+ <profile name="wearable" />
+ <ui-application appid="org.tizen.example.Weather" exec="Weather.dll" multiple="false" nodisplay="false" taskmanage="true" splash-screen-display="true" type="dotnet" launch_mode="single">
+ <label>Weather</label>
+ <icon>Weather.png</icon>
+ <metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
+ <splash-screens />
+ </ui-application>
+ <shortcut-list />
+ <privileges>
+ <privilege>http://tizen.org/privilege/haptic</privilege>
+ <privilege>http://tizen.org/privilege/internet</privilege>
+ <privilege>http://tizen.org/privilege/ime</privilege>
+ <privilege>http://tizen.org/privilege/imemanager</privilege>
+ </privileges>
+ <provides-appdefined-privileges />
+</manifest>
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoiceMemo", "Test\Voicememo2020\VoiceMemo\VoiceMemo.csproj", "{6DADDF43-87F3-43C6-822D-12DE155CCF86}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Weather", "Weather", "{F167380D-C156-45D1-A0BA-DB3A5AE34E98}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Weather", "Test\Weather\Weather\Weather.csproj", "{076A3B6E-64FE-422C-9D54-845BA6036DF7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU