summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2013-03-29 12:47:25 +0400
committerGravatar Vitaly Takmazov2013-03-29 12:47:25 +0400
commite20d55789c6ab0c2c6324eb6806c12cc96723eca (patch)
tree0b947cf4394b086fa27ac4c210ffc9c673e6f4c5
parenta84c894c89f23e3eb6416ff8943d4d8a2fe84a49 (diff)
parent6df49ee7c25d739ec6445a13e1aa828a4da41730 (diff)
Merge branch 'master' of https://bitbucket.org/vitalyster/juick-windowsphone
-rw-r--r--Juick/App.xaml.cs13
-rw-r--r--Juick/ApplicationSmallTile.pngbin0 -> 3889 bytes
-rw-r--r--Juick/ApplicationTile.pngbin4187 -> 5690 bytes
-rw-r--r--Juick/ApplicationWideTile.pngbin0 -> 5534 bytes
-rw-r--r--Juick/Classes/DeferredLoadListBox.cs275
-rw-r--r--Juick/Classes/DeferredLoadListBoxItem.cs45
-rw-r--r--Juick/Classes/ExpressionHelper.cs20
-rw-r--r--Juick/Classes/LowProfileImageLoader.cs294
-rw-r--r--Juick/Classes/TileHelper.cs78
-rw-r--r--Juick/Juick.csproj10
-rw-r--r--Juick/MainPage.xaml8
-rw-r--r--Juick/Properties/AssemblyInfo.cs4
-rw-r--r--Juick/Properties/WMAppManifest.xml3
-rw-r--r--Juick/ThreadView.xaml8
-rw-r--r--Juick/ViewModels/MessageListViewModelBase.cs16
-rw-r--r--Juick/ViewModels/MessageViewModel.cs16
-rw-r--r--Juick/ViewModels/ThreadViewModel.cs9
17 files changed, 772 insertions, 27 deletions
diff --git a/Juick/App.xaml.cs b/Juick/App.xaml.cs
index 9d3ac9a..c0f6134 100644
--- a/Juick/App.xaml.cs
+++ b/Juick/App.xaml.cs
@@ -96,7 +96,18 @@ namespace Juick
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
-
+ const string JuickCaption = "Juick";
+ TileHelper.UpdateFlipTile(
+ JuickCaption,
+ string.Empty,
+ JuickCaption,
+ JuickCaption,
+ null,
+ "/ApplicationSmallTile.png",
+ "/ApplicationTile.png",
+ null,
+ "/ApplicationWideTile.png",
+ null);
}
// Code to execute when the application is activated (brought to foreground)
diff --git a/Juick/ApplicationSmallTile.png b/Juick/ApplicationSmallTile.png
new file mode 100644
index 0000000..95947f4
--- /dev/null
+++ b/Juick/ApplicationSmallTile.png
Binary files differ
diff --git a/Juick/ApplicationTile.png b/Juick/ApplicationTile.png
index 96d0872..703c107 100644
--- a/Juick/ApplicationTile.png
+++ b/Juick/ApplicationTile.png
Binary files differ
diff --git a/Juick/ApplicationWideTile.png b/Juick/ApplicationWideTile.png
new file mode 100644
index 0000000..e0b6bd6
--- /dev/null
+++ b/Juick/ApplicationWideTile.png
Binary files differ
diff --git a/Juick/Classes/DeferredLoadListBox.cs b/Juick/Classes/DeferredLoadListBox.cs
new file mode 100644
index 0000000..165a395
--- /dev/null
+++ b/Juick/Classes/DeferredLoadListBox.cs
@@ -0,0 +1,275 @@
+// Copyright (C) Microsoft Corporation. All Rights Reserved.
+// This code released under the terms of the Microsoft Public License
+// (Ms-PL, http://opensource.org/licenses/ms-pl.html).
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace Juick.Classes
+{
+ /// <summary>
+ /// Implements a subclass of ListBox based on a StackPanel that defers the
+ /// loading of off-screen items until necessary in order to minimize impact
+ /// to the UI thread.
+ /// </summary>
+ public class DeferredLoadListBox : ListBox
+ {
+ private enum OverlapKind { Overlap, ChildAbove, ChildBelow };
+
+ private ScrollViewer _scrollViewer;
+ private ItemContainerGenerator _generator;
+ private bool _queuedUnmaskVisibleContent;
+ private bool _inOnApplyTemplate;
+
+ /// <summary>
+ /// Handles the application of the Control's Template.
+ /// </summary>
+ public override void OnApplyTemplate()
+ {
+ // Unhook from old Template elements
+ _inOnApplyTemplate = true;
+ ClearValue(VerticalOffsetShadowProperty);
+ _scrollViewer = null;
+ _generator = null;
+
+ // Apply new Template
+ base.OnApplyTemplate();
+
+ // Hook up to new Template elements
+ _scrollViewer = FindFirstChildOfType<ScrollViewer>(this);
+ if (null == _scrollViewer)
+ {
+ throw new NotSupportedException("Control Template must include a ScrollViewer (wrapping ItemsHost).");
+ }
+ _generator = ItemContainerGenerator;
+ SetBinding(VerticalOffsetShadowProperty, new Binding { Source = _scrollViewer, Path = new PropertyPath("VerticalOffset") });
+ _inOnApplyTemplate = false;
+ }
+
+ /// <summary>
+ /// Determines if the specified item is (or is eligible to be) its own item container.
+ /// </summary>
+ /// <param name="item">The specified item.</param>
+ /// <returns>true if the item is its own item container; otherwise, false.</returns>
+ protected override bool IsItemItsOwnContainerOverride(object item)
+ {
+ // Check container type
+ return item is DeferredLoadListBoxItem;
+ }
+
+ /// <summary>
+ /// Creates or identifies the element used to display a specified item.
+ /// </summary>
+ /// <returns>A DeferredLoadListBoxItem corresponding to a specified item.</returns>
+ protected override DependencyObject GetContainerForItemOverride()
+ {
+ // Create container (matches ListBox implementation)
+ var item = new DeferredLoadListBoxItem();
+ if (ItemContainerStyle != null)
+ {
+ item.Style = ItemContainerStyle;
+ }
+ return item;
+ }
+
+ /// <summary>
+ /// Prepares the specified element to display the specified item.
+ /// </summary>
+ /// <param name="element">The element used to display the specified item.</param>
+ /// <param name="item">The item to display.</param>
+ protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
+ {
+ // Perform base class preparation
+ base.PrepareContainerForItemOverride(element, item);
+
+ // Mask the container's content
+ var container = (DeferredLoadListBoxItem)element;
+ if (!DesignerProperties.IsInDesignTool)
+ {
+ container.MaskContent();
+ }
+
+ // Queue a (single) pass to unmask newly visible content on the next tick
+ if (!_queuedUnmaskVisibleContent)
+ {
+ _queuedUnmaskVisibleContent = true;
+ Dispatcher.BeginInvoke(() =>
+ {
+ _queuedUnmaskVisibleContent = false;
+ UnmaskVisibleContent();
+ });
+ }
+ }
+
+ private static readonly DependencyProperty VerticalOffsetShadowProperty =
+ DependencyProperty.Register("VerticalOffsetShadow", typeof(double), typeof(DeferredLoadListBox), new PropertyMetadata(-1.0, OnVerticalOffsetShadowChanged));
+ private static void OnVerticalOffsetShadowChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
+ {
+ // Handle ScrollViewer VerticalOffset change by unmasking newly visible content
+ ((DeferredLoadListBox)o).UnmaskVisibleContent();
+ }
+
+ private void UnmaskVisibleContent()
+ {
+ // Capture variables
+ var count = Items.Count;
+
+ // Find index of any container within view using (1-indexed) binary search
+ var index = -1;
+ var l = 0;
+ var r = count + 1;
+ while (-1 == index)
+ {
+ var p = (r - l) / 2;
+ if (0 == p)
+ {
+ break;
+ }
+ p += l;
+ var c = (DeferredLoadListBoxItem)_generator.ContainerFromIndex(p - 1);
+ if (null == c)
+ {
+ if (_inOnApplyTemplate)
+ {
+ // Applying template; don't expect to have containers at this point
+ return;
+ }
+ // Should always be able to get the container
+ var presenter = FindFirstChildOfType<ItemsPresenter>(_scrollViewer);
+ var panel = (null == presenter) ? null : FindFirstChildOfType<Panel>(presenter);
+ if (panel is VirtualizingStackPanel)
+ {
+ throw new NotSupportedException("Must change ItemsPanel to be a StackPanel (via the ItemsPanel property).");
+ }
+ else
+ {
+ throw new NotSupportedException("Couldn't find container for item (ItemsPanel should be a StackPanel).");
+ }
+ }
+ switch (Overlap(_scrollViewer, c, 0))
+ {
+ case OverlapKind.Overlap:
+ index = p - 1;
+ break;
+ case OverlapKind.ChildAbove:
+ l = p;
+ break;
+ case OverlapKind.ChildBelow:
+ r = p;
+ break;
+ }
+ }
+
+ if (-1 != index)
+ {
+ // Unmask visible items below the current item
+ for (var i = index; i < count; i++)
+ {
+ if (!UnmaskItemContent(i))
+ {
+ break;
+ }
+ }
+
+ // Unmask visible items above the current item
+ for (var i = index - 1; 0 <= i; i--)
+ {
+ if (!UnmaskItemContent(i))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ private bool UnmaskItemContent(int index)
+ {
+ var container = (DeferredLoadListBoxItem)_generator.ContainerFromIndex(index);
+ if (null != container)
+ {
+ // Return quickly if not masked (but periodically check visibility anyway so we can stop once we're out of range)
+ if (!container.Masked && (0 != (index % 16)))
+ {
+ return true;
+ }
+ // Check necessary conditions
+ if (0 == container.ActualHeight)
+ {
+ // In some cases, ActualHeight will be 0 here, but can be "fixed" with an explicit call to UpdateLayout
+ container.UpdateLayout();
+ if (0 == container.ActualHeight)
+ {
+ throw new NotSupportedException("All containers must have a Height set (ex: via ItemContainerStyle), though the heights do not all need to be the same.");
+ }
+ }
+ // If container overlaps the "visible" area (i.e. on or near the screen), unmask it
+ if (OverlapKind.Overlap == Overlap(_scrollViewer, container, 2 * _scrollViewer.ActualHeight))
+ {
+ container.UnmaskContent();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static bool Overlap(double startA, double endA, double startB, double endB)
+ {
+ return (((startA <= startB) && (startB <= endA)) ||
+ ((startB <= startA) && (startA <= endB)));
+ }
+
+ private static OverlapKind Overlap(ScrollViewer parent, FrameworkElement child, double padding)
+ {
+ // Get child transform relative to parent
+ //var transform = child.TransformToVisual(parent); // Unreliable on Windows Phone 7; throws ArgumentException sometimes
+ var layoutSlot = LayoutInformation.GetLayoutSlot(child);
+ var transform = new TranslateTransform { /*X = layoutSlot.Left - parent.HorizontalOffset,*/ Y = layoutSlot.Top - parent.VerticalOffset };
+ // Get child bounds relative to parent
+ var bounds = new Rect(transform.Transform(new Point()), transform.Transform(new Point(/*child.ActualWidth*/ 0, child.ActualHeight)));
+ // Return kind of overlap
+ if (Overlap(0 - padding, parent.ActualHeight + padding, bounds.Top, bounds.Bottom))
+ {
+ return OverlapKind.Overlap;
+ }
+ else if (bounds.Top < 0)
+ {
+ return OverlapKind.ChildAbove;
+ }
+ else
+ {
+ return OverlapKind.ChildBelow;
+ }
+ }
+
+ private static T FindFirstChildOfType<T>(DependencyObject root) where T : class
+ {
+ // Enqueue root node
+ var queue = new Queue<DependencyObject>();
+ queue.Enqueue(root);
+ while (0 < queue.Count)
+ {
+ // Dequeue next node and check its children
+ var current = queue.Dequeue();
+ for (var i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
+ {
+ var child = VisualTreeHelper.GetChild(current, i);
+ var typedChild = child as T;
+ if (null != typedChild)
+ {
+ return typedChild;
+ }
+ // Enqueue child
+ queue.Enqueue(child);
+ }
+ }
+ // No children match
+ return null;
+ }
+ }
+}
diff --git a/Juick/Classes/DeferredLoadListBoxItem.cs b/Juick/Classes/DeferredLoadListBoxItem.cs
new file mode 100644
index 0000000..97bfc18
--- /dev/null
+++ b/Juick/Classes/DeferredLoadListBoxItem.cs
@@ -0,0 +1,45 @@
+// Copyright (C) Microsoft Corporation. All Rights Reserved.
+// This code released under the terms of the Microsoft Public License
+// (Ms-PL, http://opensource.org/licenses/ms-pl.html).
+
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Juick.Classes
+{
+ /// <summary>
+ /// Implements a subclass of ListBoxItem that is used in conjunction with
+ /// the DeferredLoadListBox to defer the loading of off-screen items.
+ /// </summary>
+ public class DeferredLoadListBoxItem : ListBoxItem
+ {
+ private object _maskedContent;
+ private DataTemplate _maskedContentTemplate;
+
+ internal bool Masked { get; set; }
+
+ internal void MaskContent()
+ {
+ if (!Masked)
+ {
+ _maskedContent = Content;
+ _maskedContentTemplate = ContentTemplate;
+ Content = null;
+ ContentTemplate = null;
+ Masked = true;
+ }
+ }
+
+ internal void UnmaskContent()
+ {
+ if (Masked)
+ {
+ ContentTemplate = _maskedContentTemplate;
+ Content = _maskedContent;
+ _maskedContentTemplate = null;
+ _maskedContent = null;
+ Masked = false;
+ }
+ }
+ }
+}
diff --git a/Juick/Classes/ExpressionHelper.cs b/Juick/Classes/ExpressionHelper.cs
new file mode 100644
index 0000000..3eb453a
--- /dev/null
+++ b/Juick/Classes/ExpressionHelper.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Juick.Classes
+{
+ static class ExpressionHelper
+ {
+ public static string GetPropertyName<T>(Expression<Func<T, object>> propertyExpression)
+ {
+ var bodyExpression = propertyExpression.Body;
+ var unaryExpression = bodyExpression as UnaryExpression;
+ if (unaryExpression != null)
+ {
+ bodyExpression = unaryExpression.Operand;
+ }
+ var memberExpression = (MemberExpression)bodyExpression;
+ return memberExpression.Member.Name;
+ }
+ }
+}
diff --git a/Juick/Classes/LowProfileImageLoader.cs b/Juick/Classes/LowProfileImageLoader.cs
new file mode 100644
index 0000000..d1eb3da
--- /dev/null
+++ b/Juick/Classes/LowProfileImageLoader.cs
@@ -0,0 +1,294 @@
+// Copyright (C) Microsoft Corporation. All Rights Reserved.
+// This code released under the terms of the Microsoft Public License
+// (Ms-PL, http://opensource.org/licenses/ms-pl.html).
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
+
+namespace Juick.Classes
+{
+ /// <summary>
+ /// Provides access to the Image.UriSource attached property which allows
+ /// Images to be loaded by Windows Phone with less impact to the UI thread.
+ /// </summary>
+ public static class LowProfileImageLoader
+ {
+ private const int WorkItemQuantum = 5;
+ private static readonly Thread _thread = new Thread(WorkerThreadProc);
+ private static readonly Queue<PendingRequest> _pendingRequests = new Queue<PendingRequest>();
+ private static readonly Queue<IAsyncResult> _pendingResponses = new Queue<IAsyncResult>();
+ private static readonly object _syncBlock = new object();
+ private static bool _exiting;
+
+ /// <summary>
+ /// Gets the value of the Uri to use for providing the contents of the Image's Source property.
+ /// </summary>
+ /// <param name="obj">Image needing its Source property set.</param>
+ /// <returns>Uri to use for providing the contents of the Source property.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "UriSource is applicable only to Image elements.")]
+ public static Uri GetUriSource(Image obj)
+ {
+ if (null == obj)
+ {
+ throw new ArgumentNullException("obj");
+ }
+ return (Uri)obj.GetValue(UriSourceProperty);
+ }
+
+ /// <summary>
+ /// Sets the value of the Uri to use for providing the contents of the Image's Source property.
+ /// </summary>
+ /// <param name="obj">Image needing its Source property set.</param>
+ /// <param name="value">Uri to use for providing the contents of the Source property.</param>
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "UriSource is applicable only to Image elements.")]
+ public static void SetUriSource(Image obj, Uri value)
+ {
+ if (null == obj)
+ {
+ throw new ArgumentNullException("obj");
+ }
+ obj.SetValue(UriSourceProperty, value);
+ }
+
+ /// <summary>
+ /// Identifies the UriSource attached DependencyProperty.
+ /// </summary>
+ public static readonly DependencyProperty UriSourceProperty = DependencyProperty.RegisterAttached(
+ "UriSource", typeof(Uri), typeof(LowProfileImageLoader), new PropertyMetadata(OnUriSourceChanged));
+
+ /// <summary>
+ /// Gets or sets a value indicating whether low-profile image loading is enabled.
+ /// </summary>
+ public static bool IsEnabled { get; set; }
+
+ [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Static constructor performs additional tasks.")]
+ static LowProfileImageLoader()
+ {
+ // Start worker thread
+ _thread.Start();
+ Application.Current.Exit += new EventHandler(HandleApplicationExit);
+ IsEnabled = true;
+ }
+
+ private static void HandleApplicationExit(object sender, EventArgs e)
+ {
+ // Tell worker thread to exit
+ _exiting = true;
+ if (Monitor.TryEnter(_syncBlock, 100))
+ {
+ Monitor.Pulse(_syncBlock);
+ Monitor.Exit(_syncBlock);
+ }
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Relevant exceptions don't have a common base class.")]
+ [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Linear flow is easy to understand.")]
+ private static void WorkerThreadProc(object unused)
+ {
+ Random rand = new Random();
+ var pendingRequests = new List<PendingRequest>();
+ var pendingResponses = new Queue<IAsyncResult>();
+ while (!_exiting)
+ {
+ lock (_syncBlock)
+ {
+ // Wait for more work if there's nothing left to do
+ if ((0 == _pendingRequests.Count) && (0 == _pendingResponses.Count) && (0 == pendingRequests.Count) && (0 == pendingResponses.Count))
+ {
+ Monitor.Wait(_syncBlock);
+ if (_exiting)
+ {
+ return;
+ }
+ }
+ // Copy work items to private collections
+ while (0 < _pendingRequests.Count)
+ {
+ var pendingRequest = _pendingRequests.Dequeue();
+ // Search for another pending request for the same Image element
+ for (var i = 0; i < pendingRequests.Count; i++)
+ {
+ if (pendingRequests[i].Image == pendingRequest.Image)
+ {
+ // Found one; replace it
+ pendingRequests[i] = pendingRequest;
+ pendingRequest = null;
+ break;
+ }
+ }
+ if (null != pendingRequest)
+ {
+ // Unique request; add it
+ pendingRequests.Add(pendingRequest);
+ }
+ }
+ while (0 < _pendingResponses.Count)
+ {
+ pendingResponses.Enqueue(_pendingResponses.Dequeue());
+ }
+ }
+ Queue<PendingCompletion> pendingCompletions = new Queue<PendingCompletion>();
+ // Process pending requests
+ var count = pendingRequests.Count;
+ for (var i = 0; (0 < count) && (i < WorkItemQuantum); i++)
+ {
+ // Choose a random item to behave reasonably at both extremes (FIFO/FILO)
+ var index = rand.Next(count);
+ var pendingRequest = pendingRequests[index];
+ pendingRequests[index] = pendingRequests[count - 1];
+ pendingRequests.RemoveAt(count - 1);
+ count--;
+ if (pendingRequest.Uri.IsAbsoluteUri)
+ {
+ // Download from network
+ var webRequest = HttpWebRequest.CreateHttp(pendingRequest.Uri);
+ webRequest.AllowReadStreamBuffering = true; // Don't want to block this thread or the UI thread on network access
+ webRequest.BeginGetResponse(HandleGetResponseResult, new ResponseState(webRequest, pendingRequest.Image, pendingRequest.Uri));
+ }
+ else
+ {
+ // Load from application (must have "Build Action"="Content")
+ var originalUriString = pendingRequest.Uri.OriginalString;
+ // Trim leading '/' to avoid problems
+ var resourceStreamUri = originalUriString.StartsWith("/", StringComparison.Ordinal) ? new Uri(originalUriString.TrimStart('/'), UriKind.Relative) : pendingRequest.Uri;
+ // Enqueue resource stream for completion
+ var streamResourceInfo = Application.GetResourceStream(resourceStreamUri);
+ if (null != streamResourceInfo)
+ {
+ pendingCompletions.Enqueue(new PendingCompletion(pendingRequest.Image, pendingRequest.Uri, streamResourceInfo.Stream));
+ }
+ }
+ // Yield to UI thread
+ Thread.Sleep(1);
+ }
+ // Process pending responses
+ for (var i = 0; (0 < pendingResponses.Count) && (i < WorkItemQuantum); i++)
+ {
+ var pendingResponse = pendingResponses.Dequeue();
+ var responseState = (ResponseState)pendingResponse.AsyncState;
+ try
+ {
+ var response = responseState.WebRequest.EndGetResponse(pendingResponse);
+ pendingCompletions.Enqueue(new PendingCompletion(responseState.Image, responseState.Uri, response.GetResponseStream()));
+ }
+ catch (WebException)
+ {
+ // Ignore web exceptions (ex: not found)
+ }
+ // Yield to UI thread
+ Thread.Sleep(1);
+ }
+ // Process pending completions
+ if (0 < pendingCompletions.Count)
+ {
+ // Get the Dispatcher and process everything that needs to happen on the UI thread in one batch
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ while (0 < pendingCompletions.Count)
+ {
+ // Decode the image and set the source
+ var pendingCompletion = pendingCompletions.Dequeue();
+ if (GetUriSource(pendingCompletion.Image) == pendingCompletion.Uri)
+ {
+ var bitmap = new BitmapImage();
+ try
+ {
+ bitmap.SetSource(pendingCompletion.Stream);
+ }
+ catch
+ {
+ // Ignore image decode exceptions (ex: invalid image)
+ }
+ pendingCompletion.Image.Source = bitmap;
+ }
+ else
+ {
+ // Uri mis-match; do nothing
+ }
+ // Dispose of response stream
+ pendingCompletion.Stream.Dispose();
+ }
+ });
+ }
+ }
+ }
+
+ private static void OnUriSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
+ {
+ var image = (Image)o;
+ var uri = (Uri)e.NewValue;
+
+ if (!IsEnabled || DesignerProperties.IsInDesignTool)
+ {
+ // Avoid handing off to the worker thread (can cause problems for design tools)
+ image.Source = new BitmapImage(uri);
+ }
+ else
+ {
+ // Clear-out the current image because it's now stale (helps when used with virtualization)
+ image.Source = null;
+ lock (_syncBlock)
+ {
+ // Enqueue the request
+ _pendingRequests.Enqueue(new PendingRequest(image, uri));
+ Monitor.Pulse(_syncBlock);
+ }
+ }
+ }
+
+ private static void HandleGetResponseResult(IAsyncResult result)
+ {
+ lock (_syncBlock)
+ {
+ // Enqueue the response
+ _pendingResponses.Enqueue(result);
+ Monitor.Pulse(_syncBlock);
+ }
+ }
+
+ private class PendingRequest
+ {
+ public Image Image { get; private set; }
+ public Uri Uri { get; private set; }
+ public PendingRequest(Image image, Uri uri)
+ {
+ Image = image;
+ Uri = uri;
+ }
+ }
+
+ private class ResponseState
+ {
+ public WebRequest WebRequest { get; private set; }
+ public Image Image { get; private set; }
+ public Uri Uri { get; private set; }
+ public ResponseState(WebRequest webRequest, Image image, Uri uri)
+ {
+ WebRequest = webRequest;
+ Image = image;
+ Uri = uri;
+ }
+ }
+
+ private class PendingCompletion
+ {
+ public Image Image { get; private set; }
+ public Uri Uri { get; private set; }
+ public Stream Stream { get; private set; }
+ public PendingCompletion(Image image, Uri uri, Stream stream)
+ {
+ Image = image;
+ Uri = uri;
+ Stream = stream;
+ }
+ }
+ }
+}
diff --git a/Juick/Classes/TileHelper.cs b/Juick/Classes/TileHelper.cs
new file mode 100644
index 0000000..223340b
--- /dev/null
+++ b/Juick/Classes/TileHelper.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Phone.Shell;
+
+namespace Juick.Classes
+{
+ static class TileHelper
+ {
+ public static void UpdateFlipTile(
+ string title,
+ string backTitle,
+ string backContent,
+ string wideBackContent,
+ int? count,
+ string smallBackgroundImageStringUri,
+ string backgroundImageStringUri,
+ string backBackgroundImageStringUri,
+ string wideBackgroundImageStringUri,
+ string wideBackBackgroundImageStringUri)
+ {
+ if (!CanUseLiveTiles)
+ {
+ return;
+ }
+ var smallBackgroundImage = CreateRelativeUri(smallBackgroundImageStringUri);
+ var backgroundImage = CreateRelativeUri(backgroundImageStringUri);
+ var backBackgroundImage = CreateRelativeUri(backBackgroundImageStringUri);
+ var wideBackgroundImage = CreateRelativeUri(wideBackgroundImageStringUri);
+ var wideBackBackgroundImage = CreateRelativeUri(wideBackBackgroundImageStringUri);
+
+ Type flipTileDataType = Type.GetType("Microsoft.Phone.Shell.FlipTileData, Microsoft.Phone");
+
+ // Get the ShellTile type so we can call the new version of "Update" that takes the new Tile templates.
+ Type shellTileType = Type.GetType("Microsoft.Phone.Shell.ShellTile, Microsoft.Phone");
+
+ // Loop through any existing Tiles that are pinned to Start.
+ foreach (var tileToUpdate in ShellTile.ActiveTiles)
+ {
+ var UpdateTileData = flipTileDataType.GetConstructor(new Type[] { }).Invoke(null);
+
+ // Set the properties.
+ SetProperty(UpdateTileData, "Title", title);
+ SetProperty(UpdateTileData, "Count", count);
+ SetProperty(UpdateTileData, "BackTitle", backTitle);
+ SetProperty(UpdateTileData, "BackContent", backContent);
+ SetProperty(UpdateTileData, "SmallBackgroundImage", smallBackgroundImage);
+ SetProperty(UpdateTileData, "BackgroundImage", backgroundImage);
+ SetProperty(UpdateTileData, "BackBackgroundImage", backBackgroundImage);
+ SetProperty(UpdateTileData, "WideBackgroundImage", wideBackgroundImage);
+ SetProperty(UpdateTileData, "WideBackBackgroundImage", wideBackBackgroundImage);
+ SetProperty(UpdateTileData, "WideBackContent", wideBackContent);
+
+ // Invoke the new version of ShellTile.Update.
+ shellTileType.GetMethod("Update").Invoke(tileToUpdate, new Object[] { UpdateTileData });
+ }
+ }
+
+ static Uri CreateRelativeUri(string uriString)
+ {
+ return !string.IsNullOrEmpty(uriString) ? new Uri(uriString, UriKind.Relative) : null;
+ }
+
+ static void SetProperty(object instance, string name, object value)
+ {
+ var setMethod = instance.GetType().GetProperty(name).GetSetMethod();
+ setMethod.Invoke(instance, new object[] { value });
+ }
+
+ static readonly Version targetedVersion78 = new Version(7, 10, 8858);
+
+ static bool CanUseLiveTiles
+ {
+ get { return Environment.OSVersion.Version >= targetedVersion78; }
+ }
+ }
+}
diff --git a/Juick/Juick.csproj b/Juick/Juick.csproj
index 4ab974d..fbb217b 100644
--- a/Juick/Juick.csproj
+++ b/Juick/Juick.csproj
@@ -46,6 +46,7 @@
<NoConfig>true</NoConfig>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <RunCodeAnalysis>true</RunCodeAnalysis>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Phone" />
@@ -74,9 +75,14 @@
</Compile>
<Compile Include="Classes\AccountManager.cs" />
<Compile Include="Classes\BindingChangedEventArgs.cs" />
+ <Compile Include="Classes\DeferredLoadListBox.cs" />
+ <Compile Include="Classes\DeferredLoadListBoxItem.cs" />
<Compile Include="Classes\DelegateCommand.cs" />
<Compile Include="Classes\DependencyPropertyListener.cs" />
+ <Compile Include="Classes\ExpressionHelper.cs" />
+ <Compile Include="Classes\LowProfileImageLoader.cs" />
<Compile Include="Classes\ScrollViewerMonitor.cs" />
+ <Compile Include="Classes\TileHelper.cs" />
<Compile Include="Controls\HyperLinkRichTextBox.cs" />
<Compile Include="LoginView.xaml.cs">
<DependentUpon>LoginView.xaml</DependentUpon>
@@ -144,7 +150,9 @@
<Resource Include="Images\appbar.favs.addto.rest.png" />
<Resource Include="Images\appbar.favs.rest.png" />
<Resource Include="Images\appbar.feature.camera.rest.png" />
- <Resource Include="ApplicationTile.png" />
+ <Content Include="ApplicationTile.png" />
+ <Content Include="ApplicationSmallTile.png" />
+ <Content Include="ApplicationWideTile.png" />
<Content Include="Images\appbar.attach.png" />
<Content Include="Images\appbar.feature.email.rest.png" />
<Resource Include="Images\appbar.feature.search.rest.png" />
diff --git a/Juick/MainPage.xaml b/Juick/MainPage.xaml
index ded6d33..013fc34 100644
--- a/Juick/MainPage.xaml
+++ b/Juick/MainPage.xaml
@@ -67,7 +67,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
- <Image Source="{Binding UserAvatar}" Grid.Row="0" Grid.Column="0" Margin="3" />
+ <Image bindings:LowProfileImageLoader.UriSource="{Binding AvatarUri}" Grid.Row="0" Grid.Column="0" Margin="3" />
<TextBlock Text="{Binding Username}" Grid.Row="0" Grid.Column="1"
Margin="5,0,5,5" VerticalAlignment="Top"
HorizontalAlignment="Left"
@@ -80,7 +80,7 @@
Foreground="{StaticResource PhoneForegroundBrush}"
Margin="5,0,5,5" VerticalAlignment="Top"
IsReadOnly="True" Text="{Binding MessageText}" />
- <Image Source="{Binding Attachment}" Grid.Row="2" Grid.Column="0" Margin="3" Grid.ColumnSpan="2" />
+ <Image bindings:LowProfileImageLoader.UriSource="{Binding Attachment}" Grid.Row="2" Grid.Column="0" Margin="3" Grid.ColumnSpan="2" />
<TextBlock Text="{Binding Status}" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
Foreground="{StaticResource PhoneForegroundBrush}"
Style="{StaticResource PhoneTextAccentStyle}"
@@ -122,7 +122,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
- <Image Source="{Binding UserAvatar}" Grid.Row="0" Grid.Column="0" Margin="3" />
+ <Image bindings:LowProfileImageLoader.UriSource="{Binding AvatarUri}" Grid.Row="0" Grid.Column="0" Margin="3" />
<TextBlock Text="{Binding Username}" Grid.Row="0" Grid.Column="1"
Margin="5,0,5,5" VerticalAlignment="Top"
HorizontalAlignment="Left"
@@ -134,7 +134,7 @@
Foreground="{StaticResource PhoneForegroundBrush}"
Margin="5,0,5,5" VerticalAlignment="Top"
IsReadOnly="True" Text="{Binding MessageText}" />
- <Image Source="{Binding Attachment}" Grid.Row="2" Grid.Column="0" Margin="3" Grid.ColumnSpan="2" />
+ <Image bindings:LowProfileImageLoader.UriSource="{Binding Attachment}" Grid.Row="2" Grid.Column="0" Margin="3" Grid.ColumnSpan="2" />
<TextBlock Text="{Binding Status}" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
Foreground="{StaticResource PhoneForegroundBrush}"
Style="{StaticResource PhoneTextAccentStyle}"
diff --git a/Juick/Properties/AssemblyInfo.cs b/Juick/Properties/AssemblyInfo.cs
index 9a52d9e..9a99ba0 100644
--- a/Juick/Properties/AssemblyInfo.cs
+++ b/Juick/Properties/AssemblyInfo.cs
@@ -32,6 +32,6 @@ using System.Resources;
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
-[assembly: AssemblyVersion("1.0.9.9")]
-[assembly: AssemblyFileVersion("1.0.9.9")]
+[assembly: AssemblyVersion("1.0.9.10")]
+[assembly: AssemblyFileVersion("1.0.9.10")]
[assembly: NeutralResourcesLanguageAttribute("en-US")]
diff --git a/Juick/Properties/WMAppManifest.xml b/Juick/Properties/WMAppManifest.xml
index 8f7b6da..15e3611 100644
--- a/Juick/Properties/WMAppManifest.xml
+++ b/Juick/Properties/WMAppManifest.xml
@@ -33,5 +33,8 @@
<Extensions>
<Extension ExtensionName="Photos_Extra_Share" ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5632}" TaskID="_default" />
</Extensions>
+ <!--<AppExtra xmlns="" AppPlatformVersion="8.0">
+ <Extra Name="Tiles"/>
+ </AppExtra>-->
</App>
</Deployment> \ No newline at end of file
diff --git a/Juick/ThreadView.xaml b/Juick/ThreadView.xaml
index 7de5533..efab81a 100644
--- a/Juick/ThreadView.xaml
+++ b/Juick/ThreadView.xaml
@@ -6,7 +6,7 @@
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:controls="clr-namespace:Juick.Controls"
+ xmlns:controls="clr-namespace:Juick.Controls" xmlns:bindings="clr-namespace:Juick.Classes"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
@@ -23,7 +23,7 @@
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
- <TextBlock x:Name="ApplicationTitle" Text="JUICK" Style="{StaticResource PhoneTextNormalStyle}"/>
+ <TextBlock x:Name="ApplicationTitle" Text="{Binding Caption}" Style="{StaticResource PhoneTextNormalStyle}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
@@ -48,7 +48,7 @@
<RowDefinition Height="Auto" />
<!-- RowDefinition Height="Auto" / -->
</Grid.RowDefinitions>
- <Image Source="{Binding UserAvatar}" Grid.Row="0" Grid.Column="0" Margin="3" />
+ <Image bindings:LowProfileImageLoader.UriSource="{Binding AvatarUri}" Grid.Row="0" Grid.Column="0" Margin="3" />
<TextBlock Text="{Binding Username}" Grid.Row="0" Grid.Column="1"
Margin="5,0,5,5" VerticalAlignment="Top"
HorizontalAlignment="Left"
@@ -61,7 +61,7 @@
Margin="5,0,5,5" VerticalAlignment="Top"
TextWrapping="Wrap" HorizontalAlignment="Left"
IsReadOnly="True" Text="{Binding MessageText}" />
- <Image Source="{Binding Attachment}" Grid.Row="2" Grid.Column="0" Margin="3" Grid.ColumnSpan="2" />
+ <Image bindings:LowProfileImageLoader.UriSource="{Binding Attachment}" Grid.Row="2" Grid.Column="0" Margin="3" Grid.ColumnSpan="2" />
<!-- TextBlock Text="{Binding Status}" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
Foreground="{StaticResource PhoneForegroundBrush}"
Style="{StaticResource PhoneTextAccentStyle}"
diff --git a/Juick/ViewModels/MessageListViewModelBase.cs b/Juick/ViewModels/MessageListViewModelBase.cs
index b9d7070..3eb1842 100644
--- a/Juick/ViewModels/MessageListViewModelBase.cs
+++ b/Juick/ViewModels/MessageListViewModelBase.cs
@@ -13,6 +13,7 @@ namespace Juick.ViewModels
{
public class MessageListViewModelBase : INotifyPropertyChanged
{
+ static readonly string IsDataLoadingPropertyName = ExpressionHelper.GetPropertyName<MessageListViewModelBase>(x => x.IsDataLoading);
bool isDataLoading;
public MessageListViewModelBase()
@@ -22,6 +23,7 @@ namespace Juick.ViewModels
}
public string RestUri { get; set; }
+ public virtual string Caption { get { return "juick"; } }
/// <summary>
/// A collection for MessageViewModel objects.
@@ -36,7 +38,7 @@ namespace Juick.ViewModels
set
{
isDataLoading = value;
- NotifyPropertyChanged("IsDataLoading");
+ NotifyPropertyChanged(IsDataLoadingPropertyName);
LoadMessagesPageCommand.NotifyCanExecuteChanged();
}
}
@@ -89,14 +91,16 @@ namespace Juick.ViewModels
foreach (var post in response.Data)
{
var status = string.Format("Posted on: {0}, replies: {1}", post.Timestamp, post.Replies);
- var item = new MessageViewModel(post) { Status = status };
- Items.Add(item);
- var imageUri = new Uri(string.Format("http://i.juick.com/as/{0}.png", post.User.Uid), UriKind.Absolute);
- item.UserAvatar = new BitmapImage { UriSource = imageUri };
+ var item = new MessageViewModel(post)
+ {
+ Status = status,
+ AvatarUri = new Uri(string.Format("http://i.juick.com/as/{0}.png", post.User.Uid), UriKind.Absolute)
+ };
if (post.Photo != null)
{
- item.Attachment = new BitmapImage { UriSource = new Uri(post.Photo.Small, UriKind.Absolute) };
+ item.Attachment = new Uri(post.Photo.Small, UriKind.Absolute) ;
}
+ Items.Add(item);
}
}
diff --git a/Juick/ViewModels/MessageViewModel.cs b/Juick/ViewModels/MessageViewModel.cs
index 6a53669..750864a 100644
--- a/Juick/ViewModels/MessageViewModel.cs
+++ b/Juick/ViewModels/MessageViewModel.cs
@@ -88,22 +88,22 @@ namespace Juick.ViewModels
}
}
- private BitmapImage _avatar;
- public BitmapImage UserAvatar
+ private Uri _avatarUri;
+ public Uri AvatarUri
{
- get { return _avatar; }
+ get { return _avatarUri; }
set
{
- if (value != _avatar)
+ if (value != _avatarUri)
{
- _avatar = value;
- NotifyPropertyChanged("UserAvatar");
+ _avatarUri = value;
+ NotifyPropertyChanged("AvatarUri");
}
}
}
- private BitmapImage _attach;
- public BitmapImage Attachment
+ private Uri _attach;
+ public Uri Attachment
{
get { return _attach; }
set
diff --git a/Juick/ViewModels/ThreadViewModel.cs b/Juick/ViewModels/ThreadViewModel.cs
index 4a80f31..ec1f92d 100644
--- a/Juick/ViewModels/ThreadViewModel.cs
+++ b/Juick/ViewModels/ThreadViewModel.cs
@@ -1,9 +1,11 @@
-using System;
+using Juick.Classes;
namespace Juick.ViewModels
{
public class ThreadViewModel : MessageListViewModelBase
{
+ static readonly string CaptionPropertyName = ExpressionHelper.GetPropertyName<ThreadViewModel>(x => x.Caption);
+
private int _mid;
public int Mid
{
@@ -12,7 +14,12 @@ namespace Juick.ViewModels
{
_mid = value;
RestUri = string.Format("/thread?mid={0}", _mid);
+ NotifyPropertyChanged(CaptionPropertyName);
}
}
+
+ public override string Caption {
+ get { return "#" + _mid; }
+ }
}
}