This commit is contained in:
Alexander Koren 2016-09-21 15:32:31 -07:00
Коммит 23efa617e8
105 изменённых файлов: 6539 добавлений и 0 удалений

63
.gitattributes поставляемый Normal file
Просмотреть файл

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

230
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,230 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Azure Emulator
efc/
rfc/
# Windows Store app package directory
AppPackages/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/

1
CONTRIBUTING.md Normal file
Просмотреть файл

@ -0,0 +1 @@
We're not ready to accept contributions at this time, but if you have any feedback, please post to the Issues list. Thanks!

23
LICENSE Normal file
Просмотреть файл

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2016 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

101
LunchScheduler.sln Normal file
Просмотреть файл

@ -0,0 +1,101 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LunchScheduler.App", "app\LunchScheduler.App.csproj", "{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LunchScheduler.Models", "models\LunchScheduler.Models.csproj", "{633B2B38-C19D-4C17-BA1B-22D549625F0F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LunchScheduler.Site", "site\LunchScheduler.Site.csproj", "{9994333A-C9EB-43A9-B3D1-7E40B75651C0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{49ECA34E-3820-44F9-9906-9E13E03F080B}"
ProjectSection(SolutionItems) = preProject
CONTRIBUTING.md = CONTRIBUTING.md
LICENSE = LICENSE
README.md = README.md
ThirdPartyNotices.txt = ThirdPartyNotices.txt
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Screenshots", "Screenshots", "{61F10E94-77D5-4DDA-904A-6BFFB64A68D5}"
ProjectSection(SolutionItems) = preProject
screenshot1.png = screenshot1.png
screenshot2.png = screenshot2.png
screenshot3.png = screenshot3.png
screenshot4.png = screenshot4.png
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|Any CPU.ActiveCfg = Debug|x86
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|Any CPU.Build.0 = Debug|x86
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|Any CPU.Deploy.0 = Debug|x86
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|ARM.ActiveCfg = Debug|ARM
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|ARM.Build.0 = Debug|ARM
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|ARM.Deploy.0 = Debug|ARM
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|x64.ActiveCfg = Debug|x64
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|x64.Build.0 = Debug|x64
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|x64.Deploy.0 = Debug|x64
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|x86.ActiveCfg = Debug|x86
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|x86.Build.0 = Debug|x86
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Debug|x86.Deploy.0 = Debug|x86
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|Any CPU.ActiveCfg = Release|x86
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|ARM.ActiveCfg = Release|ARM
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|ARM.Build.0 = Release|ARM
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|ARM.Deploy.0 = Release|ARM
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|x64.ActiveCfg = Release|x64
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|x64.Build.0 = Release|x64
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|x64.Deploy.0 = Release|x64
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|x86.ActiveCfg = Release|x86
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|x86.Build.0 = Release|x86
{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}.Release|x86.Deploy.0 = Release|x86
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Debug|ARM.ActiveCfg = Debug|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Debug|ARM.Build.0 = Debug|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Debug|x64.ActiveCfg = Debug|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Debug|x64.Build.0 = Debug|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Debug|x86.ActiveCfg = Debug|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Debug|x86.Build.0 = Debug|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Release|Any CPU.Build.0 = Release|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Release|ARM.ActiveCfg = Release|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Release|ARM.Build.0 = Release|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Release|x64.ActiveCfg = Release|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Release|x64.Build.0 = Release|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Release|x86.ActiveCfg = Release|Any CPU
{633B2B38-C19D-4C17-BA1B-22D549625F0F}.Release|x86.Build.0 = Release|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Debug|ARM.ActiveCfg = Debug|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Debug|ARM.Build.0 = Debug|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Debug|x64.ActiveCfg = Debug|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Debug|x64.Build.0 = Debug|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Debug|x86.ActiveCfg = Debug|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Debug|x86.Build.0 = Debug|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Release|Any CPU.Build.0 = Release|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Release|ARM.ActiveCfg = Release|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Release|ARM.Build.0 = Release|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Release|x64.ActiveCfg = Release|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Release|x64.Build.0 = Release|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Release|x86.ActiveCfg = Release|Any CPU
{9994333A-C9EB-43A9-B3D1-7E40B75651C0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{61F10E94-77D5-4DDA-904A-6BFFB64A68D5} = {49ECA34E-3820-44F9-9906-9E13E03F080B}
EndGlobalSection
EndGlobal

163
README.md Normal file
Просмотреть файл

@ -0,0 +1,163 @@
# Lunch Scheduler app sample
Lunch Scheduler is a Universal Windows Platform (UWP) app sample that simplifies organizing lunch with friends or co-workers. You choose a time and location, invite friends, and the app takes care of sending out notifications and collecting responses.
The app demonstrates features you may find useful for your own projects, like push notifications, user authentication, UI controls, and Azure integration.
![Lunch Scheduler home menu](screenshot4.png)
## Features
Lunch Scheduler highlights features like:
- Out Of Box Experience (OOBE)
- Authentication using a Microsoft or Facebook account
- Basic app UI and navigation, including a navigation pane and settings page
- Integration with third party services such as [Yelp](https://www.yelp.com) and [Twilio](https://www.twilio.com)
Lunch Scheduler is designed to run well on both Windows PC and Windows Phone.
## Run the sample
Because Lunch Scheduler relies on some authentication APIs and third party services, it requires a bit of setup before you can run it.
### Prepare your environment
This sample requires Visual Studio 2015, the Windows 10 SDK (build 14393 or above), and the Azure SDK.
* [Get a free copy of Visual Studio 2015 Community Edition with support for building Universal Windows apps](http://go.microsoft.com/fwlink/?LinkID=280676)
* [Get the Azure SDK](https://azure.microsoft.com/downloads/)
Additionally, to receive the latest updates to Windows and the development tools, and to help shape their development, join the [Windows Insider Program](https://insider.windows.com/ "Become a Windows Insider").
### Associate Lunch Scheduler with the Store
Lunch Scheduler uses APIs that require store association. To associate the app with the Store, right click the project in Visual Studio and select **Store** -> **Associate App with the Store**. Then follow the instructions in the wizard.
Note that you don't actually need to submit the app to the Store, just associate it.
### Configure 3rd party services
Lunch Scheduler integrates a number of 3rd party services that require some additional setup to function correctly.
You don't necessarily need to configure these before running the app. However, if you try and use one and it's not configured, the app might throw an exception.
#### Yelp
Lunch Scheduler uses Yelp to suggest tasty lunch spots near your current location.
To use Yelp:
1. Create a free [Yelp](https://www.yelp.com) account and log in.
2. Obtain API keys from the [developer portal](https://www.yelp.com/developers/manage_api_keys).
3. Swap the Consumer Key, Consumer Secret, Token, and Token Secret into the constants in the *site/controllers/LocationsController.cs* file.
For more information on using the Yelp API, see the [Yelp documentation](https://www.yelp.com/developers/documentation/v2/overview).
#### Facebook
Using a Microsoft Account (MSA) is the easiest way users can authenticate with Lunch Scheduler. However, the app also offers the option to log in with Facebook.
To turn on Facebook authentication:
1. Navigate to the [Facebook developer panel](https://developers.facebook.com/apps/).
2. Select **Add a New App** and choose the **Website** template.
3. Complete the form. Specifically, make sure to fill in the Windows Store SID field. You can obtain your SID from the [Windows Developer Dashboard](https://developer.microsoft.com/dashboard/).
4. Insert your App ID and App Secret into the constants in the [AuthenticationHelper.cs](app/helpers/AuthenticationHelper.cs) and [IdpHelper.cs](site/helpers/IdpHelper.cs) files.
For more information, see the [Facebook documentation](https://developers.facebook.com/docs/facebook-login).
#### Twilio
Twilio is a service that enables sending and receiving text messages via an API. Lunch Scheduler can optionally use Twilio to send a text notification to users when they are invited to lunch.
To enable Twilio:
1. Create a free [Twilio](https://www.twilio.com/) account and log in.
2. Create a new trial phone number from the [Phone Numbers Dashboard](https://www.twilio.com/console/phone-numbers/dashboard).
3. Change the URL endpoint for the "message comes in" field on the numbers dashboard to the URL of your LunchScheduler service's device endpoint. For example: https://<your_url_here>.azurewebsites.net/api/device?ZUMO-API-VERSION=2.0.0.
4. Add your Twilio phone number, SID, and key to the constants in the *site/helpers/NotificationHelper.cs* file.
For a more complete tutorial on getting started with Twilio using C#/.NET, check out the [Twilio documentation](https://www.twilio.com/api).
### Set the API endpoint
In [ApiHelper.cs](app/Helpers/ApiHelper.cs), set the value of the BaseUrl constant to match the url the backing service is running on. See the next step for how to run the service.
### Start the service
Lunch Scheduler relies on a backing [Azure App Service](https://azure.microsoft.com/documentation/services/app-service) to handle authentication, send and receive data, communicate with REST services, and send push notifications. This sample includes code for the service written in C#/ASP.NET.
The service works best when it's running in the cloud. However, you can deploy a local copy using the Azure SDK and Debugger to try out Lunch Scheduler without deploying anything to Azure. You won't get the app's full functionality, but you can explore and check out some of the basic features.
#### Run locally
Here's how to run the service locally alongside the app:
1. Right click your solution and select **Properties**.
2. Select the **Startup Project** tab.
3. Check the **Multiple startup projects** radio button.
4. Enable both *LunchScheduler.App* and *LunchScheduler.Site* to start.
The next time you run the sample, both the Azure service and the app will launch (with debugging enabled) simultaneously.
#### Deploy to Azure
To fully explore Lunch Scheduler, you'll want to deploy the backing App Service to Azure.
If you don't already have an Azure account, you can get a [free one-month trial](https://azure.microsoft.com/pricing/free-trial). Or, if you have an MSDN subscription, an Azure subscription with [free monthly Azure credits](https://azure.microsoft.com/pricing/member-offers/msdn-benefits-details) is included.
> **WARNING** The following steps may incur a charge to your Azure subscription, depending on the deployment tiers you select. We reccomend choosing "Free" tier to minimize your development and testing costs. See the [Azure pricing](https://azure.microsoft.com/pricing/) documentation for more information.
To deploy the Lunch Scheduler service to Azure:
1. Right-click the *LunchScheduler.Site* project and select **Publish.**
2. Select **Microsoft Azure App Service** as a publish target.
3. In the top-right corner, either select or log in with your Azure account.
4. Click **New** on the right.
5. Fill out the **Hosting** form field. Don't click **Create** just yet.
6. Select the **Services** tab on the left.
7. Deploy the (suggested) SQL Database by selecting the green plus arrow.
8. Fill in the SQL Database form and click **OK.**
9. Click **Create**.
Azure will automatically create and configure your App Service and SQL Database, and deploy the service to it.
## Code at a glance
Lunch Scheduler was designed with modularity in mind. If there's a specific part of the app that's interesting, you can grab that component and drop it in to your own app.
### Controls
- [AddFriendDialog](app/Controls/AddFriendDialog.xaml#L21): Dialog that allows the user to enter the name of a person in their contacts to add as a friend.
- [PhoneSignUpDialog](app/Controls/PhoneSignUpDialog.xaml#L21): Dialog that allows the user to enter their phone number to receive text notifications.
- [UserIconControl](app/Controls/UserIconControl.xaml#L21): Circular icon for displaying a user name and photo.
- [WaitDialog](app/Controls/WaitDialog.xaml#L21): Dialog that blocks the UI with a "loading" control until a long-running operation finishes.
### Helpers and utilities
- [ApiHelper.cs](app/Helpers/ApiHelper.cs#L25): Simplifies strongly-typed HTTP calls to RESTful services.
- [AsyncErrorHandler.cs](app/Helpers/AsyncErrorHandler.cs#L25): Third-party NuGet package which automatically catches errors in async methods for easier debugging.
- [AuthenticationHelper.cs](app/Helpers/AuthenticationHelper.cs#L25): Handles authentication for different services, such as Facebook.
- [DispatchHelper.cs](app/Helpers/DispatchHelper.cs#L25): Runs a call on the UI thread with normal priority.
- [SynchronizationHelper.cs](app/Helpers/SynchronizationHelper.cs#L25): Handles sending updates to the database.
- [IdpHelper.cs](site/Helpers/IdpHelper.cs#L25): Wrapper for authentication calls to identity providers (IDPs).
- [NotificationHelper.cs](site/Helpers/NotificationHelper.cs#L25): Wrapper for sending and receiving notifications using Azure notification hubs and Twilio.
### Views and ViewModels
- [Friends.xaml](app/Views/Friends.xaml#L21) & [FriendsViewModel.cs](app/ViewModels/FriendsViewModel.cs#L25): Lets the user add and manage their friends.
- [Lunches.xaml](app/Views/Lunches.xaml#L21) & [LunchesViewModel.cs](app/ViewModels/LunchesViewModel.cs#L25): Lets the user view and respond to lunch invitations.
- [NewLunch.xaml](app/Views/NewLunch.xaml#L21) & [NewLunchViewModel.cs](app/ViewModels/NewLunchViewModel.cs#L25): Lets the user send a new lunch invitation to their friends for a certain time and place.
- [Settings.xaml](app/Views/Settings.xaml#L21) & [SettingsViewModel.cs](app/ViewModels/SettingsViewModel.cs#L25): Lets the user configure their settings, including registering their phone and turning push notifications on or off.
- [Shell.xaml](app/Views/Shell.xaml#L21) & [ShellViewModel.cs](app/ViewModels/ShellViewModel.cs#L25): [Navigation pane](https://msdn.microsoft.com/windows/uwp/controls-and-patterns/nav-pane) (also known as *hamburger menu*). Commonly used app building block that allows the user to quickly navigate to different pages by selecting the different buttons in the pane.
- [Welcome.xaml](app/Views/Welcome.xaml#L21) & [WelcomeViewModel.cs](app/ViewModels/WelcomeViewModel.cs#L25): OOBE (Out Of Box Experience). Displays when the user first runs the app.
## See also
* [Web account manager](https://msdn.microsoft.com/windows/uwp/security/web-account-manager)
* [Web authentication broker](https://msdn.microsoft.com/windows/uwp/security/web-authentication-broker)
* [Http client](https://msdn.microsoft.com/windows/uwp/networking/httpclient)
* [Facebook login](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/)
* [Yelp API 2.0](https://www.yelp.com/developers/documentation/v2/overview)
* [Twilio C# Helper Library](https://www.twilio.com/docs/libraries/csharp)

97
ThirdPartyNotices.txt Normal file
Просмотреть файл

@ -0,0 +1,97 @@
This file is based on or incorporates material from the projects listed below (Third Party IP).
The original copyright notice and the license under which Microsoft received such Third Party IP,
are set forth below. Such licenses and notices are provided for informational purposes only.
Microsoft licenses the Third Party IP to you under the licensing terms for the Microsoft product.
Microsoft reserves all other rights not expressly granted under this agreement, whether by implication,
estoppel or otherwise.
---
Newtonsoft.Json
The MIT License (MIT)
Copyright (c) 2007 James Newton-King
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
PropertyChanged.Fody
The MIT License
Copyright (c) 2012 Simon Cropp and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
AsyncErrorHandler.Fody
The MIT License
Copyright (c) Simon Cropp and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
Pixaybay
Many assets in this sample are courtesy https://pixabay.com/. These assets were obtained
and are distributed under the CC0 license.
---
Facebook
The Facebook icon is displayed on the login screen is courtesy Facebook, Inc.
See https://facebookbrand.com/guidelines/brand for more info.

26
app/App.xaml Normal file
Просмотреть файл

@ -0,0 +1,26 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<Application x:Class="LunchScheduler.App.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:LunchScheduler.App">
<Application.Resources />
</Application>

122
app/App.xaml.cs Normal file
Просмотреть файл

@ -0,0 +1,122 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.Helpers;
using LunchScheduler.Models;
using Microsoft.WindowsAzure.Messaging;
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Networking.PushNotifications;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
namespace LunchScheduler.App
{
/// <summary>
/// Main application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// Name of the Azure notification hub.
/// </summary>
private const string HubName = "<TODO: Your hub name here>";
/// <summary>
/// Endpoint for the Azure notification hub. Configure at https://www.portal.azure.com
/// </summary>
private const string HubEndpoint = "<TODO: Your endpoint here>";
/// <summary>
/// The current user of the app. Null if no user is not yet logged in.
/// </summary>
public static User User { get; set; }
/// <summary>
/// Global helper for making calls to the REST API service.
/// </summary>
public static ApiHelper Api { get; set; } = new ApiHelper();
/// <summary>
/// Width of window for narrow layout (phone, etc.).
/// </summary>
public const int NarrowWidth = 600;
public const double FontSizeSmall = 15;
public const double FontSizeMedium = 20;
/// <summary>
/// Creates a new App.
/// </summary>
public App()
{
InitializeComponent();
}
/// <summary>
/// Fires when the app is activated.
/// </summary>
protected override async void OnActivated(IActivatedEventArgs args) => await InitalizeAsync();
/// <summary>
/// Fires when the app is launched.
/// </summary>
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
await InitalizeAsync();
ApplicationView.GetForCurrentView().SetPreferredMinSize(new Size(500, 800));
ApplicationView.PreferredLaunchViewSize = new Size(3000, 3000);
}
/// <summary>
/// Prepares the app for use.
/// </summary>
private async Task InitalizeAsync()
{
Application.Current.UnhandledException += (s, e) => AsyncErrorHandler.HandleException(e.Exception);
AuthenticationHelper.UserLoggedIn += async (s, e) =>
{
User = e;
SynchronizationHelper.RecursiveRegisterPropertyChanged(e);
var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
var hub = new NotificationHub(HubName, HubEndpoint);
await hub.RegisterNativeAsync(channel.Uri, new[] { User.Email });
};
await AuthenticationHelper.TryLogInSilently();
if (App.User != null)
{
Window.Current.Content = new Views.Shell();
}
else
{
Window.Current.Content = new Views.Welcome();
}
Window.Current.Activate();
}
}
}

Двоичные данные
app/Assets/ApplicationLogo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.7 KiB

Двоичные данные
app/Assets/FbIcon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.1 KiB

Двоичные данные
app/Assets/Plus.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.6 KiB

9
app/Assets/Privacy.txt Normal file
Просмотреть файл

@ -0,0 +1,9 @@
Lorem ipsum dolor sit amet, novum ludus est no, eu esse deseruisse sea, pri sale delicata ad. Ex assum atomorum elaboraret mei. Pro at melius sententiae, in postulant facilisis pri. Commodo impetus quaeque sed at.
Quo at iriure constituto, alia persecuti interesset ne nam, mei integre reprehendunt an. Ne est magna simul posidonium. Possit nostrum invidunt et vix. At quo percipit maluisset assentior, nec sumo esse vivendum ne, cum ei molestie oportere.
In est splendide efficiendi. Ullum intellegat constituam mei id, vel ex natum vocent. Qui facer constituto vituperatoribus eu, qui eu meis mundi aeterno. Vix ullum mollis explicari ea, ad porro concludaturque sed. Sea ex scripserit dissentiet, est id prima detracto, minim ludus an quo. At sea malis facete dissentiunt. Eleifend euripidis scribentur eam cu, postea iriure appellantur at has, ex illum aliquip contentiones eam.
His porro conceptam appellantur ne, has id possit debitis, ornatus principes sit cu. Quas iracundia eos ex, eos ad iuvaret postulant definiebas. Oratio molestie ocurreret vix in, id mel vero summo reformidans. No eum cetero intellegam, ne dolorem epicurei eam.
Pro no primis splendide hendrerit, te pro saperet aliquando mnesarchum. Sit ad unum soluta mnesarchum, mel consetetur adipiscing ad. Simul offendit intellegam cu vis, ius solet legendos id. No putant dicunt diceret mei, te unum eirmod vix. An oblique accusata partiendo eam, est gloriatur voluptaria ut, ea melius iuvaret quo. Per ex euismod tibique consequuntur, in fierent aliquando cum.

Двоичные данные
app/Assets/SplashScreen.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.3 KiB

Двоичные данные
app/Assets/Square150x150Logo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.7 KiB

Двоичные данные
app/Assets/Square310x310Logo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 11 KiB

Двоичные данные
app/Assets/Square44x44Logo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.5 KiB

Двоичные данные
app/Assets/Square71x71Logo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.6 KiB

Двоичные данные
app/Assets/StoreLogo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Двоичные данные
app/Assets/StoreLogo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Двоичные данные
app/Assets/Welcome/Check.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.7 KiB

Двоичные данные
app/Assets/Welcome/Keys.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 29 KiB

Двоичные данные
app/Assets/Welcome/Location.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 112 KiB

Двоичные данные
app/Assets/Welcome/Lunchbox.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 14 KiB

Двоичные данные
app/Assets/Welcome/Notification.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 52 KiB

Двоичные данные
app/Assets/Welcome/Plate.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 51 KiB

Двоичные данные
app/Assets/Wide310x150Logo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.3 KiB

Просмотреть файл

@ -0,0 +1,41 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<ContentDialog x:Class="LunchScheduler.App.Controls.AddFriendDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Add friend"
PrimaryButtonText="Add"
SecondaryButtonText="Cancel"
mc:Ignorable="d">
<Grid>
<StackPanel>
<AutoSuggestBox x:Name="SearchFriendTextBox"
ItemsSource="{Binding Names,
Mode=OneWay}"
QueryIcon="Find"
SuggestionChosen="SuggestionChosen"
Text="{x:Bind Text, Mode=TwoWay}"
TextChanged="TextChanged" />
</StackPanel>
</Grid>
</ContentDialog>

Просмотреть файл

@ -0,0 +1,96 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using Newtonsoft.Json.Linq;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace LunchScheduler.App.Controls
{
/// <summary>
/// DialogBox for adding a friend.
/// </summary>
[ImplementPropertyChanged]
public sealed partial class AddFriendDialog : ContentDialog
{
/// <summary>
/// Creates a new AddFriendDialog.
/// </summary>
public AddFriendDialog()
{
this.InitializeComponent();
((FrameworkElement)this.Content).DataContext = this;
PrimaryButtonClick += (s, e) =>
{
string email = SearchFriendTextBox.Text.Split('(')[1].Replace(")", "");
FriendAdded?.Invoke(this, email);
};
}
/// <summary>
/// The text in the entry box.
/// </summary>
public string Text { get; set; }
/// <summary>
/// The names of users matching the user's partial entry.
/// </summary>
public ObservableCollection<string> Names { get; set; } = new ObservableCollection<string>();
/// <summary>
/// Gets users matching the currently entered text. Fires whenever the text changes.
/// </summary>
private async void TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
var names = await App.Api.GetAsync<IEnumerable<JToken>>("Usernames",
new { Name = SearchFriendTextBox.Text });
if (null != names)
{
Names = new ObservableCollection<string>(names
.Select(x => new { Name = x["Name"].ToString(), Email = x["Email"].ToString() })
.Where(x => x.Email != App.User.Email)
.Select(x => $"{x.Name} ({x.Email})"));
}
}
}
/// <summary>
/// Fills the AutoSuggestionBox when a specific user is chosen.
/// </summary>
private void SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) =>
SearchFriendTextBox.Text = args.SelectedItem.ToString();
/// <summary>
/// Fires when a friend is added.
/// </summary>
public event EventHandler<string> FriendAdded;
}
}

Просмотреть файл

@ -0,0 +1,63 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<ContentDialog x:Class="LunchScheduler.App.Controls.PhoneSignUpDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Text notifications"
VerticalAlignment="Stretch"
PrimaryButtonClick="RegisterClick"
PrimaryButtonText="Register"
SecondaryButtonClick="CancelClick"
SecondaryButtonText="Cancel"
mc:Ignorable="d">
<Grid x:Name="RootGrid">
<StackPanel x:Name="EntryStackPanel">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="5" />
</Style>
</StackPanel.Resources>
<TextBlock x:Name="Directions"
Text="Want to receive a text when someone invites you to lunch? Enter your phone number below."
TextWrapping="Wrap" />
<TextBox x:Name="PhoneNumberTextBox" />
<TextBlock Margin="5,5,5,5"
Text="Data charges may apply. We'll never share your number with third parties, and you can opt out at any time."
TextWrapping="WrapWholeWords" />
</StackPanel>
<StackPanel x:Name="ProgressStackPanel" Visibility="Collapsed">
<StackPanel.Resources>
<Style TargetType="ProgressBar">
<Setter Property="Margin" Value="5" />
</Style>
</StackPanel.Resources>
<TextBlock x:Name="PressButtonText"
Text="We sent a text message to your phone. Please respond to continue."
TextWrapping="Wrap" />
<ProgressBar Margin="0, 10, 0, 0" IsIndeterminate="True" />
</StackPanel>
<StackPanel x:Name="ResultStackPanel" Visibility="Collapsed">
<TextBlock x:Name="ResultsTextBlock" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</ContentDialog>

Просмотреть файл

@ -0,0 +1,113 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using PropertyChanged;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace LunchScheduler.App.Controls
{
/// <summary>
/// Dialog box for signing up a phone for text notifications.
/// </summary>
[ImplementPropertyChanged]
public sealed partial class PhoneSignUpDialog : ContentDialog
{
/// <summary>
/// Indicates whether the phone sign up process was cancelled by the user.
/// </summary>
private bool _cancelled = false;
/// <summary>
/// Creates a new PhoneSignUpDialog.
/// </summary>
public PhoneSignUpDialog()
{
this.InitializeComponent();
}
/// <summary>
/// Registers the user's phone number, sends a verification text, and waits for the user to respond.
/// </summary>
private async void RegisterClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
App.User.Devices.Clear();
if (EntryStackPanel.Visibility == Visibility.Visible)
{
// Progress
args.Cancel = true;
EntryStackPanel.Visibility = Visibility.Collapsed;
ProgressStackPanel.Visibility = Visibility.Visible;
PrimaryButtonText = "";
PrimaryButtonClick -= RegisterClick;
await VerifyPhoneNumberAsync();
// Results
ProgressStackPanel.Visibility = Visibility.Collapsed;
ResultStackPanel.Visibility = Visibility.Visible;
}
}
/// <summary>
/// Waits up to 3 minutes for the user to verify their number.
/// </summary>
private async Task VerifyPhoneNumberAsync()
{
var device = new Device
{
NotificationsEnabled = true,
PhoneNumber = PhoneNumberTextBox.Text,
NotificationType = NotificationType.Text
};
App.User.Devices.Add(device);
// Check verification every 3 seconds for up to 3 minutes.
// If they don't respond within the time frame, verify fails.
bool verified = false;
int attempts = 0;
while (!verified && !_cancelled && attempts < 30)
{
await Task.Delay(3000);
verified = await App.Api.GetAsync<bool>("Device", new { Number = device.PhoneNumber });
attempts++;
}
ResultsTextBlock.Text = verified ? "Success! We've verified your number." :
"We couldn't verify your number. If you want to try again, you can do so from the settings page.";
PrimaryButtonText = "Continue";
SecondaryButtonText = "";
}
/// <summary>
/// Processes user cancel and tells the dialog to stop waiting.
/// </summary>
private void CancelClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
_cancelled = true;
}
}
}

Просмотреть файл

@ -0,0 +1,51 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<UserControl x:Class="LunchScheduler.App.Controls.UserIconControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:LunchScheduler.App.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<StackPanel Orientation="Vertical">
<Grid Width="140" Height="140">
<Ellipse x:Name="Ellipse"
Width="130"
Height="130"
Margin="4,4,4,4"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Ellipse.Fill>
<ImageBrush ImageFailed="ImageFailed" ImageSource="{x:Bind PhotoUrl, Mode=OneWay}" />
</Ellipse.Fill>
</Ellipse>
</Grid>
<TextBlock x:Name="UserNameTextBlock"
Margin="4,4,0,4"
HorizontalAlignment="Center"
Style="{ThemeResource BodyTextBlockStyle}"
Text="{x:Bind UserName, Mode=OneWay}" />
</StackPanel>
</Grid>
</UserControl>

Просмотреть файл

@ -0,0 +1,127 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace LunchScheduler.App.Controls
{
/// <summary>
/// Circular icon containing a user's name and photo.
/// </summary>
public sealed partial class UserIconControl : UserControl
{
/// <summary>
/// Creates a new UserIconControl.
/// </summary>
public UserIconControl()
{
this.InitializeComponent();
((FrameworkElement)Content).DataContext = this;
}
/// <summary>
/// Gets or sets the user's display name.
/// </summary>
public string UserName
{
get { return (string)GetValue(UserNameProperty); }
set { SetValueDp(UserNameProperty, value); }
}
public static readonly DependencyProperty UserNameProperty = DependencyProperty.Register(
nameof(UserName), typeof(string), typeof(UserIconControl), null);
/// <summary>
/// Gets or sets the url to the user's photo.
/// </summary>
public string PhotoUrl
{
get { return (string)GetValue(PhotoUrlProperty); }
set { SetValueDp(PhotoUrlProperty, value); }
}
/// <summary>
/// Dependency property for the user's photo url.
/// </summary>
public static readonly DependencyProperty PhotoUrlProperty = DependencyProperty.Register(
nameof(PhotoUrl), typeof(string), typeof(UserIconControl), null);
/// <summary>
/// Sets a green or red circle around the user's icon to indicate whether they have
/// accepted or rejected a lunch invite.
/// </summary>
public bool? ResponseMark
{
get { return (bool?)GetValue(ResponseMarkProperty); }
set
{
SetValueDp(ResponseMarkProperty, value);
if (null != value)
{
Ellipse.Stroke = (bool)value ?
new SolidColorBrush(Colors.Green) :
new SolidColorBrush(Colors.Red);
Ellipse.StrokeThickness = 4;
}
else
{
Ellipse.StrokeThickness = 0;
}
}
}
/// <summary>
/// Dependency property for the user's response mark.
/// </summary>
public static readonly DependencyProperty ResponseMarkProperty = DependencyProperty.Register(
nameof(ResponseMark), typeof(bool?), typeof(UserIconControl), null);
/// <summary>
/// Sets the value of a dependency property and raises change notification.
/// </summary>
private void SetValueDp(DependencyProperty property, object value, [CallerMemberName]string p = null)
{
SetValue(property, value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
}
/// <summary>
/// Handles failiure to load a user's image.
/// </summary>
private void ImageFailed(object sender, ExceptionRoutedEventArgs e)
{
// Do nothing, leave the ellipse empty.
}
/// <summary>
/// Provides change notification.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
}

Просмотреть файл

@ -0,0 +1,31 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<ContentDialog x:Class="LunchScheduler.App.Controls.WaitDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:LunchScheduler.App.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ProgressBar Margin="0, 30, 0, 0"
VerticalAlignment="Center"
IsIndeterminate="True" />
</ContentDialog>

Просмотреть файл

@ -0,0 +1,51 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using Windows.UI.Xaml.Controls;
namespace LunchScheduler.App.Controls
{
/// <summary>
/// Dialog for showing a progress bar while the user is logged in.
/// </summary>
public sealed partial class WaitDialog : ContentDialog
{
/// <summary>
/// Creates a new WaitDialog.
/// </summary>
public WaitDialog()
{
this.InitializeComponent();
}
/// <summary>
/// Creates a new WaitDialog with the given title.
/// </summary>
public WaitDialog(string title)
{
this.InitializeComponent();
Title = title;
}
}
}

5
app/FodyWeavers.xml Normal file
Просмотреть файл

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<PropertyChanged />
<AsyncErrorHandler />
</Weavers>

225
app/Helpers/ApiHelper.cs Normal file
Просмотреть файл

@ -0,0 +1,225 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace LunchScheduler.App.Helpers
{
/// <summary>
/// Wrapper for making strongly-typed REST calls to the Lunch Scheduler web service.
/// </summary>
public class ApiHelper
{
/// <summary>
/// Gets or sets whether to use an Azure URL or locahost for API calls.
/// </summary>
public bool IsDebug { get; set; } = true;
/// <summary>
/// The Base URL for the API. Switch to localhost for debugging.
/// </summary>
private string BaseUrl => IsDebug ? @"http://localhost:16274/api/" :
@"https://<your_azure_url_here>.azurewebsites.net/api/";
/// <summary>
/// Makes an HTTP GET request to the given controller and returns the HttpResponseMessage.
/// </summary>
public async Task<HttpResponseMessage> GetAsync(string controller)
{
try
{
using (var client = BaseClient())
{
return await client.GetAsync(controller);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Exception in GetAsync({controller}):\r\n{ex.Message})");
return null;
}
}
/// <summary>
/// Makes an HTTP GET request to the given controller and returns the deserialized response content.
/// </summary>
public async Task<T> GetAsync<T>(string controller)
{
try
{
using (var client = BaseClient())
{
var response = await client.GetAsync(controller);
string json = await response.Content.ReadAsStringAsync();
T obj = JsonConvert.DeserializeObject<T>(json);
return obj;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Exception in GetAsync<{(typeof(T)).Name}>({controller}):\r\n{ex.Message})");
return default(T);
}
}
/// <summary>
/// Makes an HTTP GET request to the given controller and includes all the given
/// object's properties as URL parameters. Returns the deserialized response content.
/// </summary>
public async Task<T> GetAsync<T>(string controller, object request)
{
try
{
using (var client = BaseClient())
{
var parameters = String.Join("&", request.GetType().GetRuntimeProperties().Select(x =>
$"{x.Name}={x.GetValue(request)}"));
var response = await client.GetAsync($"{controller}?{parameters}");
string json = await response.Content.ReadAsStringAsync();
T obj = JsonConvert.DeserializeObject<T>(json);
return obj;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Exception in GetAsync<{(typeof(T)).Name}>({controller}):\r\n{ex.Message})");
return default(T);
}
}
/// <summary>
/// Makes an HTTP GET request to the given controller and includes all the selected properties
/// of the given object's as URL parameters. Returns the deserialized response content.
/// </summary>
public async Task<TU> GetAsync<T, TU>(string controller, T request, params Func<T, object>[] selectors)
{
try
{
using (var client = BaseClient())
{
var parameters = new StringBuilder();
parameters.Append("?");
foreach (var item in selectors)
{
parameters.Append($"{item.GetMethodInfo().Name}&{Uri.EscapeDataString(item(request).ToString())}");
}
var response = await client.GetAsync($"{controller}?{parameters}");
string json = await response.Content.ReadAsStringAsync();
TU obj = JsonConvert.DeserializeObject<TU>(json);
return obj;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Exception in GetAsync<{(typeof(T)).Name}>({controller}):\r\n{ex.Message})");
return default(TU);
}
}
/// <summary>
/// Makes an HTTP POST request to the given controller with the given object as the body.
/// Returns HttpResponseMessage.
/// </summary>
public async Task<HttpResponseMessage> PostAsync<T>(string controller, T body)
{
try
{
using (var client = BaseClient())
{
return await client.PostAsync(controller, new JsonStringContent(body));
}
}
catch (Exception ex)
{
Debug.WriteLine($"Exception in GetAsync<{(typeof(T)).Name}>({controller}):\r\n{ex.Message})");
return null;
}
}
/// <summary>
/// Makes an HTTP POST request to the given controller with the given object as the body.
/// Returns the deserialized response content.
/// </summary>
public async Task<TU> PostAsync<T, TU>(string controller, T body)
{
try
{
using (var client = BaseClient())
{
string str = JsonConvert.SerializeObject(body);
var response = await client.PostAsync(controller, new JsonStringContent(body));
string json = await response.Content.ReadAsStringAsync();
TU obj = JsonConvert.DeserializeObject<TU>(json);
return obj;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Exception in GetAsync<{(typeof(T)).Name}, {typeof(TU).Name}>({controller}):\r\n{ex.Message})");
return default(TU);
}
}
/// <summary>
/// Constructs the base HTTP client, including correct authorization and API version headers.
/// </summary>
private HttpClient BaseClient()
{
var client = new HttpClient
{
BaseAddress = new Uri(BaseUrl)
};
client.DefaultRequestHeaders.Add("ZUMO-API-VERSION", "2.0.0");
if (null != App.User)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
App.User.Auth.Provider.ToString(), App.User.Auth.Token);
client.DefaultRequestHeaders.From = Uri.EscapeUriString(App.User.Email);
}
return client;
}
/// <summary>
/// Helper class for formatting StringContent as Json.
/// </summary>
private class JsonStringContent : StringContent
{
/// <summary>
/// Creates new JsonStringContent, which is just StringContent formatted as application/json.
/// </summary>
public JsonStringContent(object obj)
: base(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json")
{
}
}
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System;
using System.Diagnostics;
namespace LunchScheduler.App.Helpers
{
/// <summary>
/// Enables automatic catching of unahandled exceptions using AsyncErrorHandler.Fody.
/// </summary>
public static class AsyncErrorHandler
{
/// <summary>
/// Logs unhandled exceptions and breaks the debugger (if attached),
/// even if the exception occurs within an async method where
/// exceptions would normally be swallowed.
/// </summary>
public static void HandleException(Exception ex)
{
Debug.WriteLine("Unhandled exception: " + ex.Message);
if (Debugger.IsAttached)
{
Debugger.Break();
}
}
}
}

Просмотреть файл

@ -0,0 +1,298 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.Controls;
using LunchScheduler.Models;
using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Security.Authentication.Web;
using Windows.Security.Authentication.Web.Core;
using Windows.Security.Credentials;
using Windows.Storage;
using Windows.System;
using Windows.UI.ApplicationSettings;
using User = LunchScheduler.Models.User;
namespace LunchScheduler.App.Helpers
{
/// <summary>
/// Handles authentication.
/// </summary>
public static class AuthenticationHelper
{
/// <summary>
/// The dictonary key for the current username in roaming storage.
/// </summary>
private static readonly string RoamingUserKey = "current_user";
/// <summary>
/// The dictonary key for the current authentication provider in roaming storage.
/// </summary>
private static readonly string RoamingProviderKey = "current_provider";
/// <summary>
/// The dictonary key for the app in roaming storage.
/// </summary>
private static readonly string VaultKey = "lunch_scheduler_app";
/// <summary>
/// The app Id for Facebook authentication.
/// </summary>
private static readonly string FacebookClientId = "<TODO: Your Facebook client Id here>";
/// <summary>
/// Additional text to display on the AccountsSettingsPane. Used in event of error or other unexpected behavior.
/// </summary>
private static string _paneMessage = "";
/// <summary>
/// Initializes authentication by instructing the AccountsSettingsPane to show the custom
/// panel when requested.
/// </summary>
static AuthenticationHelper()
{
AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested += BuildSettingsPaneAsync;
}
/// <summary>
/// Logs out the current user and removes their credentials from storage.
/// </summary>
public static void Logout()
{
var vault = new PasswordVault();
ApplicationData.Current.RoamingSettings.Values.Remove(RoamingUserKey);
ApplicationData.Current.RoamingSettings.Values.Remove(RoamingProviderKey);
foreach (var credential in vault.FindAllByResource(VaultKey))
{
vault.Remove(credential);
}
App.User = null;
}
/// <summary>
/// Creates an AccountsSettingsPane with options for MSA or Facebook login.
/// </summary>
private static async void BuildSettingsPaneAsync(AccountsSettingsPane s,
AccountsSettingsPaneCommandsRequestedEventArgs e)
{
var deferral = e.GetDeferral();
e.HeaderText = _paneMessage + "Get signed in to Lunch Scheduler. " +
"You can use an existing Microsoft or Facebook account.";
// Microsoft account
var msa = new WebAccountProviderCommand(
await WebAuthenticationCoreManager.FindAccountProviderAsync("https://login.microsoft.com", "consumers"),
MicrosoftAccountAuth);
e.WebAccountProviderCommands.Add(msa);
// Facebook
// Built-in provider exists only on phone; if not availble, use WAB as a fallback
var fbProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync("https://facebook.com");
var fb = new WebAccountProviderCommand(
fbProvider ??
new WebAccountProvider("LunchScheduler", "Facebook", new Uri(@"ms-appx:///Assets/FbIcon.png")),
FacebookAuth);
e.WebAccountProviderCommands.Add(fb);
// Help commands
e.Commands.Add(new SettingsCommand("help", "Get help signing in",
async x => await Launcher.LaunchUriAsync(new Uri("http://bing.com"))));
e.Commands.Add(new SettingsCommand("privacy", "Privacy policy",
async x => await Launcher.LaunchUriAsync(new Uri("http://bing.com"))));
deferral.Complete();
}
/// <summary>
/// Obtains a MSA OAuth token using WebAccountManager and tries to log the user in with it.
/// </summary>
private static async void MicrosoftAccountAuth(WebAccountProviderCommand command)
{
await ShowWait("Microsoft");
WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, "wl.basic,wl.emails");
WebTokenRequestResult result = await WebAuthenticationCoreManager.GetTokenSilentlyAsync(request);
if (result.ResponseStatus == WebTokenRequestStatus.UserInteractionRequired)
{
result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
}
if (result.ResponseStatus == WebTokenRequestStatus.ProviderError)
{
ReportFail();
return;
}
await LoginOrRegisterAsync(new ProviderInfo
{
Provider = ProviderType.Msa,
Token = result.ResponseData.FirstOrDefault()?.Token
});
}
/// <summary>
/// Obtains a Facebook OAuth token using WebAuthenticationBroker and tries to log the user in with it.
/// </summary>
private static async void FacebookAuth(WebAccountProviderCommand command)
{
await ShowWait("Facebook");
Uri callback = new Uri("https://www.facebook.com/connect/login_success.html");
Uri request = new Uri(String.Format("https://www.facebook.com/dialog/oauth?client_id={0}&redirect_uri={1}" +
"&display=popup&response_type=token", FacebookClientId, callback.ToString()));
WebAuthenticationResult webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None, request, callback);
if (webAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
{
await LoginOrRegisterAsync(new ProviderInfo
{
Provider = ProviderType.Facebook,
Token = webAuthenticationResult.ResponseData.Split('=', '&')[1]
});
}
else
{
ReportFail();
}
}
/// <summary>
/// Shows a progress dialog while the login operation completes, then closes it when login is finished.
/// </summary>
private static async Task ShowWait(string provider)
{
var wait = new WaitDialog("Logging in with " + provider);
UserLoggedIn += (s, e) => wait.Hide();
AuthError += (s, e) => wait.Hide();
await DispatchHelper.RunAsync(async () => await wait.ShowAsync());
}
/// <summary>
/// Attempts to log in or register the user using the given provider info.
/// If successful, saves their credentials for future use.
/// </summary>
private static async Task LoginOrRegisterAsync(ProviderInfo info)
{
var exists = await App.Api.GetAsync<User>("Users", info);
string action = null != exists ? "Login" : "Register";
var user = await App.Api.PostAsync<ProviderInfo, User>(action, info);
if (null != user && !String.IsNullOrWhiteSpace(user.Email))
{
UserLoggedIn?.Invoke(DateTime.Now, user);
StoreUserInfo(user.Auth);
}
}
/// <summary>
/// Relaunches the AccountsSettingsPane with an error message that the last login attempt failed.
/// </summary>
private static void ReportFail()
{
AuthError?.Invoke(null, null);
if (String.IsNullOrEmpty(_paneMessage))
{
_paneMessage = "Whoops, something went wrong. Let's try again.\n\n";
}
AccountsSettingsPane.Show();
}
/// <summary>
/// Stores the user's login info in roamingsettings and the password vault for auto-login on
/// future app launches.
/// </summary>
private static void StoreUserInfo(ProviderInfo request)
{
ApplicationData.Current.RoamingSettings.Values[RoamingUserKey] = request.Email;
ApplicationData.Current.RoamingSettings.Values[RoamingProviderKey] = request.Provider.ToString();
var cred = new PasswordCredential
{
UserName = request.Email,
Password = request.Token,
Resource = VaultKey
};
var vault = new PasswordVault();
vault.Add(cred);
}
/// <summary>
/// Checks if the user has credentials stored and tries to automatically log in with them.
/// </summary>
public static async Task TryLogInSilently()
{
if (ApplicationData.Current.RoamingSettings.Values.ContainsKey(RoamingUserKey) &&
ApplicationData.Current.RoamingSettings.Values.ContainsKey(RoamingProviderKey))
{
string username = ApplicationData.Current.RoamingSettings.Values[RoamingUserKey].ToString();
var vault = new PasswordVault();
var credential = vault.RetrieveAll().FirstOrDefault(x =>
x.Resource == VaultKey && x.UserName == username);
if (null == credential)
{
return;
}
// MSA tokens are short-lived, so the existing cached token is likely invalid
// Silently request a new token
if (ApplicationData.Current.RoamingSettings.Values[RoamingProviderKey].ToString() == "msa")
{
var msaProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync("https://login.microsoft.com", "consumers");
WebTokenRequest request = new WebTokenRequest(msaProvider, "wl.basic, wl.emails");
WebTokenRequestResult result = await WebAuthenticationCoreManager.GetTokenSilentlyAsync(request);
if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
await LoginOrRegisterAsync(new ProviderInfo
{
Provider = ProviderType.Msa,
Token = result.ResponseData[0].Token
});
}
}
// Facebook and google tokens last 60 days, so we can probably re-log in with one
// If not, it will fail and show the OOBE expereince
else
{
credential.RetrievePassword();
await LoginOrRegisterAsync(new ProviderInfo
{
Email = username,
Provider = (ProviderType)Enum.Parse(typeof(ProviderType),
ApplicationData.Current.RoamingSettings.Values[RoamingProviderKey].ToString(),
true),
Token = credential.Password
});
}
}
}
/// <summary>
/// Raised when the user successfully logs in.
/// </summary>
public static event EventHandler<User> UserLoggedIn;
/// <summary>
/// Raised when an authentication error occurs.
/// </summary>
public static event EventHandler AuthError;
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
namespace LunchScheduler.App.Helpers
{
/// <summary>
/// Helper class for sending calls to the UI thread.
/// </summary>
public static class DispatchHelper
{
/// <summary>
/// Runs a call on the UI thread with normal priority.
/// </summary>
public static async Task RunAsync(DispatchedHandler func) =>
await CoreApplication.MainView.Dispatcher.RunAsync(
CoreDispatcherPriority.Normal, func);
}
}

Просмотреть файл

@ -0,0 +1,110 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace LunchScheduler.App.Helpers
{
/// <summary>
/// Handles sending updates to the database.
/// </summary>
public static class SynchronizationHelper
{
/// <summary>
/// Semaphore for ensuring only a single user update is sent at a time.
/// </summary>
private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
/// <summary>
/// Recursively registers all properties and properties in collections in the object
/// for change notification.
/// </summary>
public static void RecursiveRegisterPropertyChanged(object item)
{
foreach (object property in item.GetType().GetProperties().Select(x => x.GetValue(item))
.Where(x => null != x && x.GetType().GetInterfaces().Contains(typeof(INotifyPropertyChanged))))
{
// For collections, only raise an event when items are added or removed
// Otherwise, duplicate events will fire when collection properties like Count, Items[], etc change
if (property is INotifyCollectionChanged)
{
((INotifyCollectionChanged)property).CollectionChanged += CollectionChanged;
foreach (object child in (IEnumerable)property)
{
RecursiveRegisterPropertyChanged(child);
}
}
else
{
((INotifyPropertyChanged)property).PropertyChanged += async (s, e) => await SyncAsync();
}
}
}
/// <summary>
/// Sends the updated user object to the database using a semaphore to prevent desynchronization.
/// </summary>
private static async Task SyncAsync()
{
await Semaphore.WaitAsync();
await App.Api.PostAsync<User, HttpResponseMessage>("Users", App.User);
Semaphore.Release();
}
/// <summary>
/// Fires whenever a collection changes.
/// </summary>
private static async void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in args.NewItems)
{
RecursiveRegisterPropertyChanged(item);
}
await SyncAsync();
}
else if (args.Action == NotifyCollectionChangedAction.Remove)
{
await SyncAsync();
// TODO: Consider possible memory issues.
// RecursiveRegisterPropertyChanged creates a strong reference to each
// object, which prevents them from ever being garbage collected.
// Because Lunch Scheduler does not create a lot of objects, the amount of
// memory leaked is neglible. However, if you are reusing this component
// in a scenario with a lot of objects, you should use weak references instead.
// For more info, see https://msdn.microsoft.com/library/aa970850(v=vs.100).aspx.
}
}
}
}

Просмотреть файл

@ -0,0 +1,242 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{B64B4A95-A3D5-499D-81C1-B3D024AC21BC}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>LunchScheduler.App</RootNamespace>
<AssemblyName>LunchScheduler.App</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.14393.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.14393.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<EnableDotNetNativeCompatibleProfile>true</EnableDotNetNativeCompatibleProfile>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<PackageCertificateKeyFile>app3_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;;CODE_ANALYSIS;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .Net Framework and Windows SDK are automatically included -->
<None Include="project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\AddFriendDialog.xaml.cs">
<DependentUpon>AddFriendDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\UserIconControl.xaml.cs">
<DependentUpon>UserIconControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\WaitDialog.xaml.cs">
<DependentUpon>WaitDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Helpers\ApiHelper.cs" />
<Compile Include="Helpers\AsyncErrorHandler.cs" />
<Compile Include="Helpers\DispatchHelper.cs" />
<Compile Include="Helpers\SynchronizationHelper.cs" />
<Compile Include="Helpers\AuthenticationHelper.cs" />
<Compile Include="Controls\PhoneSignUpDialog.xaml.cs">
<DependentUpon>PhoneSignUpDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModels\FriendsViewModel.cs" />
<Compile Include="ViewModels\LunchesViewModel.cs" />
<Compile Include="ViewModels\NewLunchViewModel.cs" />
<Compile Include="ViewModels\SettingsViewModel.cs" />
<Compile Include="ViewModels\ShellViewModel.cs" />
<Compile Include="ViewModels\WelcomeViewModel.cs" />
<Compile Include="Views\Friends.xaml.cs">
<DependentUpon>Friends.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Lunches.xaml.cs">
<DependentUpon>Lunches.xaml</DependentUpon>
</Compile>
<Compile Include="Views\NewLunch.xaml.cs">
<DependentUpon>NewLunch.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Settings.xaml.cs">
<DependentUpon>Settings.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Shell.xaml.cs">
<DependentUpon>Shell.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Welcome.xaml.cs">
<DependentUpon>Welcome.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<Content Include="Assets\ApplicationLogo.png" />
<Content Include="Assets\FbIcon.png" />
<Content Include="Assets\Plus.png" />
<Content Include="Assets\Privacy.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Assets\SplashScreen.scale-100.png" />
<Content Include="Assets\Square150x150Logo.scale-100.png" />
<Content Include="Assets\Square310x310Logo.scale-100.png" />
<Content Include="Assets\Square44x44Logo.scale-100.png" />
<Content Include="Assets\Square71x71Logo.scale-100.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\StoreLogo.scale-100.png" />
<Content Include="Assets\Welcome\Check.png" />
<Content Include="Assets\Welcome\Keys.png" />
<Content Include="Assets\Welcome\Location.png" />
<Content Include="Assets\Welcome\Lunchbox.png" />
<Content Include="Assets\Welcome\Notification.png" />
<Content Include="Assets\Welcome\Plate.png" />
<Content Include="Assets\Wide310x150Logo.scale-100.png" />
<Content Include="FodyWeavers.xml" />
<None Include="Package.StoreAssociation.xml" />
<Content Include="Properties\Default.rd.xml" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<Page Include="Controls\UserIconControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\AddFriendDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\PhoneSignUpDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\WaitDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Friends.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Lunches.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\NewLunch.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Settings.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Shell.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Welcome.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\models\LunchScheduler.Models.csproj">
<Project>{633b2b38-c19d-4c17-ba1b-22d549625f0f}</Project>
<Name>LunchScheduler.Models</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(NuGetPackageRoot)' == ''">
<NuGetPackageRoot>$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
</PropertyGroup>
<ImportGroup>
<Import Project="$(NuGetPackageRoot)\Fody\1.29.2\build\dotnet\Fody.targets" Condition="Exists('$(NuGetPackageRoot)\Fody\1.29.2\build\dotnet\Fody.targets')" />
</ImportGroup>
</Project>

33
app/Package.appxmanifest Normal file
Просмотреть файл

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp">
<Identity Name="31939AlexanderKoren.Lunchtime" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="1.1.0.0" />
<mp:PhoneIdentity PhoneProductId="bcfe344a-53bc-4526-acef-7f9cf6144323" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>Lunchtime</DisplayName>
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="LunchScheduler.App.App">
<uap:VisualElements DisplayName="Lunchtime" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="Organizing lunch made easy." BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" ShortName="Lunchtime" Square310x310Logo="Assets\Square310x310Logo.png" Square71x71Logo="Assets\Square71x71Logo.png">
<uap:ShowNameOnTiles>
<uap:ShowOn Tile="square310x310Logo" />
</uap:ShowNameOnTiles>
</uap:DefaultTile>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<Capability Name="internetClientServer" />
<DeviceCapability Name="location" />
</Capabilities>
</Package>

Просмотреть файл

@ -0,0 +1,52 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System.Reflection;
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("LunchScheduler.App")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LunchScheduler.App")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 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")]
[assembly: ComVisible(false)]

Просмотреть файл

@ -0,0 +1,31 @@
<!--
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
developers. However, you can modify these parameters to modify the behavior of the .NET Native
optimizer.
Runtime Directives are documented at http://go.microsoft.com/fwlink/?LinkID=391919
To fully enable reflection for App1.MyClass and all of its public/private members
<Type Name="App1.MyClass" Dynamic="Required All"/>
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
<Namespace Name="DataClasses.ViewModels" Seralize="All" />
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!--
An Assembly element with Name="*Application*" applies to all assemblies in
the application package. The asterisks are not wildcards.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Add your application specific runtime directives here. -->
</Application>
</Directives>

Просмотреть файл

@ -0,0 +1,98 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.Controls;
using LunchScheduler.App.Helpers;
using LunchScheduler.Models;
using PropertyChanged;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Windows.UI.Xaml;
namespace LunchScheduler.App.ViewModels
{
/// <summary>
/// View model for the Friends view.
/// </summary>
[ImplementPropertyChanged]
public class FriendsViewModel
{
/// <summary>
/// Gets the user's frirends.
/// </summary>
public ObservableCollection<User> Friends => App.User?.Friends;
/// <summary>
/// Gets or sets the currently selected friend.
/// </summary>
public User SelectedFriend { get; set; }
/// <summary>
/// Gets or sets whether the "No friends" string is visible.
/// </summary>
public Visibility NoFriendsStringVisibility =>
Friends.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
/// <summary>
/// Creates a new FriendsViewModel.
/// </summary>
public FriendsViewModel()
{
Friends.CollectionChanged += (s, e) => PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(NoFriendsStringVisibility)));
}
/// <summary>
/// Shows the add friend dialog to add a friend.
/// </summary>
public async void AddFriend()
{
await DispatchHelper.RunAsync(async () =>
{
var dialog = new AddFriendDialog();
dialog.FriendAdded += async (s, e) =>
{
var friend = await App.Api.GetAsync<User>("Users", new { Email = e });
if (null != friend)
{
Friends.Add(friend);
}
};
await dialog.ShowAsync();
});
}
/// <summary>
/// Removes the selected friend.
/// </summary>
public void DeleteFriend() => Friends.Remove(SelectedFriend);
/// <summary>
/// Event handler for registering for change notification. Actual implementation
/// is injected at compile-time by Fody.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
}

Просмотреть файл

@ -0,0 +1,126 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.Helpers;
using LunchScheduler.Models;
using PropertyChanged;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using WinRTXamlToolkit.Tools;
namespace LunchScheduler.App.ViewModels
{
/// <summary>
/// View model for Lunches view.
/// </summary>
[ImplementPropertyChanged]
public class LunchesViewModel
{
/// <summary>
/// Creates a new LunchesViewModel.
/// </summary>
public LunchesViewModel()
{
Task.Run(Initialize);
}
/// <summary>
/// Contains all lunches.
/// </summary>
public ObservableCollection<Lunch> Lunches { get; set; } = new ObservableCollection<Lunch>();
/// <summary>
/// Gets or sets the currently selected lunch.
/// </summary>
public Lunch SelectedLunch { get; set; }
/// <summary>
/// Sets whether to display the "No lunches" string.
/// </summary>
public Visibility NoLunchesStringVisibility { get; set; } = Visibility.Visible;
/// <summary>
/// Sets whether to display the "Accept" or "Reject" lunch buttons.
/// </summary>
public Visibility ResponseButtonVisibility =>
null != SelectedLunch ? Visibility.Visible : Visibility.Collapsed;
/// <summary>
/// Accepts the selected lunch.
/// </summary>
public async void Accept() => await SetResponse(true);
/// <summary>
/// Declines the selected lunch.
/// </summary>
public async void Decline() => await SetResponse(false);
/// <summary>
/// Sends the user's response (accept or decline) to the server.
/// </summary>
private async Task SetResponse(bool response)
{
var invite = SelectedLunch.Invites.FirstOrDefault(x => x.User.Email == App.User.Email);
invite.Response = response;
await App.Api.PostAsync("Invite", invite);
}
/// <summary>
/// Navigates to the NewLunch page.
/// </summary>
public void NewLunch() => ShellViewModel.Current.Navigate(typeof(Views.NewLunch));
/// <summary>
/// Fires when the selected lunch changes.
/// </summary>
public void OnSelectedLunchChanged() => PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(ResponseButtonVisibility)));
/// <summary>
/// Loads current lunches.
/// </summary>
private async Task Initialize()
{
var lunches = await App.Api.GetAsync<IEnumerable<Lunch>>("Lunch");
if (null != lunches && lunches.Any())
{
await DispatchHelper.RunAsync(() =>
{
NoLunchesStringVisibility = Visibility.Collapsed;
lunches.ForEach(x => Lunches.Add(x));
});
}
}
/// <summary>
/// Event handler for registering for change notification. Actual implementation
/// is injected at compile-time by Fody.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
}

Просмотреть файл

@ -0,0 +1,148 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.Helpers;
using LunchScheduler.App.Views;
using LunchScheduler.Models;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.Devices.Geolocation;
using Windows.UI.Xaml;
namespace LunchScheduler.App.ViewModels
{
/// <summary>
/// View model for the NewLunch view.
/// </summary>
[ImplementPropertyChanged]
internal class NewLunchViewModel
{
/// <summary>
/// Creates a new NewLunchViewModel.
/// </summary>
public NewLunchViewModel()
{
Task.Run(Initialize);
}
/// <summary>
/// Gets the user's friends.
/// </summary>
public ObservableCollection<User> Friends => App.User.Friends ?? new ObservableCollection<User>();
/// <summary>
/// Gets or sets the location options for the lunch.
/// </summary>
public ObservableCollection<Location> Locations { get; set; } = new ObservableCollection<Location>();
/// <summary>
/// Gets or sets the friends invited to lunch.
/// </summary>
public ObservableCollection<User> SelectedFriends { get; set; } = new ObservableCollection<User>();
/// <summary>
/// Gets or sets the selected lunch location.
/// </summary>
public Location SelectedLocation { get; set; }
/// <summary>
/// Gets or sets the selected lunch date. Defaults to the current date.
/// </summary>
public DateTimeOffset SelectedDate { get; set; } = DateTime.Now.Date;
/// <summary>
/// Gets or sets the selected lunch time. Defaults to 1 hour from the current time.
/// </summary>
public TimeSpan SelectedTime { get; set; } = DateTime.Now.TimeOfDay.Add(new TimeSpan(1, 0, 0));
/// <summary>
/// Gets or sets the name of the lunch.
/// </summary>
public string Name { get; set; } = $"{App.User.Name}'s lunch";
/// <summary>
/// Gets or sets the custom location text.
/// </summary>
public string CustomLocation { get; set; }
/// <summary>
/// Gets or sets whether to show the custom location text box.
/// </summary>
public Visibility CustomLocationVisibility { get; set; } = Visibility.Collapsed;
/// <summary>
/// Fires when the selected location changes. If the selected location is "custom," shows the
/// lunch location text box.
/// </summary>
public void OnSelectedLocationChanged() => CustomLocationVisibility = SelectedLocation.Name == "Custom" ?
Visibility.Visible : Visibility.Collapsed;
/// <summary>
/// Handles the user clicking the "Ask to lunch" button.
/// </summary>
public async void AskToLunch()
{
var location = SelectedLocation.Name == "Custom"
? new Location {Name = CustomLocation}
: SelectedLocation;
var lunch = new Lunch(Name,
App.User,
SelectedFriends.Concat(new[] { App.User }),
location,
SelectedDate.Add(SelectedTime).LocalDateTime);
await App.Api.PostAsync("Lunch", lunch);
ShellViewModel.Current.Navigate(typeof(Lunches));
}
/// <summary>
/// Loads possible lunch locations based on the user's current location.
/// </summary>
private async Task Initialize()
{
await DispatchHelper.RunAsync(async () =>
{
var restaurants = new List<Location>
{
new Location
{
Name = "Custom",
ImageUrl = @"ms-appx:///Assets/Plus.png"
}
};
if (await Geolocator.RequestAccessAsync() == GeolocationAccessStatus.Allowed)
{
var location = await new Geolocator().GetGeopositionAsync();
var nearby = await App.Api.GetAsync<IEnumerable<Location>>("Locations",
new { location.Coordinate.Point.Position.Longitude, location.Coordinate.Point.Position.Latitude });
restaurants.AddRange(nearby);
}
restaurants.ForEach(x => Locations.Add(x));
});
}
}
}

Просмотреть файл

@ -0,0 +1,91 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.Controls;
using LunchScheduler.App.Helpers;
using LunchScheduler.Models;
using PropertyChanged;
using System;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Xaml;
namespace LunchScheduler.App.ViewModels
{
/// <summary>
/// View model for the Settings view.
/// </summary>
[ImplementPropertyChanged]
public class SettingsViewModel
{
/// <summary>
/// Creates a new SettingsViewModel.
/// </summary>
public SettingsViewModel()
{
Task.Run(Initialize);
}
/// <summary>
/// Gets the current user.
/// </summary>
public User CurrentUser => App.User;
/// <summary>
/// Gets or sets the privacy text.
/// </summary>
public string PrivacyText { get; private set; }
/// <summary>
/// Gets or sets the text of the clear collections text box.
/// </summary>
public string ClearCollectionText { get; set; }
/// <summary>
/// Loads the privacy text from local file.
/// </summary>
private async Task Initialize()
{
var file = await StorageFile.GetFileFromApplicationUriAsync(
new Uri("ms-appx:///Assets/Privacy.txt"));
PrivacyText = await FileIO.ReadTextAsync(file);
}
/// <summary>
/// Logs the current user out of the app and returns to the OOBE screen.
/// </summary>
public void Logout()
{
AuthenticationHelper.Logout();
Window.Current.Content = new Views.Welcome();
}
/// <summary>
/// Shows the phone notification registration dialog.
/// </summary>
public async void RegisterPhone() => await DispatchHelper.RunAsync(
async () => await new PhoneSignUpDialog().ShowAsync());
}
}

Просмотреть файл

@ -0,0 +1,133 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.Views;
using PropertyChanged;
using System;
using System.Collections.ObjectModel;
using Windows.UI.Xaml.Controls;
namespace LunchScheduler.App.ViewModels
{
/// <summary>
/// View model for the Shell.
/// </summary>
[ImplementPropertyChanged]
public class ShellViewModel
{
/// <summary>
/// Static instance of this shell. Used by other parts of the app for navigation
/// outside the menu.
/// </summary>
public static ShellViewModel Current { get; set; }
/// <summary>
/// Creates a new ShellViewModel.
/// </summary>
public ShellViewModel(Frame frame)
{
Frame = frame;
Items = new ObservableCollection<MenuItem>(new[]
{
new MenuItem
{
Name = "Lunches",
Icon = "\uE10F",
Target = typeof(Lunches)
},
new MenuItem
{
Name = "Friends",
Icon = "\uE77B",
Target = typeof(Friends)
},
new MenuItem
{
Name = "Settings",
Icon = "\uE713",
Target = typeof(Settings)
}
});
SelectedItem = Items[0];
Current = this;
}
/// <summary>
/// The content frame aside the hamburger.
/// </summary>
public Frame Frame { get; set; }
/// <summary>
/// Contains the hamburger menu items.
/// </summary>
public ObservableCollection<MenuItem> Items { get; }
/// <summary>
/// Gets or sets the selected hamburger menu item.
/// </summary>
public MenuItem SelectedItem { get; set; }
/// <summary>
/// Gets or sets whether the full hamburger menu is open.
/// </summary>
public bool IsMenuOpen { get; set; }
/// <summary>
/// Toggles the hamburger menu open and closed.
/// </summary>
public void ToggleMenu() => IsMenuOpen = !IsMenuOpen;
/// <summary>
/// Navigates to the selected menu item.
/// </summary>
public void OnSelectedItemChanged() => Navigate(SelectedItem.Target);
/// <summary>
/// Navigates to the given page.
/// </summary>
public void Navigate(Type target) => Frame.Navigate(target);
}
/// <summary>
/// Represents a menu item.
/// </summary>
[ImplementPropertyChanged]
public class MenuItem
{
/// <summary>
/// The name of the menu item.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The icon to show in the menu.
/// </summary>
public string Icon { get; set; }
/// <summary>
/// The page to navigate to when the menu item is clicked.
/// </summary>
public Type Target { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,201 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.Controls;
using LunchScheduler.App.Helpers;
using PropertyChanged;
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.Devices.Geolocation;
using Windows.UI.ApplicationSettings;
using Windows.UI.Xaml;
namespace LunchScheduler.App.ViewModels
{
/// <summary>
/// View model for the Welcome view.
/// </summary>
[ImplementPropertyChanged]
public class WelcomeViewModel
{
/// <summary>
/// The base Uri for all OOBE images.
/// </summary>
private const string ImageBase = @"ms-appx:///Assets/Welcome/";
/// <summary>
/// Creates a new WelcomeViewModel with the default OOBE items.
/// </summary>
public WelcomeViewModel()
{
WelcomeItems = new ObservableCollection<WelcomeItem>(new[]
{
new WelcomeItem(
"Welcome to Lunch Scheduler",
"The easiest way to organize lunch with your friends, family, or coworkers.",
ImageBase + "Lunchbox.png"),
new WelcomeItem(
"Organizing made easy",
"Hungry? Avoid endless back-and-forth emails or texts. Simply tap 'New Lunch' " +
"on the main menu and pick a who, where, and when. We'll take care of the rest.",
ImageBase + "Plate.png"),
new WelcomeItem(
"Discover local delights",
"When you're scheduling a new lunch, Lunch Scheduler can use your current location to " +
"suggest tasty restaurants nearby.",
ImageBase + "Location.png",
async () => await DispatchHelper.RunAsync(async () => await Geolocator.RequestAccessAsync())),
new WelcomeItem(
"Get signed up",
"To keep track of everything, you'll need an account with Lunch Scheduler. You can log in " +
"with an existing Microsoft, Google, or Facebook account. Lunch Scheduler will never post " +
"to your wall or send you unsolicted email. And there's no extra passwords to remember.",
ImageBase + "Keys.png",
AccountsSettingsPane.Show),
new WelcomeItem(
"Be notified",
"Invited to lunch? We'll let you know with a push notification or text message." +
" You can see the location and who's invited before you RSVP.",
ImageBase + "Notification.png",
async () => await new PhoneSignUpDialog().ShowAsync()),
new WelcomeItem(
"Good to go!",
"You're all set! Enjoy lunching!",
ImageBase + "Check.png",
() => Window.Current.Content = new Views.Shell()),
new WelcomeItem("", "", "")
});
SelectedWelcomeItem = WelcomeItems[0];
}
/// <summary>
/// The collection of Welcome Items.
/// </summary>
public ObservableCollection<WelcomeItem> WelcomeItems { get; }
/// <summary>
/// Gets or sets the current welcome item and invokes
/// the previous item's "OnNavigatedFrom" command, if any.
/// </summary>
public WelcomeItem SelectedWelcomeItem { get; set; }
/// <summary>
/// The enabled manipulation modes for the welcome FlipView.
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// Invokes a command when the user navigates from a welcome item.
/// </summary>
private async void OnSelectedWelcomeItemChanged()
{
IsEnabled = false;
if (null != WelcomeItems)
{
int index = WelcomeItems.IndexOf(SelectedWelcomeItem) - 1;
if (index >= 0 && null != WelcomeItems[index]?.OnNavigatedFrom)
{
WelcomeItems[index].OnNavigatedFrom();
}
ChangeFontSize();
await Task.Run(async () =>
{
await Task.Delay(1000);
await DispatchHelper.RunAsync(() => IsEnabled = true);
});
}
}
/// <summary>
/// Make text larger or smaller depending on window size.
/// </summary>
public void ChangeFontSize() => SelectedWelcomeItem.ContentFontSize =
Window.Current.Bounds.Width < App.NarrowWidth ? App.FontSizeSmall : App.FontSizeMedium;
}
/// <summary>
/// Represents an welcome item displayed during the OOTB experience.
/// </summary>
[ImplementPropertyChanged]
public class WelcomeItem
{
/// <summary>
/// Creates a new welcome item.
/// </summary>
public WelcomeItem(string headerText, string contentText,
string image, Action onNavigatedFrom = null)
{
HeaderText = headerText;
ContentText = contentText;
if (!String.IsNullOrEmpty(image))
{
Image = new Uri(image);
}
OnNavigatedFrom = onNavigatedFrom;
// Make text larger or smaller depending on window size
if (Window.Current.Bounds.Width < App.NarrowWidth)
{
ContentFontSize = App.FontSizeSmall;
}
else
{
ContentFontSize = App.FontSizeMedium;
}
}
/// <summary>
/// Gets or sets the content text.
/// </summary>
public string ContentText { get; set; }
/// <summary>
/// The font size of the content text.
/// </summary>
public double ContentFontSize { get; set; }
/// <summary>
/// Gets or sets the header text.
/// </summary>
public string HeaderText { get; set; }
/// <summary>
/// Gets or sets the image.
/// </summary>
public Uri Image { get; set; }
/// <summary>
/// An action to fire when the user navigates away from the welcome item.
/// </summary>
public Action OnNavigatedFrom { get; set; }
}
}

76
app/Views/Friends.xaml Normal file
Просмотреть файл

@ -0,0 +1,76 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<Page x:Class="LunchScheduler.App.Views.Friends"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="using:LunchScheduler.App.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:LunchScheduler.App.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:LunchScheduler.Models"
xmlns:vm="using:LunchScheduler.App.ViewModels"
mc:Ignorable="d">
<Page.DataContext>
<vm:FriendsViewModel x:Name="ViewModel" />
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Name="FriendsGridViewTemplate" x:DataType="models:User">
<c:UserIconControl PhotoUrl="{x:Bind PhotoUrl}" UserName="{x:Bind Name}" />
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<GridView ItemTemplate="{StaticResource FriendsGridViewTemplate}"
ItemsSource="{Binding Friends,
Mode=TwoWay}"
SelectedItem="{Binding SelectedFriend,
Mode=TwoWay}" />
</StackPanel>
<StackPanel Margin="0,0,0,50" VerticalAlignment="Center">
<TextBlock Margin="0,0,0,10"
HorizontalAlignment="Center"
FontSize="200"
Text=":("
Visibility="{Binding NoFriendsStringVisibility,
Mode=OneWay}" />
<TextBlock Margin="10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Looks like you don't have any friends yet. Add one by tapping the icon below!"
TextAlignment="Center"
TextWrapping="WrapWholeWords"
Visibility="{Binding NoFriendsStringVisibility,
Mode=OneWay}" />
</StackPanel>
</Grid>
<Page.BottomAppBar>
<CommandBar>
<AppBarButton x:Name="AddFriendButton"
Click="{x:Bind ViewModel.AddFriend}"
Icon="AddFriend"
Label="Add Friend" />
<AppBarButton x:Name="DeleteFriendButton"
Click="{x:Bind ViewModel.DeleteFriend}"
Icon="BlockContact"
Label="Remove Friend" />
</CommandBar>
</Page.BottomAppBar>
</Page>

36
app/Views/Friends.xaml.cs Normal file
Просмотреть файл

@ -0,0 +1,36 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using Windows.UI.Xaml.Controls;
namespace LunchScheduler.App.Views
{
public sealed partial class Friends : Page
{
public Friends()
{
this.InitializeComponent();
}
}
}

130
app/Views/Lunches.xaml Normal file
Просмотреть файл

@ -0,0 +1,130 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<Page x:Class="LunchScheduler.App.Views.Lunches"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="using:LunchScheduler.App.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dm="using:LunchScheduler.Models"
xmlns:local="using:LunchScheduler.App.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:LunchScheduler.App.ViewModels"
mc:Ignorable="d">
<Page.DataContext>
<vm:LunchesViewModel x:Name="ViewModel" />
</Page.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="Phone">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
</VisualState>
<VisualState x:Name="Desktop">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="800" />
</VisualState.StateTriggers>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ListView ItemsSource="{Binding Lunches, Mode=TwoWay}" SelectedItem="{Binding SelectedLunch, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="dm:Lunch">
<StackPanel>
<TextBlock Margin="5"
FontSize="24"
Text="{x:Bind Name}" />
<TextBlock Margin="5,5,5,5"
FontSize="24"
Text="{x:Bind Location.Name}" />
<TextBlock Margin="5,5,5,5"
FontSize="24"
Text="{x:Bind FormattedTime}" />
<ScrollViewer HorizontalScrollBarVisibility="Auto"
HorizontalScrollMode="Auto"
VerticalScrollBarVisibility="Disabled"
VerticalScrollMode="Disabled">
<GridView x:Name="InviteesGridView"
Margin="5,5,5,5"
ItemsSource="{x:Bind Invites}"
ScrollViewer.HorizontalScrollBarVisibility="Auto">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid x:Name="LunchesGrid"
MaximumRowsOrColumns="1"
Orientation="Vertical"
ScrollViewer.HorizontalScrollBarVisibility="Auto" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate x:DataType="dm:Invite">
<c:UserIconControl PhotoUrl="{x:Bind User.PhotoUrl}"
ResponseMark="{x:Bind Response, Mode=TwoWay}"
UserName="{x:Bind User.Name}" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</ScrollViewer>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Margin="0,0,0,50" VerticalAlignment="Center">
<TextBlock Margin="0,0,0,10"
HorizontalAlignment="Center"
FontSize="200"
Text=":("
Visibility="{Binding NoLunchesStringVisibility,
Mode=OneWay}" />
<TextBlock Margin="10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="You don't have lunches right now. Send one by tapping '+'!"
TextAlignment="Center"
TextWrapping="WrapWholeWords"
Visibility="{Binding NoLunchesStringVisibility,
Mode=OneWay}" />
</StackPanel>
</Grid>
<Page.BottomAppBar>
<CommandBar>
<AppBarButton Click="{x:Bind ViewModel.NewLunch}"
Icon="Add"
Label="New lunch" />
<AppBarButton Click="{x:Bind ViewModel.Accept}"
Icon="Accept"
Label="Accept"
Visibility="{Binding ResponseButtonVisibility}" />
<AppBarButton Click="{x:Bind ViewModel.Decline}"
Icon="Cancel"
Label="Decline"
Visibility="{Binding ResponseButtonVisibility}" />
</CommandBar>
</Page.BottomAppBar>
</Page>

36
app/Views/Lunches.xaml.cs Normal file
Просмотреть файл

@ -0,0 +1,36 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using Windows.UI.Xaml.Controls;
namespace LunchScheduler.App.Views
{
public sealed partial class Lunches : Page
{
public Lunches()
{
InitializeComponent();
}
}
}

125
app/Views/NewLunch.xaml Normal file
Просмотреть файл

@ -0,0 +1,125 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<Page x:Class="LunchScheduler.App.Views.NewLunch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="using:LunchScheduler.App.Controls"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:extensions="using:WinRTXamlToolkit.Controls.Extensions"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:LunchScheduler.App.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:LunchScheduler.Models"
xmlns:vm="using:LunchScheduler.App.ViewModels"
mc:Ignorable="d">
<Page.DataContext>
<vm:NewLunchViewModel x:Name="ViewModel" />
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ScrollViewer HorizontalScrollMode="Disabled">
<StackPanel>
<StackPanel>
<TextBox Margin="25,25,25,10" Text="{Binding Name, Mode=TwoWay}" />
<GridView Margin="10, 10, 10, 10"
extensions:ListViewExtensions.BindableSelection="{Binding SelectedFriends,
Mode=TwoWay}"
ItemsSource="{Binding Friends,
Mode=TwoWay}"
SelectionMode="Multiple">
<GridView.ItemTemplate>
<DataTemplate x:DataType="models:User">
<c:UserIconControl PhotoUrl="{x:Bind PhotoUrl}" UserName="{x:Bind Name}" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
HorizontalScrollMode="Auto"
VerticalScrollBarVisibility="Disabled"
VerticalScrollMode="Disabled">
<GridView Margin="10,10,10,10"
Background="Transparent"
ItemsSource="{Binding Locations}"
SelectedItem="{Binding SelectedLocation,
Mode=TwoWay}"
SelectionMode="Single"
ShowsScrollingPlaceholders="True">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid MaximumRowsOrColumns="1" Orientation="Vertical" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate x:DataType="models:Location">
<Grid>
<StackPanel VerticalAlignment="Center">
<Ellipse x:Name="Ellipse"
Width="100"
Height="100"
Margin="4,4,4,4"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Ellipse.Fill>
<ImageBrush ImageSource="{x:Bind ImageUrl}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Height="40"
Margin="4,4,4,4"
HorizontalAlignment="Center"
Text="{x:Bind Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="WrapWholeWords" />
</StackPanel>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</ScrollViewer>
<TextBox Margin="10,10,10,10"
Text="Where's lunch?"
Visibility="{Binding CustomLocationVisibility,
Mode=TwoWay}" />
<DatePicker Margin="10,10,10,10"
HorizontalAlignment="Center"
Date="{x:Bind ViewModel.SelectedDate, Mode=TwoWay}" />
<TimePicker Grid.Row="1"
Width="242"
Height="32"
Margin="10,10,10,10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Time="{x:Bind ViewModel.SelectedTime, Mode=TwoWay}" />
<Button Grid.Row="3"
Margin="10,10,10,10"
HorizontalAlignment="Center"
Click="{x:Bind ViewModel.AskToLunch}"
Content="Get lunch!" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
</Page>

Просмотреть файл

@ -0,0 +1,36 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using Windows.UI.Xaml.Controls;
namespace LunchScheduler.App.Views
{
public sealed partial class NewLunch : Page
{
public NewLunch()
{
this.InitializeComponent();
}
}
}

74
app/Views/Settings.xaml Normal file
Просмотреть файл

@ -0,0 +1,74 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<Page x:Class="LunchScheduler.App.Views.Settings"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:lc="using:LunchScheduler.App.Controls"
xmlns:local="using:LunchScheduler.App.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:LunchScheduler.App.ViewModels"
mc:Ignorable="d">
<Page.DataContext>
<vm:SettingsViewModel x:Name="ViewModel" />
</Page.DataContext>
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Pivot>
<PivotItem Header="Account">
<StackPanel HorizontalAlignment="Left">
<lc:UserIconControl HorizontalAlignment="Center"
PhotoUrl="{Binding CurrentUser.PhotoUrl}"
UserName="{Binding CurrentUser.Name}" />
<Button HorizontalAlignment="Center"
Click="{x:Bind ViewModel.RegisterPhone}"
Content="Register phone" />
<Button HorizontalAlignment="Center"
Click="{x:Bind ViewModel.Logout}"
Content="Log out" />
</StackPanel>
</PivotItem>
<PivotItem Header="Settings">
<ScrollViewer HorizontalContentAlignment="Left" VerticalScrollBarVisibility="Auto">
<StackPanel>
<ToggleSwitch Header="Push notifications"
OffContent="Off"
OnContent="On" />
<ToggleSwitch Header="Text notifications"
OffContent="Off"
OnContent="On" />
</StackPanel>
</ScrollViewer>
</PivotItem>
<PivotItem Header="Privacy" Padding="0">
<TextBlock Text="{x:Bind ViewModel.PrivacyText, Mode=OneWay}" TextWrapping="WrapWholeWords" />
</PivotItem>
</Pivot>
</RelativePanel>
</Page>

Просмотреть файл

@ -0,0 +1,36 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using Windows.UI.Xaml.Controls;
namespace LunchScheduler.App.Views
{
public sealed partial class Settings : Page
{
public Settings()
{
InitializeComponent();
}
}
}

82
app/Views/Shell.xaml Normal file
Просмотреть файл

@ -0,0 +1,82 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<Page x:Class="LunchScheduler.App.Views.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:LunchScheduler.App.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:LunchScheduler.App.ViewModels"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Width="48"
Height="50"
HorizontalAlignment="Left"
Click="{x:Bind ViewModel.ToggleMenu}"
Content="&#xE700;"
FontFamily="Segoe MDL2 Assets"
FontSize="24" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Margin="10,0,0,0"
VerticalAlignment="Center"
FontSize="24"
Text="{Binding SelectedItem.Name,
Mode=OneWay}" />
<SplitView Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
CompactPaneLength="48"
DisplayMode="CompactOverlay"
IsPaneOpen="{Binding IsMenuOpen}">
<SplitView.Pane>
<StackPanel>
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="viewModels:MenuItem">
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets"
FontSize="24"
Text="{x:Bind Icon}" />
<TextBlock Margin="20,5,0,0"
FontSize="14"
Text="{x:Bind Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</SplitView.Pane>
<SplitView.Content>
<Frame Name="MainFrame" />
</SplitView.Content>
</SplitView>
</Grid>
</Page>

41
app/Views/Shell.xaml.cs Normal file
Просмотреть файл

@ -0,0 +1,41 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.ViewModels;
using Windows.UI.Xaml.Controls;
namespace LunchScheduler.App.Views
{
public sealed partial class Shell : Page
{
public ShellViewModel ViewModel { get; set; }
public Shell()
{
InitializeComponent();
ViewModel = new ShellViewModel(MainFrame);
DataContext = ViewModel;
}
}
}

72
app/Views/Welcome.xaml Normal file
Просмотреть файл

@ -0,0 +1,72 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<Page x:Class="LunchScheduler.App.Views.Welcome"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:LunchScheduler.App.ViewModels"
SizeChanged="PageSizeChanged"
mc:Ignorable="d">
<Grid>
<FlipView Background="White"
IsEnabled="{Binding IsEnabled,
Mode=OneWay}"
ItemsSource="{Binding WelcomeItems}"
SelectedItem="{Binding SelectedWelcomeItem,
Mode=TwoWay}"
UseTouchAnimationsForAllNavigation="True">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="vm:WelcomeItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="10, 10, 10, 10">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EdgeUIThemeTransition Edge="Left" />
</TransitionCollection>
</StackPanel.ChildrenTransitions>
<TextBlock HorizontalAlignment="Center"
FontSize="36"
Foreground="Black"
Text="{Binding HeaderText}"
TextAlignment="Center"
TextWrapping="WrapWholeWords" />
<Image MaxWidth="250"
Source="{Binding Image}"
Stretch="Uniform" />
<TextBlock Margin="20,0"
HorizontalAlignment="Center"
FontSize="{Binding ContentFontSize,
Mode=OneWay}"
Foreground="Black"
Text="{Binding ContentText}"
TextAlignment="Justify"
TextWrapping="WrapWholeWords" />
</StackPanel>
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Grid>
</Page>

47
app/Views/Welcome.xaml.cs Normal file
Просмотреть файл

@ -0,0 +1,47 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.App.ViewModels;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace LunchScheduler.App.Views
{
public sealed partial class Welcome : Page
{
public WelcomeViewModel ViewModel { get; set; }
public Welcome()
{
this.InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Disabled;
ViewModel = new WelcomeViewModel();
DataContext = ViewModel;
}
private void PageSizeChanged(object sender, SizeChangedEventArgs e) =>
ViewModel.ChangeFontSize();
}
}

24
app/project.json Normal file
Просмотреть файл

@ -0,0 +1,24 @@
{
"dependencies": {
"AsyncErrorHandler.Fody": "1.0.11",
"Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0",
"Microsoft.Xaml.Behaviors.Uwp.Managed": "1.0.3",
"Newtonsoft.Json": "8.0.2",
"PropertyChanged.Fody": "1.50.4",
"WindowsAzure.Messaging.Managed": "0.1.7.9",
"WindowsAzure.MobileServices": "1.3.2",
"WindowsAzure.Storage": "6.2.0",
"winrtxamltoolkit": "2.0.0"
},
"frameworks": {
"uap10.0": {}
},
"runtimes": {
"win10-arm": {},
"win10-arm-aot": {},
"win10-x86": {},
"win10-x86-aot": {},
"win10-x64": {},
"win10-x64-aot": {}
}
}

9
global.json Normal file
Просмотреть файл

@ -0,0 +1,9 @@
{
"projects": [
"wrap",
"."
],
"sdk": {
"version": "1.0.0-rc1-update1"
}
}

46
models/DbObject.cs Normal file
Просмотреть файл

@ -0,0 +1,46 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System;
using System.ComponentModel;
namespace LunchScheduler.Models
{
/// <summary>
/// Root model object.
/// </summary>
public abstract class DbObject : INotifyPropertyChanged
{
/// <summary>
/// The database Id
/// </summary>
public Guid Id { get; set; } = Guid.NewGuid();
/// <summary>
/// Event handler for registering for change notification. Actual implementation
/// is injected at compile-time by Fody.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
}

57
models/Device.cs Normal file
Просмотреть файл

@ -0,0 +1,57 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
namespace LunchScheduler.Models
{
/// <summary>
/// Represents a device.
/// </summary>
public class Device : DbObject
{
/// <summary>
/// The type of notifications the device accepts.
/// </summary>
public NotificationType NotificationType { get; set; }
/// <summary>
/// The device's push notification tag.
/// </summary>
public string Tag { get; set; }
/// <summary>
/// The device's phone number.
/// </summary>
public string PhoneNumber { get; set; }
/// <summary>
/// Indicates whether notifications are enabled.
/// </summary>
public bool NotificationsEnabled { get; set; }
/// <summary>
/// Indicates whether the user has verified the device.
/// </summary>
public bool Verified { get; set; }
}
}

5
models/FodyWeavers.xml Normal file
Просмотреть файл

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<PropertyChanged />
<EmptyConstructor />
</Weavers>

53
models/Invite.cs Normal file
Просмотреть файл

@ -0,0 +1,53 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using PropertyChanged;
namespace LunchScheduler.Models
{
/// <summary>
/// Represents an invite to a lunch
/// </summary>
[ImplementPropertyChanged]
public class Invite : DbObject
{
/// <summary>
/// Creates a new invite.
/// </summary>
public Invite(User user)
{
User = user;
}
/// <summary>
/// The user invited to the lunch
/// </summary>
public User User { get; set; }
/// <summary>
/// The invitee's response to the lunch; null indicates no response
/// </summary>
public bool? Response { get; set; }
}
}

52
models/Location.cs Normal file
Просмотреть файл

@ -0,0 +1,52 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
namespace LunchScheduler.Models
{
/// <summary>
/// Represents a location (e.g., restaurant).
/// </summary>
public class Location : DbObject
{
/// <summary>
/// The name of the location.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The address of the location.
/// </summary>
public string Address { get; set; }
/// <summary>
/// The url for the location's image on Yelp.
/// </summary>
public string ImageUrl { get; set; }
/// <summary>
/// The location's rating on Yelp.
/// </summary>
public float Rating { get; set; }
}
}

87
models/Lunch.cs Normal file
Просмотреть файл

@ -0,0 +1,87 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace LunchScheduler.Models
{
/// <summary>
/// Represents a lunch.
/// </summary>
public class Lunch : DbObject
{
/// <summary>
/// Creates a new lunch.
/// </summary>
public Lunch()
{
Invites = new ObservableCollection<Invite>();
}
/// <summary>
/// Creates a new lunch.
/// </summary>
public Lunch(string name, User host, IEnumerable<User> invites, Location location, DateTime time)
{
Name = name;
Host = host;
Invites = new ObservableCollection<Invite>(invites.Select(x => new Invite(x)));
Location = location;
Time = time;
}
/// <summary>
/// The name of the lunch.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The creator of the lunch.
/// </summary>
public User Host { get; set; }
/// <summary>
/// The invitations to the lunch.
/// </summary>
public ObservableCollection<Invite> Invites { get; set; }
/// <summary>
/// The location of the lunch.
/// </summary>
public Location Location { get; set; }
/// <summary>
/// The date and time of the lunch.
/// </summary>
public DateTime Time { get; set; }
/// <summary>
/// The formatted time of the lunch.
/// </summary>
public string FormattedTime { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{633B2B38-C19D-4C17-BA1B-22D549625F0F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>LunchScheduler.Models</RootNamespace>
<AssemblyName>LunchScheduler.Models</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Compile Include="DbObject.cs" />
<Compile Include="Device.cs" />
<Compile Include="Invite.cs" />
<Compile Include="Location.cs" />
<Compile Include="Lunch.cs" />
<Compile Include="ProviderTypes.cs" />
<Compile Include="NotificationType.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProviderInfo.cs" />
<Compile Include="User.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Reference Include="PropertyChanged, Version=1.50.4.0, Culture=neutral, PublicKeyToken=ee3ee20bcf148ddd, processorArchitecture=MSIL">
<HintPath>..\packages\PropertyChanged.Fody.1.50.4\lib\portable-net4+sl4+wp8+win8+wpa81+MonoAndroid16+MonoTouch40\PropertyChanged.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Windows.Foundation.UniversalApiContract">
<HintPath>C:\Program Files (x86)\Windows Kits\10\References\Windows.Foundation.UniversalApiContract\2.0.0.0\Windows.Foundation.UniversalApiContract.winmd</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Content Include="FodyWeavers.xml" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,38 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System;
namespace LunchScheduler.Models
{
/// <summary>
/// Represents types of supported notifications.
/// </summary>
[Flags]
public enum NotificationType
{
Text,
Wns
}
}

Просмотреть файл

@ -0,0 +1,52 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System.Reflection;
using System.Resources;
// 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("LunchScheduler.Models")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LunchScheduler.Models")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[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")]

62
models/ProviderInfo.cs Normal file
Просмотреть файл

@ -0,0 +1,62 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
namespace LunchScheduler.Models
{
/// <summary>
/// Provides information about authentication with an identity provider (IDP).
/// </summary>
public class ProviderInfo : DbObject
{
/// <summary>
/// The IDP account Id.
/// </summary>
public string AccountId { get; set; }
/// <summary>
/// The provider.
/// </summary>
public ProviderType Provider { get; set; }
/// <summary>
/// The user's name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The user's email.
/// </summary>
public string Email { get; set; }
/// <summary>
/// The url to the user's photo.
/// </summary>
public string PhotoUrl { get; set; }
/// <summary>
/// The provider token.
/// </summary>
public string Token { get; set; }
}
}

35
models/ProviderTypes.cs Normal file
Просмотреть файл

@ -0,0 +1,35 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
namespace LunchScheduler.Models
{
/// <summary>
/// Represents supported IDPs.
/// </summary>
public enum ProviderType
{
Msa,
Facebook
}
}

66
models/User.cs Normal file
Просмотреть файл

@ -0,0 +1,66 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using PropertyChanged;
using System.Collections.ObjectModel;
namespace LunchScheduler.Models
{
/// <summary>
/// Represents a user.
/// </summary>
[ImplementPropertyChanged]
public class User : DbObject
{
/// <summary>
/// The user's name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The user's email.
/// </summary>
public string Email { get; set; }
/// <summary>
/// The user's IDP info.
/// </summary>
public ProviderInfo Auth { get; set; }
/// <summary>
/// The user's devices.
/// </summary>
public ObservableCollection<Device> Devices { get; set; } = new ObservableCollection<Device>();
/// <summary>
/// The user's friends.
/// </summary>
public ObservableCollection<User> Friends { get; set; } = new ObservableCollection<User>();
/// <summary>
/// URL to the user's IDP photo.
/// </summary>
public string PhotoUrl { get; set; }
}
}

6
models/packages.config Normal file
Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EmptyConstructor.Fody" version="1.1.6.0" targetFramework="portable45-net45+win8" developmentDependency="true" />
<package id="Fody" version="1.29.2" targetFramework="portable45-net45+win8" developmentDependency="true" />
<package id="PropertyChanged.Fody" version="1.50.4" targetFramework="portable45-net45+win8" developmentDependency="true" />
</packages>

Двоичные данные
screenshot1.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 49 KiB

Двоичные данные
screenshot2.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 71 KiB

Двоичные данные
screenshot3.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 112 KiB

Двоичные данные
screenshot4.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 335 KiB

115
site/AuthRequired.cs Normal file
Просмотреть файл

@ -0,0 +1,115 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using LunchScheduler.Site.Helpers;
using System;
using System.Data.Entity;
using System.Diagnostics;
using System.Net;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Net.Http;
namespace LunchScheduler.Site
{
/// <summary>
/// Attribute that indicates a controller requires authorization to access.
/// </summary>
public class AuthRequired : FilterAttribute, IAuthorizationFilter
{
private LunchSchedulerContext _db;
private HttpActionContext _context;
/// <summary>
/// Runs whenever a controller decorated with the [AuthRequired] attribute is called
/// to determine whether the user is authorized (continue) or not (return 403 (Unauthorized)).
/// </summary>
public async Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext,
CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
_context = actionContext;
_db = new LunchSchedulerContext();
try
{
var result = await Authorize();
if (!result.IsSuccessStatusCode)
{
return result;
}
return await continuation();
}
catch (Exception ex)
{
Debug.WriteLine("Failiure authenticating: " + ex.Message);
return _context.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
/// <summary>
/// Checks that the user's claimed identity (email and token) matches
/// thier actual identity according to their provider.
/// </summary>
private async Task<HttpResponseMessage> Authorize()
{
// TODO: Consider more robust security.
// This sample provides a basic demonstration of how to use tokens to communicate
// with identity providers (IDPs) and authorize users. However, in a production application,
// making repeated calls to IDPs is not reccomended as it is not performant and may result
// in your app being rate-limited. Instead, you should perform an initial verification of
// the user's identity, and then persist it on your own token (or a similar mechanism).
string email = _context.Request.Headers.From;
string token = _context.Request.Headers.Authorization.Parameter;
ProviderType provider;
bool providerParsed = Enum.TryParse<ProviderType>(
_context.Request.Headers.Authorization.Scheme, out provider);
if (!providerParsed || String.IsNullOrEmpty(email) || String.IsNullOrEmpty(token))
{
return _context.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
var user = await _db.Users.FirstOrDefaultAsync(x => x.Email == email);
if (null == user)
{
return _context.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
ProviderInfo response = await IdpHelper.GetUserInfoAsync(provider, token);
if (response?.Email != email)
{
return _context.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
_context.ControllerContext.RequestContext.Principal =
new GenericPrincipal(new GenericIdentity(email), null);
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System.Web.Http;
namespace LunchScheduler.Site.Controllers
{
/// <summary>
/// Base controller.
/// </summary>
public class ControllerBase : ApiController
{
/// <summary>
/// The Entity Framework data context.
/// </summary>
protected LunchSchedulerContext Db { get; } = new LunchSchedulerContext();
}
}

Просмотреть файл

@ -0,0 +1,77 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Site.Helpers;
using Microsoft.Azure.Mobile.Server.Config;
using Newtonsoft.Json.Linq;
using System;
using System.Data.Entity;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
namespace LunchScheduler.Site.Controllers
{
/// <summary>
/// Handles device verification.
/// </summary>
[MobileAppController]
public class DeviceController : ControllerBase
{
/// <summary>
/// Returns whether a phone number exists and is verified.
/// </summary>
public async Task<bool> Get(string number)
{
var user = await Db.Users.FirstOrDefaultAsync(x => x.Devices.Any(y => y.PhoneNumber.Contains(number)));
return null != user && user.Devices.Any(y => y.Verified);
}
/// <summary>
/// Processes text messages received from the Twilio service.
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]JToken message)
{
string twilioMessage = "Thanks!";
try
{
string number = NotificationHelper.ScrubNumber(message["From"].ToString());
foreach (var user in Db.Users.Where(x => x.Devices.Any(y => y.PhoneNumber == number)))
{
foreach (var device in user.Devices.Where(x => x.PhoneNumber == number))
{
device.Verified = true;
}
}
await Db.SaveChangesAsync();
}
catch (Exception ex)
{
twilioMessage = $"Exception: {ex.Message}. Input: {message}";
}
return NotificationHelper.CreateTwilioHttpResponse(twilioMessage);
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using Microsoft.Azure.Mobile.Server.Config;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
namespace LunchScheduler.Site.Controllers
{
/// <summary>
///
/// </summary>
[MobileAppController]
[AuthRequired]
public class InviteController : ControllerBase
{
/// <summary>
/// Set's a user's response to a lunch invite.
/// </summary>
public async Task Post([FromBody]Invite invite)
{
var lunch = await Db.Lunches.FirstOrDefaultAsync(x =>
x.Invites.Any(y => y.Id == invite.Id));
if (null == lunch)
{
return;
}
lunch.Invites.First(x => x.Id == invite.Id).Response = invite.Response;
await Db.SaveChangesAsync();
}
}
}

Просмотреть файл

@ -0,0 +1,97 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using Microsoft.Azure.Mobile.Server.Config;
using Newtonsoft.Json.Linq;
using RestSharp;
using RestSharp.Authenticators;
using System.Collections.Generic;
using System.Linq;
namespace LunchScheduler.Site.Controllers
{
/// <summary>
/// Handles suggesting locations to have lunch.
/// </summary>
[MobileAppController]
[AuthRequired]
public class LocationsController : ControllerBase
{
/// <summary>
/// Yelp API consumer key. See https://www.yelp.com/developers/documentation/v2/authentication.
/// </summary>
private const string ConsumerKey = "<TODO: Your Yelp consumer key here>";
/// <summary>
/// Yelp API consumer secret. See https://www.yelp.com/developers/documentation/v2/authentication.
/// </summary>
private const string ConsumerSecret = "<TODO: Your Yelp consumer secret here>";
/// <summary>
/// Yelp API token. See https://www.yelp.com/developers/documentation/v2/authentication.
/// </summary>
private const string Token = "<TODO: Your Yelp token here>";
/// <summary>
/// Yelp API token secret. See https://www.yelp.com/developers/documentation/v2/authentication.
/// </summary>
private const string TokenSecret = "<TODO: Your Yelp token secret here>";
/// <summary>
/// Base url for the Yelp API.
/// </summary>
private const string BaseYelpUrl = "http://api.yelp.com/v2/search";
/// <summary>
/// Gets top-rated lunch locations near the given coordinates using the Yelp API.
/// </summary>
public IEnumerable<Location> Get(double latitude, double longitude)
{
if (longitude == 0 || latitude == 0)
{
return null;
}
var client = new RestClient(BaseYelpUrl)
{
Authenticator = OAuth1Authenticator.ForProtectedResource(
ConsumerKey, ConsumerSecret, Token, TokenSecret)
};
var request = new RestRequest(Method.GET);
request.AddParameter("term", "lunch");
request.AddParameter("ll", $"{latitude},{longitude}");
var result = client.Execute(request);
JToken token = JToken.Parse(result.Content);
var yelpLocations = token["businesses"].Select(x => new Location
{
Name = x["name"].ToString(),
ImageUrl = x["image_url"].ToString(),
Address = x["location"]["address"].First.ToString(),
Rating = float.Parse(x["rating"].ToString())
}).OrderBy(x => x.Rating);
return yelpLocations;
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using LunchScheduler.Site.Helpers;
using Microsoft.Azure.Mobile.Server.Config;
using System.Data.Entity;
using System.Threading.Tasks;
using System.Web.Http;
namespace LunchScheduler.Site.Controllers
{
/// <summary>
/// Handles user login.
/// </summary>
[MobileAppController]
public class LoginController : ControllerBase
{
/// <summary>
/// Authenticates a user.
/// </summary>
public async Task<User> Post([FromBody]ProviderInfo request)
{
var user = await Db.Users.FirstOrDefaultAsync(x => x.Email == request.Email);
if (null == user)
{
return null;
}
ProviderInfo info = await IdpHelper.GetUserInfoAsync(request.Provider, request.Token);
if (info.Email == request.Email)
{
user.Auth = info;
return user;
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,128 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using LunchScheduler.Site.Helpers;
using Microsoft.Azure.Mobile.Server.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
namespace LunchScheduler.Site.Controllers
{
/// <summary>
/// Gets existing lunches and creates new ones.
/// </summary>
[MobileAppController]
[AuthRequired]
public class LunchController : ControllerBase
{
/// <summary>
/// Gets all lunches the current user has created or is invited to.
/// </summary>
public IEnumerable<Lunch> Get()
{
DateTime lookback = DateTime.UtcNow.Subtract(new TimeSpan(24, 0, 0));
var result = Db.Lunches.Where(x => (x.Host.Email == User.Identity.Name ||
x.Invites.Any(y => y.User.Email == User.Identity.Name)) &&
x.Time > lookback).ToList();
return result;
}
/// <summary>
/// Inserts a new lunch into the database, then notifies all invitees.
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Lunch lunch)
{
Db.Lunches.Add(lunch);
await Db.SaveChangesAsync();
await NotifyAsync(lunch);
return new HttpResponseMessage(HttpStatusCode.OK);
}
/// <summary>
/// Notifies all invitees about a new lunch.
/// </summary>
private async Task NotifyAsync(Lunch lunch)
{
foreach (var invite in lunch.Invites)
{
foreach (var textDevice in invite.User.Devices?.Where(x => x.NotificationType == NotificationType.Text))
{
NotifyText(textDevice, lunch);
}
await NotifyWindowsAppAsync(new Device {Tag = invite.User.Email}, lunch);
}
foreach (var textDevice in lunch.Host.Devices.Where(x => x.NotificationType == NotificationType.Text))
{
NotifyText(textDevice, lunch);
}
await NotifyWindowsAppAsync(new Device { Tag = lunch.Host.Email }, lunch);
}
/// <summary>
/// Sends a toast to Windows devices about the new lunch.
/// </summary>
private async Task NotifyWindowsAppAsync(Device device, Lunch lunch)
{
string message = $"{lunch.Host.Name} wants to get lunch {GetDateTime(lunch)}!";
await NotificationHelper.SendWindowsNotificationAsync(device, message);
}
/// <summary>
/// Sends a text notification to a device about the new lunch.
/// </summary>
private void NotifyText(Device device, Lunch lunch)
{
string message = $"{lunch.Host.Name} invited you to lunch at {lunch.Location.Name} " +
$"{GetDateTime(lunch)}!";
NotificationHelper.SendText(device, message);
}
/// <summary>
/// Converts a DateTime to a nicer phrased string (e.g., today/tomorrow).
/// </summary>
private string GetDateTime(Lunch lunch)
{
string time = lunch.Time.ToShortTimeString();
if (DateTime.Now.Date == lunch.Time.Date)
{
return "today at " + time;
}
else if (DateTime.Now.Date.AddDays(1) == lunch.Time.Date)
{
return "tomorrow at " + time;
}
else
{
return $"on {DateTime.Now.ToString("MM/dd")} at {time}";
}
}
}
}

Просмотреть файл

@ -0,0 +1,57 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using LunchScheduler.Site.Helpers;
using Microsoft.Azure.Mobile.Server.Config;
using System.Threading.Tasks;
using System.Web.Http;
namespace LunchScheduler.Site.Controllers
{
/// <summary>
/// Handles user registration.
/// </summary>
[MobileAppController]
public class RegisterController : ControllerBase
{
/// <summary>
/// Registers a new user.
/// </summary>
public async Task<User> Post([FromBody]ProviderInfo info)
{
ProviderInfo result = await IdpHelper.GetUserInfoAsync(info.Provider, info.Token);
var user = new User
{
Name = result.Name,
Email = result.Email,
Auth = result,
PhotoUrl = result.PhotoUrl
};
Db.Users.Add(user);
await Db.SaveChangesAsync();
return user;
}
}
}

Просмотреть файл

@ -0,0 +1,47 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using Microsoft.Azure.Mobile.Server.Config;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
namespace LunchScheduler.Site.Controllers
{
/// <summary>
/// Enables wildcard searching of usernames.
/// </summary>
[MobileAppController]
public class UsernamesController : ControllerBase
{
/// <summary>
/// Gets usernames and emails that start with the given string.
/// </summary>
public IEnumerable<JToken> Get(string name)
{
var matches = Db.Users.Where(x => x.Name.Contains(name)).ToList();
return matches.Select(x => JToken.FromObject(new { x.Name, x.Email }));
}
}
}

Просмотреть файл

@ -0,0 +1,77 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using LunchScheduler.Site.Helpers;
using Microsoft.Azure.Mobile.Server.Config;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
namespace LunchScheduler.Site.Controllers
{
/// <summary>
/// Provides info about users and updates users.
/// </summary>
[MobileAppController]
public class UsersController : ControllerBase
{
/// <summary>
/// Gets a user with the given email, but with secure info (auth, devices) removed.
/// </summary>
public async Task<User> Get([FromUri]string email)
{
var user = await Db.Users.FirstOrDefaultAsync(x => x.Email == email);
if (null != user)
{
user.Auth = null;
user.Devices = null;
}
return user;
}
/// <summary>
/// Updates a user.
/// </summary>
[AuthRequired]
public async Task<HttpResponseMessage> Post([FromBody]User update)
{
var match = await Db.Users.FirstOrDefaultAsync(x => x.Email == update.Email);
if (match?.Devices.Count < update.Devices.Count)
{
foreach (var device in update.Devices.Where(x => match.Devices.All(y => y.Id != x.Id)))
{
device.PhoneNumber = NotificationHelper.ScrubNumber(device.PhoneNumber);
NotificationHelper.SendText(device,
"Welcome to Lunchtime! Please respond to verify your number.");
}
}
await Db.SaveChangesAsync();
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
}

4
site/FodyWeavers.xml Normal file
Просмотреть файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<AsyncErrorHandler />
</Weavers>

Просмотреть файл

@ -0,0 +1,49 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System;
using System.Diagnostics;
namespace LunchScheduler.Site.Helpers
{
/// <summary>
/// Enables automatic catching of unahandled exceptions using AsyncErrorHandler.Fody.
/// </summary>
public static class AsyncErrorHandler
{
/// <summary>
/// Logs unhandled exceptions and breaks the debugger (if attached),
/// even if the exception occurs within an async method where
/// exceptions would normally be swallowed.
/// </summary>
public static void HandleException(Exception ex)
{
Debug.WriteLine("Unhandled exception: " + ex.Message);
if (Debugger.IsAttached)
{
Debugger.Break();
}
}
}
}

158
site/Helpers/IdpHelper.cs Normal file
Просмотреть файл

@ -0,0 +1,158 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using Newtonsoft.Json.Linq;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace LunchScheduler.Site.Helpers
{
/// <summary>
/// Wrapper for authentication calls to identity providers (IDPs).
/// </summary>
public static class IdpHelper
{
/// <summary>
/// Facebook App Id. Set at https://developers.facebook.com/apps.
/// </summary>
private const string AppId = "<TODO: Your Facebook app Id here>";
/// <summary>
/// Facebook app secret. Set at https://developers.facebook.com/apps.
/// </summary>
private const string AppSecret = "<TODO: Your Facebook app secret here>";
/// <summary>
/// Gets info on a user from their provider's API.
/// </summary>
public static Task<ProviderInfo> GetUserInfoAsync(ProviderType type, string token)
{
try
{
switch (type)
{
case ProviderType.Msa:
return GetMsaUserInfoAsync(token);
case ProviderType.Facebook:
return GetFacebookUserInfoAsync(token);
default:
return null;
}
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// Gets info on a MSA user via the Live API using the given token.
/// </summary>
private static async Task<ProviderInfo> GetMsaUserInfoAsync(string token)
{
using (var client = new HttpClient())
{
var result = await client.GetAsync(@"https://apis.live.net/v5.0/me?access_token=" +
token);
string content = await result.Content.ReadAsStringAsync();
var jtoken = JToken.Parse(content);
if (null != jtoken["error"])
{
return null;
}
var info = new ProviderInfo
{
AccountId = jtoken["id"].ToString(),
Name = jtoken["name"].ToString(),
Token = token,
Provider = ProviderType.Msa,
Email = jtoken["emails"]["account"].ToString(),
PhotoUrl = "https://apis.live.net/v5.0/" + jtoken["id"] + "/picture",
};
return info;
}
}
/// <summary>
/// Gets info on a Facebook user via the Graph API using the given token.
/// </summary>
private static async Task<ProviderInfo> GetFacebookUserInfoAsync(string token)
{
using (var client = new HttpClient())
{
var response = await client.GetAsync(
"https://graph.facebook.com/me?fields=id,name,email,picture.width(500)&access_token="
+ token);
string content = await response.Content.ReadAsStringAsync();
JToken jtoken = JToken.Parse(content);
if (null != jtoken["error"])
{
return null;
}
var info = new ProviderInfo
{
AccountId = jtoken["id"].ToString(),
Email = jtoken["email"].ToString(),
Name = jtoken["name"].ToString(),
PhotoUrl = jtoken["picture"]["data"]["url"].ToString(),
Token = token,
Provider = ProviderType.Facebook
};
await RequestLongLivedTokenAsync(info);
return info;
}
}
/// <summary>
/// Requests a long-lived Facebook token and replaces the existing token with it.
/// Long-lived tokens last 60 days and refresh automatically when used.
/// </summary>
private static async Task RequestLongLivedTokenAsync(ProviderInfo info)
{
Uri uri = new Uri(Uri.EscapeUriString($@"https://graph.facebook.com/oauth/" +
$"access_token?grant_type=fb_exchange_token&client_id={AppId}&" +
$"client_secret={AppSecret}&fb_exchange_token={info.Token}"));
using (var client = new HttpClient())
{
var response = await client.GetAsync(uri);
var content = await response.Content.ReadAsStringAsync();
try
{
string longToken = content.Split('=', '&')[1];
if (longToken.Length > 50)
{
info.Token = longToken;
}
}
catch (IndexOutOfRangeException)
{
// If the swap fails, do nothing.
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,123 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using Microsoft.Azure.NotificationHubs;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Twilio;
using Twilio.Lookups;
using Twilio.TwiML;
using Task = System.Threading.Tasks.Task;
namespace LunchScheduler.Site.Helpers
{
/// <summary>
/// Wrapper for sending/receiving notifications using Azure notification hubs and Twilio.
/// </summary>
public static class NotificationHelper
{
/// <summary>
/// SID for Twilio. Configure at https://www.twilio.com/user/account/phone-numbers/incoming.
/// </summary>
private const string TwilioSid = "<TODO: Your Twilio SID here>";
/// <summary>
/// Key for twilio. Configure at https://www.twilio.com/user/account/phone-numbers/incoming.
/// </summary>
private const string TwilioKey = "<TODO: Your Twilio key here>";
/// <summary>
/// Outgoing phone number for Twilio. Configure at https://www.twilio.com/user/account/phone-numbers/incoming.
/// </summary>
private const string TwilioOutgoingNumber = "<TODO: Your Twilio number here>";
/// <summary>
/// Name of the Azure notification hub.
/// </summary>
private const string HubName = "<TODO: Your hub name here>";
/// <summary>
/// Endpoint for the Azure notification hub. Configure at https://www.portal.azure.com
/// </summary>
private const string HubEndpoint = "<TODO: Your endpoint here>";
/// <summary>
/// Client for interacting with the Azure notification hubs REST API.
/// </summary>
private static readonly NotificationHubClient HubClient =
NotificationHubClient.CreateClientFromConnectionString(
HubEndpoint, HubName);
/// <summary>
/// Client for interacting with the Twilio REST API.
/// </summary>
private static readonly TwilioRestClient TwilioClient = new TwilioRestClient(TwilioSid, TwilioKey);
/// <summary>
/// Client for interacting with the Twilio Lookups REST API.
/// </summary>
private static readonly LookupsClient LookupClient = new LookupsClient(TwilioSid, TwilioKey);
/// <summary>
/// Sends a toast to Windows over the WNS using the Azure notification hub.
/// </summary>
public static async Task SendWindowsNotificationAsync(Device device, string text)
{
string payload = "<?xml version=\"1.0\" encoding=\"utf-8\"?><toast>" +
"<visual><binding template=\"ToastText01\"><text id=\"1\">" + text +
"</text></binding></visual></toast>";
await HubClient.SendNotificationAsync(new WindowsNotification(payload), new[] { device.Tag });
}
/// <summary>
/// Sends a text to a mobile phone using Twilio.
/// </summary>
public static void SendText(Device device, string text) =>
TwilioClient.SendMessage(TwilioOutgoingNumber, ScrubNumber(device.PhoneNumber), text);
/// <summary>
/// Creates an HTTP response to reply to a text received via Twilio.
/// </summary>
public static HttpResponseMessage CreateTwilioHttpResponse(string message)
{
var twilioResponse = new TwilioResponse();
twilioResponse.Message(message);
var httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(twilioResponse.Element.ToString())
};
httpResponse.Content.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
return httpResponse;
}
/// <summary>
/// Converts a fuzzy phone number string to an E.164 compliant number.
/// </summary>
public static string ScrubNumber(string number) =>
LookupClient.GetPhoneNumber(number)?.PhoneNumber;
}
}

Просмотреть файл

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{9994333A-C9EB-43A9-B3D1-7E40B75651C0}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>LunchScheduler.Site</RootNamespace>
<AssemblyName>LunchScheduler.Site</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<UseIISExpress>true</UseIISExpress>
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
<UseGlobalApplicationHostFile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
<HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="AutoMapper.Net4, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
<HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.Mobile.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.Mobile.Server.1.0.119.0\lib\net45\Microsoft.Azure.Mobile.Server.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.Mobile.Server.Authentication, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.Mobile.Server.Authentication.1.0.119.0\lib\net45\Microsoft.Azure.Mobile.Server.Authentication.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.Mobile.Server.CrossDomain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.Mobile.Server.CrossDomain.1.0.119.0\lib\net45\Microsoft.Azure.Mobile.Server.CrossDomain.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.Mobile.Server.Entity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.Mobile.Server.Entity.1.0.119.0\lib\net45\Microsoft.Azure.Mobile.Server.Entity.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.Mobile.Server.Home, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.Mobile.Server.Home.1.0.119.0\lib\net45\Microsoft.Azure.Mobile.Server.Home.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.Mobile.Server.Notifications, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.Mobile.Server.Notifications.1.0.119.0\lib\net45\Microsoft.Azure.Mobile.Server.Notifications.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.Mobile.Server.Quickstart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.Mobile.Server.Quickstart.1.0.119.0\lib\net45\Microsoft.Azure.Mobile.Server.Quickstart.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.Mobile.Server.Tables, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.Mobile.Server.Tables.1.0.119.0\lib\net45\Microsoft.Azure.Mobile.Server.Tables.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.NotificationHubs, Version=2.16.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.NotificationHubs.1.0.4\lib\net45-full\Microsoft.Azure.NotificationHubs.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Data.Edm, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.Edm.5.6.4\lib\net40\Microsoft.Data.Edm.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Data.OData, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.OData.5.6.4\lib\net40\Microsoft.Data.OData.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.WindowsAzure.Configuration, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.1.0\lib\net40\Microsoft.WindowsAzure.Configuration.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="RestSharp, Version=105.2.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.105.2.2\lib\net46\RestSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=4.0.20511.1437, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.205111437\lib\net45\System.IdentityModel.Tokens.Jwt.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Spatial, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\System.Spatial.5.6.4\lib\net40\System.Spatial.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Cors, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.Cors.5.2.3\lib\net45\System.Web.Cors.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data.Services.Client" />
<Reference Include="System.Web.Http.Cors, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Cors.5.2.3\lib\net45\System.Web.Http.Cors.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http.OData, Version=5.5.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.OData.5.5.1\lib\net45\System.Web.Http.OData.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Routing" />
<Reference Include="System.Web.Services" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="Twilio.Api, Version=3.4.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Twilio.4.7.1\lib\3.5\Twilio.Api.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Twilio.Lookups, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Twilio.Lookups.1.1.0\lib\3.5\Twilio.Lookups.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Twilio.Twiml, Version=3.4.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Twilio.TwiML.3.5.0\lib\3.5\Twilio.Twiml.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Helpers\AsyncErrorHandler.cs" />
<Compile Include="AuthRequired.cs" />
<Compile Include="Controllers\ControllerBase.cs" />
<Compile Include="Controllers\DeviceController.cs" />
<Compile Include="Controllers\InviteController.cs" />
<Compile Include="Controllers\LocationsController.cs" />
<Compile Include="Controllers\LoginController.cs" />
<Compile Include="Controllers\LunchController.cs" />
<Compile Include="Controllers\RegisterController.cs" />
<Compile Include="Controllers\UsernamesController.cs" />
<Compile Include="Controllers\UsersController.cs" />
<Compile Include="LunchSchedulerContext.cs" />
<Compile Include="Helpers\IdpHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Startup.cs" />
<Compile Include="Helpers\NotificationHelper.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="FodyWeavers.xml" />
<Content Include="Web.config" />
<Content Include="Web.Debug.config">
<DependentUpon>Web.config</DependentUpon>
</Content>
<Content Include="Web.Release.config">
<DependentUpon>Web.config</DependentUpon>
</Content>
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />
</ItemGroup>
<ItemGroup>
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\models\LunchScheduler.Models.csproj">
<Project>{633b2b38-c19d-4c17-ba1b-22d549625f0f}</Project>
<Name>LunchScheduler.Models</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="Properties\PublishProfiles\LunchtimeApp - Web Deploy %282%29.pubxml" />
<None Include="Properties\PublishProfiles\LunchtimeApp - Web Deploy.pubxml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
</Target>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>16274</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:16274/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
<Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" />
<Import Project="..\packages\Fody.1.29.2\build\dotnet\Fody.targets" Condition="Exists('..\packages\Fody.1.29.2\build\dotnet\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Fody.1.29.2\build\dotnet\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.29.2\build\dotnet\Fody.targets'))" />
</Target>
</Project>

Просмотреть файл

@ -0,0 +1,72 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using LunchScheduler.Models;
using Microsoft.Azure.Mobile.Server.Tables;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
namespace LunchScheduler.Site
{
/// <summary>
/// The Entity Framework database context.
/// </summary>
public class LunchSchedulerContext : DbContext
{
/// <summary>
/// The name of the connection string in Web.config.
/// </summary>
private const string ConnectionStringName = "Name=MS_TableConnectionString";
/// <summary>
/// The Users table.
/// </summary>
public DbSet<User> Users => this.Set<User>();
/// <summary>
/// The lunches table.
/// </summary>
public DbSet<Lunch> Lunches => this.Set<Lunch>();
/// <summary>
/// Creates a new lunchtime context.
/// </summary>
public LunchSchedulerContext() : base(ConnectionStringName)
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<LunchSchedulerContext>());
Database.CommandTimeout = 180;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (x, y) => y.Single().ColumnType.ToString()));
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<Lunch>().ToTable("Lunches");
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System.Reflection;
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("LunchScheduler.Site")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LunchScheduler.Site")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("05ebc036-1bb1-46b6-afe9-2c06923b9ce0")]
// 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 Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше