├── .gitignore ├── Art └── apps.png ├── CoffeeCups.Droid ├── Assets │ └── AboutAssets.txt ├── CoffeeCups.Droid.csproj ├── Helpers │ └── Settings.cs ├── MainActivity.cs ├── Properties │ ├── AndroidManifest.xml │ └── AssemblyInfo.cs ├── Resources │ ├── AboutResources.txt │ ├── Resource.designer.cs │ ├── drawable-hdpi │ │ └── icon.png │ ├── drawable-xhdpi │ │ └── icon.png │ ├── drawable-xxhdpi │ │ └── icon.png │ ├── drawable │ │ └── icon.png │ ├── layout │ │ ├── Tabbar.axml │ │ └── toolbar.axml │ ├── values-v21 │ │ └── styles.xml │ └── values │ │ ├── Colors.xml │ │ ├── Strings.xml │ │ └── styles.xml ├── app.config └── packages.config ├── CoffeeCups.Shared ├── App.cs ├── Authentication │ └── AuthHandler.cs ├── CoffeeCups.Shared.projitems ├── CoffeeCups.shproj ├── Helpers │ └── Settings.cs ├── Model │ └── CupOfCoffee.cs ├── Services │ └── AzureService.cs ├── View │ ├── CoffeesPage.xaml │ └── CoffeesPage.xaml.cs └── ViewModel │ └── CoffeesViewModel.cs ├── CoffeeCups.UITests ├── AppInitializer.cs ├── CoffeeCups.UITests.csproj ├── Tests.cs ├── app.config └── packages.config ├── CoffeeCups.UWP ├── App.xaml ├── App.xaml.cs ├── Assets │ ├── LockScreenLogo.scale-200.png │ ├── SplashScreen.scale-200.png │ ├── Square150x150Logo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── StoreLogo.png │ └── Wide310x150Logo.scale-200.png ├── CoffeeCups.UWP.csproj ├── CoffeeCups.UWP.nuget.props ├── CoffeeCups.UWP.nuget.targets ├── MainPage.xaml ├── MainPage.xaml.cs ├── Package.appxmanifest ├── Properties │ ├── AssemblyInfo.cs │ └── Default.rd.xml ├── project.json └── project.lock.json ├── CoffeeCups.iOS ├── AppDelegate.cs ├── CoffeeCups.iOS.csproj ├── Entitlements.plist ├── Helpers │ └── Settings.cs ├── Info.plist ├── Main.cs ├── Resources │ ├── Images.xcassets │ │ └── AppIcons.appiconset │ │ │ └── Contents.json │ └── LaunchScreen.xib ├── app.config └── packages.config ├── CoffeeCups.sln ├── LICENSE.md ├── README.md └── ilovecoffee_Runtime ├── ilovecoffee.sln └── ilovecoffeeService ├── App_Start └── Startup.MobileApp.cs ├── Controllers ├── CupOfCoffeeController.cs ├── TodoItemController.cs └── ValuesController.cs ├── DataObjects ├── CupOfCoffee.cs └── TodoItem.cs ├── Models └── ilovecoffeeContext.cs ├── Properties ├── AssemblyInfo.cs └── PublishProfiles │ └── ilovecoffee - Web Deploy.pubxml ├── Startup.cs ├── Web.Debug.config ├── Web.Release.config ├── Web.config ├── ilovecoffeeService.csproj └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | *.userprefs 9 | 10 | # Xamarin Components 11 | Components/ 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | x64/ 18 | build/ 19 | bld/ 20 | [Bb]in/ 21 | [Oo]bj/ 22 | [Pp]ackages/ 23 | [Cc]omponents/ 24 | data/ 25 | .nuget/ 26 | .vs/ 27 | *.csproj.bak 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | #NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | *_i.c 43 | *_p.c 44 | *_i.h 45 | *.ilk 46 | *.meta 47 | *.obj 48 | *.pch 49 | *.pdb 50 | *.pgc 51 | *.pgd 52 | *.rsp 53 | *.sbr 54 | *.tlb 55 | *.tli 56 | *.tlh 57 | *.tmp 58 | *.tmp_proj 59 | *.log 60 | *.vspscc 61 | *.vssscc 62 | .builds 63 | *.pidb 64 | *.svclog 65 | *.scc 66 | 67 | # Chutzpah Test files 68 | _Chutzpah* 69 | 70 | # Visual C++ cache files 71 | ipch/ 72 | *.aps 73 | *.ncb 74 | *.opensdf 75 | *.sdf 76 | *.cachefile 77 | 78 | # Visual Studio profiler 79 | *.psess 80 | *.vsp 81 | *.vspx 82 | 83 | # TFS 2012 Local Workspace 84 | $tf/ 85 | 86 | # Guidance Automation Toolkit 87 | *.gpState 88 | 89 | # ReSharper is a .NET coding add-in 90 | _ReSharper*/ 91 | *.[Rr]e[Ss]harper 92 | *.DotSettings.user 93 | 94 | # JustCode is a .NET coding addin-in 95 | .JustCode 96 | 97 | # TeamCity is a build add-in 98 | _TeamCity* 99 | 100 | # DotCover is a Code Coverage Tool 101 | *.dotCover 102 | 103 | # NCrunch 104 | *.ncrunch* 105 | _NCrunch_* 106 | .*crunch*.local.xml 107 | 108 | # MightyMoose 109 | *.mm.* 110 | AutoTest.Net/ 111 | 112 | # Web workbench (sass) 113 | .sass-cache/ 114 | 115 | # Installshield output folder 116 | [Ee]xpress/ 117 | 118 | # DocProject is a documentation generator add-in 119 | DocProject/buildhelp/ 120 | DocProject/Help/*.HxT 121 | DocProject/Help/*.HxC 122 | DocProject/Help/*.hhc 123 | DocProject/Help/*.hhk 124 | DocProject/Help/*.hhp 125 | DocProject/Help/Html2 126 | DocProject/Help/html 127 | 128 | # Click-Once directory 129 | publish/ 130 | 131 | # Publish Web Output 132 | *.[Pp]ublish.xml 133 | *.azurePubxml 134 | 135 | # Windows Azure Build Output 136 | csx/ 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.dbproj.schemaview 151 | *.pfx 152 | *.publishsettings 153 | node_modules/ 154 | .DS_Store 155 | 156 | # RIA/Silverlight projects 157 | Generated_Code/ 158 | 159 | # Backup & report files from converting an old project file to a newer 160 | # Visual Studio version. Backup files are not needed, because we have git ;-) 161 | _UpgradeReport_Files/ 162 | Backup*/ 163 | UpgradeLog*.XML 164 | UpgradeLog*.htm 165 | 166 | # SQL Server files 167 | *.mdf 168 | *.ldf 169 | 170 | # Business Intelligence projects 171 | *.rdl.data 172 | *.bim.layout 173 | *.bim_*.settings 174 | 175 | # Microsoft Fakes 176 | FakesAssemblies/ 177 | -------------------------------------------------------------------------------- /Art/apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/app-coffeecups/4f49646ecd5fcba26e7ea7f6b0974f0699fe9b46/Art/apps.png -------------------------------------------------------------------------------- /CoffeeCups.Droid/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with your package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/CoffeeCups.Droid.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 7 | {3FA9FA7A-A746-41C1-A0EB-316C0BAA4ACF} 8 | Library 9 | CoffeeCups.Droid 10 | Assets 11 | Resources 12 | Resource 13 | Resources\Resource.designer.cs 14 | True 15 | true 16 | CoffeeCups.Droid 17 | Properties\AndroidManifest.xml 18 | 19 | 20 | v7.1 21 | 22 | 23 | true 24 | full 25 | false 26 | bin\Debug 27 | DEBUG; 28 | prompt 29 | 4 30 | None 31 | false 32 | 33 | 34 | full 35 | true 36 | bin\Release 37 | prompt 38 | 4 39 | false 40 | false 41 | 42 | 43 | false 44 | bin\iPhone\UITest 45 | 4 46 | ENABLE_TEST_CLOUD 47 | false 48 | armeabi-v7a;x86 49 | full 50 | 51 | 52 | 53 | ..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\FormsViewGroup.dll 54 | 55 | 56 | False 57 | ..\packages\Microsoft.Azure.Mobile.Client.4.0.1\lib\monoandroid70\Microsoft.Azure.Mobile.Client.dll 58 | 59 | 60 | False 61 | ..\packages\Microsoft.Azure.Mobile.Client.SQLiteStore.4.0.1\lib\netstandard1.4\Microsoft.Azure.Mobile.Client.SQLiteStore.dll 62 | 63 | 64 | 65 | 66 | ..\packages\Refractored.MvvmHelpers.1.3.0\lib\netstandard1.0\MvvmHelpers.dll 67 | 68 | 69 | ..\packages\Newtonsoft.Json.10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll 70 | 71 | 72 | ..\packages\PCLCrypto.2.0.147\lib\MonoAndroid23\PCLCrypto.dll 73 | 74 | 75 | ..\packages\PInvoke.BCrypt.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.BCrypt.dll 76 | 77 | 78 | ..\packages\PInvoke.Kernel32.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.Kernel32.dll 79 | 80 | 81 | ..\packages\PInvoke.NCrypt.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.NCrypt.dll 82 | 83 | 84 | ..\packages\PInvoke.Windows.Core.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.Windows.Core.dll 85 | 86 | 87 | ..\packages\Xam.Plugin.Connectivity.3.0.2\lib\MonoAndroid10\Plugin.Connectivity.dll 88 | 89 | 90 | ..\packages\Xam.Plugin.Connectivity.3.0.2\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll 91 | 92 | 93 | ..\packages\Xam.Plugins.Settings.3.0.1\lib\MonoAndroid10\Plugin.Settings.dll 94 | 95 | 96 | ..\packages\Xam.Plugins.Settings.3.0.1\lib\MonoAndroid10\Plugin.Settings.Abstractions.dll 97 | 98 | 99 | ..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\MonoAndroid\SQLitePCLRaw.batteries_green.dll 100 | 101 | 102 | ..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\MonoAndroid\SQLitePCLRaw.batteries_v2.dll 103 | 104 | 105 | ..\packages\SQLitePCLRaw.core.1.1.8\lib\MonoAndroid\SQLitePCLRaw.core.dll 106 | 107 | 108 | ..\packages\SQLitePCLRaw.lib.e_sqlite3.android.1.1.8\lib\MonoAndroid\SQLitePCLRaw.lib.e_sqlite3.dll 109 | 110 | 111 | ..\packages\SQLitePCLRaw.provider.e_sqlite3.android.1.1.8\lib\MonoAndroid\SQLitePCLRaw.provider.e_sqlite3.dll 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | ..\packages\Validation.2.4.15\lib\netstandard1.3\Validation.dll 120 | 121 | 122 | ..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Animated.Vector.Drawable.dll 123 | 124 | 125 | ..\packages\Xamarin.Android.Support.CustomTabs.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.CustomTabs.dll 126 | 127 | 128 | ..\packages\Xamarin.Android.Support.Design.23.3.0\lib\MonoAndroid43\Xamarin.Android.Support.Design.dll 129 | 130 | 131 | ..\packages\Xamarin.Android.Support.v4.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v4.dll 132 | 133 | 134 | ..\packages\Xamarin.Android.Support.v7.AppCompat.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.AppCompat.dll 135 | 136 | 137 | ..\packages\Xamarin.Android.Support.v7.CardView.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.CardView.dll 138 | 139 | 140 | ..\packages\Xamarin.Android.Support.v7.RecyclerView.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.RecyclerView.dll 141 | 142 | 143 | ..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Vector.Drawable.dll 144 | 145 | 146 | ..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Core.dll 147 | 148 | 149 | ..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Platform.dll 150 | 151 | 152 | ..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll 153 | 154 | 155 | ..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 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}. 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Helpers/Settings.cs: -------------------------------------------------------------------------------- 1 | /* 2 | // Helpers/Settings.cs This file was automatically added when you installed the Settings Plugin. If you are not using a PCL then comment this file back in to use it. 3 | using Plugin.Settings; 4 | using Plugin.Settings.Abstractions; 5 | 6 | namespace CoffeeCups.Droid.Helpers 7 | { 8 | /// 9 | /// This is the Settings static class that can be used in your Core solution or in any 10 | /// of your client applications. All settings are laid out the same exact way with getters 11 | /// and setters. 12 | /// 13 | public static class Settings 14 | { 15 | private static ISettings AppSettings 16 | { 17 | get 18 | { 19 | return CrossSettings.Current; 20 | } 21 | } 22 | 23 | #region Setting Constants 24 | 25 | private const string SettingsKey = "settings_key"; 26 | private static readonly string SettingsDefault = string.Empty; 27 | 28 | #endregion 29 | 30 | 31 | public static string GeneralSettings 32 | { 33 | get 34 | { 35 | return AppSettings.GetValueOrDefault(SettingsKey, SettingsDefault); 36 | } 37 | set 38 | { 39 | AppSettings.AddOrUpdateValue(SettingsKey, value); 40 | } 41 | } 42 | 43 | } 44 | }*/ -------------------------------------------------------------------------------- /CoffeeCups.Droid/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Android.App; 3 | using Android.Content.PM; 4 | using Android.OS; 5 | using Xamarin.Forms.Platform.Android; 6 | 7 | namespace CoffeeCups.Droid 8 | { 9 | [Activity (Label = "Coffee Cups", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] 10 | public class MainActivity : FormsAppCompatActivity 11 | { 12 | protected override void OnCreate (Bundle bundle) 13 | { 14 | 15 | ToolbarResource = Resource.Layout.Toolbar; 16 | TabLayoutResource = Resource.Layout.Tabbar; 17 | 18 | base.OnCreate (bundle); 19 | global::Xamarin.Forms.Forms.Init (this, bundle); 20 | 21 | Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init(); 22 | 23 | LoadApplication (new App ()); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using Android.App; 4 | 5 | // Information about this assembly is defined by the following attributes. 6 | // Change them to the values specific to your project. 7 | 8 | [assembly: AssemblyTitle("CoffeeCups.Droid")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Refractored LLC")] 12 | [assembly: AssemblyProduct("")] 13 | [assembly: AssemblyCopyright("Refractored LLC / James Montemagno")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 18 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 19 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 20 | 21 | [assembly: AssemblyVersion("1.0.0")] 22 | 23 | // The following attributes are used to specify the signing key for the assembly, 24 | // if desired. See the Mono documentation for more information about signing. 25 | 26 | //[assembly: AssemblyDelaySign(false)] 27 | //[assembly: AssemblyKeyFile("")] 28 | 29 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.axml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable/ 12 | icon.png 13 | 14 | layout/ 15 | main.axml 16 | 17 | values/ 18 | strings.xml 19 | 20 | In order to get the build system to recognize Android resources, set the build action to 21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 22 | instead operate on resource IDs. When you compile an Android application that uses resources, 23 | the build system will package the resources for distribution and generate a class called "R" 24 | (this is an Android convention) that contains the tokens for each one of the resources 25 | included. For example, for the above Resources layout, this is what the R class would expose: 26 | 27 | public class R { 28 | public class drawable { 29 | public const int icon = 0x123; 30 | } 31 | 32 | public class layout { 33 | public const int main = 0x456; 34 | } 35 | 36 | public class strings { 37 | public const int first_string = 0xabc; 38 | public const int second_string = 0xbcd; 39 | } 40 | } 41 | 42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main 43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first 44 | string in the dictionary file values/strings.xml. 45 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/app-coffeecups/4f49646ecd5fcba26e7ea7f6b0974f0699fe9b46/CoffeeCups.Droid/Resources/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/app-coffeecups/4f49646ecd5fcba26e7ea7f6b0974f0699fe9b46/CoffeeCups.Droid/Resources/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/app-coffeecups/4f49646ecd5fcba26e7ea7f6b0974f0699fe9b46/CoffeeCups.Droid/Resources/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/app-coffeecups/4f49646ecd5fcba26e7ea7f6b0974f0699fe9b46/CoffeeCups.Droid/Resources/drawable/icon.png -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/layout/Tabbar.axml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/layout/toolbar.axml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/values/Colors.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | #F2C500 4 | #E9972E 5 | #1E8E80 6 | #F5F5F5 7 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/values/Strings.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Hello World, Click Me! 4 | Hanselman.Android 5 | 6 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 5 | 6 | 20 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /CoffeeCups.Droid/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /CoffeeCups.Shared/App.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Xamarin.Forms; 4 | namespace CoffeeCups 5 | { 6 | public class App : Application 7 | { 8 | public App() 9 | { 10 | // The root page of your application 11 | MainPage = new NavigationPage(new CoffeesPage()) 12 | { 13 | BarTextColor = Color.White, 14 | BarBackgroundColor = Color.FromHex("#F2C500") 15 | }; 16 | } 17 | 18 | protected override void OnStart() 19 | { 20 | 21 | // Handle when your app starts 22 | } 23 | 24 | protected override void OnSleep() 25 | { 26 | // Handle when your app sleeps 27 | } 28 | 29 | protected override void OnResume() 30 | { 31 | // Handle when your app resumes 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /CoffeeCups.Shared/Authentication/AuthHandler.cs: -------------------------------------------------------------------------------- 1 |  2 | using Microsoft.WindowsAzure.MobileServices; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Net.Http; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using Xamarin.Forms; 13 | using System.Diagnostics; 14 | using CoffeeCups.Helpers; 15 | 16 | namespace CoffeeCups.Authentication 17 | { 18 | class AuthHandler : DelegatingHandler 19 | { 20 | 21 | private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1); 22 | private static bool isReauthenticating = false; 23 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 24 | { 25 | //Clone the request in case we need to send it again 26 | var clonedRequest = await CloneRequest(request); 27 | var response = await base.SendAsync(clonedRequest, cancellationToken); 28 | 29 | //If the token is expired or is invalid, then we need to either refresh the token or prompt the user to log back in 30 | if (response.StatusCode == HttpStatusCode.Unauthorized) 31 | { 32 | if (isReauthenticating) 33 | return response; 34 | 35 | var service = DependencyService.Get(); 36 | var client = service.Client; 37 | 38 | string authToken = client.CurrentUser.MobileServiceAuthenticationToken; 39 | await semaphore.WaitAsync(); 40 | //In case two threads enter this method at the same time, only one should do the refresh (or re-login), the other should just resend the request with an updated header. 41 | if (authToken != client.CurrentUser.MobileServiceAuthenticationToken) // token was already renewed 42 | { 43 | semaphore.Release(); 44 | return await ResendRequest(client, request, cancellationToken); 45 | } 46 | 47 | isReauthenticating = true; 48 | bool gotNewToken = false; 49 | try 50 | { 51 | 52 | gotNewToken = await RefreshToken(client); 53 | 54 | 55 | //Otherwise if refreshing the token failed or Facebook\Twitter is being used, prompt the user to log back in via the login screen 56 | if (!gotNewToken) 57 | { 58 | gotNewToken = await service.LoginAsync(); 59 | } 60 | } 61 | catch (System.Exception e) 62 | { 63 | Debug.WriteLine("Unable to refresh token: " + e); 64 | } 65 | finally 66 | { 67 | isReauthenticating = false; 68 | semaphore.Release(); 69 | } 70 | 71 | 72 | if (gotNewToken) 73 | { 74 | if (!request.RequestUri.OriginalString.Contains("/.auth/me")) //do not resend in this case since we're not using the return value of auth/me 75 | { 76 | //Resend the request since the user has successfully logged in and return the response 77 | return await ResendRequest(client, request, cancellationToken); 78 | } 79 | } 80 | } 81 | 82 | return response; 83 | } 84 | 85 | 86 | private async Task ResendRequest(IMobileServiceClient client, HttpRequestMessage request, CancellationToken cancellationToken) 87 | { 88 | // Clone the request 89 | var clonedRequest = await CloneRequest(request); 90 | 91 | // Set the authentication header 92 | clonedRequest.Headers.Remove("X-ZUMO-AUTH"); 93 | clonedRequest.Headers.Add("X-ZUMO-AUTH", client.CurrentUser.MobileServiceAuthenticationToken); 94 | 95 | // Resend the request 96 | return await base.SendAsync(clonedRequest, cancellationToken); 97 | } 98 | 99 | private async Task RefreshToken(IMobileServiceClient client) 100 | { 101 | 102 | 103 | try 104 | { 105 | await client.RefreshUserAsync(); 106 | return true; 107 | } 108 | catch (System.Exception e) 109 | { 110 | Debug.WriteLine("Unable to refresh user: " + e); 111 | } 112 | 113 | return false; 114 | } 115 | 116 | 117 | 118 | private async Task CloneRequest(HttpRequestMessage request) 119 | { 120 | var result = new HttpRequestMessage(request.Method, request.RequestUri); 121 | foreach (var header in request.Headers) 122 | { 123 | result.Headers.Add(header.Key, header.Value); 124 | } 125 | 126 | if (request.Content != null && request.Content.Headers.ContentType != null) 127 | { 128 | var requestBody = await request.Content.ReadAsStringAsync(); 129 | var mediaType = request.Content.Headers.ContentType.MediaType; 130 | result.Content = new StringContent(requestBody, Encoding.UTF8, mediaType); 131 | foreach (var header in request.Content.Headers) 132 | { 133 | if (!header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase)) 134 | { 135 | result.Content.Headers.Add(header.Key, header.Value); 136 | } 137 | } 138 | } 139 | 140 | return result; 141 | } 142 | } 143 | 144 | } -------------------------------------------------------------------------------- /CoffeeCups.Shared/CoffeeCups.Shared.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | cbca0e96-d552-4218-a0ba-dc4cdadbc99a 7 | 8 | 9 | CoffeeCups 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | CoffeesPage.xaml 20 | 21 | 22 | 23 | 24 | MSBuild:UpdateDesignTimeXaml 25 | 26 | 27 | -------------------------------------------------------------------------------- /CoffeeCups.Shared/CoffeeCups.shproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | cbca0e96-d552-4218-a0ba-dc4cdadbc99a 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /CoffeeCups.Shared/Helpers/Settings.cs: -------------------------------------------------------------------------------- 1 | // Helpers/Settings.cs 2 | using Plugin.Settings; 3 | using Plugin.Settings.Abstractions; 4 | using System; 5 | 6 | namespace CoffeeCups.Helpers 7 | { 8 | /// 9 | /// This is the Settings static class that can be used in your Core solution or in any 10 | /// of your client applications. All settings are laid out the same exact way with getters 11 | /// and setters. 12 | /// 13 | public static class Settings 14 | { 15 | private static ISettings AppSettings 16 | { 17 | get 18 | { 19 | return CrossSettings.Current; 20 | } 21 | } 22 | 23 | #region Setting Constants 24 | 25 | const string LastSyncKey = "last_sync"; 26 | static readonly DateTime LastSyncDefault = DateTime.Now.AddDays(-30); 27 | 28 | 29 | const string UserIdKey = "userid"; 30 | static readonly string UserIdDefault = string.Empty; 31 | 32 | const string AuthTokenKey = "authtoken"; 33 | static readonly string AuthTokenDefault = string.Empty; 34 | 35 | const string LoginAttemptsKey = "login_attempts"; 36 | const int LoginAttemptsDefault = 0; 37 | 38 | const string NeedsSyncKey = "needs_sync"; 39 | const bool NeedsSyncDefault = true; 40 | 41 | const string HasSyncedDataKey = "has_synced"; 42 | const bool HasSyncedDataDefault = false; 43 | 44 | #endregion 45 | 46 | 47 | public static string AuthToken 48 | { 49 | get 50 | { 51 | return AppSettings.GetValueOrDefault(AuthTokenKey, AuthTokenDefault); 52 | } 53 | set 54 | { 55 | AppSettings.AddOrUpdateValue(AuthTokenKey, value); 56 | } 57 | } 58 | 59 | public static string UserId 60 | { 61 | get 62 | { 63 | return AppSettings.GetValueOrDefault(UserIdKey, UserIdDefault); 64 | } 65 | set 66 | { 67 | AppSettings.AddOrUpdateValue(UserIdKey, value); 68 | } 69 | } 70 | 71 | public static bool IsLoggedIn 72 | { 73 | get 74 | { 75 | if (!AzureService.UseAuth) 76 | return true; 77 | 78 | return !string.IsNullOrWhiteSpace(UserId); 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /CoffeeCups.Shared/Model/CupOfCoffee.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CoffeeCups 4 | { 5 | public class CupOfCoffee 6 | { 7 | 8 | 9 | [Newtonsoft.Json.JsonProperty("Id")] 10 | public string Id { get; set; } 11 | /// 12 | /// Gets or sets the user identifier. 13 | /// 14 | /// The user identifier. 15 | [Newtonsoft.Json.JsonProperty("userId")] 16 | public string UserId { get; set; } 17 | 18 | /// 19 | /// Gets or sets the date UTC. 20 | /// 21 | /// The date UTC. 22 | public DateTime DateUtc { get; set;} 23 | 24 | /// 25 | /// Gets or sets a value indicating whether this made at home. 26 | /// 27 | /// true if made at home; otherwise, false. 28 | public bool MadeAtHome{ get; set; } 29 | 30 | /// 31 | /// Gets or sets the location of the coffee 32 | /// 33 | public string Location { get; set; } 34 | 35 | /// 36 | /// Gets or sets the OS of the user 37 | /// 38 | /// The OS 39 | public string OS { get; set; } 40 | 41 | 42 | [Newtonsoft.Json.JsonIgnore] 43 | public string DateDisplay { get { return DateUtc.ToLocalTime().ToString("d"); } } 44 | 45 | [Newtonsoft.Json.JsonIgnore] 46 | public string TimeDisplay { get { return DateUtc.ToLocalTime().ToString("t") + " " + OS.ToString(); } } 47 | 48 | [Newtonsoft.Json.JsonIgnore] 49 | public string AtHomeDisplay { get { return MadeAtHome ? "Made At Home" : Location; } } 50 | 51 | 52 | 53 | [Microsoft.WindowsAzure.MobileServices.Version] 54 | public string AzureVersion { get; set; } 55 | 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /CoffeeCups.Shared/Services/AzureService.cs: -------------------------------------------------------------------------------- 1 | //#define AUTH 2 | 3 | using System; 4 | using Microsoft.WindowsAzure.MobileServices; 5 | using Microsoft.WindowsAzure.MobileServices.Sync; 6 | using System.Threading.Tasks; 7 | using System.Collections.Generic; 8 | using Microsoft.WindowsAzure.MobileServices.SQLiteStore; 9 | using System.Diagnostics; 10 | using Xamarin.Forms; 11 | using CoffeeCups.Helpers; 12 | using CoffeeCups.Authentication; 13 | using CoffeeCups; 14 | using System.IO; 15 | using Plugin.Connectivity; 16 | 17 | 18 | 19 | [assembly: Dependency(typeof(AzureService))] 20 | namespace CoffeeCups 21 | { 22 | public class AzureService 23 | { 24 | 25 | public MobileServiceClient Client { get; set; } = null; 26 | IMobileServiceSyncTable coffeeTable; 27 | 28 | #if AUTH 29 | public static bool UseAuth { get; set; } = true; 30 | #else 31 | public static bool UseAuth { get; set; } = false; 32 | #endif 33 | 34 | public async Task Initialize() 35 | { 36 | if (Client?.SyncContext?.IsInitialized ?? false) 37 | return; 38 | 39 | 40 | var appUrl = "https://ENTER-APP-SERVICE-NAME.azurewebsites.net"; 41 | 42 | #if AUTH 43 | Client = new MobileServiceClient(appUrl, new AuthHandler()); 44 | 45 | if (!string.IsNullOrWhiteSpace (Settings.AuthToken) && !string.IsNullOrWhiteSpace (Settings.UserId)) { 46 | Client.CurrentUser = new MobileServiceUser (Settings.UserId); 47 | Client.CurrentUser.MobileServiceAuthenticationToken = Settings.AuthToken; 48 | } 49 | #else 50 | //Create our client 51 | 52 | Client = new MobileServiceClient(appUrl); 53 | 54 | #endif 55 | 56 | //InitialzeDatabase for path 57 | var path = "syncstore.db"; 58 | path = Path.Combine(MobileServiceClient.DefaultDatabasePath, path); 59 | 60 | //setup our local sqlite store and intialize our table 61 | var store = new MobileServiceSQLiteStore(path); 62 | 63 | //Define table 64 | store.DefineTable(); 65 | 66 | 67 | //Initialize SyncContext 68 | await Client.SyncContext.InitializeAsync(store); 69 | 70 | //Get our sync table that will call out to azure 71 | coffeeTable = Client.GetSyncTable(); 72 | 73 | 74 | } 75 | 76 | public async Task SyncCoffee() 77 | { 78 | try 79 | { 80 | if (!CrossConnectivity.Current.IsConnected) 81 | return; 82 | 83 | await coffeeTable.PullAsync("allCoffee", coffeeTable.CreateQuery()); 84 | 85 | await Client.SyncContext.PushAsync(); 86 | } 87 | catch (Exception ex) 88 | { 89 | Debug.WriteLine("Unable to sync coffees, that is alright as we have offline capabilities: " + ex); 90 | } 91 | 92 | } 93 | 94 | public async Task> GetCoffees() 95 | { 96 | //Initialize & Sync 97 | await Initialize(); 98 | await SyncCoffee(); 99 | 100 | return await coffeeTable.OrderBy(c => c.DateUtc).ToEnumerableAsync(); ; 101 | 102 | } 103 | 104 | public async Task AddCoffee(bool atHome, string location) 105 | { 106 | await Initialize(); 107 | 108 | var coffee = new CupOfCoffee 109 | { 110 | DateUtc = DateTime.UtcNow, 111 | MadeAtHome = atHome, 112 | OS = Device.RuntimePlatform, 113 | Location = location ?? string.Empty 114 | }; 115 | 116 | await coffeeTable.InsertAsync(coffee); 117 | 118 | await SyncCoffee(); 119 | //return coffee 120 | return coffee; 121 | } 122 | 123 | 124 | 125 | public async Task LoginAsync() 126 | { 127 | 128 | await Initialize(); 129 | 130 | var provider = MobileServiceAuthenticationProvider.Twitter; 131 | var uriScheme = "coffeecups"; 132 | 133 | 134 | #if __ANDROID__ 135 | var user = await Client.LoginAsync(Forms.Context, provider, uriScheme); 136 | 137 | #elif __IOS__ 138 | CoffeeCups.iOS.AppDelegate.ResumeWithURL = url => url.Scheme == uriScheme && Client.ResumeWithURL(url); 139 | var user = await Client.LoginAsync(GetController(), provider, uriScheme); 140 | 141 | #else 142 | var user = await Client.LoginAsync(provider, uriScheme); 143 | 144 | #endif 145 | if (user == null) 146 | { 147 | Settings.AuthToken = string.Empty; 148 | Settings.UserId = string.Empty; 149 | Device.BeginInvokeOnMainThread(async () => 150 | { 151 | await App.Current.MainPage.DisplayAlert("Login Error", "Unable to login, please try again", "OK"); 152 | }); 153 | return false; 154 | } 155 | else 156 | { 157 | Settings.AuthToken = user.MobileServiceAuthenticationToken; 158 | Settings.UserId = user.UserId; 159 | } 160 | 161 | return true; 162 | } 163 | 164 | 165 | #if __IOS__ 166 | UIKit.UIViewController GetController() 167 | { 168 | var window = UIKit.UIApplication.SharedApplication.KeyWindow; 169 | var root = window.RootViewController; 170 | if (root == null) 171 | return null; 172 | 173 | var current = root; 174 | while (current.PresentedViewController != null) 175 | { 176 | current = current.PresentedViewController; 177 | } 178 | 179 | return current; 180 | } 181 | #endif 182 | } 183 | } 184 | 185 | -------------------------------------------------------------------------------- /CoffeeCups.Shared/View/CoffeesPage.xaml: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 31 |