diff --git a/CarouselViewChallenge/CarouselViewChallenge.Android/CarouselViewChallenge.Android.csproj b/CarouselViewChallenge/CarouselViewChallenge.Android/CarouselViewChallenge.Android.csproj
index 1e33499..49627f7 100644
--- a/CarouselViewChallenge/CarouselViewChallenge.Android/CarouselViewChallenge.Android.csproj
+++ b/CarouselViewChallenge/CarouselViewChallenge.Android/CarouselViewChallenge.Android.csproj
@@ -32,6 +32,11 @@
prompt
4
None
+ false
+ false
+ false
+ false
+ d8
true
@@ -53,11 +58,15 @@
+
+
+
-
-
+
+
+
@@ -101,4 +110,4 @@
-
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge.Android/GlobalSuppressions.cs b/CarouselViewChallenge/CarouselViewChallenge.Android/GlobalSuppressions.cs
new file mode 100644
index 0000000..21bad7a
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge.Android/GlobalSuppressions.cs
@@ -0,0 +1,8 @@
+
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0059:Unnecessary assignment of a value", Scope = "member", Target = "~M:CarouselViewChallenge.Droid.MainActivity.OnCreate(Android.OS.Bundle)")]
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge.Android/MainActivity.cs b/CarouselViewChallenge/CarouselViewChallenge.Android/MainActivity.cs
index fd4e22a..70e0123 100644
--- a/CarouselViewChallenge/CarouselViewChallenge.Android/MainActivity.cs
+++ b/CarouselViewChallenge/CarouselViewChallenge.Android/MainActivity.cs
@@ -1,11 +1,10 @@
-using System;
-
+using Acr.UserDialogs;
using Android.App;
using Android.Content.PM;
-using Android.Runtime;
-using Android.Views;
-using Android.Widget;
using Android.OS;
+using Android.Runtime;
+using FFImageLoading.Forms.Platform;
+using FFImageLoading.Svg.Forms;
namespace CarouselViewChallenge.Droid
{
@@ -20,10 +19,16 @@ protected override void OnCreate(Bundle savedInstanceState)
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.SetFlags("CollectionView_Experimental");
+
+ CachedImageRenderer.Init(true);
+ var ignore = typeof(SvgCachedImage);
+ UserDialogs.Init(this);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
+
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
+
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
diff --git a/CarouselViewChallenge/CarouselViewChallenge.UWP/CarouselViewChallenge.UWP.csproj b/CarouselViewChallenge/CarouselViewChallenge.UWP/CarouselViewChallenge.UWP.csproj
index 4b6b2c9..55db78b 100644
--- a/CarouselViewChallenge/CarouselViewChallenge.UWP/CarouselViewChallenge.UWP.csproj
+++ b/CarouselViewChallenge/CarouselViewChallenge.UWP/CarouselViewChallenge.UWP.csproj
@@ -146,8 +146,8 @@
-
-
+
+
@@ -159,4 +159,4 @@
14.0
-
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge.iOS/CarouselViewChallenge.iOS.csproj b/CarouselViewChallenge/CarouselViewChallenge.iOS/CarouselViewChallenge.iOS.csproj
index 6241d2c..f5738b9 100644
--- a/CarouselViewChallenge/CarouselViewChallenge.iOS/CarouselViewChallenge.iOS.csproj
+++ b/CarouselViewChallenge/CarouselViewChallenge.iOS/CarouselViewChallenge.iOS.csproj
@@ -130,7 +130,7 @@
-
+
@@ -139,4 +139,4 @@
CarouselViewChallenge
-
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/App.xaml b/CarouselViewChallenge/CarouselViewChallenge/App.xaml
index f3469f1..a495c26 100644
--- a/CarouselViewChallenge/CarouselViewChallenge/App.xaml
+++ b/CarouselViewChallenge/CarouselViewChallenge/App.xaml
@@ -6,5 +6,12 @@
mc:Ignorable="d"
x:Class="CarouselViewChallenge.App">
+
+ #2F3136
+ #484B51
+ LightGray
+ White
+ #2196F3
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/AppShell.xaml b/CarouselViewChallenge/CarouselViewChallenge/AppShell.xaml
index 7f8fdee..32dd03a 100644
--- a/CarouselViewChallenge/CarouselViewChallenge/AppShell.xaml
+++ b/CarouselViewChallenge/CarouselViewChallenge/AppShell.xaml
@@ -39,6 +39,9 @@
+
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/CarouselViewChallenge.csproj b/CarouselViewChallenge/CarouselViewChallenge/CarouselViewChallenge.csproj
index a58ec88..08642af 100644
--- a/CarouselViewChallenge/CarouselViewChallenge/CarouselViewChallenge.csproj
+++ b/CarouselViewChallenge/CarouselViewChallenge/CarouselViewChallenge.csproj
@@ -6,21 +6,98 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- WelcomePage.xaml
-
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:UpdateDesignTimeXaml
+
+
+ MSBuild:UpdateDesignTimeXaml
+
MSBuild:UpdateDesignTimeXaml
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Converters/FactionToLogo.cs b/CarouselViewChallenge/CarouselViewChallenge/Converters/FactionToLogo.cs
new file mode 100644
index 0000000..d8cfbdb
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Converters/FactionToLogo.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace CarouselViewChallenge.Converters
+{
+ internal class FactionToLogo : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return String.Empty;
+ }
+
+ string faction = ((string)value).Trim().ToLower();
+ switch (faction)
+ {
+ case "alliance":
+ return "resource://CarouselViewChallenge.Resources.alliance.green.svg";
+ case "empire":
+ return "resource://CarouselViewChallenge.Resources.empire.blue.svg";
+ case "federation":
+ return "resource://CarouselViewChallenge.Resources.federation.red.svg";
+ case "independent":
+ case "independant": // Uranius can't spell
+ return "resource://CarouselViewChallenge.Resources.independent.orange.svg";
+ default:
+ return String.Empty;
+ }
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Converters/NewsItemConverter.cs b/CarouselViewChallenge/CarouselViewChallenge/Converters/NewsItemConverter.cs
new file mode 100644
index 0000000..5de38f3
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Converters/NewsItemConverter.cs
@@ -0,0 +1,73 @@
+using CarouselViewChallenge.Models;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+
+namespace CarouselViewChallenge.Converters
+{
+ public class NewsItemConverter : JsonConverter
+ {
+ public static readonly NewsItemConverter Instance = new NewsItemConverter();
+
+ private static readonly Type NewsItemType = typeof(ICollection);
+
+ public override bool CanConvert(Type objectType)
+ {
+ return NewsItemType.IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var collection = new List();
+ NewsItem item = null;
+ while (reader.Read())
+ {
+ switch (reader.TokenType)
+ {
+ case JsonToken.StartObject:
+ item = new NewsItem();
+ collection.Add(item);
+ break;
+ case JsonToken.PropertyName:
+ SetProperty(reader, item);
+ break;
+ case JsonToken.EndArray:
+ return collection;
+ }
+ }
+ return collection;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
+
+ private static void SetProperty(JsonReader reader, NewsItem item)
+ {
+ var name = (string)reader.Value;
+ reader.Read();
+ switch (name)
+ {
+ case "title":
+ item.Title = (string)reader.Value;
+ break;
+ case "body":
+ item.Body = (string)reader.Value;
+ break;
+ case "date":
+ item.PublishDateTime = Convert.ToDateTime(reader.Value);
+ break;
+ case "nid":
+ item.Id = Convert.ToInt32(reader.Value);
+ break;
+ case "image":
+ item.FDImageName = (string)reader.Value;
+ break;
+ case "slug":
+ item.Slug = (string)reader.Value;
+ break;
+ }
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Converters/NewsTopicToImage.cs b/CarouselViewChallenge/CarouselViewChallenge/Converters/NewsTopicToImage.cs
new file mode 100644
index 0000000..69da8d9
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Converters/NewsTopicToImage.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace CarouselViewChallenge.Converters
+{
+ internal class NewsTopicToImage : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return String.Empty;
+ }
+ string topic = ((string)value).Trim().ToLower();
+ switch (topic)
+ {
+ case "unclassified":
+ return "resource://CarouselViewChallenge.Resources.galnet.orange.svg";
+ case "aliens":
+ return "resource://CarouselViewChallenge.Resources.aliens-ufo1.svg";
+ case "crime":
+ return "resource://CarouselViewChallenge.Resources.crime.svg";
+ case "combat":
+ return "resource://CarouselViewChallenge.Resources.combat-fighter2.svg";
+ case "culture":
+ return "resource://CarouselViewChallenge.Resources.theatre.svg";
+ case "economy":
+ return "resource://CarouselViewChallenge.Resources.economy-chart.svg";
+ case "exploration":
+ return "resource://CarouselViewChallenge.Resources.exploration-planet.svg";
+ case "health":
+ return "resource://CarouselViewChallenge.Resources.health.svg";
+ case "mining":
+ return "resource://CarouselViewChallenge.Resources.mining-pick.svg";
+ case "mystery":
+ return "resource://CarouselViewChallenge.Resources.mystery.svg";
+ case "politics":
+ return "resource://CarouselViewChallenge.Resources.politics.svg";
+ case "religion":
+ return "resource://CarouselViewChallenge.Resources.religion-ankh.svg";
+ case "science":
+ return "resource://CarouselViewChallenge.Resources.science-atom.svg";
+ case "alliance":
+ return "resource://CarouselViewChallenge.Resources.alliance.green.svg";
+ case "empire":
+ return "resource://CarouselViewChallenge.Resources.empire.blue.svg";
+ case "federation":
+ return "resource://CarouselViewChallenge.Resources.federation.red.svg";
+ case "ad":
+ return "resource://CarouselViewChallenge.Resources.aisling.logo.svg";
+ case "delaine":
+ return "resource://CarouselViewChallenge.Resources.delaine.logo.svg";
+ case "ald":
+ return "resource://CarouselViewChallenge.Resources.ald.logo.svg";
+ case "patreus":
+ return "resource://CarouselViewChallenge.Resources.patreus.logo.svg";
+ case "mahon":
+ return "resource://CarouselViewChallenge.Resources.mahon.logo.svg";
+ case "winters":
+ return "resource://CarouselViewChallenge.Resources.winters.logo.svg";
+ case "lyr":
+ return "resource://CarouselViewChallenge.Resources.lyr.logo.svg";
+ case "antal":
+ return "resource://CarouselViewChallenge.Resources.antal.logo.svg";
+ case "grom":
+ return "resource://CarouselViewChallenge.Resources.grom.logo.svg";
+ case "hudson":
+ return "resource://CarouselViewChallenge.Resources.hudson.logo.svg";
+ case "torval":
+ return "resource://CarouselViewChallenge.Resources.torval.logo.svg";
+ case "turner":
+ case "tarquin":
+ case "dekker":
+ case "vatermann":
+ case "martuuk":
+ case "dorn":
+ case "farseer":
+ case "tani":
+ case "ishmaak":
+ case "cheung":
+ case "ryder":
+ case "jameson":
+ case "qwent":
+ case "hicks":
+ case "brandon":
+ case "olmanova":
+ case "palin":
+ case "ram tah":
+ case "jean":
+ case "dweller":
+ case "sarge":
+ case "fortune":
+ case "blaster":
+ case "nemo":
+ return "resource://CarouselViewChallenge.Resources.engineer.orange.svg";
+ default:
+ return "resource://CarouselViewChallenge.Resources.galnet.orange.svg";
+ }
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Converters/StationPadToImage.cs b/CarouselViewChallenge/CarouselViewChallenge/Converters/StationPadToImage.cs
new file mode 100644
index 0000000..3d35559
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Converters/StationPadToImage.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace CarouselViewChallenge.Converters
+{
+ internal class StationPadToImage : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return String.Empty;
+ }
+
+ string pad = ((string)value).Trim().ToLower();
+ switch (pad)
+ {
+ case "large":
+ case "station":
+ return "resource://CarouselViewChallenge.Resources.coriolis.orange.svg";
+ case "large (planet)":
+ case "large(planet)":
+ case "planetary":
+ return "resource://CarouselViewChallenge.Resources.surface-port.orange.svg";
+ case "medium":
+ case "outpost":
+ return "resource://CarouselViewChallenge.Resources.outpost.orange.svg";
+ default:
+ return String.Empty;
+ }
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/GlobalSuppressions.cs b/CarouselViewChallenge/CarouselViewChallenge/GlobalSuppressions.cs
new file mode 100644
index 0000000..35e42c2
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/GlobalSuppressions.cs
@@ -0,0 +1,11 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("General", "RCS1079:Throwing of new NotImplementedException.", Scope = "member", Target = "~M:CarouselViewChallenge.Converters.NewsItemConverter.WriteJson(Newtonsoft.Json.JsonWriter,System.Object,Newtonsoft.Json.JsonSerializer)")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Formatting", "RCS1057:Add empty line between declarations.", Scope = "type", Target = "~T:CarouselViewChallenge.Models.NewsItem")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Formatting", "RCS1057:Add empty line between declarations.", Scope = "type", Target = "~T:CarouselViewChallenge.ViewModels.CarouselViewChallengeViewModel")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("General", "RCS1079:Throwing of new NotImplementedException.", Scope = "member", Target = "~M:CarouselViewChallenge.Converters.NewsTopicToImage.ConvertBack(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)~System.Object")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Formatting", "RCS1057:Add empty line between declarations.", Justification = "", Scope = "type", Target = "~T:CarouselViewChallenge.ViewModels.CarouselViewTwoViewModel")]
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Helpers/HttpHelper.cs b/CarouselViewChallenge/CarouselViewChallenge/Helpers/HttpHelper.cs
new file mode 100644
index 0000000..8fe3e2a
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Helpers/HttpHelper.cs
@@ -0,0 +1,39 @@
+using System.IO;
+using System.IO.Compression;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace CarouselViewChallenge.Helpers
+{
+ public static class HttpHelper
+ {
+ public static async Task ReadContentAsync(HttpResponseMessage response)
+ {
+ string content;
+
+ if (response.Content.Headers.ContentEncoding.Contains("gzip"))
+ {
+ Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using (GZipStream gzipStream = new GZipStream(stream, CompressionMode.Decompress))
+ using (StreamReader reader = new StreamReader(gzipStream))
+ {
+ content = reader.ReadToEnd();
+ }
+ }
+ else if (response.Content.Headers.ContentEncoding.Contains("deflate"))
+ {
+ Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using (DeflateStream gzipStream = new DeflateStream(stream, CompressionMode.Decompress))
+ using (StreamReader reader = new StreamReader(gzipStream))
+ {
+ content = reader.ReadToEnd();
+ }
+ }
+ else
+ {
+ content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ }
+ return content;
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Helpers/ToastHelper.cs b/CarouselViewChallenge/CarouselViewChallenge/Helpers/ToastHelper.cs
new file mode 100644
index 0000000..0692e09
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Helpers/ToastHelper.cs
@@ -0,0 +1,14 @@
+using Acr.UserDialogs;
+
+namespace CarouselViewChallenge.Helpers
+{
+ public static class ToastHelper
+ {
+ public static void Toast(string message)
+ {
+ ToastConfig toastConfig = new ToastConfig(message);
+ toastConfig.SetDuration(2500);
+ UserDialogs.Instance.Toast(toastConfig);
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Models/FortItem.cs b/CarouselViewChallenge/CarouselViewChallenge/Models/FortItem.cs
new file mode 100644
index 0000000..60fb353
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Models/FortItem.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace CarouselViewChallenge.Models
+{
+ public class FortItem
+ {
+ public string SystemName { get; set; }
+ public string DistanceToHQ { get; }
+ public string StationPad { get; }
+ public string StationDistance { get; }
+ public string MajorFaction { get; }
+
+ public FortItem(string systemName, string distanceToHQ, string stationPad, string stationDistance, string majorFaction)
+ {
+ SystemName = systemName;
+ DistanceToHQ = distanceToHQ;
+ StationPad = stationPad;
+ StationDistance = stationDistance;
+ MajorFaction = majorFaction;
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0} ({1}) - {2} ({3})", SystemName, DistanceToHQ, StationPad, StationDistance);
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Models/NewsItem.cs b/CarouselViewChallenge/CarouselViewChallenge/Models/NewsItem.cs
new file mode 100644
index 0000000..4a1e6c5
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Models/NewsItem.cs
@@ -0,0 +1,124 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace CarouselViewChallenge.Models
+{
+ public class NewsItem
+ {
+ #region Properties
+
+ private string _title;
+ [JsonProperty(PropertyName = "title")]
+ public string Title
+ {
+ get { return _title; }
+ internal set
+ {
+ if (_title != value)
+ {
+ _title = value.Trim();
+ }
+ }
+ }
+
+ private string _body;
+ [JsonProperty(PropertyName = "body")]
+ public string Body
+ {
+ get { return _body; }
+ internal set
+ {
+ if (_body != value)
+ {
+ _body = value.Replace("", "").Replace("
", "\n").Replace("
", "\n").Replace("'", "'").Replace(""", "\u201d").Trim();
+ }
+ }
+ }
+
+ [JsonProperty(PropertyName = "date")]
+ public DateTime PublishDateTime { get; internal set; }
+
+ [JsonProperty(PropertyName = "nid")]
+ public int Id { get; internal set; }
+
+ [JsonProperty(PropertyName = "image")]
+ public string FDImageName { get; internal set; }
+
+ [JsonProperty(PropertyName = "slug")]
+ public string Slug { get; internal set; }
+
+ public string Topic { get; private set; }
+ public List Tags { get; private set; }
+
+ public string PublishDate
+ {
+ get
+ {
+ // return the date part of PublishDateTime
+ string date = PublishDateTime.ToString();
+ return date.Substring(0, date.IndexOf(" "));
+ }
+ }
+
+ #endregion
+
+ public override string ToString()
+ {
+ return String.Format("{0}: {1}", Title, Body);
+ }
+
+ private List SplitSentences()
+ {
+ List sentences = new List
+ {
+ Title.Trim().ToLower(),
+ Slug.Replace("-", " ").Trim().ToLower()
+ };
+ foreach (string sentence in Regex.Split(Body, @"(?<=[\w\s](?:[\.\!\? ]+[\x20]*[\x22\xBB]*))(?:\s+(?))"))
+ {
+ sentences.Add(sentence.Trim().ToLower());
+ }
+ return sentences;
+ }
+
+ public void ClassifyArticle()
+ {
+ Tags = new List();
+ // analyse article using Bag of Words technique
+ TopicsList topicsList = new TopicsList("CarouselViewChallenge.Resources.NewsBoW.csv");
+ foreach (string sentence in SplitSentences())
+ {
+ foreach (Topic topic in topicsList.Topics)
+ {
+ foreach (string term in topic.Terms)
+ {
+ if (sentence.Contains(term.ToLower()))
+ {
+ topic.Count++;
+ }
+ }
+ }
+ }
+ // select topic + tags
+ Topic tempTopic = topicsList.Topics.OrderByDescending(o => o.Count).First();
+ if (tempTopic.Count < 2 || string.Equals(Title, "week in review", StringComparison.OrdinalIgnoreCase))
+ {
+ Topic = "Unclassified";
+ }
+ else
+ {
+ Topic = tempTopic.Name;
+ }
+ foreach (Topic topic in topicsList.Topics.OrderByDescending(o => o.Count).Take(4))
+ {
+ if (topic.Count > 0)
+ {
+ Tags.Add(topic.Name);
+ }
+ }
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Models/Topic.cs b/CarouselViewChallenge/CarouselViewChallenge/Models/Topic.cs
new file mode 100644
index 0000000..ff9ac4b
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Models/Topic.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace CarouselViewChallenge.Models
+{
+ public class Topic
+ {
+ #region Properties
+
+ public string Name { get; }
+ public List Terms { get; }
+ public int Count { get; set; }
+
+ #endregion
+
+ public Topic(string name, List terms)
+ {
+ Name = name;
+ Terms = terms;
+ Count = 0;
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Models/TopicsList.cs b/CarouselViewChallenge/CarouselViewChallenge/Models/TopicsList.cs
new file mode 100644
index 0000000..db25174
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Models/TopicsList.cs
@@ -0,0 +1,63 @@
+using CsvHelper;
+using CsvHelper.Configuration;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+
+namespace CarouselViewChallenge.Models
+{
+ public class TopicsList
+ {
+ private readonly string BoWFilename;
+
+ #region Properties
+
+ public List Topics { get; set; }
+
+ #endregion
+
+ public TopicsList(string filename)
+ {
+ BoWFilename = filename;
+ Topics = new List();
+ GetBagOfWords();
+ }
+
+ private void GetBagOfWords()
+ {
+ try
+ {
+ Assembly assembly = GetType().GetTypeInfo().Assembly;
+ using (Stream stream = assembly.GetManifestResourceStream(BoWFilename))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ Configuration csvConfig = new Configuration
+ {
+ Delimiter = ",",
+ IgnoreQuotes = true
+ };
+ using (CsvReader csv = new CsvReader(reader, csvConfig))
+ {
+ while (csv.Read())
+ {
+ string name = csv.GetField(0);
+ List terms = new List();
+ for (int i = 1; csv.TryGetField(i, out string value); i++)
+ {
+ terms.Add(value);
+ }
+ Topics.Add(new Topic(name, terms));
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new Exception("Unable to load Bag of Words for linguistic analysis.", ex);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/NewsBoW.csv b/CarouselViewChallenge/CarouselViewChallenge/Resources/NewsBoW.csv
new file mode 100644
index 0000000..c995277
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/NewsBoW.csv
@@ -0,0 +1,50 @@
+Aliens,alien,thargoid,guardian,aegis,meta-alloy,incursion,xenological
+Crime,crime,criminal,illegal,theft,steal,stole,murder,homicide,assassin,arrest,hijack,blackmail,hack,kidnap,corrupt,prosecution,guilt,unlicensed,regulation,police,cartel,narcotic,autopsy,black market
+Combat,combat,conflict,battle,navy,naval,armada,military,bounty,bounties,insurrection,kill order,hostilities,mercenar,violent,raid,attack,defence
+Culture,fine art,artwork,sculpture,painting,literature,masterpiece,artist,author,theatre,theatrical,festival,exhibition,culinary,cuisine,delicacy,delicacies,connoisseur,orchestra
+Economy,economy,economic,trade,deliver,construct,investor,supplier,logistics,merger,conglomerate,commodities,consumer,business analyst,investment,wealth,fortune,ambrose foundation,industry,industrial,manufacture,engineering,bank of zaonce,megaship,resources,homeless,immigra,agricultur,crop yield,harvest
+Exploration,exploration,explorers,distant worlds,mapping,cartographics,unexplored,sagittarius,a*,beagle point,galactic core,codex
+Health,health,medical,medicine,meds,pharmaceutical,outbreak,disease,illness,injury,injuries,clinic,immune,patient,addict,narcotic,fertility
+Mining,mining,miner,mineral,prospector
+Mystery,mystery,mysteri,vanish,phenomenon,missing,disappear,anomaly,inexplicabl,dream journal,visions,gan romero,halsey,starship one,raxxla,dark wheel,omphalos rift,salomé,salome,metadrive
+Politics,politic,election,senate,senator,congress,president,rebellion,diplomacy,corrupt,emancipat,homeless,immigra,activist,anti-slavery
+Religion,religion,god,church,worship,faith,theology,thelogian
+Science,scientific,technolog,professor,engineer,hyperspace,research,canonn,anomaly,codex,synthetic treatment,fertility,sustainability
+Alliance,alliance,mahon,yamamoto,kincaid,interpol,bank of zaonce
+Empire,empire,imperial,emperor,arissa,lavigny,duval,patreus,torval,senate,senator,chancellor,blaine,slave,achenar,capitol,imperium
+Federation,federation,federal,hudson,winters,halsey,congress,olympus village
+AD,aisling duval
+Delaine,archon delaine
+ALD,arissa lavigny-duval,lavigny-duval,emperor
+Patreus,denton patreus,patreus
+Mahon,edmund mahon,prime minister mahon
+Winters,felicia winters,shadow president winters
+LYR,li yong-rui,yong-rui,sirius corp
+Antal,pranav antal,simguru,utopia
+Grom,yuri grom,eg pilots
+Hudson,zachary hudson,president hudson
+Torval,zemina torval,senator torval
+Turner,bill turner,turner metallics inc
+Tarquin,broo tarquin,broo's legacy
+Dekker,colonel bris dekker,dekker's yard
+Vatermann,didi vatermann,vatermann llc
+Martuuk,elvira martuuk,long sight base
+Dorn,etienne dorn,kraken's retreat
+Farseer,felicity farseer,farseer inc
+Tani,hera tani,the jet's hole
+Ishmaak,juri ishmaak,pater's memorial
+Cheung,lei cheung,trader's rest
+Ryder,liz ryder,demolition unlimited
+Jameson,lori jameson,jameson base
+Qwent,marco qwent,qwent research base
+Hicks,marsha hicks,the watchtower
+Brandon,mel brandon,the brig
+Olmanova,petra olmanova,asura
+Palin,professor palin,palin research centre
+Ram Tah,ram tah,phoenix base
+Jean,selene jean,prospector's rest
+Dweller,the dweller,black hide
+Sarge,the sarge,beta-3 tucani
+Fortune,tiana fortune,fortune's loss
+Blaster,tod "the blaster" mcquinn,trophy camp
+Nemo,zacariah nemo,nemo cyber party base
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/aisling.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/aisling.logo.svg
new file mode 100644
index 0000000..8096428
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/aisling.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/ald.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/ald.logo.svg
new file mode 100644
index 0000000..df854a1
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/ald.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/aliens-ufo1.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/aliens-ufo1.svg
new file mode 100644
index 0000000..9c2da90
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/aliens-ufo1.svg
@@ -0,0 +1,34 @@
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/alliance.green.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/alliance.green.svg
new file mode 100644
index 0000000..d18512c
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/alliance.green.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/antal.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/antal.logo.svg
new file mode 100644
index 0000000..1163883
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/antal.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/combat-fighter2.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/combat-fighter2.svg
new file mode 100644
index 0000000..ad10984
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/combat-fighter2.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/coriolis.orange.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/coriolis.orange.svg
new file mode 100644
index 0000000..789499d
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/coriolis.orange.svg
@@ -0,0 +1,34 @@
+
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/crime.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/crime.svg
new file mode 100644
index 0000000..67805c2
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/crime.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/delaine.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/delaine.logo.svg
new file mode 100644
index 0000000..266738f
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/delaine.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/economy-chart.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/economy-chart.svg
new file mode 100644
index 0000000..f2fd9df
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/economy-chart.svg
@@ -0,0 +1,19 @@
+
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/empire.blue.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/empire.blue.svg
new file mode 100644
index 0000000..c21590b
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/empire.blue.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/engineer.orange.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/engineer.orange.svg
new file mode 100644
index 0000000..a4cc0d3
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/engineer.orange.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/exploration-planet.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/exploration-planet.svg
new file mode 100644
index 0000000..da90670
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/exploration-planet.svg
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/federation.red.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/federation.red.svg
new file mode 100644
index 0000000..c030f18
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/federation.red.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/galnet.orange.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/galnet.orange.svg
new file mode 100644
index 0000000..2c4ba33
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/galnet.orange.svg
@@ -0,0 +1,26 @@
+
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/grom.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/grom.logo.svg
new file mode 100644
index 0000000..ab28148
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/grom.logo.svg
@@ -0,0 +1,54 @@
+
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/health.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/health.svg
new file mode 100644
index 0000000..4509b66
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/health.svg
@@ -0,0 +1,18 @@
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/hudson.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/hudson.logo.svg
new file mode 100644
index 0000000..df84885
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/hudson.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/independent.orange.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/independent.orange.svg
new file mode 100644
index 0000000..a0f2a28
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/independent.orange.svg
@@ -0,0 +1,58 @@
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/lyr.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/lyr.logo.svg
new file mode 100644
index 0000000..a5993e3
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/lyr.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/mahon.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/mahon.logo.svg
new file mode 100644
index 0000000..66a947a
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/mahon.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/mining-pick.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/mining-pick.svg
new file mode 100644
index 0000000..df7359b
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/mining-pick.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/mystery.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/mystery.svg
new file mode 100644
index 0000000..74da07d
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/mystery.svg
@@ -0,0 +1,44 @@
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/outpost.orange.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/outpost.orange.svg
new file mode 100644
index 0000000..251dceb
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/outpost.orange.svg
@@ -0,0 +1,69 @@
+
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/patreus.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/patreus.logo.svg
new file mode 100644
index 0000000..f03ceab
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/patreus.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/politics.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/politics.svg
new file mode 100644
index 0000000..241e16f
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/politics.svg
@@ -0,0 +1,24 @@
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/religion-ankh.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/religion-ankh.svg
new file mode 100644
index 0000000..f33a213
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/religion-ankh.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/science-atom.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/science-atom.svg
new file mode 100644
index 0000000..31ba6b5
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/science-atom.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/surface-port.orange.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/surface-port.orange.svg
new file mode 100644
index 0000000..a1e3aef
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/surface-port.orange.svg
@@ -0,0 +1,62 @@
+
+
+
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/theatre.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/theatre.svg
new file mode 100644
index 0000000..14f6b09
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/theatre.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/torval.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/torval.logo.svg
new file mode 100644
index 0000000..da8dbe4
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/torval.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Resources/winters.logo.svg b/CarouselViewChallenge/CarouselViewChallenge/Resources/winters.logo.svg
new file mode 100644
index 0000000..624730a
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Resources/winters.logo.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Services/APIException.cs b/CarouselViewChallenge/CarouselViewChallenge/Services/APIException.cs
new file mode 100644
index 0000000..fbac808
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Services/APIException.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace CarouselViewChallenge.Services
+{
+ [Serializable]
+ public class APIException : Exception
+ {
+ public int? StatusCode { get; }
+
+ public APIException()
+ {
+ }
+
+ public APIException(string message) : base(message)
+ {
+ }
+
+ public APIException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected APIException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public APIException(string message, int? code) : base(message)
+ {
+ StatusCode = code;
+ }
+
+ public APIException(string message, Exception innerException, int? code) : base(message, innerException)
+ {
+ StatusCode = code;
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Services/DownloadService.cs b/CarouselViewChallenge/CarouselViewChallenge/Services/DownloadService.cs
new file mode 100644
index 0000000..0d2df93
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Services/DownloadService.cs
@@ -0,0 +1,67 @@
+using CarouselViewChallenge.Helpers;
+using MonkeyCache.FileStore;
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Xamarin.Essentials;
+
+namespace CarouselViewChallenge.Services
+{
+ public sealed class DownloadService
+ {
+ private static readonly DownloadService instance = new DownloadService();
+
+ private readonly HttpClient client;
+
+ private DownloadService()
+ {
+ client = new HttpClient();
+ client.DefaultRequestHeaders.Add("X-Requested-With", "CarouselViewChallenge");
+ client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
+ client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate"));
+ client.Timeout = TimeSpan.FromSeconds(40);
+ }
+
+ public static DownloadService Instance()
+ {
+ return instance;
+ }
+
+ public async Task<(string data, DateTime updated)> GetData(string url, string dataKey, string lastUpdatedKey, TimeSpan expiry, bool ignoreCache = false)
+ {
+ string data;
+ DateTime lastUpdated;
+
+ if (!ignoreCache && Barrel.Current.Exists(dataKey) && !Barrel.Current.IsExpired(dataKey))
+ {
+ // use cached data
+ data = Barrel.Current.Get(dataKey);
+ lastUpdated = DateTime.Parse(Barrel.Current.Get(lastUpdatedKey));
+ }
+ else if (Connectivity.NetworkAccess != NetworkAccess.Internet)
+ {
+ throw new NoNetworkNoCacheException("No Internet available and no data cached.");
+ }
+ else
+ {
+ // download data
+ var uri = new Uri(url);
+ HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new APIException(String.Format("{0} - {1}", response.StatusCode, response.ReasonPhrase), (int)response.StatusCode);
+ }
+ else
+ {
+ data = await HttpHelper.ReadContentAsync(response).ConfigureAwait(false);
+ lastUpdated = DateTime.Now;
+ // cache data
+ Barrel.Current.Add(dataKey, data, expiry);
+ Barrel.Current.Add(lastUpdatedKey, lastUpdated.ToString(), expiry);
+ }
+ }
+ return (data, lastUpdated);
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Services/GalNetService.cs b/CarouselViewChallenge/CarouselViewChallenge/Services/GalNetService.cs
new file mode 100644
index 0000000..7ab457f
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Services/GalNetService.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Threading.Tasks;
+
+namespace CarouselViewChallenge.Services
+{
+ public class GalNetService
+ {
+ private const string GalNetURL = "https://elitedangerous-website-backend-production.elitedangerous.com/api/galnet?_format=json";
+ private const string dataKey = "NewsFeed";
+ private const string lastUpdatedKey = "NewsLastUpdated";
+
+ public async Task<(string data, DateTime updated)> GetData(bool ignoreCache = false)
+ {
+ DateTime lastUpdated;
+ string json = String.Empty;
+ TimeSpan expiry = TimeSpan.FromHours(1);
+
+ // download the json feed
+ DownloadService downloadService = DownloadService.Instance();
+ (json, lastUpdated) = await downloadService.GetData(GalNetURL, dataKey, lastUpdatedKey, expiry, ignoreCache).ConfigureAwait(false);
+ return (json, lastUpdated);
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Services/NoNetworkNoCacheException.cs b/CarouselViewChallenge/CarouselViewChallenge/Services/NoNetworkNoCacheException.cs
new file mode 100644
index 0000000..04f8017
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Services/NoNetworkNoCacheException.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace CarouselViewChallenge.Services
+{
+ [Serializable]
+ public class NoNetworkNoCacheException : Exception
+ {
+ public NoNetworkNoCacheException()
+ {
+ }
+
+ public NoNetworkNoCacheException(string message) : base(message)
+ {
+ }
+
+ public NoNetworkNoCacheException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected NoNetworkNoCacheException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/ViewModels/CarouselViewChallengeViewModel.cs b/CarouselViewChallenge/CarouselViewChallenge/ViewModels/CarouselViewChallengeViewModel.cs
new file mode 100644
index 0000000..488c39c
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/ViewModels/CarouselViewChallengeViewModel.cs
@@ -0,0 +1,96 @@
+using CarouselViewChallenge.Converters;
+using CarouselViewChallenge.Helpers;
+using CarouselViewChallenge.Models;
+using CarouselViewChallenge.Services;
+using MonkeyCache.FileStore;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+
+namespace CarouselViewChallenge.ViewModels
+{
+ public class CarouselViewChallengeViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+ protected virtual void OnPropertyChanged(string propertyName)
+ {
+ var changed = PropertyChanged;
+ if (changed != null)
+ {
+ PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+
+ public ObservableCollection GalNetNewsList { get; set; }
+
+ private DateTime _galnetLastUpdated;
+ public DateTime GalNetLastUpdated
+ {
+ get { return _galnetLastUpdated; }
+ private set
+ {
+ if (_galnetLastUpdated != value)
+ {
+ _galnetLastUpdated = value;
+ OnPropertyChanged(nameof(GalNetLastUpdated));
+ }
+ }
+ }
+
+ private string _message;
+ public string Message
+ {
+ get { return _message; }
+ set
+ {
+ if (_message != value)
+ {
+ _message = value;
+ OnPropertyChanged(nameof(Message));
+ }
+ }
+ }
+
+ public CarouselViewChallengeViewModel()
+ {
+ Barrel.ApplicationId = "com.carouselchallenge.galnet";
+ GalNetNewsList = new ObservableCollection();
+ GetGalNetNewsAsync();
+ }
+
+ private async void GetGalNetNewsAsync(bool ignoreCache = false)
+ {
+ Message = "Loading...";
+ try
+ {
+ // get the news feed
+ string json = String.Empty;
+ GalNetService news = new GalNetService();
+ (json, GalNetLastUpdated) = await news.GetData(ignoreCache).ConfigureAwait(false);
+
+ // parse the json data
+ GalNetNewsList.Clear();
+ await Task.Run(() =>
+ {
+ List fullNews = JsonConvert.DeserializeObject>(json, NewsItemConverter.Instance);
+ foreach (NewsItem item in fullNews.Where(o => !String.IsNullOrEmpty(o.Body)).OrderByDescending(o => o.PublishDateTime).Take(20))
+ {
+ item.ClassifyArticle();
+ Device.BeginInvokeOnMainThread(() => GalNetNewsList.Add(item));
+ }
+ }).ConfigureAwait(false);
+ Message = null;
+ }
+ catch (Exception ex)
+ {
+ Message = String.Format("Error: {0}", ex.Message);
+ ToastHelper.Toast(Message);
+ }
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/ViewModels/CarouselViewTwoViewModel.cs b/CarouselViewChallenge/CarouselViewChallenge/ViewModels/CarouselViewTwoViewModel.cs
new file mode 100644
index 0000000..9c7c656
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/ViewModels/CarouselViewTwoViewModel.cs
@@ -0,0 +1,113 @@
+using CarouselViewChallenge.Helpers;
+using CarouselViewChallenge.Models;
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows.Input;
+using Xamarin.Essentials;
+using Xamarin.Forms;
+
+namespace CarouselViewChallenge.ViewModels
+{
+ public class CarouselViewTwoViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+ protected virtual void OnPropertyChanged(string propertyName)
+ {
+ var changed = PropertyChanged;
+ if (changed != null)
+ {
+ PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+
+ public ICommand CopySystemNameCommand { get; }
+
+ public ObservableCollection LargeShipList { get; set; }
+
+ public ObservableCollection SmallShipList { get; set; }
+
+ public string _ccBalance;
+ public string CCBalance
+ {
+ get { return _ccBalance; }
+ private set
+ {
+ if (_ccBalance != value)
+ {
+ _ccBalance = value;
+ OnPropertyChanged(nameof(CCBalance));
+ }
+ }
+ }
+
+ private string _cycle;
+ public string Cycle
+ {
+ get { return _cycle; }
+ protected set
+ {
+ if (_cycle != value)
+ {
+ _cycle = value;
+ OnPropertyChanged(nameof(Cycle));
+ }
+ }
+ }
+
+ private DateTime _lastUpdated;
+ public DateTime LastUpdated
+ {
+ get { return _lastUpdated; }
+ private set
+ {
+ if (_lastUpdated != value)
+ {
+ _lastUpdated = value;
+ OnPropertyChanged(nameof(LastUpdated));
+ }
+ }
+ }
+
+ public CarouselViewTwoViewModel()
+ {
+ // fake data
+ LargeShipList = new ObservableCollection
+ {
+ new FortItem("Xinca", "101 ly", "Station", "343 ls", "independent"),
+ new FortItem("Ngarawe", "86 ly", "Station", "35 ls", "empire"),
+ new FortItem("Yanerones", "115 ly", "Planetary", "42 ls", "empire"),
+ new FortItem("Damoorai", "86 ly", "Station", "1,324 ls", "empire"),
+ new FortItem("Ostyat", "117 ly", "Station", "41 ls", "independent"),
+ new FortItem("Itzamni", "64 ly", "Planetary", "158,992 ls", "empire"),
+ new FortItem("HIP 20524", "53 ly", "Planetary", "2,192 ls", "empire"),
+ new FortItem("Amenta", "47 ly", "Station", "236 ls", "empire"),
+ new FortItem("27 G. Caeli", "54 ly", "Outpost", "3,145 ls", "independent"),
+ new FortItem("Kappa Reticuli", "81 ly", "Planetary", "350 ls", "federation")
+ };
+ SmallShipList = new ObservableCollection
+ {
+ new FortItem("Lutni", "30 ly", "Planetary", "21 ls", "empire"),
+ new FortItem("AB Pictoris", "41 ly", "Station", "1,739 ls", "empire"),
+ new FortItem("HIP 21778", "66 ly", "Outpost", "181 ls", "independent"),
+ new FortItem("Biliri", "68 ly", "Outpost", "181 ls", "empire"),
+ new FortItem("Ngarawe", "86 ly", "Station", "35 ls", "empire"),
+ new FortItem("Lopocares", "125 ly", "Outpost", "329 ls", "federation"),
+ new FortItem("Carverda", "41 ly", "Station", "727 ls", "empire"),
+ new FortItem("HIP 32812", "62 ly", "Outpost", "1,753 ls", "federation"),
+ new FortItem("Tiburnat", "53 ly", "Planetary", "2,192 ls", "empire"),
+ new FortItem("Ju Shiva", "47 ly", "Station", "236 ls", "independent"),
+ };
+ Cycle = "Cycle 150";
+ CCBalance = "853 CC";
+ LastUpdated = DateTime.Now;
+ CopySystemNameCommand = new Command(CopySystemNameAsync);
+ }
+
+ private async void CopySystemNameAsync(string name)
+ {
+ await Clipboard.SetTextAsync(name).ConfigureAwait(false);
+ ToastHelper.Toast(String.Format("{0} copied", name));
+ }
+ }
+}
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewChallengePage.xaml b/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewChallengePage.xaml
index 1a7cc0d..93e4eb0 100644
--- a/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewChallengePage.xaml
+++ b/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewChallengePage.xaml
@@ -3,11 +3,103 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:ffsvg="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"
+ xmlns:c="clr-namespace:CarouselViewChallenge.Converters"
+ xmlns:m="clr-namespace:CarouselViewChallenge.Models"
+ xmlns:vm="clr-namespace:CarouselViewChallenge.ViewModels"
mc:Ignorable="d"
- x:Class="CarouselViewChallenge.Views.CarouselViewChallengePage">
-
-
-
-
-
+ x:Class="CarouselViewChallenge.Views.CarouselViewChallengePage"
+ x:DataType="vm:CarouselViewChallengeViewModel"
+ BackgroundColor="{StaticResource pageBackground}">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Horizontal
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewChallengePage.xaml.cs b/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewChallengePage.xaml.cs
index 38f2e9f..c42482d 100644
--- a/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewChallengePage.xaml.cs
+++ b/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewChallengePage.xaml.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
+using CarouselViewChallenge.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
@@ -12,9 +7,12 @@ namespace CarouselViewChallenge.Views
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CarouselViewChallengePage : ContentPage
{
+ private readonly CarouselViewChallengeViewModel vm = new CarouselViewChallengeViewModel();
+
public CarouselViewChallengePage()
{
InitializeComponent();
+ BindingContext = vm;
}
}
}
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewTwoPage.xaml b/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewTwoPage.xaml
new file mode 100644
index 0000000..85e225f
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewTwoPage.xaml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Horizontal
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Horizontal
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewTwoPage.xaml.cs b/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewTwoPage.xaml.cs
new file mode 100644
index 0000000..8936126
--- /dev/null
+++ b/CarouselViewChallenge/CarouselViewChallenge/Views/CarouselViewTwoPage.xaml.cs
@@ -0,0 +1,18 @@
+using CarouselViewChallenge.ViewModels;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace CarouselViewChallenge.Views
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class CarouselViewTwoPage : ContentPage
+ {
+ private readonly CarouselViewTwoViewModel vm = new CarouselViewTwoViewModel();
+
+ public CarouselViewTwoPage()
+ {
+ InitializeComponent();
+ BindingContext = vm;
+ }
+ }
+}
\ No newline at end of file