Robin-Manuel Thiel 2018-03-25 17:02:50 +02:00
Родитель cf0ac2c57f afaf31341f
Коммит d0c2b872c5
41 изменённых файлов: 359 добавлений и 126 удалений

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

@ -17,29 +17,36 @@ namespace ContosoMaintenance.WebAPI.Controllers
public SearchController(IConfiguration configuration)
{
serviceClient = new SearchServiceClient(configuration["AzureSearch:AzureSearchServiceName"], new SearchCredentials(configuration["AzureSearch:AzureSearchApiKey"]));
serviceClient = new SearchServiceClient(configuration["AzureSearch:AzureSearchServiceName"], new SearchCredentials(configuration["AzureSearch:AzureSearchApiKey"]));
}
[Route("/api/search/jobs")]
public async Task<List<Job>> Get(string keyword)
{
var sp = new SearchParameters();
var sp = new SuggestParameters();
sp.HighlightPreTag = "[";
sp.HighlightPostTag = "]";
sp.UseFuzzyMatching = true;
sp.MinimumCoverage = 50;
sp.Top = 100;
var indexClient = serviceClient.Indexes.GetClient("job-index");
var response = await indexClient.Documents.SearchAsync<Job>(keyword, sp);
var response = await indexClient.Documents.SuggestAsync<Job>(keyword, "suggestions", sp);
var jobList = new List<Job>();
foreach (var document in response.Results)
{
Job job = new Job
var job = new Job
{
Name = document.Document.Name,
Details = document.Document.Details
Name = document.Text,
Details = document.Document.Details,
Status = document.Document.Status,
};
jobList.Add(job);
}
return jobList;
return jobList;
}

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

@ -0,0 +1,39 @@
using System;
using Newtonsoft.Json;
namespace ContosoMaintenance.WebAPI.Models
{
public class GeoPoint
{
[JsonProperty("longitude")]
public double Longitude { get; set; }
[JsonProperty("latitude")]
public double Latitude { get; set; }
/// <summary>
/// This is here as a small hack due to ensure compatibilty between old data in our DB and new data.
/// </summary>
/// <value>The coordinates.</value>
double[] coordinates;
[Obsolete("Coordinates is deprecated, please use the Longitude and Latitude properties instead.")]
[JsonProperty("coordinates")]
public double[] Coordinates
{
get
{
if (coordinates == null)
return new double[] { Longitude, Latitude };
return coordinates;
}
set
{
coordinates = value;
Latitude = coordinates[0];
Longitude = coordinates[1];
}
}
}
}

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

