<repo_name>jrmax/MyDevoxxApp<file_sep>/README.md
# Devoxx Windows Mobile App, "My Devoxx"

This was the official Windows Phone App for Devoxx Conferences 2015 and 2016. It is compatible to Windows Phone 8.1 and Windows 10 Mobile.

**The project has been discontinued!**

## Setup
Just get the code, open it in [Visual Studio 2015](https://www.visualstudio.com/) (Community Edtion is enough) and start coding.
You will need to install a Windows Mobile Emulator to test the app.
<file_sep>/MyDevoxx/UserControls/EventTalkControl.xaml.cs
﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;

namespace MyDevoxx.UserControls
{
    public sealed partial class EventTalkControl : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public static readonly DependencyProperty ShowSpeakerProperty =
            DependencyProperty.Register("ShowSpeaker", typeof(bool), typeof(EventTalkControl), new PropertyMetadata(true));
        public bool ShowSpeaker
        {
            get { return (bool)GetValue(ShowSpeakerProperty); }
            set
            {
                SetValue(ShowSpeakerProperty, value);
                NotifyPropertyChanged();
            }
        }

        public EventTalkControl()
        {
            this.InitializeComponent();
        }

        private void ImageBrush_ImageFailed(object sender, Windows.UI.Xaml.ExceptionRoutedEventArgs e)
        {
            ((ImageBrush)sender).ImageSource = new BitmapImage(new Uri(@"ms-appx:///Assets/speaker_placeholder.png"));
        }

        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}
<file_sep>/MyDevoxx/Converter/View/SelectorViewDateConverter.cs
﻿using MyDevoxx.Model;
using System;
using System.Diagnostics;
using System.Globalization;
using Windows.UI.Xaml.Data;

namespace MyDevoxx.Converter.View
{
    public class SelectorViewDateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value == null)
            {
                return "";
            }

            Conference conference = (Conference)value;
            DateTime fromDate, toDate;
            Debug.WriteLine(CultureInfo.CurrentCulture);
            bool isSuccessfulFrom = DateTime.TryParseExact(conference.fromDate, "yyyy-MM-dTHH:mm:ss.000Z", CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal, out fromDate);
            bool isSuccessfulTo = DateTime.TryParseExact(conference.toDate, "yyyy-MM-dTHH:mm:ss.000Z", CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal, out toDate);

            if (fromDate != null && toDate != null && isSuccessfulFrom && isSuccessfulTo)
            {
                return "from " + fromDate.Day + " to " + toDate.ToString("dd/MM/yyyy");
            }
            return "";
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
}
<file_sep>/MyDevoxx/Services/RestService.cs
﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Windows.Web.Http;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using MyDevoxx.Services.RestModel;
using Windows.Storage;
using System.Threading;
using MyDevoxx.Converter.Model;
using SQLite.Net;
using SQLite.Net.Async;
using MyDevoxx.Services.RestModel.Voting;
using MyDevoxx.Utils;
using Windows.Storage.Streams;

namespace MyDevoxx.Services
{
    public class RestService : IRestService
    {
        private CancellationTokenSource cts;
        private StorageFolder localFolder = ApplicationData.Current.LocalFolder;
        private ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
        private SQLiteAsyncConnection sqlConnection;

        private string cfpUrl = "https://s3-eu-west-1.amazonaws.com/cfpdevoxx/cfp.json";
        private string speakersUrl = "/speakers";
        private string tracksUrl = "/tracks";
        private string scheduleUrl = "/schedules";

        private const int CACHE_LIVETIME_IN_HOURS = 6;
        private const int CACHE_LIVETIME_WITHOUT_PUSH_IN_MINUTES = 15;

        public RestService()
        {
            cts = new CancellationTokenSource();
            cts.CancelAfter(TimeSpan.FromSeconds(5));

            DBConnection();
        }

        private HttpClient CreateHttpClient()
        {
            var client = new HttpClient();
            // Add a user-agent header 
            var headers = client.DefaultRequestHeaders;
            headers.UserAgent.ParseAdd("ie");
            headers.UserAgent.ParseAdd("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
            headers.Accept.ParseAdd("application/json");

            return client;
        }

        public async Task<Tuple<List<Model.Event>, bool>> GetEvents(string day)
        {
            string endpoint = (string)settings.Values[Settings.CFP_ENDPOINT];
            string confId = (string)settings.Values[Settings.CONFERENCE_ID];
            string country = (string)settings.Values[Settings.COUNTRY];

            string url = endpoint + "/conferences" + "/" + confId + scheduleUrl + "/" + day + "/";
            Tuple<Schedule, bool> result = await fetch<Schedule>(url, "schedule_" + day + "_" + confId + ".json");

            return Tuple.Create(EventConverter.apply(result.Item1, confId, country, day), result.Item2);
        }

        public async Task<Tuple<List<Model.Speaker>, bool>> GetSpeakers()
        {
            string endpoint = (string)settings.Values[Settings.CFP_ENDPOINT];
            string confId = (string)settings.Values[Settings.CONFERENCE_ID];
            string url = endpoint + "/conferences" + "/" + confId + speakersUrl;
            Tuple<List<Speaker>, bool> result = await fetch<List<Speaker>>(url, "speakerlist_" + confId + ".json");
            List<Model.Speaker> speakerList = new List<Model.Speaker>();
            foreach (Speaker s in result.Item1)
            {
                speakerList.Add(SpeakerConverter.apply(s, confId));
            }
            return Tuple.Create(speakerList, result.Item2);
        }

        public async Task<Tuple<Model.Speaker, bool>> GetSpeaker(string uuid)
        {
            string endpoint = (string)settings.Values[Settings.CFP_ENDPOINT];
            string confId = (string)settings.Values[Settings.CONFERENCE_ID];
            string url = endpoint + "/conferences" + "/" + confId + speakersUrl + "/" + uuid;
            Tuple<Speaker, bool> result = await fetch<Speaker>(url, "speaker_" + uuid + "_" + confId + ".json");
            return Tuple.Create(SpeakerConverter.apply(result.Item1, confId), result.Item2);
        }

        public async Task<Tuple<List<Model.Track>, bool>> GetTracks()
        {
            string endpoint = (string)settings.Values[Settings.CFP_ENDPOINT];
            string confId = (string)settings.Values[Settings.CONFERENCE_ID];
            string url = endpoint + "/conferences" + "/" + confId + tracksUrl;
            Tuple<TrackList, bool> result = await fetch<TrackList>(url, "tracksList_" + confId + ".json");

            return Tuple.Create(TrackListConverter.apply(result.Item1, confId), result.Item2);
        }

        public async Task<Tuple<List<Model.Conference>, bool>> GetConferences()
        {
            Tuple<List<CFP>, bool> cfp = await fetch<List<CFP>>(cfpUrl, "cfp.json");
            List<Model.Conference> conferences = new List<Model.Conference>();
            foreach (CFP c in cfp.Item1)
            {
                conferences.Add(ConferenceConverter.apply(c));
            }

            return Tuple.Create(conferences, cfp.Item2);
        }

        public async Task<Tuple<List<Model.Floor>, bool>> GetFloors()
        {
            string confId = (string)settings.Values[Settings.CONFERENCE_ID];
            Tuple<List<CFP>, bool> cfp = await fetch<List<CFP>>(cfpUrl, "cfp.json");
            foreach (CFP c in cfp.Item1)
            {
                if (c.id.Equals(confId))
                {
                    return Tuple.Create(FloorConverter.apply(c), cfp.Item2);
                }
            }

            return Tuple.Create(new List<Model.Floor>(), cfp.Item2);
        }

        private async Task<Tuple<T, bool>> fetch<T>(string url, string cacheFileName)
        {
            Debug.WriteLine("Requested URL: " + url);

            HttpClient client = CreateHttpClient();

            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
            Uri resourceUri = new Uri(url, UriKind.Absolute);
            string responseText;
            Model.ETag eTag;
            bool updated = false;

            client.DefaultRequestHeaders.Remove("If-None-Match");
            eTag = await GetEtag(url);
            if (eTag != null && eTag.eTag != null)
            {
                client.DefaultRequestHeaders.Add(new KeyValuePair<string, string>("If-None-Match", eTag.eTag));
            }

            try
            {
                bool pushEnabled;
                if (settings.Values[Settings.PUSH_UPDATE_ENABLED] == null
                    || !Boolean.TryParse((string)settings.Values[Settings.PUSH_UPDATE_ENABLED], out pushEnabled))
                {
                    pushEnabled = false;
                }
                bool isCacheInvalid = eTag == null
                    || ((DateTime.Now - eTag.timeStamp).TotalHours >= CACHE_LIVETIME_IN_HOURS && pushEnabled)
                    || ((DateTime.Now - eTag.timeStamp).TotalMinutes >= CACHE_LIVETIME_WITHOUT_PUSH_IN_MINUTES && !pushEnabled);

                if (isCacheInvalid)
                {
                    //last request was more than 10 minutes / 6 hours ago
                    HttpResponseMessage msg = await client.GetAsync(resourceUri);
                    if (HttpStatusCode.NotModified.Equals(msg.StatusCode))
                    {
                        //content has not changed since last request
                        responseText = await loadFromFile(cacheFileName);
                        SaveEtag(url, eTag.eTag);
                        Debug.WriteLine("from Cache because not modified");
                    }
                    else
                    {
                        //content is new or has changed since last request
                        responseText = await msg.Content.ReadAsStringAsync();
                        await saveToFile(cacheFileName, responseText);

                        string newEtag;
                        msg.Headers.TryGetValue("Etag", out newEtag);
                        SaveEtag(url, newEtag);
                        updated = true;
                        Debug.WriteLine("from Server");
                    }
                }
                else
                {
                    //last request was less than CACHE_LIVETIME_IN_HOURS ago
                    responseText = await loadFromFile(cacheFileName);
                    Debug.WriteLine("from Cache");
                }

                return Tuple.Create((T)serializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(responseText))), updated);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
                responseText = await loadFromFile(cacheFileName);
                if (responseText == null)
                {
                    responseText = await loadFromFallbackFile(cacheFileName);
                    Debug.WriteLine("Read fallback file!");
                }
                if (responseText != null)
                {
                    return Tuple.Create((T)serializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(responseText))), false);
                }
            }
            return Tuple.Create(Activator.CreateInstance<T>(), true);
        }

        private async Task saveToFile(string filename, string data)
        {
            StorageFile file = await localFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);

            await FileIO.WriteTextAsync(file, data);
        }

        private async Task<string> loadFromFile(string filename)
        {
            try
            {
                StorageFile file = await localFolder.GetFileAsync(filename);
                return await FileIO.ReadTextAsync(file);
            }
            catch (Exception)
            {
                return null;
            }
        }

        private async Task<string> loadFromFallbackFile(string filename)
        {
            try
            {
                StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri(@"ms-appx:///Assets/FallbackData/" + filename));
                return await FileIO.ReadTextAsync(file);
            }
            catch (Exception)
            {
                Debug.WriteLine("Fallback file not found " + filename);
                return null;
            }
        }

        private async Task<Model.ETag> GetEtag(string url)
        {
            Model.ETag result = await sqlConnection.Table<Model.ETag>().Where(i => i.url.Equals(url)).FirstOrDefaultAsync();
            if (result != null)
            {
                Debug.WriteLine(result.eTag + " - " + result.url + " created at " + result.timeStamp);
                return result;
            }
            return null;
        }

        private async void SaveEtag(string url, string eTag)
        {
            await sqlConnection.InsertOrReplaceAsync(new Model.ETag() { url = url, eTag = eTag, timeStamp = DateTime.Now });
        }

        private void DBConnection()
        {
            if (sqlConnection != null) return;

            var path = Path.Combine(ApplicationData.Current.LocalFolder.Path, "db.sqlite");

            SQLiteConnectionString connString = new SQLiteConnectionString(path.ToString(), false);
            SQLiteConnectionWithLock connLock = new SQLiteConnectionWithLock(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), connString);

            sqlConnection = new SQLiteAsyncConnection(() => connLock);
        }

        public async Task<VoteMessage> VoteTalk(VoteBasic vote)
        {
            return await Vote(vote);
        }

        public async Task<VoteMessage> VoteTalk(VoteReviews vote)
        {
            return await Vote(vote);
        }

        private async Task<VoteMessage> Vote<T>(T vote)
        {
            HttpClient client = CreateHttpClient();

            string confId = (string)settings.Values[Settings.CONFERENCE_ID];
            string votingURL = (string)settings.Values[Settings.VOTING_URL];

            MemoryStream stream = new MemoryStream();
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
            serializer.WriteObject(stream, vote);
            stream.Position = 0;
            StreamReader sr = new StreamReader(stream);
            string dataString = sr.ReadToEnd();

            try
            {
                HttpResponseMessage response = await client.PostAsync(new Uri(votingURL + "api/voting/v1/vote"), new HttpStringContent(dataString, Windows.Storage.Streams.UnicodeEncoding.Utf8, "application/json"));
                if (!HttpStatusCode.Created.Equals(response.StatusCode))
                {
                    string responseTxt = await response.Content.ReadAsStringAsync();
                    DataContractJsonSerializer obj = new DataContractJsonSerializer(typeof(VoteMessage));
                    VoteMessage message = obj.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(responseTxt))) as VoteMessage;
                    return message;
                }
            }
            catch (Exception)
            {
                VoteMessage message = new VoteMessage();
                message.message = "Could not reach server, please try again later.";
                return message;
            }
            return null;
        }

        public async Task<VoteResults> GetVoteResults(int limit, string day, string talkType, string track)
        {
            //[/DV15/top/talks{?limit}&{day}&{talkType}&{track}]
            HttpClient client = CreateHttpClient();
            string confId = (string)settings.Values[Settings.CONFERENCE_ID];
            string url = (string)settings.Values[Settings.VOTING_URL];
            string parameters = buildVoteParameters(limit, day, talkType, track);
            try
            {
                HttpResponseMessage response = await client.GetAsync(new Uri(url + confId + "/top/talks" + parameters));
                if (HttpStatusCode.Ok.Equals(response.StatusCode))
                {
                    string responseTxt = await response.Content.ReadAsStringAsync();
                    DataContractJsonSerializer obj = new DataContractJsonSerializer(typeof(VoteResults));
                    VoteResults result = obj.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(responseTxt))) as VoteResults;
                    return result;
                }
            }
            catch (Exception)
            {
                Debug.WriteLine("Could not reach server to get vote results!");
            }
            return new VoteResults();
        }

        private string buildVoteParameters(int limit, string day, string talkType, string track)
        {
            StringBuilder b = new StringBuilder("");
            if (limit > 0)
            {
                b.Append("?limit=").Append(limit);
            }
            if (!String.IsNullOrEmpty(day))
            {
                b.Append(getConChar(b)).Append("day=").Append(day);
            }
            if (!String.IsNullOrEmpty(talkType))
            {

                b.Append(getConChar(b)).Append("talkType=").Append(System.Net.WebUtility.UrlEncode(talkType));
            }
            if (!String.IsNullOrEmpty(track))
            {
                b.Append(getConChar(b)).Append("track=").Append(System.Net.WebUtility.UrlEncode(track));
            }

            return b.ToString();
        }

        private string getConChar(StringBuilder b)
        {
            if (b.Length == 0)
            {
                return "?";
            }
            else
            {
                return "&";
            }
        }

        public async Task<Categories> GetVoteCategories()
        {
            HttpClient client = CreateHttpClient();
            string confId = (string)settings.Values[Settings.CONFERENCE_ID];
            string url = (string)settings.Values[Settings.VOTING_URL];
            try
            {
                HttpResponseMessage response = await client.GetAsync(new Uri(url + confId + "/categories"));
                if (HttpStatusCode.Ok.Equals(response.StatusCode))
                {
                    string responseTxt = await response.Content.ReadAsStringAsync();
                    DataContractJsonSerializer obj = new DataContractJsonSerializer(typeof(Categories));
                    Categories result = obj.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(responseTxt))) as Categories;
                    return result;
                }
            }
            catch (Exception)
            { }
            return null;
        }

        //public async Task<VoteResult> getVoteForTalk(string talkId)
        //{
        //    string conference = (string)settings.Values["conference"];
        //    string url = Config.Instance.getConference(conference).voteUrl;
        //    try
        //    {
        //        HttpResponseMessage response = await httpClient.GetAsync(new Uri(url + voteResultTalkAddress));
        //        if (HttpStatusCode.Ok.Equals(response.StatusCode))
        //        {
        //            string responseTxt = await response.Content.ReadAsStringAsync();
        //            DataContractJsonSerializer obj = new DataContractJsonSerializer(typeof(VoteResult));
        //            VoteResult result = obj.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(responseTxt))) as VoteResult;
        //            return result;
        //        }
        //    }
        //    catch (Exception)
        //    { }
        //    return null;
        //}

        public async Task LoadImage(List<string> httpUrls, string folder)
        {
            string confId = (string)settings.Values[Settings.CONFERENCE_ID];
            var imageFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(folder + confId, CreationCollisionOption.OpenIfExists);

            try
            {
                foreach (string httpUrl in httpUrls)
                {
                    Debug.WriteLine("Loading Image from: " + httpUrl);
                    var url = new Uri(httpUrl + "?bust=" + DateTime.Now.Millisecond);
                    var fileName = Path.GetFileName(url.LocalPath);

                    HttpClient client = CreateHttpClient();
                    IBuffer iBuffer = await client.GetBufferAsync(url);

                    var imageFile = await imageFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
                    await FileIO.WriteBufferAsync(imageFile, iBuffer);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Could not update track images");
                Debug.WriteLine(ex.Message);
            }
        }

    }
}
<file_sep>/MyDevoxx/ViewModel/ViewModelLocator.cs
using GalaSoft.MvvmLight.Ioc;
using GalaSoft.MvvmLight.Views;
using Microsoft.Practices.ServiceLocation;
using MyDevoxx.Services;
using MyDevoxx.Views;

namespace MyDevoxx.ViewModel
{
    /// <summary>
    /// This class contains static references to all the view models in the
    /// application and provides an entry point for the bindings.
    /// </summary>
    public class ViewModelLocator
    {
        public const string ScheduleViewKey = "ScheduleView";
        public const string MapViewKey = "MapView";
        public const string TracksViewKey = "TracksView";
        public const string SpeakersViewKey = "SpeakersView";
        public const string ConferenceSelectorViewKey = "ConferenceSelectorView";
        public const string TalkDetailsViewKey = "TalkDetailsView";
        public const string SpeakerDetailsViewKey = "SpeakerDetailsView";
        public const string SettingsViewKey = "SettingsView";
        public const string AboutViewKey = "AboutView";
        public const string CreditsViewKey = "CreditsView";
        public const string RegisterViewKey = "RegisterView";
        public const string VotingResultViewKey = "VotingResultView";

        static ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            var nav = new NavigationService();
            SimpleIoc.Default.Register<INavigationService>(() => nav);
            nav.Configure(ViewModelLocator.ScheduleViewKey, typeof(ScheduleView));
            nav.Configure(ViewModelLocator.MapViewKey, typeof(MapView));
            nav.Configure(ViewModelLocator.TracksViewKey, typeof(TracksView));
            nav.Configure(ViewModelLocator.SpeakersViewKey, typeof(SpeakersView));
            nav.Configure(ViewModelLocator.ConferenceSelectorViewKey, typeof(ConferenceSelectorView));
            nav.Configure(ViewModelLocator.TalkDetailsViewKey, typeof(TalkDetailsView));
            nav.Configure(ViewModelLocator.SpeakerDetailsViewKey, typeof(SpeakerDetailsView));
            nav.Configure(ViewModelLocator.SettingsViewKey, typeof(SettingsView));
            nav.Configure(ViewModelLocator.CreditsViewKey, typeof(CreditsView));
            nav.Configure(ViewModelLocator.AboutViewKey, typeof(AboutView));
            nav.Configure(ViewModelLocator.RegisterViewKey, typeof(RegisterView));
            nav.Configure(ViewModelLocator.VotingResultViewKey, typeof(VotingResultView));

            SimpleIoc.Default.Register<IDialogService, DialogService>();
            SimpleIoc.Default.Register<IRestService, RestService>();
            SimpleIoc.Default.Register<IDataService, DataService>();
            SimpleIoc.Default.Register<ITwitterService, TwitterService>();
            SimpleIoc.Default.Register<IVotingService, VotingService>();
            SimpleIoc.Default.Register<INotificationService, NotificationService>();

            SimpleIoc.Default.Register<MapViewModel>();
            SimpleIoc.Default.Register<ScheduleViewModel>();
            SimpleIoc.Default.Register<SpeakersViewModel>();
            SimpleIoc.Default.Register<SpeakerDetailsViewModel>();
            SimpleIoc.Default.Register<TracksViewModel>();
            SimpleIoc.Default.Register<TalkDetailsViewModel>();
            SimpleIoc.Default.Register<SettingsViewModel>();
            SimpleIoc.Default.Register<AboutViewModel>();
            SimpleIoc.Default.Register<VotingResultViewModel>();
            SimpleIoc.Default.Register<ConferenceSelectorViewModel>(true);
        }

        public MapViewModel Map => ServiceLocator.Current.GetInstance<MapViewModel>();
        public ScheduleViewModel Schedule => ServiceLocator.Current.GetInstance<ScheduleViewModel>();
        public SpeakersViewModel Speakers => ServiceLocator.Current.GetInstance<SpeakersViewModel>();
        public TracksViewModel Tracks => ServiceLocator.Current.GetInstance<TracksViewModel>();
        public ConferenceSelectorViewModel ConferenceSelectorViewModel => ServiceLocator.Current.GetInstance<ConferenceSelectorViewModel>();
        public TalkDetailsViewModel TalkDetails => ServiceLocator.Current.GetInstance<TalkDetailsViewModel>();
        public SpeakerDetailsViewModel SpeakerDetails => ServiceLocator.Current.GetInstance<SpeakerDetailsViewModel>();
        public AboutViewModel About => ServiceLocator.Current.GetInstance<AboutViewModel>();
        public SettingsViewModel Settings => ServiceLocator.Current.GetInstance<SettingsViewModel>();
        public VotingResultViewModel VotingResult => ServiceLocator.Current.GetInstance<VotingResultViewModel>();

        public static void Cleanup()
        {
            ServiceLocator.Current.GetInstance<MapViewModel>().Cleanup();
            ServiceLocator.Current.GetInstance<ScheduleViewModel>().Cleanup();
            ServiceLocator.Current.GetInstance<SpeakersViewModel>().Cleanup();
            ServiceLocator.Current.GetInstance<TracksViewModel>().Cleanup();
        }
    }
}<file_sep>/MyDevoxx/Views/ScheduleView.xaml.cs
﻿using MyDevoxx.UserControls;
using MyDevoxx.Utils;
using MyDevoxx.ViewModel;
using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Views;
using Microsoft.Practices.ServiceLocation;
using System.Linq;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.Graphics.Display;
using System.Collections.Generic;
using MyDevoxx.Model;
using System.Diagnostics;

namespace MyDevoxx.Views
{
    public sealed partial class ScheduleView : Page
    {
        private ScheduleViewModel vm;
        private ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
        private static string SEARCH_SETTINGS = "SearchStringScheduleView";

        private Event changedEvent;

        public ScheduleView()
        {
            DisplayInformation.AutoRotationPreferences = DisplayOrientations.None;
            this.InitializeComponent();
            this.NavigationCacheMode = NavigationCacheMode.Required;

            vm = ((ScheduleViewModel)DataContext);

            Window.Current.SizeChanged += Current_SizeChanged;

            Messenger.Default.Register<Event>(this, EventChanged);
        }

        private void Current_SizeChanged(object sender, WindowSizeChangedEventArgs e)
        {
            var o = ApplicationView.GetForCurrentView().Orientation;
            if (o.Equals(ApplicationViewOrientation.Landscape))
            {
                VisualStateManager.GoToState(this, "Landscape", true);
            }
            else
            {
                VisualStateManager.GoToState(this, "Portrait", true);
            }
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            vm.LoadData();

            if (vm.FilterCount > 0)
            {
                ScheduleHeader.FilterIcon = Header.FILTER_ACTIVE;
            }
            else
            {
                ScheduleHeader.FilterIcon = Header.FILTER_INACTIVE;
            }

            var searchSettings = settings.Values[SEARCH_SETTINGS];
            if (searchSettings != null && (string)searchSettings != "")
            {
                ScheduleHeader.SearchString = (string)searchSettings;
                ScheduleHeader.ShowSearch();
            }

            ScheduleHeader.IndicatorFavorites = vm.JustFavorites;
        }

        private void Grid_Tapped(object sender, TappedRoutedEventArgs e)
        {
            Grid header = (Grid)sender;
            UIElement arrow = header.Children.Last();
            Grid parent = (Grid)header.Parent;
            UIElement list = parent.Children.Last();
            if (list.Visibility.Equals(Visibility.Visible))
            {
                list.Visibility = Visibility.Collapsed;
                ((CompositeTransform)arrow.RenderTransform).Rotation = 0;
            }
            else
            {
                list.Visibility = Visibility.Visible;
                ((CompositeTransform)arrow.RenderTransform).Rotation = 180;
            }
        }

        private void EventTalkControl_Tapped(object sender, TappedRoutedEventArgs e)
        {
            if (!typeof(EventTalkControl).Equals(sender.GetType()))
            {
                return;
            }
            var nav = ServiceLocator.Current.GetInstance<INavigationService>();
            nav.NavigateTo(ViewModelLocator.TalkDetailsViewKey, ((EventTalkControl)sender).DataContext);
        }

        private void ScheduleHeader_FilterTapped(object sender)
        {
            FilterGrid.Visibility = Visibility.Visible;
        }

        private void EventChanged(Event e)
        {
            this.changedEvent = e;
        }

        private void SetFocus()
        {
            ItemCollection items = SchedulePivot.Items;
            if (items.Count > 0)
            {
                SchedulePivotItem item = (SchedulePivotItem)items.FirstOrDefault(i => ((SchedulePivotItem)i).day.Equals(changedEvent.day));
                if (item == null)
                {
                    changedEvent = null;
                    return;
                }
                var container = SchedulePivot.ContainerFromItem(item);
                var children = AllChildren(container);
                var listView = (ListView)children.Find(c => c.Name == "ScheduleList");

                if (listView == null)
                {
                    changedEvent = null;
                    return;
                }
                var listViewItems = listView.Items;
                foreach (var listViewItem in listViewItems)
                {
                    if (listViewItem is List<Event>)
                    {
                        foreach (var e in listViewItem as List<Event>)
                        {
                            if (this.changedEvent.id.Equals(e.id))
                            {
                                listView.ScrollIntoView(e, ScrollIntoViewAlignment.Leading);
                                break;
                            }
                        }
                    }
                    else if (listViewItem is Event)
                    {
                        Event e = listViewItem as Event;
                        if (EventType.TALK.Equals(e.type) && this.changedEvent.id.Equals(e.id))
                        {
                            listView.ScrollIntoView(e, ScrollIntoViewAlignment.Leading);
                            break;
                        }

                    }
                }
            }
            this.changedEvent = null;
        }

        private List<Control> AllChildren(DependencyObject parent)
        {
            var result = new List<Control> { };
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
            {
                var child = VisualTreeHelper.GetChild(parent, i);
                if (child is Control)
                {
                    result.Add(child as Control);
                }
                result.AddRange(AllChildren(child));
            }
            return result;
        }

        public bool IsFilterVisile()
        {
            return FilterGrid.Visibility.Equals(Visibility.Visible);
        }

        public void CloseFilter()
        {
            this.Apply();
        }

        private void Apply_Tapped(object sender, TappedRoutedEventArgs e)
        {
            this.Apply();
        }

        private void Apply()
        {
            if (vm.FilterCount > 0)
            {
                ScheduleHeader.FilterIcon = Header.FILTER_ACTIVE;
            }
            else
            {
                ScheduleHeader.FilterIcon = Header.FILTER_INACTIVE;
            }
            FilterGrid.Visibility = Visibility.Collapsed;
            Messenger.Default.Send<MessageType>(MessageType.REFRESH_SCHEDULE);
        }

        private void Clear_Tapped(object sender, TappedRoutedEventArgs e)
        {
            ScheduleHeader.FilterIcon = Header.FILTER_INACTIVE;
            FilterGrid.Visibility = Visibility.Collapsed;
            Messenger.Default.Send<MessageType>(MessageType.CLEAR_FILTER_SCHEDULE);
        }

        private void DayFilterGrid_Tapped(object sender, TappedRoutedEventArgs e)
        {
            if (DayFilterList.Visibility.Equals(Visibility.Collapsed))
            {
                DayFilterList.Visibility = Visibility.Visible;
            }
            else
            {
                DayFilterList.Visibility = Visibility.Collapsed;
            }
        }

        private void TrackFilterGrid_Tapped(object sender, TappedRoutedEventArgs e)
        {
            if (TrackFilterList.Visibility.Equals(Visibility.Collapsed))
            {
                TrackFilterList.Visibility = Visibility.Visible;
            }
            else
            {
                TrackFilterList.Visibility = Visibility.Collapsed;
            }
        }

        private void PivotItem_Loaded(object sender, RoutedEventArgs e)
        {
            if (changedEvent != null)
            {
                SetFocus();
            }
        }
    }
}
<file_sep>/MyDevoxx/Utils/Settings.cs
﻿using System;

