diff --git a/GalaSoft.MvvmLight/GalaSoft.MvvmLight (NET35)/GalaSoft.MvvmLight (NET35).csproj b/GalaSoft.MvvmLight/GalaSoft.MvvmLight (NET35)/GalaSoft.MvvmLight (NET35).csproj index 1947f3a..b950caa 100644 --- a/GalaSoft.MvvmLight/GalaSoft.MvvmLight (NET35)/GalaSoft.MvvmLight (NET35).csproj +++ b/GalaSoft.MvvmLight/GalaSoft.MvvmLight (NET35)/GalaSoft.MvvmLight (NET35).csproj @@ -1,58 +1,26 @@  - + + Debug AnyCPU - 9.0.30729 - 2.0 - {ADB28C09-8DE9-4006-80D5-74856EB4A48B} + {80BFFA86-0EFF-4B5A-BA81-02EC6B681BE2} Library Properties GalaSoft.MvvmLight GalaSoft.MvvmLight v3.5 512 - true - GalaSoft.MvvmLight %28NET35%29.snk - - - - - - - - - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true + Client true full false bin\Debug\ - TRACE;DEBUG + DEBUG;TRACE prompt 4 - true - 618 - AllRules.ruleset bin\Debug\GalaSoft.MvvmLight.XML @@ -62,79 +30,101 @@ TRACE prompt 4 - AllRules.ruleset bin\Release\GalaSoft.MvvmLight.XML - - 3.0 - - - 3.0 - + + - - 3.5 - + + + - - 3.0 - + - - - - - - - - - - - - - - - - - - - - - - - - + + Command\RelayCommand.cs + + + Command\RelayCommandGeneric.cs + + + Helpers\IExecuteWithObject.cs + + + Helpers\IExecuteWithObjectAndResult.cs + + + Helpers\WeakAction.cs + + + Helpers\WeakActionGeneric.cs + + + Helpers\WeakFunc.cs + + + Helpers\WeakFuncGeneric.cs + + + ICleanup.cs + + + Messaging\DialogMessage.cs + + + Messaging\GenericMessage.cs + + + Messaging\IMessenger.cs + + + Messaging\MessageBase.cs + + + Messaging\Messenger.cs + + + Messaging\NotificationMessage.cs + + + Messaging\NotificationMessageAction.cs + + + Messaging\NotificationMessageActionGeneric.cs + + + Messaging\NotificationMessageGeneric.cs + + + Messaging\NotificationMessageWithCallback.cs + + + Messaging\PropertyChangedMessage.cs + + + Messaging\PropertyChangedMessageBase.cs + + + ObservableObject.cs + + + Properties\AssemblyInfo.cs + + + ViewModelBase.cs + + + Threading\DispatcherHelper.cs + - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - + + License.txt + + \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Data/Model/Comment.cs b/Samples/Flowers/Flowers.Data/Model/Comment.cs new file mode 100644 index 0000000..f0593d4 --- /dev/null +++ b/Samples/Flowers/Flowers.Data/Model/Comment.cs @@ -0,0 +1,29 @@ +using System; +using Newtonsoft.Json; + +namespace Flowers.Data.Model +{ + public class Comment + { + [JsonProperty("id")] + public string Id + { + get; + set; + } + + [JsonProperty("inputdate")] + public DateTime InputDate + { + get; + set; + } + + [JsonProperty("text")] + public string Text + { + get; + set; + } + } +} \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Data/Model/Flower.cs b/Samples/Flowers/Flowers.Data/Model/Flower.cs new file mode 100644 index 0000000..c8d4f34 --- /dev/null +++ b/Samples/Flowers/Flowers.Data/Model/Flower.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using GalaSoft.MvvmLight; +using Newtonsoft.Json; + +namespace Flowers.Data.Model +{ + public class Flower : ObservableObject + { + public const string CommentsPropertyName = "Comments"; + public const string DescriptionPropertyName = "Description"; + public const string NamePropertyName = "Name"; + + private string _description; + private string _name; + + [JsonProperty("comments")] + public ObservableCollection Comments + { + get; + set; + } + + [JsonProperty("description")] + public string Description + { + get + { + return _description; + } + set + { + Set(() => Description, ref _description, value); + } + } + + [JsonProperty("id")] + public string Id + { + get; + set; + } + + [JsonProperty("image")] + public string Image + { + get; + set; + } + + [JsonProperty("name")] + public string Name + { + get + { + return _name; + } + set + { + Set(() => Name, ref _name, value); + } + } + + public Flower() + { + Comments = new ObservableCollection(); + } + + public void AddComment(string comment) + { + Comments.Add( + new Comment + { + Id = Guid.NewGuid().ToString(), + InputDate = DateTime.Now, + Text = comment + }); + } + + public void DeleteComment(string id) + { + var comment = Comments.FirstOrDefault(c => c.Id == id); + if (comment != null) + { + Comments.Remove(comment); + } + } + } +} \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Data/Model/FlowersResult.cs b/Samples/Flowers/Flowers.Data/Model/FlowersResult.cs new file mode 100644 index 0000000..9d5d274 --- /dev/null +++ b/Samples/Flowers/Flowers.Data/Model/FlowersResult.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Flowers.Data.Model +{ + public class FlowersResult + { + [JsonProperty("data")] + public IList Data + { + get; + set; + } + } +} \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Data/Model/FlowersService.cs b/Samples/Flowers/Flowers.Data/Model/FlowersService.cs new file mode 100644 index 0000000..2c6f0fa --- /dev/null +++ b/Samples/Flowers/Flowers.Data/Model/FlowersService.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Flowers.Model; +using Newtonsoft.Json; + +namespace Flowers.Data.Model +{ + public class FlowersService : IFlowersService + { + private const string RequestUrl = "http://www.galasoft.ch/labs/Flowers/FlowersService.ashx?{0}={1}&{2}={3}&ticks={4}"; + + public async Task> Refresh() + { + using (var client = new HttpClient()) + { + var url = string.Format( + RequestUrl, + WebConstants.AuthenticationKey, + WebConstants.AuthenticationId, + WebConstants.ActionKey, + WebConstants.ActionGet, + DateTime.Now.Ticks); + + var json = await client.GetStringAsync(url); + + var result = JsonConvert.DeserializeObject(json); + return result.Data; + } + } + + public async Task Save(Flower flower) + { + using (var client = new HttpClient()) + { + var url = string.Format( + RequestUrl, + WebConstants.AuthenticationKey, + WebConstants.AuthenticationId, + WebConstants.ActionKey, + WebConstants.ActionSave, + DateTime.Now.Ticks); + + var json = JsonConvert.SerializeObject(flower); + + var content = new FormUrlEncodedContent( + new[] + { + new KeyValuePair("flower", json) + }); + + var response = await client.PostAsync(url, content); + return response.StatusCode == System.Net.HttpStatusCode.OK; + } + } + } +} \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Data/Model/IFlowersService.cs b/Samples/Flowers/Flowers.Data/Model/IFlowersService.cs new file mode 100644 index 0000000..42029e4 --- /dev/null +++ b/Samples/Flowers/Flowers.Data/Model/IFlowersService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Flowers.Data.Model +{ + public interface IFlowersService + { + Task> Refresh(); + + Task Save(Flower flower); + } +} \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Data/Properties/AssemblyInfo.cs b/Samples/Flowers/Flowers.Data/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c18d8b4 --- /dev/null +++ b/Samples/Flowers/Flowers.Data/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Flowers.Data")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Flowers.Data")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/Flowers/Flowers.Data/ViewModel/FlowerViewModel.cs b/Samples/Flowers/Flowers.Data/ViewModel/FlowerViewModel.cs new file mode 100644 index 0000000..ff8f591 --- /dev/null +++ b/Samples/Flowers/Flowers.Data/ViewModel/FlowerViewModel.cs @@ -0,0 +1,138 @@ +using System; +using Flowers.Data.Design; +using Flowers.Data.Model; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using GalaSoft.MvvmLight.Views; +using Microsoft.Practices.ServiceLocation; + +namespace Flowers.Data.ViewModel +{ + public class FlowerViewModel : ViewModelBase + { + private readonly IFlowersService _flowerService; + + private RelayCommand _addCommentCommand; + private RelayCommand _saveCommentCommand; + + public RelayCommand AddCommentCommand + { + get + { + return _addCommentCommand + ?? (_addCommentCommand = new RelayCommand( + () => + { + var nav = ServiceLocator.Current.GetInstance(); + nav.NavigateTo(ViewModelLocator.AddCommentPageKey, this); + })); + } + } + + public string ImageFileName + { + get + { + return ImageUri.LocalPath; + } + } + + public Uri ImageUri + { + get + { + return new Uri(Model.Image); + } + } + + public Flower Model + { + get; + private set; + } + + /// + /// The property's name. + /// + public const string IsSavingPropertyName = "IsSaving"; + + private bool _isSaving = false; + + /// + /// Sets and gets the IsSaving property. + /// Changes to that property's value raise the PropertyChanged event. + /// + public bool IsSaving + { + get + { + return _isSaving; + } + set + { + if (Set(() => IsSaving, ref _isSaving, value)) + { + SaveCommentCommand.RaiseCanExecuteChanged(); + } + } + } + + public RelayCommand SaveCommentCommand + { + get + { + return _saveCommentCommand + ?? (_saveCommentCommand = new RelayCommand( + async text => + { + IsSaving = true; + Model.Comments.Add( + new Comment + { + Id = Guid.NewGuid().ToString(), + InputDate = DateTime.Now, + Text = text + }); + + var result = await _flowerService.Save(Model); + + if (!result) + { + // Handle error when saving + var dialog = ServiceLocator.Current.GetInstance(); + await + dialog.ShowError( + "Error when saving, your comment was not saved", + "Error", + "OK", + null); + } + + var nav = ServiceLocator.Current.GetInstance(); + nav.GoBack(); + IsSaving = false; + }, + text => !string.IsNullOrEmpty(text) && !IsSaving)); + } + } + + public FlowerViewModel(IFlowersService flowerService, Flower model) + { + _flowerService = flowerService; + Model = model; + } + +#if DEBUG + // This constructor is used in the Windows Phone app at design time, + // for the Blend visual designer. + public FlowerViewModel() + { + if (IsInDesignMode) + { + var service = new DesignFlowersService(); + Model = service.GetFlower(0); + } + } +#endif + } +} \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Data/ViewModel/MainViewModel.cs b/Samples/Flowers/Flowers.Data/ViewModel/MainViewModel.cs new file mode 100644 index 0000000..d4deb65 --- /dev/null +++ b/Samples/Flowers/Flowers.Data/ViewModel/MainViewModel.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.ObjectModel; +using Flowers.Data.Model; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using GalaSoft.MvvmLight.Views; +using Microsoft.Practices.ServiceLocation; + +namespace Flowers.Data.ViewModel +{ + public class MainViewModel : ViewModelBase + { + public const string LastLoadedPropertyName = "LastLoaded"; + + private readonly IFlowersService _flowersService; + private readonly INavigationService _navigationService; + private bool _isLoading; + private DateTime _lastLoaded = DateTime.MinValue; + private RelayCommand _refreshCommand; + private RelayCommand _showDetailsCommand; + + public ObservableCollection Flowers + { + get; + private set; + } + + public DateTime LastLoaded + { + get + { + return _lastLoaded; + } + set + { + if (Set(() => LastLoaded, ref _lastLoaded, value)) + { + RaisePropertyChanged(() => LastLoadedFormatted); + } + } + } + + public string LastLoadedFormatted + { + get + { + return _isLoading + ? "Loading..." + : "Last loaded: " + (LastLoaded == DateTime.MinValue ? "Never" : LastLoaded.ToString()); + } + } + + public RelayCommand RefreshCommand + { + get + { + return _refreshCommand + ?? (_refreshCommand = new RelayCommand( + async () => + { + Flowers.Clear(); + + _isLoading = true; + RaisePropertyChanged(() => LastLoadedFormatted); + + try + { + var list = await _flowersService.Refresh(); + + foreach (var flower in list) + { + Flowers.Add(new FlowerViewModel(_flowersService, flower)); + } + + _isLoading = false; + LastLoaded = DateTime.Now; + } + catch (Exception ex) + { + var dialog = ServiceLocator.Current.GetInstance(); + dialog.ShowError(ex, "Error when refreshing", "OK", null); + } + + _isLoading = false; + RaisePropertyChanged(() => LastLoadedFormatted); + })); + } + } + + public RelayCommand ShowDetailsCommand + { + get + { + return _showDetailsCommand + ?? (_showDetailsCommand = new RelayCommand( + flower => + { + if (!ShowDetailsCommand.CanExecute(flower)) + { + return; + } + + _navigationService.NavigateTo(ViewModelLocator.DetailsPageKey, flower); + }, + flower => flower != null)); + } + } + + public MainViewModel( + IFlowersService flowersService, + INavigationService navigationService) + { + _flowersService = flowersService; + _navigationService = navigationService; + Flowers = new ObservableCollection(); + +#if DEBUG + if (IsInDesignMode) + { + RefreshCommand.Execute(null); + } +#endif + } + } +} \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Data/ViewModel/ViewModelLocator.cs b/Samples/Flowers/Flowers.Data/ViewModel/ViewModelLocator.cs new file mode 100644 index 0000000..63c984e --- /dev/null +++ b/Samples/Flowers/Flowers.Data/ViewModel/ViewModelLocator.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.CodeAnalysis; +using Flowers.Data.Design; +using Flowers.Data.Model; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Ioc; +using Microsoft.Practices.ServiceLocation; + +namespace Flowers.Data.ViewModel +{ + public class ViewModelLocator + { + public const string AddCommentPageKey = "AddCommentPage"; + public const string DetailsPageKey = "DetailsPage"; + + [SuppressMessage("Microsoft.Performance", + "CA1822:MarkMembersAsStatic", + Justification = "This non-static member is needed for data binding purposes.")] + public MainViewModel Main + { + get + { + return ServiceLocator.Current.GetInstance(); + } + } + + static ViewModelLocator() + { + ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); + + if (ViewModelBase.IsInDesignModeStatic) + { + SimpleIoc.Default.Register(); + SimpleIoc.Default.Register(); + } + else + { + SimpleIoc.Default.Register(); + } + + SimpleIoc.Default.Register(); + } + } +} \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Data/packages.config b/Samples/Flowers/Flowers.Data/packages.config new file mode 100644 index 0000000..cfdc8e4 --- /dev/null +++ b/Samples/Flowers/Flowers.Data/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Droid/AddCommentActivity.cs b/Samples/Flowers/Flowers.Droid/AddCommentActivity.cs new file mode 100644 index 0000000..2c5c2c3 --- /dev/null +++ b/Samples/Flowers/Flowers.Droid/AddCommentActivity.cs @@ -0,0 +1,37 @@ +using Android.App; +using Android.OS; +using Flowers.Data.ViewModel; +using GalaSoft.MvvmLight.Helpers; +using GalaSoft.MvvmLight.Views; + +namespace Flowers +{ + [Activity(Label = "Add Comment")] + public partial class AddCommentActivity + { + private Binding _saveBinding; + + private FlowerViewModel Vm + { + get; + set; + } + + protected override void OnCreate(Bundle bundle) + { + base.OnCreate(bundle); + SetContentView(Resource.Layout.AddComment); + + // Retrieve navigation parameter and set as current "DataContext" + Vm = GlobalNavigation.GetAndRemoveParameter(Intent); + + _saveBinding = this.SetBinding( + () => CommentText.Text); + + SaveCommentButton.SetCommand( + "Click", + Vm.SaveCommentCommand, + _saveBinding); + } + } +} \ No newline at end of file diff --git a/Samples/Flowers/Flowers.Droid/AddCommentActivity.ui.cs b/Samples/Flowers/Flowers.Droid/AddCommentActivity.ui.cs new file mode 100644 index 0000000..9be71b5 --- /dev/null +++ b/Samples/Flowers/Flowers.Droid/AddCommentActivity.ui.cs @@ -0,0 +1,29 @@ +using Android.Widget; +using Flowers.Helpers; + +namespace Flowers +{ + public partial class AddCommentActivity : ActivityBaseEx + { + private EditText _commentText; + private Button _saveCommentButton; + + public EditText CommentText + { + get + { + return _commentText + ?? (_commentText = FindViewById(Resource.Id.CommentText)); + } + } + + public Button SaveCommentButton + { + get + { + return _saveCommentButton + ?? (_saveCommentButton = FindViewById