@ -1,6 +1,6 @@
using System;
using Newtonsoft.Json;
using Microsoft.Azure.Documents.Spatial;
using Newtonsoft.Json;
namespace ContosoMaintenance.WebAPI.Models
{

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

@ -28,7 +28,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContosoMaintenance.WebAPI",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContosoMaintenance.Functions", "Backend\Functions\ContosoMaintenance.Functions.csproj", "{0980AA2E-1C59-4AD4-A8B4-C38C8676F323}"
EndProject
Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "ContosoMaintenance.Bot.WebApp", "Backend\BotBackend\ContosoMaintenance.Bot.WebApp.csproj", "{34B405E5-E0D0-47FE-B3E2-2C770E5983EF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -180,6 +182,7 @@ Global
{0980AA2E-1C59-4AD4-A8B4-C38C8676F323}.ReleaseBackend|iPhone.Build.0 = Release|Any CPU
{0980AA2E-1C59-4AD4-A8B4-C38C8676F323}.ReleaseBackend|iPhoneSimulator.ActiveCfg = Release|Any CPU
{0980AA2E-1C59-4AD4-A8B4-C38C8676F323}.ReleaseBackend|iPhoneSimulator.Build.0 = Release|Any CPU
{34B405E5-E0D0-47FE-B3E2-2C770E5983EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34B405E5-E0D0-47FE-B3E2-2C770E5983EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34B405E5-E0D0-47FE-B3E2-2C770E5983EF}.Debug|iPhone.ActiveCfg = Debug|Any CPU
@ -219,6 +222,7 @@ Global
{61BFA852-2AAC-42BB-9073-B8857D45FE3A} = {9F0FD859-1134-4C77-99E5-83703A2DE5A2}
{0980AA2E-1C59-4AD4-A8B4-C38C8676F323} = {A5E6EEE2-C985-4C48-A6FD-BD95BFAA25EA}
{34B405E5-E0D0-47FE-B3E2-2C770E5983EF} = {140A5FB1-CD49-4998-8196-74EF067452B3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {60FF5FF1-44FA-4082-8169-95F16F23BE13}

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

@ -2,7 +2,7 @@
using System.Collections.Generic;
using ContosoFieldService.Abstractions;
using ContosoFieldService.Helpers;
using ContosoFieldService.PageModels;
using ContosoFieldService.ViewModels;
using FreshMvvm;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
@ -41,18 +41,18 @@ namespace ContosoFieldService
tabbedNavigation.BarTextColor = (Color)Current.Resources["AccentColor"];
// Add first level navigationpages as tabs
tabbedNavigation.AddTab<JobsPageModel>("Jobs", "icon_jobs.png");
tabbedNavigation.AddTab<PartsPageModel>("Parts", "icon_parts.png");
tabbedNavigation.AddTab<ProfilePageModel>("Me", "icon_user.png");
tabbedNavigation.AddTab<JobsViewModel>("Jobs", "icon_jobs.png");
tabbedNavigation.AddTab<PartsViewModel>("Parts", "icon_parts.png");
tabbedNavigation.AddTab<ProfileViewModel>("Me", "icon_user.png");
MainPage = tabbedNavigation;
}
else
{
var navContainer = new CustomAndroidNavigation("AndroidNavigation");
navContainer.Init("Menu", "hamburger.png");
navContainer.AddPage<JobsPageModel>("Jobs");
navContainer.AddPage<PartsPageModel>("Parts");
navContainer.AddPage<ProfilePageModel>("Me");
navContainer.AddPage<JobsViewModel>("Jobs");
navContainer.AddPage<PartsViewModel>("Parts");
navContainer.AddPage<ProfileViewModel>("Me");
MainPage = navContainer;
}
}
@ -103,5 +103,19 @@ namespace ContosoFieldService
{
// Handle when your app resumes
}
protected override void OnAppLinkRequestReceived(Uri uri)
{
var data = uri.ToString().ToLowerInvariant();
//only if deep linking
if (!data.Contains("/parts/"))
return;
var id = data.Substring(data.LastIndexOf("/", StringComparison.Ordinal) + 1);
//Navigate based on id here.
base.OnAppLinkRequestReceived(uri);
}
}
}

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