namespace MyDevoxx.Utils
{
    class Settings
    {
        public const string LOADED_SUCCESSFUL = "loadedSuccessful";
        public const string VOTING_URL = "votingURL";
        public const string CONFERENCE_ID = "conferenceId";
        public const string CFP_ENDPOINT = "cfpEndpoint";
        public const string TALK_URL = "talkURL";
        public const string COUNTRY = "country";

        public const string FORCE_UPDATE = "ForceUpdate";
        public const string PUSH_UPDATE_ENABLED = "pushUpdateEnabled";

        public const string SHOW_SPEAKER_IMG = "showSpeakerImages";
        public const string LAST_UPDATE = "lastUpdate";
    }
}
<file_sep>/MyDevoxx/Utils/MessageType.cs
﻿namespace MyDevoxx.Utils
{
    public enum MessageType
    {
        REFRESH_SCHEDULE,
        REQUEST_REFRESH_SCHEDULE,
        CLEAR_FILTER_SCHEDULE,
        SEARCH_COMPLETED,

        REFRESH_TRACKS,
        CLEAR_FILTER_TRACKS,

        REFRESH_SPEAKERS,

        SHOW_FAVS,
        HIDE_FAVS,
        TOGGLE_SPEAKER_IMG
    }
}
<file_sep>/MyDevoxx/Services/IRestService.cs
﻿using MyDevoxx.Services.RestModel.Voting;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MyDevoxx.Services
{
    public interface IRestService
    {
        Task<Tuple<List<Model.Event>, bool>> GetEvents(string day);

        Task<Tuple<List<Model.Speaker>, bool>> GetSpeakers();

        Task<Tuple<Model.Speaker, bool>> GetSpeaker(string uuid);

        Task<Tuple<List<Model.Track>, bool>> GetTracks();

        Task<Tuple<List<Model.Conference>, bool>> GetConferences();

        Task<Tuple<List<Model.Floor>, bool>> GetFloors();

        Task<VoteMessage> VoteTalk(VoteBasic vote);

        Task<VoteMessage> VoteTalk(VoteReviews vote);

        Task<VoteResults> GetVoteResults(int limit, string day, string talkType, string track);

        Task<Categories> GetVoteCategories();

        Task LoadImage(List<string> httpUrls, string folder);
    }
}
<file_sep>/BackgroundTasks/PushNotificationBackgroundTask.cs
﻿using System;
using Windows.ApplicationModel.Background;
using Windows.Networking.PushNotifications;
using Windows.Storage;

namespace BackgroundTasks
{
    public sealed class PushNotificationBackgroundTask : IBackgroundTask
    {
        private ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
        private const String FORCE_UPDATE = "ForceUpdate";

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            if (taskInstance != null
                && taskInstance.TriggerDetails != null
                && taskInstance.TriggerDetails.GetType().Equals(typeof(RawNotification)))
            {
                settings.Values[FORCE_UPDATE] = DateTime.Now.ToString();
            }

            taskInstance.GetDeferral().Complete();
        }
    }
}
<file_sep>/MyDevoxx/Services/NotificationService.cs
﻿using Amazon;
using Amazon.SimpleNotificationService;
using Amazon.SimpleNotificationService.Model;
using Microsoft.Practices.ServiceLocation;
using MyDevoxx.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Networking.PushNotifications;
using Windows.Storage;

namespace MyDevoxx.Services
{
    class NotificationService : INotificationService
    {
        private const string TASK_NAME = "PushNotifyTask";
        private const string TASK_ENTRYPOINT = "BackgroundTasks.PushNotificationBackgroundTask";
        private const string ACCESS_KEY_ID = "ACCESS_KEY";
        private const string SECRET_ACCESS_KEY = "GEHEIM!";

        private const string PLATFORM_APPLICATION_ARN = "arn:aws:sns:eu-west-1:390215441069:app/WNS/MyDevoxxWindows";
        private const string TOPIC_ARN_PREFIX = "arn:aws:sns:eu-west-1:390215441069:";
        private const string TOPIC_GLOBAL = "cfp_schedule_updates";

        private ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
        private const string ENDPOINT_ARN = "EndpointArn";
        private AmazonSimpleNotificationServiceClient client;

        private IDataService dataService;
        private bool isServiceEnabled = false;

        public NotificationService()
        {
            if (!isServiceEnabled)
            {
                return;
            }
            client = new AmazonSimpleNotificationServiceClient(ACCESS_KEY_ID, SECRET_ACCESS_KEY, RegionEndpoint.EUWest1);
            dataService = ServiceLocator.Current.GetInstance<IDataService>();
        }

        public async void TryRegisterAndSubscribeKnownTopics()
        {
            if (!isServiceEnabled)
            {
                return;
            }

            bool pushEnabled;
            if (settings.Values[Settings.PUSH_UPDATE_ENABLED] == null
                || !Boolean.TryParse((string)settings.Values[Settings.PUSH_UPDATE_ENABLED], out pushEnabled)
                || !pushEnabled)
            {
                Unregister();
            }
            else
            {
                if (await TryRegister())
                {
                    SubscribeTobic(TOPIC_GLOBAL);
                }
            }
        }

        public async void Unregister()
        {
            try
            {
                PushNotificationChannel channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
                channel.PushNotificationReceived -= OnPushNotification;
                UnregisterBackgroundTask();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unregister notifications: " + ex.Message);
                Debug.WriteLine("Unregister notifications: " + ex.StackTrace);
            }
        }

