diff options
author | Vitaly Takmazov | 2013-03-29 12:47:25 +0400 |
---|---|---|
committer | Vitaly Takmazov | 2013-03-29 12:47:25 +0400 |
commit | e20d55789c6ab0c2c6324eb6806c12cc96723eca (patch) | |
tree | 0b947cf4394b086fa27ac4c210ffc9c673e6f4c5 /Juick | |
parent | a84c894c89f23e3eb6416ff8943d4d8a2fe84a49 (diff) | |
parent | 6df49ee7c25d739ec6445a13e1aa828a4da41730 (diff) |
Merge branch 'master' of https://bitbucket.org/vitalyster/juick-windowsphone
Diffstat (limited to 'Juick')
-rw-r--r-- | Juick/App.xaml.cs | 13 | ||||
-rw-r--r-- | Juick/ApplicationSmallTile.png | bin | 0 -> 3889 bytes | |||
-rw-r--r-- | Juick/ApplicationTile.png | bin | 4187 -> 5690 bytes | |||
-rw-r--r-- | Juick/ApplicationWideTile.png | bin | 0 -> 5534 bytes | |||
-rw-r--r-- | Juick/Classes/DeferredLoadListBox.cs | 275 | ||||
-rw-r--r-- | Juick/Classes/DeferredLoadListBoxItem.cs | 45 | ||||
-rw-r--r-- | Juick/Classes/ExpressionHelper.cs | 20 | ||||
-rw-r--r-- | Juick/Classes/LowProfileImageLoader.cs | 294 | ||||
-rw-r--r-- | Juick/Classes/TileHelper.cs | 78 | ||||
-rw-r--r-- | Juick/Juick.csproj | 10 | ||||
-rw-r--r-- | Juick/MainPage.xaml | 8 | ||||
-rw-r--r-- | Juick/Properties/AssemblyInfo.cs | 4 | ||||
-rw-r--r-- | Juick/Properties/WMAppManifest.xml | 3 | ||||
-rw-r--r-- | Juick/ThreadView.xaml | 8 | ||||
-rw-r--r-- | Juick/ViewModels/MessageListViewModelBase.cs | 16 | ||||
-rw-r--r-- | Juick/ViewModels/MessageViewModel.cs | 16 | ||||
-rw-r--r-- | Juick/ViewModels/ThreadViewModel.cs | 9 |
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 Binary files differnew file mode 100644 index 0000000..95947f4 --- /dev/null +++ b/Juick/ApplicationSmallTile.png diff --git a/Juick/ApplicationTile.png b/Juick/ApplicationTile.png Binary files differindex 96d0872..703c107 100644 --- a/Juick/ApplicationTile.png +++ b/Juick/ApplicationTile.png diff --git a/Juick/ApplicationWideTile.png b/Juick/ApplicationWideTile.png Binary files differnew file mode 100644 index 0000000..e0b6bd6 --- /dev/null +++ b/Juick/ApplicationWideTile.png 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; }
+ }
}
}
|