@ -26,6 +26,8 @@
<PackageReference Include="Refractored.MvvmHelpers" Version="1.3.0" />
<PackageReference Include="Humanizer" Version="2.2.0" />
<PackageReference Include="Corcav.Behaviors" Version="2.3.7" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.3.4" />
<PackageReference Include="Xamarin.FFImageLoading.Transformations" Version="2.3.4" />
<PackageReference Include="Xam.Plugin.Geolocator" Version="4.2.0" />
<PackageReference Include="Polly" Version="5.8.0" />
<PackageReference Include="Plugin.VersionTracking" Version="2.1.0" />
@ -50,19 +52,18 @@
<PackageReference Include="MonkeyCache" Version="0.1.0.1-beta" />
<PackageReference Include="MonkeyCache.FileStore" Version="0.1.0.1-beta" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.2.1" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.3.5" />
<PackageReference Include="Xamarin.FFImageLoading.Transformations" Version="2.3.5" />
<PackageReference Include="Plugin.Share" Version="7.1.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="Pages\" />
<Folder Include="PageModels\" />
<Folder Include="ViewModels\" />
<Folder Include="Helpers\" />
<Folder Include="Resources\" />
<Folder Include="Models\" />
<Folder Include="Services\" />
<Folder Include="Pages\Jobs\" />
<Folder Include="PageModels\Jobs\" />
<Folder Include="PageModels\Android\" />
<Folder Include="ViewModels\Jobs\" />
<Folder Include="ViewModels\Android\" />
<Folder Include="Pages\Android\" />
<Folder Include="Pages\Profile\" />
<Folder Include="Abstractions\" />

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

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using ContosoFieldService.PageModels.Android;
using ContosoFieldService.ViewModels.Android;
using FormsToolkit;
using FreshMvvm;
using Xamarin.Forms;
@ -71,7 +71,7 @@ namespace ContosoFieldService.Helpers
protected virtual void CreateMenuPage(string menuPageTitle, string menuIcon = null)
{
var menuPage = FreshPageModelResolver.ResolvePageModel<MenuPageModel>();
var menuPage = FreshPageModelResolver.ResolvePageModel<MenuViewModel>();
menuPage.Title = menuPageTitle;
MessagingService.Current.Subscribe<string>("NavigationTriggered", (x, args) =>

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

@ -2,7 +2,7 @@
using System.Collections.ObjectModel;
using ContosoFieldService.Models;
using MvvmHelpers;
using ContosoFieldService.PageModels;
using ContosoFieldService.ViewModels;
using System.Linq;
namespace ContosoFieldService.Helpers

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

@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using ContosoFieldService.Models;
using Xamarin.Forms;
namespace ContosoFieldService.Helpers
{
@ -20,5 +24,59 @@ namespace ContosoFieldService.Helpers
return $"https://www.gravatar.com/avatar/{hash}?s=512";
}
public static FormattedString ConvertNameToFormattedString(this Job job)
{
//We want to return a FormattedString which is a Xamarin.Forms Type that allows us to style text elements
var formattedString = new FormattedString();
//We'll use regex to nd content between square brackets [contents]
var regexPattern = @"\[(\w*)\]";
//Lets create a MatchCollection which will contain any matches from our job.name.
var patternMatches = Regex.Matches(job.Name, regexPattern);
System.Diagnostics.Debug.WriteLine($"Text: {job.Name}");
//If the name doesn't contain a matches then we just a default FormattedString with the name set. No extra work is required.
if (patternMatches.Count == 0)
{
formattedString = new FormattedString { Spans = { new Span { Text = job.Name } } };
}
else
{
//We create a list of matched wordsready for use in building the FormattedStrings property.
var highlightedWords = new List<string>();
//We loop through the matches and copy the values to a list of strings for easier use.
foreach (Match match in patternMatches)
{
highlightedWords.Add(RemoveBrackets(match.Value));
}
//We split the name input parts based on our RegEx. "Hello [World]" would become an array of string containing two items, "Hello" and "World";
var splitList = Regex.Split(job.Name, regexPattern);
//We then loop through each subString and add a span, checking that the contents isn't contained in the highlightedWords property.
foreach (var subString in splitList)
{
if (highlightedWords.Contains(subString) == true)
//We have found that the text is a highlighted word and thus needs to be bold.
formattedString.Spans.Add(new Span { Text = subString, FontAttributes = FontAttributes.Bold });
else
//The text isn't a highlighted item so we
formattedString.Spans.Add(new Span { Text = subString });
}
}
return formattedString;
}
static string RemoveBrackets(string input)
{
var removedPreTag = input.Replace("[", "");
var removedPostTag = removedPreTag.Replace("]", "");
return removedPostTag;
}
}
}

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

@ -1,7 +1,7 @@
using System;
using System.Linq;
using ContosoFieldService.Helpers;
using ContosoFieldService.PageModels;
using ContosoFieldService.ViewModels;
using Xamarin.Forms;
using ContosoFieldService.Pages;
@ -12,16 +12,16 @@ namespace ContosoFieldService
/// </summary>
public static class PageModelLocator
{
static JobsPageModel jobsPageModel;
public static JobsPageModel JobsPageModel => jobsPageModel ?? (jobsPageModel = new JobsPageModel { Jobs = DummyData.GetGroupedDummyJobs() });
static JobDetailsPageModel jobDetailsPageModel;
public static JobDetailsPageModel JobDetailsPageModel
static JobsViewModel jobsPageModel;
public static JobsViewModel JobsPageModel => jobsPageModel ?? (jobsPageModel = new JobsViewModel { Jobs = DummyData.GetGroupedDummyJobs() });
static JobDetailsViewModel jobDetailsPageModel;
public static JobDetailsViewModel JobDetailsPageModel
{
get
{
if (jobDetailsPageModel == null)
{
jobDetailsPageModel = new JobDetailsPageModel();
jobDetailsPageModel = new JobDetailsViewModel();
jobDetailsPageModel.Init(DummyData.GetDummyJobs().First());
}
@ -29,16 +29,16 @@ namespace ContosoFieldService
}
}
static PartsPageModel partsPageModel;
public static PartsPageModel PartsPageModel => partsPageModel ?? (partsPageModel = new PartsPageModel { Parts = DummyData.GetDummyParts() });
static PartDetailsPageModel partDetailsPageModel;
public static PartDetailsPageModel PartDetailsPageModel
static PartsViewModel partsPageModel;
public static PartsViewModel PartsPageModel => partsPageModel ?? (partsPageModel = new PartsViewModel { Parts = DummyData.GetDummyParts() });
static PartDetailsViewModel partDetailsPageModel;
public static PartDetailsViewModel PartDetailsPageModel
{
get
{
if (partDetailsPageModel == null)
{
partDetailsPageModel = new PartDetailsPageModel();
partDetailsPageModel = new PartDetailsViewModel();
partDetailsPageModel.Init(DummyData.GetDummyParts().First());
}
@ -46,8 +46,8 @@ namespace ContosoFieldService
}
}
static SettingsPageModel settingsPageModel;
public static SettingsPageModel SettingsPageModel = settingsPageModel ?? (settingsPageModel = new SettingsPageModel());
static SettingsViewModel settingsPageModel;
public static SettingsViewModel SettingsPageModel = settingsPageModel ?? (settingsPageModel = new SettingsViewModel());
}
}

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

@ -0,0 +1,38 @@
using System;
using Newtonsoft.Json;
namespace ContosoFieldService.Models
{
public class GeoPoint
{
[JsonProperty("longitude")]
public double Longitude { get; set; }
[JsonProperty("latitude")]
public double Latitude { get; set; }
double[] coordinates;
/// <summary>
/// This is here as a small hack due to ensure compatibilty between old data in our DB and new data.
/// </summary>
/// <value>The coordinates.</value>
[Obsolete("Coordinates is deprecated, please use the Longitude and Latitude properties instead.")]
[JsonProperty("coordinates")]
public double[] Coordinates
{
get
{
return coordinates;
}
set
{
coordinates = value;
Latitude = coordinates[0];
Longitude = coordinates[1];
}
}
}
}

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

@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Xamarin.Forms;
using System.Text.RegularExpressions;
using System.Linq;
using ContosoFieldService.Helpers;
namespace ContosoFieldService.Models
{
@ -25,8 +29,18 @@ namespace ContosoFieldService.Models
public Location Address { get; set; }
[JsonProperty("photos")]
public List<Photo> Photos { get; set; }
}
public List<Photo> Photos { get; set; }
[JsonIgnore]
public FormattedString NameAsFormattedString
{
get
{
return this.ConvertNameToFormattedString();
}
}
}
public enum JobType
{

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

@ -1,5 +1,4 @@
using System;
using Microsoft.Azure.Documents.Spatial;
using Newtonsoft.Json;
namespace ContosoFieldService.Models
@ -19,6 +18,6 @@ namespace ContosoFieldService.Models
public string ZipCode { get; set; }
[JsonProperty("point")]
public Point GeoPosition { get; set; }
public GeoPoint GeoPosition { get; set; }
}
}

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

@ -1,12 +0,0 @@
using System;
using FreshMvvm;
namespace ContosoFieldService.PageModels
{
public class BotPageModel : FreshBasePageModel
{
public BotPageModel()
{
}
}
}

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

@ -18,6 +18,15 @@
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem
x:Name="btnAddJob"
AutomationId="btnAddJob"
Icon="icon_share.png"
Command="{Binding ShareJobClicked}"/>
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout>

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

@ -1,7 +1,7 @@
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Forms.Maps;
using ContosoFieldService.PageModels;
using ContosoFieldService.ViewModels;
namespace ContosoFieldService.Pages
{
@ -18,10 +18,10 @@ namespace ContosoFieldService.Pages
base.OnAppearing();
// Setup map
var pageModel = BindingContext as JobDetailsPageModel;
if (pageModel?.Point?.Position?.Latitude != null && pageModel?.Point?.Position?.Longitude != null)
var pageModel = BindingContext as JobDetailsViewModel;
if (pageModel?.Point?.Latitude != null && pageModel?.Point?.Longitude != null)
{
var pos = new Position(pageModel.Point.Position.Latitude, pageModel.Point.Position.Longitude);
var pos = new Position(pageModel.Point.Latitude, pageModel.Point.Longitude);
// Move map to point
mapView.IsVisible = true;

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

@ -81,7 +81,7 @@
FontFamily="{StaticResource FontFamilyRegular}"
FontSize="{StaticResource FontSizeRegular}"
TextColor="{StaticResource TextColorItem}"
Text="{Binding Name}"
FormattedText="{Binding NameAsFormattedString}"
LineBreakMode="TailTruncation" />
<Label

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

@ -2,7 +2,7 @@
using System.Collections.Generic;
using Xamarin.Forms;
using ContosoFieldService.PageModels;
using ContosoFieldService.ViewModels;
using System.Linq;
namespace ContosoFieldService.Pages

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

@ -51,11 +51,11 @@ namespace ContosoFieldService.Services
// TODO: THERE IS A BUG WITH GEOSPARTIAL DATA AT THE MOMENT
// ----
// If the data isn't too old, we'll go ahead and return it rather than call the backend again.
//if (!Barrel.Current.IsExpired(key) && Barrel.Current.Exists(key))
//{
// var jobs = Barrel.Current.Get<IEnumerable<Job>>(key);
// return jobs.ToList();
//}
if (!Barrel.Current.IsExpired(key) && Barrel.Current.Exists(key))
{
var jobs = Barrel.Current.Get<IEnumerable<Job>>(key);
return jobs.ToList();
}
// Create an instance of the Refit RestService for the job interface.
var contosoMaintenanceApi = RestService.For<IJobServiceAPI>(Helpers.Constants.BaseUrl);
@ -65,7 +65,7 @@ namespace ContosoFieldService.Services
if (pollyResult.Result != null)
{
// Save jobs into the cache
Barrel.Current.Add(key, pollyResult.Result, TimeSpan.FromSeconds(5));
Barrel.Current.Add(key, pollyResult.Result, TimeSpan.FromMinutes(5));
return pollyResult.Result;
}
@ -94,6 +94,7 @@ namespace ContosoFieldService.Services
}
return null;
}
public async Task<Job> CreateJobAsync(Job job)

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

@ -4,16 +4,16 @@ using FormsToolkit;
using FreshMvvm;
using Xamarin.Forms;
namespace ContosoFieldService.PageModels.Android
namespace ContosoFieldService.ViewModels.Android
{
public class MenuPageModel : FreshBasePageModel
public class MenuViewModel : FreshBasePageModel
{
public ICommand NavigateCommand { get; set; }
public List<MenuItem> MenuItems { get; set; }
public MenuItem SelectedItem { get; set; }
public MenuPageModel()
public MenuViewModel()
{
NavigateCommand = new Command(NavigateToPageModel);

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

@ -1,9 +1,9 @@
using System;
using FreshMvvm;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class DashboardPageModel : FreshBasePageModel
public class DashboardViewModel : FreshBasePageModel
{
bool showLogin;
protected override void ViewIsAppearing(object sender, EventArgs e)
@ -12,7 +12,7 @@ namespace ContosoFieldService.PageModels
if (showLogin)
{
showLogin = false;
CoreMethods.PushPageModel<LoginPageModel>(null, true, false);
CoreMethods.PushPageModel<LoginViewModel>(null, true, false);
}
}

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

@ -7,9 +7,9 @@ using Xamarin.Forms;
using Spatial = Microsoft.Azure.Documents.Spatial;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class CreateNewJobPageModel : FreshBasePageModel
public class CreateNewJobViewModel : FreshBasePageModel
{
#region Bindable Properties
public string Name { get; set; }
@ -42,7 +42,7 @@ namespace ContosoFieldService.PageModels
try
{
var location = await Plugin.Geolocator.CrossGeolocator.Current.GetPositionAsync();
job.Address = new Location() { GeoPosition = new Spatial.Point(location.Longitude, location.Latitude) };
job.Address = new Location() { GeoPosition = new GeoPoint(){Longitude = location.Longitude, Latitude = location.Latitude} };
job = await jobApiService.CreateJobAsync(job);
Analytics.TrackEvent("New Job Created");

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

@ -1,13 +1,14 @@
using System;
using ContosoFieldService.Models;
using FreshMvvm;
using Humanizer;
using Microsoft.Azure.Documents.Spatial;
using Humanizer;
using Plugin.Share;
using Plugin.Share.Abstractions;
using Xamarin.Forms;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class JobDetailsPageModel : FreshBasePageModel
public class JobDetailsViewModel : FreshBasePageModel
{
public string Name { get; set; }
public string Details { get; set; }
@ -15,25 +16,26 @@ namespace ContosoFieldService.PageModels
public string DueDate { get; set; }
public string ContactName { get; set; }
public string CompanyName { get; set; }
public Microsoft.Azure.Documents.Spatial.Point Point { get; set; }
public GeoPoint Point { get; set; }
Job CurrentJob;
Job selectedJob;
public override void Init(object initData)
{
if (initData != null)
{
CurrentJob = (Job)initData;
Name = CurrentJob.Name;
Details = CurrentJob.Details;
selectedJob = (Job)initData;
Name = selectedJob.Name;
Details = selectedJob.Details;
DueDate = DateTime.Now.Humanize();
Age = CurrentJob.CreatedAt.Humanize();
Details = string.IsNullOrEmpty(CurrentJob.Details) ? "Not Supplied" : CurrentJob.Details;
Point = CurrentJob?.Address?.GeoPosition;
Age = selectedJob.CreatedAt.Humanize();
Details = string.IsNullOrEmpty(selectedJob.Details) ? "Not Supplied" : selectedJob.Details;
Point = selectedJob?.Address?.GeoPosition;
}
else
{
CurrentJob = new Job();
selectedJob = new Job();
}
}
@ -43,9 +45,28 @@ namespace ContosoFieldService.PageModels
{
return new Command(async () =>
{
await CoreMethods.PushPageModel<WorkingJobPageModel>(CurrentJob, true, true);
await CoreMethods.PushPageModel<WorkingJobViewModel>(selectedJob, true, true);
});
}
}
public Command ShareJobClicked
{
get
{
return new Command(async () =>
{
if (!CrossShare.IsSupported)
return;
await CrossShare.Current.Share(new ShareMessage
{
Title = selectedJob.Name,
Text = selectedJob.Details,
Url = $"{Helpers.Constants.BaseUrl}job/{selectedJob.Id}"
});
});
}
}
public Command EditJobClicked
@ -69,7 +90,7 @@ namespace ContosoFieldService.PageModels
if ("Delete" == await CoreMethods.DisplayActionSheet("You're going to delete this job and it'll be embrassing for all if we need to restore it...", "Canel", "Delete"))
{
var jobsService = new Services.JobsAPIService();
var deletedJob = await jobsService.DeleteJobByIdAsync(CurrentJob.Id);
var deletedJob = await jobsService.DeleteJobByIdAsync(selectedJob.Id);
if (deletedJob != null)
{
await CoreMethods.PopPageModel(deletedJob);
@ -89,10 +110,12 @@ namespace ContosoFieldService.PageModels
{
return new Command(async () =>
{
await CoreMethods.PushPageModel<PartsPageModel>();
await CoreMethods.PushPageModel<PartsViewModel>();
});
}
}
}
}

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

@ -10,9 +10,9 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MonkeyCache.FileStore;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class JobsPageModel : FreshBasePageModel
public class JobsViewModel : FreshBasePageModel
{
#region Bindable Properties
public ObservableRangeCollection<GroupedJobs> Jobs { get; set; }
@ -86,7 +86,7 @@ namespace ContosoFieldService.PageModels
{
return new Command<Job>(async (job) =>
{
await CoreMethods.PushPageModel<JobDetailsPageModel>(selectedJob);
await CoreMethods.PushPageModel<JobDetailsViewModel>(selectedJob);
});
}
}
@ -102,7 +102,7 @@ namespace ContosoFieldService.PageModels
{
new GroupedJobs("Search Results", searchResults)
});
});
});
}
}
@ -112,7 +112,7 @@ namespace ContosoFieldService.PageModels
{
return new Command(async () =>
{
await CoreMethods.PushPageModel<CreateNewJobPageModel>(null, false, true);
await CoreMethods.PushPageModel<CreateNewJobViewModel>(null, false, true);
});
}
}
@ -132,7 +132,7 @@ namespace ContosoFieldService.PageModels
base.ViewIsAppearing(sender, e);
if (Helpers.Settings.LoginViewShown == false)
await CoreMethods.PushPageModel<LoginPageModel>(null, true, true);
await CoreMethods.PushPageModel<LoginViewModel>(null, true, true);
await ReloadData(true);
}

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

@ -10,9 +10,9 @@ using Plugin.Media.Abstractions;
using ContosoFieldService.Models;
using ContosoFieldService.Services;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class WorkingJobPageModel : FreshBasePageModel
public class WorkingJobViewModel : FreshBasePageModel
{
JobsAPIService jobService = new JobsAPIService();
PhotoAPIService photoService = new PhotoAPIService();

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

@ -6,9 +6,9 @@ using System;
using ContosoFieldService.Services;
using ContosoFieldService.Helpers;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class LoginPageModel : FreshBasePageModel
public class LoginViewModel : FreshBasePageModel
{
readonly AuthenticationService authenticationService;
@ -45,7 +45,7 @@ namespace ContosoFieldService.PageModels
await CoreMethods.PopPageModel(true, true);
}));
public LoginPageModel()
public LoginViewModel()
{
authenticationService = new AuthenticationService();
}

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

@ -3,9 +3,9 @@ using FreshMvvm;
using ContosoFieldService.Models;
using Xamarin.Forms;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class PartDetailsPageModel : FreshBasePageModel
public class PartDetailsViewModel : FreshBasePageModel
{
public string Name { get; set; }
public string Manufacturer { get; set; }
@ -30,6 +30,7 @@ namespace ContosoFieldService.PageModels
PartNumber = Part.PartNumber;
PriceInUSD = Part.PriceInUSD;
ImageSource = Part.ImageSource;
CreateDeepLinkEntry();
}
else
{
@ -48,5 +49,27 @@ namespace ContosoFieldService.PageModels
});
}
}
void CreateDeepLinkEntry()
{
var url = $"{Helpers.Constants.BaseUrl}/part/{Part.Id}";
var entry = new AppLinkEntry
{
Title = Part.Name,
Description = Part.Manufacturer,
AppLinkUri = new Uri(url, UriKind.RelativeOrAbsolute),
IsLinkActive = true,
Thumbnail = Xamarin.Forms.ImageSource.FromFile("icon_greentool.png")
};
entry.KeyValues.Add("contentType", "Parts");
entry.KeyValues.Add("appName", "Field Service");
entry.KeyValues.Add("companyName", "Contoso Maintenance");
Application.Current.AppLinks.RegisterLink(entry);
}
}
}

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

@ -6,9 +6,9 @@ using Xamarin.Forms;
using ContosoFieldService.Services;
using System.Threading.Tasks;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class PartsPageModel : FreshBasePageModel
public class PartsViewModel : FreshBasePageModel
{
public ObservableRangeCollection<Part> Parts { get; set; }
public bool IsRefreshing
@ -59,7 +59,7 @@ namespace ContosoFieldService.PageModels
{
return new Command<Part>(async (part) =>
{
await CoreMethods.PushPageModel<PartDetailsPageModel>(part);
await CoreMethods.PushPageModel<PartDetailsViewModel>(part);
});
}
}
@ -83,7 +83,7 @@ namespace ContosoFieldService.PageModels
{
return new Command(async () =>
{
await CoreMethods.PushPageModel<CreateNewJobPageModel>(null, true, true);
await CoreMethods.PushPageModel<CreateNewJobViewModel>(null, true, true);
});
}
}

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

@ -0,0 +1,12 @@
using System;
using FreshMvvm;
namespace ContosoFieldService.ViewModels
{
public class BotViewModel : FreshBasePageModel
{
public BotViewModel()
{
}
}
}

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

@ -5,9 +5,9 @@ using Microsoft.AppCenter.Analytics;
using Xamarin.Forms;
using ContosoFieldService.Services;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class ProfilePageModel : FreshBasePageModel
public class ProfileViewModel : FreshBasePageModel
{
readonly AuthenticationService authenticationService;
@ -26,7 +26,7 @@ namespace ContosoFieldService.PageModels
{
Helpers.Settings.LoginViewShown = true;
Analytics.TrackEvent("User chatted to bot");
await CoreMethods.PushPageModel<BotPageModel>(true);
await CoreMethods.PushPageModel<BotViewModel>(true);
});
}
}
@ -39,12 +39,12 @@ namespace ContosoFieldService.PageModels
{
Helpers.Settings.LoginViewShown = true;
Analytics.TrackEvent("User chatted to bot");
await CoreMethods.PushPageModel<SettingsPageModel>(true);
await CoreMethods.PushPageModel<SettingsViewModel>(true);
});
}
}
public ProfilePageModel()
public ProfileViewModel()
{
authenticationService = new AuthenticationService();
}

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

@ -6,9 +6,9 @@ using Microsoft.AppCenter.Push;
using Plugin.VersionTracking;
using Xamarin.Forms;
namespace ContosoFieldService.PageModels
namespace ContosoFieldService.ViewModels
{
public class SettingsPageModel : FreshBasePageModel
public class SettingsViewModel : FreshBasePageModel
{
string baseUrl;
public string BaseUrl
@ -90,7 +90,7 @@ namespace ContosoFieldService.PageModels
}
}
public SettingsPageModel()
public SettingsViewModel()
{
Version = $"{CrossVersionTracking.Current.CurrentVersion} (Build {CrossVersionTracking.Current.CurrentBuild})";

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

@ -30,8 +30,6 @@
<DeviceSpecificBuild>false</DeviceSpecificBuild>
<MtouchVerbosity>
</MtouchVerbosity>
<CodesignProvision>
</CodesignProvision>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
<DebugType>pdbonly</DebugType>
@ -41,14 +39,14 @@
</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodesignKey>iPhone Distribution: Xamarin Inc</CodesignKey>
<CodesignKey>iPhone Developer: Michael James (354MN7UPEZ)</CodesignKey>
<MtouchFloat32>true</MtouchFloat32>
<CodesignEntitlements>Entitlements.prod.plist</CodesignEntitlements>
<MtouchLink>SdkOnly</MtouchLink>
<MtouchArch>ARM64</MtouchArch>
<MtouchHttpClientHandler>HttpClientHandler</MtouchHttpClientHandler>
<MtouchVerbosity></MtouchVerbosity>
<CodesignProvision>Contoso Maintenance In-House</CodesignProvision>
<CodesignProvision>VS: WildCard Development</CodesignProvision>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
<DebugType>pdbonly</DebugType>
@ -65,7 +63,7 @@
<MtouchHttpClientHandler>HttpClientHandler</MtouchHttpClientHandler>
<MtouchVerbosity>
</MtouchVerbosity>
<CodesignProvision>VS: com.microsoft.gbb.contosomaintenance Development</CodesignProvision>
<CodesignProvision>VS: WildCard Development</CodesignProvision>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
<DebugSymbols>true</DebugSymbols>
@ -88,7 +86,6 @@
<MtouchHttpClientHandler>HttpClientHandler</MtouchHttpClientHandler>
<MtouchVerbosity>
</MtouchVerbosity>
<CodesignProvision>Contoso Maintenance Development</CodesignProvision>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'DebugBackend|AnyCPU' ">
<Optimize>false</Optimize>
@ -107,10 +104,11 @@
<DefineConstants>
</DefineConstants>
<WarningLevel>4</WarningLevel>
<CodesignKey>iPhone Developer</CodesignKey>
<CodesignKey>iPhone Developer: Michael James (354MN7UPEZ)</CodesignKey>
<MtouchNoSymbolStrip>true</MtouchNoSymbolStrip>
<MtouchVerbosity>
</MtouchVerbosity>
<CodesignProvision>VS: WildCard Development</CodesignProvision>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@ -478,6 +476,9 @@
<BundleResource Include="Resources\icon_bot.png" />
<BundleResource Include="Resources\icon_usersettings%402x.png" />
<BundleResource Include="Resources\photoupload.json" />
<BundleResource Include="Resources\icon_share.png" />
<BundleResource Include="Resources\icon_share%402x.png" />
<BundleResource Include="Resources\icon_share%403x.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
<Import Project="..\..\packages\NETStandard.Library.2.0.1\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\..\packages\NETStandard.Library.2.0.1\build\netstandard2.0\NETStandard.Library.targets')" />

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

@ -7,9 +7,9 @@
<key>CFBundleName</key>
<string>Field Service</string>
<key>CFBundleIdentifier</key>
<string>com.contoso.contosomaintenance</string>
<string>com.microsoft.contosomaintenance</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.1</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>

Двоичные данные
Mobile/iOS/Resources/icon_share.png Normal file

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

После

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

Двоичные данные
Mobile/iOS/Resources/icon_share@2x.png Normal file

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

После

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

Двоичные данные
Mobile/iOS/Resources/icon_share@3x.png Normal file

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

После

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

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

@ -71,6 +71,7 @@
<package id="NETStandard.Library" version="2.0.1" targetFramework="xamarinios10" />
<package id="Newtonsoft.Json" version="11.0.1" targetFramework="xamarinios10" />
<package id="Plugin.Permissions" version="2.2.1" targetFramework="xamarinios10" />
<package id="Plugin.Share" version="7.1.1" targetFramework="xamarinios10" />
<package id="Polly" version="5.8.0" targetFramework="xamarinios10" />
<package id="Refit" version="4.3.0" targetFramework="xamarinios10" />
<package id="Refractored.MvvmHelpers" version="1.3.0" targetFramework="xamarinios10" />

Двоичные данные
Resources/Contoso Maintenance.paw

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

Двоичные данные
Resources/Design/ContosoMaintenance.sketch

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

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

@ -44,7 +44,7 @@ We opted to use [FreshMvvm](https://github.com/rid00z/FreshMvvm) as our MVVM lib
We've tried to keep the platform-specific code to a minimum with the development of this app. This is because we wanted you to see how its possible to create pleasant user experiences while maximising code reuse.
#### Core Project
The core project contains our app's Pages, ViewModels (we call them PageModels), Models and network services.
The core project contains our app's Pages (Views), ViewModels, Models and network services.
As we're using the MVVM architecture, we have a clear separation of concerns within our app.

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

@ -356,3 +356,4 @@ There are several cool things you can do with Azure Active Directory, that will
- [Add Social Authentication Providers](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-setup-fb-app)
- [Customize the Login UI](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-ui-customization)
- [Enable Multifactor authentication](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-mfa)
- [Login with and existing Azure Active Directory Account](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-setup-aad-custom)