        public async Task<bool> TryRegister()
        {
            try
            {
                //retrieve the latest token from the mobile OS
                PushNotificationChannel channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
                Debug.WriteLine("Channel: " + channel.Uri);
                channel.PushNotificationReceived += OnPushNotification;
                RegisterBackgroundTask();

                //create endpoint
                string endpointArn = await CreatePlatformEndpoint(channel);
                settings.Values[ENDPOINT_ARN] = endpointArn;

                //call GetEndpointAttributes on the endpoint arn
                var request = new GetEndpointAttributesRequest();
                request.EndpointArn = endpointArn;
                var response = await client.GetEndpointAttributesAsync(request);
                Dictionary<string, string> attributes = response.Attributes;

                // check if endpoint attributes are up to date
                if (!attributes["Token"].Equals(channel.Uri) || !attributes["Enabled"].Equals("true", StringComparison.OrdinalIgnoreCase))
                {
                    SetEndpointAttributesRequest setAttributeRequest = new SetEndpointAttributesRequest();
                    setAttributeRequest.EndpointArn = endpointArn;
                    setAttributeRequest.Attributes.Add("Token", channel.Uri);
                    setAttributeRequest.Attributes.Add("Enabled", "true");
                    await client.SetEndpointAttributesAsync(setAttributeRequest);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Register notifications");
                Debug.WriteLine(ex.Message);
                Debug.WriteLine(ex.StackTrace);
                return false;
            }

            return true;
        }

        private async Task<string> CreatePlatformEndpoint(PushNotificationChannel channel)
        {
            var request = new CreatePlatformEndpointRequest();
            request.PlatformApplicationArn = PLATFORM_APPLICATION_ARN;
            request.Token = channel.Uri;

            CreatePlatformEndpointResponse response = await client.CreatePlatformEndpointAsync(request);

            return response.EndpointArn;
        }

        public async void SubscribeTobic(string topic)
        {
            if (settings.Values[ENDPOINT_ARN] == null)
            {
                if (await TryRegister())
                {
                    return;
                }
            }
            try
            {
                string endpoint = (string)settings.Values[ENDPOINT_ARN];

                SubscribeRequest subscribeRequest = new SubscribeRequest();
                subscribeRequest.TopicArn = TOPIC_ARN_PREFIX + topic;
                subscribeRequest.Protocol = "application";
                subscribeRequest.Endpoint = endpoint;
                SubscribeResponse response = await client.SubscribeAsync(subscribeRequest);
            }

            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }

        private async void RegisterBackgroundTask()
        {
            var backgroundAccessStatus = await BackgroundExecutionManager.RequestAccessAsync();
            if (backgroundAccessStatus == BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity ||
                backgroundAccessStatus == BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity)
            {
                foreach (var task in BackgroundTaskRegistration.AllTasks)
                {
                    if (task.Value.Name == TASK_NAME)
                    {
                        task.Value.Unregister(true);
                    }
                }

                BackgroundTaskBuilder taskBuilder = new BackgroundTaskBuilder();
                taskBuilder.Name = TASK_NAME;
                taskBuilder.TaskEntryPoint = TASK_ENTRYPOINT;
                taskBuilder.SetTrigger(new PushNotificationTrigger());
                var registration = taskBuilder.Register();
            }
        }

        private async void UnregisterBackgroundTask()
        {
            var backgroundAccessStatus = await BackgroundExecutionManager.RequestAccessAsync();
            if (backgroundAccessStatus == BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity ||
                backgroundAccessStatus == BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity)
            {
                foreach (var task in BackgroundTaskRegistration.AllTasks)
                {
                    if (task.Value.Name == TASK_NAME)
                    {
                        task.Value.Unregister(true);
                    }
                }
            }
        }

        private void OnPushNotification(PushNotificationChannel sender, PushNotificationReceivedEventArgs e)
        {
            String notificationContent = String.Empty;
            switch (e.NotificationType)
            {
                case PushNotificationType.Badge:
                    Debug.WriteLine("Badge");
                    notificationContent = e.BadgeNotification.Content.GetXml();
                    break;

                case PushNotificationType.Tile:
                    Debug.WriteLine("Tile");
                    notificationContent = e.TileNotification.Content.GetXml();
                    break;

                case PushNotificationType.Toast:
                    Debug.WriteLine("Toast");
                    notificationContent = e.ToastNotification.Content.GetXml();
                    break;

                case PushNotificationType.Raw:
                    Debug.WriteLine("Raw");
                    notificationContent = e.RawNotification.Content;
                    settings.Values[Settings.FORCE_UPDATE] = DateTime.Now.ToString();

                    dataService.CheckUpdate();

                    e.Cancel = true;
                    break;
            }
            Debug.WriteLine(notificationContent);
        }
    }
}
<file_sep>/MyDevoxx/Services/INotificationService.cs
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyDevoxx.Services
{
    interface INotificationService
    {
        Task<bool> TryRegister();

        void SubscribeTobic(string topic);

        void TryRegisterAndSubscribeKnownTopics();
    }
}
<file_sep>/MyDevoxx/ViewModel/SettingsViewModel.cs
﻿using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Views;
using Microsoft.Practices.ServiceLocation;
using MyDevoxx.Services;
using MyDevoxx.Utils;
using Windows.Storage;

namespace MyDevoxx.ViewModel
{
    public class SettingsViewModel : ViewModelBase
    {
        private ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;

        private bool _updateInProgress = false;
        public bool UpdateInProgress
        {
            get { return _updateInProgress; }
            set
            {
                if (Set("UpdateInProgress", ref _updateInProgress, value))
                {
                    RaisePropertyChanged(() => UpdateInProgress);
                }
            }
        }
        private bool _removeFavoritsInProgress = false;
        public bool RemoveFavoritsInProgress
        {
            get { return _removeFavoritsInProgress; }
            set
            {
                if (Set("RemoveFavoritsInProgress", ref _removeFavoritsInProgress, value))
                {
                    RaisePropertyChanged(() => RemoveFavoritsInProgress);
                }
            }
        }

        public bool ShowSpeakerImages
        {
            get
            {
                if (settings.Values[Settings.SHOW_SPEAKER_IMG] == null)
                {
                    settings.Values[Settings.SHOW_SPEAKER_IMG] = true;
                }
                return (bool)settings.Values[Settings.SHOW_SPEAKER_IMG];
            }
            set
            {
                RaisePropertyChanged(() => ShowSpeakerImages);
                settings.Values[Settings.SHOW_SPEAKER_IMG] = value;
            }
        }

        private string _lastUpdate = "n/a";
        public string LastUpdate
        {
            get { return "last update: " + _lastUpdate; }
            set
            {
                if (Set("LastUpdate", ref _lastUpdate, value))
                {
                    RaisePropertyChanged(() => LastUpdate);
                }
            }
        }



        private RelayCommand _updateCommand;
        public RelayCommand UpdateCommand
        {
            get { return _updateCommand; }
            private set { _updateCommand = value; }
        }

        private RelayCommand _removeFavoritesCommand;
        public RelayCommand RemoveFavoritesCommand
        {
            get { return _removeFavoritesCommand; }
            private set { _removeFavoritesCommand = value; }
        }

        public SettingsViewModel()
        {
            UpdateCommand = new RelayCommand(ForceUpdate_Tapped);
            RemoveFavoritesCommand = new RelayCommand(RemoveFavorites_Tapped);
        }

        public void Load()
        {
            if(settings.Values[Settings.LAST_UPDATE + settings.Values[Settings.CONFERENCE_ID]] != null)
            {
                LastUpdate = (string)settings.Values[Settings.LAST_UPDATE + settings.Values[Settings.CONFERENCE_ID]];
            }
        }

        private async void RemoveFavorites_Tapped()
        {
            if (RemoveFavoritsInProgress) return;

            IDialogService dialogService = ServiceLocator.Current.GetInstance<IDialogService>();
            INavigationService navigationServive = ServiceLocator.Current.GetInstance<INavigationService>();
            await dialogService.ShowMessage("Do you really want to remove all favored talks?", "Confirmation", "Continue", "Discard", (confirmed) =>
            {
                if (confirmed)
                {
                    RemoveFavoritsInProgress = true;

                    IDataService dataService = ServiceLocator.Current.GetInstance<IDataService>();
                    dataService.removeFavorites((successful) => RemoveFavoritsInProgress = false);
                    ViewModelLocator.Cleanup();
                }
            });
        }

        private void ForceUpdate_Tapped()
        {
            if (UpdateInProgress) return;

            UpdateInProgress = true;
            settings.Values[Settings.FORCE_UPDATE] = "ManualUpdate";
            IDataService dataService = ServiceLocator.Current.GetInstance<IDataService>();
            dataService.CheckUpdate(OnUpdateFinished);
        }

        private void OnUpdateFinished(bool successful)
        {
            if (successful)
            {
                Messenger.Default.Send<MessageType>(MessageType.REFRESH_SPEAKERS);
                Messenger.Default.Send<MessageType>(MessageType.REQUEST_REFRESH_SCHEDULE);
                Messenger.Default.Send<MessageType>(MessageType.REFRESH_TRACKS);
                LastUpdate = (string)settings.Values[Settings.LAST_UPDATE + settings.Values[Settings.CONFERENCE_ID]];
            }
            UpdateInProgress = false;
        }
    }
}
<file_sep>/MyDevoxx/Services/DataService.cs
﻿using MyDevoxx.Model;
using Microsoft.Practices.ServiceLocation;
using SQLite.Net;
using SQLite.Net.Async;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.Storage;
using MyDevoxx.Utils;
using GalaSoft.MvvmLight.Messaging;
using System.Globalization;
using System.Diagnostics;

namespace MyDevoxx.Services
{
    public class DataService : IDataService
    {
        private ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
        private SQLiteAsyncConnection sqlConnection;
        private IRestService Service;

        private string[] DayNames = { "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" };
        private string localStoragePath = "ms-appdata:///local/";
        private string trackImageFolder = "trackimages";

        public DataService()
        {
            DBConnection();
            Service = ServiceLocator.Current.GetInstance<IRestService>();
        }

        public async Task<List<Conference>> FetchConferences()
        {
            Tuple<List<Conference>, bool> conferencesTuple = await Service.GetConferences();
            await sqlConnection.InsertOrReplaceAllAsync(conferencesTuple.Item1);
            return conferencesTuple.Item1;
        }

        public async void CollectAll(FinishedLoading callback)
        {
            try
            {
                await CollectEvents();
                await CollectSpeakers();
                await CollectFloors();
                callback(true);
                settings.Values[Settings.LAST_UPDATE + currentConferenceId()] = DateTime.Now.ToString();
            }
            catch
            {
                callback(false);
            }
        }

        public void InitDB()
        {
            var path = Path.Combine(ApplicationData.Current.LocalFolder.Path, "db.sqlite");
            SQLiteConnection conn = new SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), path);

            conn.CreateTable<Conference>();
            conn.CreateTable<Event>();
            conn.CreateTable<Model.Floor>();
            conn.CreateTable<ETag>();
            conn.CreateTable<Speaker>();
            conn.CreateTable<Track>();
            conn.CreateTable<Note>();
            conn.CreateTable<Vote>();

            conn.Close();
        }

        public async void removeFavorites(FinishedLoading callback)
        {
            await sqlConnection.ExecuteAsync("update 'Event' set Starred = 0");
            callback(true);
        }

        public async void CheckUpdate(FinishedLoading callback = null)
        {
            if (settings.Values[Settings.CONFERENCE_ID] == null
                || settings.Values[Settings.LOADED_SUCCESSFUL] == null
                || !(bool)settings.Values[Settings.LOADED_SUCCESSFUL])
            {
                return;
            }

            if (settings.Values[Settings.FORCE_UPDATE] != null)
            {
                await ClearETag();
                settings.Values[Settings.FORCE_UPDATE] = null;
            }

            try
            {
                await FetchConferences();
                Conference conference = await GetCurrentConference();
                settings.Values[Settings.CONFERENCE_ID] = conference.id;
                settings.Values[Settings.CFP_ENDPOINT] = conference.cfpEndpoint;
                settings.Values[Settings.TALK_URL] = conference.talkURL;
                settings.Values[Settings.COUNTRY] = conference.country;
                settings.Values[Settings.VOTING_URL] = conference.votingURL;
                settings.Values[Settings.PUSH_UPDATE_ENABLED] = conference.pushEnabled;

                if (await CollectEvents())
                {
                    Messenger.Default.Send<MessageType>(MessageType.REQUEST_REFRESH_SCHEDULE);
                    Messenger.Default.Send<MessageType>(MessageType.REFRESH_TRACKS);
                    settings.Values[Settings.LAST_UPDATE + conference.id] = DateTime.Now.ToString();
                }
                if (await CollectSpeakers())
                {
                    Messenger.Default.Send<MessageType>(MessageType.REFRESH_SPEAKERS);
                }
                await CollectFloors();

                callback?.Invoke(true);
            }
            catch
            {
                callback?.Invoke(false);
            }
        }

        private async Task<bool> CollectEvents()
        {
            bool updated = false;

            string confId = currentConferenceId();
            Conference conference = await sqlConnection.Table<Conference>().Where(c => c.id.Equals(confId)).FirstOrDefaultAsync();
            string cfpUrl = conference.cfpURL;
            int lastIdx = conference.cfpURL.LastIndexOf('/');
            if (lastIdx + 1 == conference.cfpURL.Length)
            {
                cfpUrl = conference.cfpURL.Substring(0, conference.cfpURL.Length - 1);
            }

            Tuple<List<Track>, bool> tracksTuple = await Service.GetTracks();
            if (tracksTuple.Item2)
            {
                updated = true;
                await ClearTracks(currentConferenceId());
                await sqlConnection.InsertOrReplaceAllAsync(tracksTuple.Item1);

                // Preload track images
                List<string> trackImages = tracksTuple.Item1.Select(track => cfpUrl + track.imgsrc).ToList();
                await Service.LoadImage(trackImages, trackImageFolder);
            }
            var trackDic = tracksTuple.Item1.Select(track => new
            {
                Key = track.id,
                Value = track.imgsrc.Substring(track.imgsrc.LastIndexOf('/', track.imgsrc.Length - 1))
            })
                .Distinct()
                .ToDictionary(track => track.Key, track => track.Value, StringComparer.OrdinalIgnoreCase);

            DateTime fromDate = DateTime.ParseExact(conference.fromDate, "yyyy-MM-dTHH:mm:ss.000Z", CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal);
            DateTime toDate = DateTime.ParseExact(conference.toDate, "yyyy-MM-dTHH:mm:ss.000Z", CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal);
            int dayCnt = (int)(toDate - fromDate).TotalDays;
            int dayStart = (int)fromDate.DayOfWeek;

            List<Event> starredEvents = await GetStarredEvents();
            Dictionary<string, Event> starredEventsDic = starredEvents.ToDictionary(p => p.id, p => p);

            List<Event> events = new List<Event>();
            List<String> daysToClean = new List<String>();
            for (int i = dayStart; i <= dayStart + dayCnt; i++)
            {
                Tuple<List<Event>, bool> eventListTuple = await Service.GetEvents(DayNames[i]);
                if (eventListTuple.Item2)
                {
                    updated = true;
                    daysToClean.Add(DayNames[i]);
                    foreach (Event e in eventListTuple.Item1)
                    {
                        if (e.trackId != null)
                        {
                            String trackImage;
                            if (trackDic.TryGetValue(e.trackId, out trackImage))
                            {
                                e.trackImage = localStoragePath + trackImageFolder + confId + trackImage;
                            }
                        }
                        if (starredEventsDic.ContainsKey(e.id))
                        {
                            e.Starred = true;
                        }
                        events.Add(e);
                    }
                }
            }
            // delete all events
            foreach (String day in daysToClean)
            {
                await ClearEventsByDay(confId, day);
            }
            if (daysToClean.Count > 0)
            {
                await sqlConnection.InsertOrReplaceAllAsync(events);
            }

            return updated;
        }

        private async Task<bool> CollectFloors()
        {
            Tuple<List<Model.Floor>, bool> floorsTuple = await Service.GetFloors();
            await ClearFloors(currentConferenceId());
            await sqlConnection.InsertOrReplaceAllAsync(floorsTuple.Item1);
            return floorsTuple.Item2;
        }

        private async Task<bool> CollectSpeakers()
        {
            Tuple<List<Speaker>, bool> speakersTuple = await Service.GetSpeakers();
            if (speakersTuple.Item2)
            {
                await ClearSpeakers(currentConferenceId());
                await sqlConnection.InsertOrReplaceAllAsync(speakersTuple.Item1);
            }
            return speakersTuple.Item2;
        }

        private async Task ClearEventsByDay(string confId, string day)
        {
            await sqlConnection.ExecuteAsync("delete from 'Event' where confId = '" + confId + "' and day = '" + day + "'");
        }

        private async Task ClearSpeakers(string confId)
        {
            await sqlConnection.ExecuteAsync("delete from 'Speaker' where confId = '" + confId + "'");
        }

        private async Task ClearFloors(string confId)
        {
            await sqlConnection.ExecuteAsync("delete from 'Floor' where confId = '" + confId + "'");
        }

        private async Task ClearTracks(string confId)
        {
            await sqlConnection.ExecuteAsync("delete from 'Track' where confId = '" + confId + "'");
        }

        private async Task ClearETag()
        {
            await sqlConnection.ExecuteAsync("delete from 'ETag'");
        }

        private void DBConnection()
        {
            if (sqlConnection != null) return;

            var path = Path.Combine(ApplicationData.Current.LocalFolder.Path, "db.sqlite");

            SQLiteConnectionString connString = new SQLiteConnectionString(path.ToString(), false);
            SQLiteConnectionWithLock connLock = new SQLiteConnectionWithLock(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), connString);

            sqlConnection = new SQLiteAsyncConnection(() => connLock);
        }

        public async Task<List<Track>> GetTracks()
        {
            string confId = currentConferenceId();
            return await sqlConnection.Table<Track>()
                .Where(t => t.confId.Equals(confId))
                .OrderBy(t => t.title)
                .ToListAsync();
        }

        public async Task<List<Speaker>> GetSpeakers()
        {
            string confId = currentConferenceId();
            return await sqlConnection.Table<Speaker>()
                .Where(s => s.confId.Equals(confId))
                .OrderBy(s => s.firstName)
                .ToListAsync();
        }

        public async Task<List<Speaker>> GetSpeakersBySearchCriteria(string searchString)
        {
            string confId = currentConferenceId();
            return await sqlConnection.Table<Speaker>()
                .Where(t => t.confId.Equals(confId) &&
                    (
                    (t.firstName != null && t.firstName.Contains(searchString)) ||
                    (t.lastName != null && t.lastName.Contains(searchString)) ||
                    (t.company != null && t.company.Contains(searchString))
                    )
                 )
                .OrderBy(s => s.firstName)
                .ToListAsync();
        }

        public async Task<Speaker> GetSpeaker(string uuid)
        {
            Tuple<Speaker, bool> speakerTuple = await Service.GetSpeaker(uuid);
            return speakerTuple.Item1;
        }

        public async Task<List<Model.Floor>> GetFloors(string target)
        {
            string confId = currentConferenceId();
            List<Model.Floor> floors = await sqlConnection.Table<Model.Floor>().Where(t => t.confId.Equals(confId) && t.target.Equals(target)).ToListAsync();
            if (floors.Count == 0)
            {
                return await sqlConnection.Table<Model.Floor>().Where(t => t.confId.Equals(confId)).ToListAsync();
            }
            return floors;
        }

        public async Task<Conference> GetCurrentConference()
        {
            string confId = currentConferenceId();
            return await sqlConnection.Table<Conference>().Where(t => t.id.Equals(confId)).FirstOrDefaultAsync();
        }

        public async Task<List<Event>> GetEventsByTrack(string trackId)
        {
            string confId = currentConferenceId();
            var eventList = await sqlConnection.Table<Event>().Where(t => t.confId.Equals(confId) && t.trackId.Equals(trackId)).ToListAsync();

            await AttachSpeakers(eventList);

            return eventList;
        }

        public async Task<List<Event>> GetEventsByDay(string day)
        {
            string confId = currentConferenceId();
            var eventList = await sqlConnection.Table<Event>()
                .Where(t => t.confId.Equals(confId) && t.day.Equals(day))
                .OrderBy(e => e.fullTime)
                .ToListAsync();

            await AttachSpeakers(eventList);

            return eventList;
        }

        public async Task<Event> GetEventsById(string id)
        {
            string confId = currentConferenceId();
            var e = await sqlConnection.Table<Event>().Where(t => t.confId.Equals(confId) && t.id.Equals(id)).FirstOrDefaultAsync();

            if (e != null)
            {
                await AttachSpeakers(e);
            }

            return e;
        }

        public async Task<List<Event>> GetEventsBySearchCriteria(string searchString)
        {
            string confId = currentConferenceId();
            var eventList = await sqlConnection.Table<Event>()
                .Where(t => t.confId.Equals(confId) && t.type.Equals(EventType.TALK) &&
                    (t.title.Contains(searchString) ||
                    t.summary.Contains(searchString) ||
                    t.speakerNames.Contains(searchString) ||
                    t.trackName.Contains(searchString)))
                .OrderByDescending(e => e.Starred)
                .OrderBy(e => e.title)
                .ToListAsync();

            await AttachSpeakers(eventList);

            return eventList;
        }

        public async Task<List<Event>> GetStarredEvents()
        {
            string confId = currentConferenceId();
            return await sqlConnection.Table<Event>().Where(e => e.confId.Equals(confId) && e.Starred).ToListAsync();
        }

        private string currentConferenceId()
        {
            return (string)settings.Values[Settings.CONFERENCE_ID];
        }

        public async Task<Event> UpdateEvent(Event e)
        {
            await sqlConnection.UpdateAsync(e);
            return e;
        }

        public async Task<Note> SaveOrUpdateNote(Note note)
        {
            string confId = currentConferenceId();
            note.confId = confId;
            note.id = confId + note.talkId;
            await sqlConnection.InsertOrReplaceAsync(note);
            return note;
        }

        public async Task<Note> GetNote(string talkId)
        {
            string confId = currentConferenceId();
            Note note;
            List<Note> notes = await sqlConnection.Table<Note>().Where(n => n.talkId.Equals(talkId) && n.confId.Equals(confId)).ToListAsync();
            if (notes.Count == 0)
            {
                note = new Note();
                note.talkId = talkId;
            }
            else
            {
                note = notes.First();
            }
            return note;
        }

        public async Task<Vote> SaveOrUpdateVote(Vote vote)
        {
            string confId = currentConferenceId();
            vote.confId = confId;
            vote.id = confId + vote.talkId;
            await sqlConnection.InsertOrReplaceAsync(vote);
            return vote;
        }

        public async Task<Vote> GetVote(string talkId)
        {
            string confId = currentConferenceId();
            Vote vote;
            List<Vote> votes = await sqlConnection.Table<Vote>().Where(n => n.talkId.Equals(talkId) && n.confId.Equals(confId)).ToListAsync();
            if (votes.Count == 0)
            {
                vote = new Vote();
                vote.talkId = talkId;
            }
            else
            {
                vote = votes.First();
            }
            return vote;
        }

        private async Task AttachSpeakers(List<Event> eventList)
        {
            List<Speaker> speakerList = await GetSpeakers();
            var speakerMap = speakerList.GroupBy(x => x.uuid)
                                .ToDictionary(x => x.Key, x => x.First());
            foreach (Event e in eventList)
            {
                if (string.IsNullOrEmpty(e.speakerId))
                {
                    continue;
                }
                string[] speakerIds = e.speakerId.Split(',');
                foreach (string id in speakerIds)
                {
                    Speaker s;
                    if (speakerMap.TryGetValue(id, out s))
                    {
                        e.SpeakerList.Add(s);
                    }
                }
            }
        }

        private async Task AttachSpeakers(Event e)
        {
            List<Speaker> speakerList = await GetSpeakers();
            var speakerMap = speakerList.GroupBy(x => x.uuid)
                                .ToDictionary(x => x.Key, x => x.First());
            if (string.IsNullOrEmpty(e.speakerId))
            {
                return;
            }
            string[] speakerIds = e.speakerId.Split(',');
            foreach (string id in speakerIds)
            {
                Speaker s;
                if (speakerMap.TryGetValue(id, out s))
                {
                    e.SpeakerList.Add(s);
                }
            }
        }
    }
}
