© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
S. HoeflingGetting Started with the Uno Platform and WinUI 3https://doi.org/10.1007/978-1-4842-8248-9_19

19. Complete App

Skye Hoefling1  
(1)
Rochester, NY, USA
 

In this chapter we will be finishing the UnoDrive application by using concepts we have already learned in the book. By the end of this chapter, you will have a fully functioning application that you can use as an example for your next project. We will be adding more code to complete the Recent Files and Shared Files pages. This includes complete changes for new APIs that invoke the Microsoft Graph. We will be adding code in ViewModels, the service layer, and the data store.

To implement the changes in the Recent Pages and Shared Pages, we are going to start with the Microsoft Graph integration and work our way into the presentation layer.

Note

Uploading and downloading files are omitted from this book. You can learn more about completing those tasks by looking at the Microsoft Graph documentation: https://docs.microsoft.com/graph .

Update the GraphFileService and Data Model

Currently the IGraphFileService only supports retrieving the current files at a specified path or root path . This is useful for our MyFilesPage , but we are going to be implementing for the methods GetRecentFilesAsync and GetSharedFilesAsync . The Microsoft Graph implementations will be very similar to the existing implementation with one exception. These pages do not require any navigation like the MyFilesPage; they render a flat page of files with no folders.

The new implementations of GetRecentFilesAsync and GetSharedFilesAsync in the GraphFileService will be almost identical to what is currently implemented. The change will be updating the specific API invoked on the Microsoft Graph. The data returned will be in a similar format, which means we can reuse a bunch of code.

We can get started by updating the IGraphFileService to support the new APIs needed for RecentFilesPage and SharedFilesPage. Update the IGraphFileService as seen in Listing 19-1.
public interface IGraphFileService
{
  Task<IEnumerable<OneDriveItem>> GetRootFilesAsync(
    Action<IEnumerable<OneDriveItem>, bool> callback = null,
    CancellationToken cancellationToken = default);
  Task<IEnumerable<OneDriveItem>> GetMyFilesAsync(
    string id,
    Action<IEnumerable<OneDriveItem>, bool> callback = null,
    CancellationToken cancellationToken = default);
  Task<IEnumerable<OneDriveItem>> GetRecentFilesAsync(
    Action<IEnumerable<OneDriveItem>, bool> callback = null,
    CancellationToken cancellationToken = default);
  Task<IEnumerable<OneDriveItem>> GetSharedFilesAsync(
    Action<IEnumerable<OneDriveItem>, bool> callback = null,
    CancellationToken cancellationToken = default);
}
Listing 19-1

IGraphFileService adds new methods for recent files and shared files. Changes are highlighted in bold

Before we can start adding new code in the GraphFileService , we need to add an enum to support the different API code paths. In the UnoDrive.Shared project, under the Models folder, create a new file named GraphRequestType.cs . See the screenshot of the Visual Studio Solution Explorer in Figure 19-1.

A screenshot of solution explorer depicts the Graph request types is selected from the platform, UnoDrive shared, and models.

Figure 19-1

Visual Studio Solution Explorer – GraphRequestType

Once you have created the new file, create the enum to support MyFiles , Recent , and SharedWithMe . See the full code implementation in Listing 19-2.
public enum GraphRequestType
{
  MyFiles,
  Recent,
  SharedWithMe
}
Listing 19-2

GraphRequestType enum implementation

Next, you will need to refactor the GraphFileService to support the new API pattern. We will be making the GetFilesAsync method private and moving the Microsoft Graph invocations to another method named ProcessGraphRequestAsync . This will allow us to leverage most of the existing code and add functionality for the new methods. Update the GetFilesAsync method to match the code in Listing 19-3. This is the entire method with the changes highlighted in bold.
private async Task<IEnumerable<OneDriveItem>> GetFilesAsync(
  Models.GraphRequestType requestType,
  string id,
  Action<IEnumerable<OneDriveItem>, bool> cachedCallback = null,
  CancellationToken cancellationToken = default)
{
  if (cachedCallback != null)
  {
    var cachedChildren = dataStore
      .GetCachedFiles(id)
      .OrderByDescending(item => item.Type)
      .ThenBy(item => item.Name);
    cachedCallback(cachedChildren, true);
  }
  logger.LogInformation(
    $"Network Connectivity: {networkConnectivity.Connectivity}");
  if (networkConnectivity.Connectivity !=
    NetworkConnectivityLevel.InternetAccess)
  {
    return null;
  }
  cancellationToken.ThrowIfCancellationRequested();
#if DEBUG
  await Task.Delay(apiDelayInMilliseconds, cancellationToken)
#endif
  var oneDriveItems = await ProcessGraphRequestAsync(
    requestType,
    id,
    cachedCallback,
    cancellationToken);
  var childrenTable = oneDriveItems
    .Select(driveItem => new OneDriveItem
    {
      Id = driveItem.Id,
      Name = driveItem.Name,
      Path = driveItem.ParentReference.Path,
      PathId = driveItem.ParentReference.Id,
      FileSize = $"{driveItem.Size}",
      Modified = driveItem.LastModifiedDateTime.HasValue ?
        driveItem.LastModifiedDateTime.Value.LocalDateTime :
        DateTime.Now,
      Type = driveItem.Folder != null ?
        OneDriveItemType.Folder :
        OneDriveItemType.File
    })
    .OrderByDescending(item => item.Type)
    .ThenBy(item => item.Name)
    .ToDictionary(item => item.Id);
  cancellationToken.ThrowIfCancellationRequested();
  var children = childrenTable
    .Select(item => item.Value)
    .ToArray();
  if (cachedCallback != null)
  {
    cachedCallback(children, false);
  }
  dataStore.SaveCachedFiles(children, id);
  await StoreThumbnailsAsync(oneDriveItems, childrenTable
    cachedCallback, cancellationToken);
  return childrenTable.Select(x => x.Value);
}
Listing 19-3

GraphFileService GetFilesAsync updates. Changes are highlighted in bold

The notable changes in Listing 19-3 are changing the method signature from public to private, adding a new parameter GraphRequestType , and then removing the Microsoft Graph API requests to use the new method ProcessGraphRequestAsync. This new method uses the GraphRequestType to perform the various Microsoft Graph API requests and return the DriveItem.

To start implementing the ProcessGraphRequestAsync method, we will need to define the method signature. Like StoreThumbnailsAsync the method signature will differ slightly if it is mobile vs. the other platforms. It needs to return UnoDrive.Models.DriveItem[] if mobile; otherwise, it can use the Microsoft Graph SDK object of DriveItem[]. See Listing 19-4 for the method signature definition.
#if __ANDROID__ || __IOS__ || __MACOS__
  async Task<UnoDrive.Models.DriveItem[]>
#else
  async Task<DriveItem[]>
#endif
    ProcessGraphRequestAsync(
      UnoDrive.Models.GraphRequestType requestType,
      string id,
      Action<IEnumerable<OneDriveItem>, bool> cachedCallback,
      CancellationToken cancellationToken)
  {
    // TODO – Add implementation
  }
Listing 19-4

GraphFileService ProcessGraphRequestAsync method stub

The method signature for ProcessGraphRequestAsync is a little odd because of the different return types depending on the platform. Since Uno Platform is using a shared project, this is an easy thing to add, but it can make it a little less readable.

In the implementation of ProcessGraphRequestAsync, it will be using the requestType parameter to determine what Microsoft Graph API to invoke. This method is split into a series of if statements and returns the correct data. See the full implementation in Listing 19-5.
#if __ANDROID__ || __IOS__ || __MACOS__
  async Task<UnoDrive.Models.DriveItem[]>
#else
  async Task<DriveItem[]>
#endif
    ProcessGraphRequestAsync(
      UnoDrive.Models.GraphRequestType requestType,
      string id,
      Action<IEnumerable<OneDriveItem>, bool> cachedCallback,
      CancellationToken cancellationToken)
  {
#if __ANDROID__ || __IOS__ || __MACOS__
    UnoDrive.Models.DriveItem[] oneDriveItems = null;
#else
    DriveItem[] oneDriveItems = null;
#endif
    if (requestType == Models.GraphRequestType.MyFiles)
    {
      var request = graphClient.Me.Drive
        .Items[id]
        .Children
        .Request()
        .Expand("thumbnails");
#if __ANDROID__ || __IOS__ || __MACOS__
      var response = await request
        .GetResponseAsync(cancellationToken);
      var data = await response.Content.ReadAsStringAsync();
      var collection = JsonConvert
        .DeserializeObject<UnoDrive.Models.DriveItemCollection>(
          data);
      oneDriveItems = collection.Value;
#else
      oneDriveItems = (await request
        .GetAsync(cancellationToken)).ToArray();
#endif
      return oneDriveItems;
    }
    else if (requestType == Models.GraphRequestType.Recent)
    {
      var request = graphClient.Me.Drive
        .Recent()
        .Request();
#if __ANDROID__ || __IOS__ || __MACOS__
      var response = await request
        .GetResponseAsync(cancellationToken);
      var data = await response.Content.ReadAsStringAsync();
      var collection = JsonConvert
        .DeserializeObject<UnoDrive.Models.DriveItemCollection>(
          data);
      oneDriveItems = collection.Value;
#else
      oneDriveItems = (await request
        .GetAsync(cancellationToken)).ToArray();
#endif
    }
    else if (requestType == Models.GraphRequestType.SharedWithMe)
    {
      var request = graphClient.Me.Drive
        .SharedWithMe()
        .Request();
#if __ANDROID__ || __IOS__ || __MACOS__
      var response = await request
        .GetResponseAsync(cancellationToken);
      var data = await response.Content.ReadAsStringAsync();
      var collection = JsonConvert
        .DeserializeObject<UnoDrive.Models.DriveItemCollection>(
          data);
      oneDriveItems = collection.Value;
#else
      oneDriveItems = (await request
        .GetAsync(cancellationToken)).ToArray();
#endif
    }
    return oneDriveItems;
  }
Listing 19-5

GraphFileService ProcessGraphRequestAsync full implementation

With the implementation of the Microsoft Graph API invocations complete, we can now implement the new methods we added to the IGraphFileService . Earlier in this chapter in Listing 19-1, we defined the interface that supports the various pages that we want to implement. The implementations of the GetRecentFilesAsync and GetSharedFilesAsync methods invoke the GetFilesAsync method and use the correct GraphRequestType parameter. See implementations for new methods in Listing 19-6.
public Task<IEnumerable<OneDriveItem>> GetMyFilesAsync(
  string id,
  Action<IEnumerable<OneDriveItem>, bool> cachedCallback = null,
  CancellationToken cancellationToken = default) =>
    GetFilesAsync(
      GraphRequestType.MyFiles,
      id, cachedCallback, cancellationToken);
public Task<IEnumerable<OneDriveItem>> GetRecentFilesAsync(
  Action<IEnumerable<OneDriveItem>, bool> cachedCallback = null,
  CancellationToken cancellationToken = default) =>
    GetFilesAsync(
      GraphRequestType.Recent,
      "RECENT-FILES", cachedCallback, cancellationToken);
public Task<IEnumerable<OneDriveItem>> GetSharedFilesAsync(
  Action<IEnumerable<OneDriveItem>, bool> cachedCallback = null,
  CancellationToken cancellationToken = default) =>
    GetFilesAsync(
      GraphRequestType.SharedWithMe,
      "SHARED-FILES", cachedCallback, cancellationToken);
Listing 19-6

GraphFileService – my files, recent files, and shared files implementations

That completes our changes to the GraphFileService implementation, and we can start using these changes in our view models.

Note

The GetSharedFilesAsync and GetRecentFilesAsync methods both pass a hard-coded string into the GetFilesAsync method. This string is a unique identifier to store the data in the LiteDB data store so it can be read during offline mode.

Update View Models

The IGraphFileService contract has changed to support MyFilesViewModel , RecentFilesViewModel , and SharedFilesViewModel . In this section we will be updating all the view models to use the correct APIs and get the data we need. These view models all perform similar tasks that we have already implemented, so we can refactor our code to extract an abstract class that is shared among all of them. The RecentFilesViewModel and SharedFilesViewModel do not allow any navigation, and they display a flat list of OneDrive files, whereas the MyFilesViewModel that we already implemented allows for navigation. As we extract out common code, we will need to implement a strategy to handle navigation only in the MyFilesViewModel.

Port Existing Code into BaseFilesViewModel

Most of the structure of the current MyFilesViewModel can be reused in our abstract class. The only parts that will not be included are the direct navigation APIs as the other pages do not support any navigation.

The abstract class will be named BaseFilesViewModel , and it will be inherited in the other view model classes. In the UnoDrive.Shared project under the ViewModels folder, create a new empty class named BaseFilesViewModel. See Figure 19-2 for a screenshot of the Visual Studio Solution Explorer.

A screenshot of solution explorer depicts the vase files view model is selected from the view models in the Platform Uno Drive share.

Figure 19-2

Visual Studio Solution Explorer – BaseFilesViewModel

We are going to port the existing code from MyFilesViewModel into our abstract class BaseFilesViewModel. This will include
  • Dependency Injection properties

  • Class properties

  • Shared methods

Start by updating the BaseFilesViewModel contract to inherit ObservableObject and defining it as an abstract class. See Listing 19-7 for the updated class definition.
public abstract class BaseFilesViewModel : ObservableObject
{
  // TODO – add implementation
}
Listing 19-7

BaseFilesViewModel abstract class definition

Note

Earlier in this book, we learned that ObservableObject provides methods to help us notify the user interface of changes to class-level properties. We are going to move this inheritance to the abstract class, which means the children classes will not need to directly inherit ObservableObject – they can inherit BaseFilesViewModel.

Update the constructor code to inject the Dependency Injection properties. Currently, the properties are stored as private fields in MyFilesViewModel. As we port them to the BaseFilesViewModel, we will make them protected properties. Using the protected keyword will allow them to be accessible in the children classes for use. In addition to the Dependency Injection properties, we will need to move the Location field and make it protected as well. This field is used to store the current location in OneDrive and will be used in all child classes. See constructor code in Listing 19-8.
protected Location Location { get; set; } = new Location();
protected IGraphFileService GraphFileService { get; set; }
protected ILogger Logger { get; set; }
public BaseFilesViewModel(
  IGraphFileService graphFileService,
  ILogger<BaseFilesViewModel> logger)
{
  GraphFileService = graphFileService;
  Logger = logger;
}
Listing 19-8

BaseFilesViewModel constructor and Dependency Injection implementation

Next, we will start adding properties from the MyFilesViewModel into our new BaseFilesViewModel class. We will just add the FilesAndFolders in Listing 19-9 because it has some changes to the constructor code . It needs to have a default instantiation. See updated constructor code in Listing 19-9 with changes highlighted in bold.
protected Location Location { get; set; } = new Location();
protected IGraphFileService GraphFileService { get; set; }
protected ILogger Logger { get; set; }
public BaseFilesViewModel(
  IGraphFileService graphFileService,
  ILogger<BaseFilesViewModel> logger)
{
  GraphFileService = graphFileService;
  Logger = logger;
  FilesAndFolders = new List<OneDriveItem>();
}
List<OneDriveItem> filesAndFolders;
public List<OneDriveItem> FilesAndFolders
{
  get => filesAndFolders;
  set
  {
    SetProperty(ref filesAndFolders, value);
    OnPropertyChanged(nameof(CurrentFolderPath));
    OnPropertyChanged(nameof(IsPageEmpty));
    OnPropertyChanged(nameof(IsMainContentLoading));
  }
}
Listing 19-9

BaseFilesViewModel updated constructor implementation to include FilesAndFolders. Changes highlighted in bold

Note

As we move existing code from the MyFilesViewModel into the BaseFilesViewModel, we still need to access APIs from the ObservableObject such as SetProperty() and OnPropertyChanged(). This is seen in Listings 19-9 and 19-10.

Now we can start porting the remaining properties over from the MyFilesViewModel class into our new BaseFilesViewModel class. As we copy these properties over, we will be making one change that is different from the original code. We will update the definition of the CurrentFolderPath property to include the keyword virtual. Using the virtual keyword means we can override the behavior in child classes. Not all of our pages will have navigation, and in those pages, we will want a different message to display in the address bar than the path. See the code snippet in Listing 19-10 for all the properties to copy over, and changes will be highlighted in bold.
public bool IsMainContentLoading =>
  IsStatusBarLoading && !FilesAndFolders.Any();
public bool IsPageEmpty =>
  !IsStatusBarLoading && !FilesAndFolders.Any();
public virtual string CurrentFolderPath =>
  FilesAndFolders.FirstOrDefault()?.Path;
string noDataMessage;
public string NoDataMessage
{
  get => noDataMessage;
  set => SetProperty(ref noDataMessage, value);
}
bool isStatusBarLoading;
public bool IsStatusBarLoading
{
  get => isStatusBarLoading;
  set
  {
    SetProperty(ref isStatusBarLoading, value);
    OnPropertyChanged(nameof(IsPageEmpty));
    OnPropertyChanged(nameof(IsMainContentLoading));
  }
}
Listing 19-10

BaseFilesViewModel property implementations

Next, we will start working on the various methods that need to be copied over from the MyFilesViewModel into the BaseFilesViewModel. We will start with the OnItemClick() method. The only change to this method is updating the signature to include the virtual keyword . This allows child classes to override the behavior if necessary. See the code snippet in Listing 19-11 for the copied implementation.
public async virtual void OnItemClick(
  object sender, ItemClickEventArgs args)
{
  if (args.ClickedItem is not OneDriveItem oneDriveItem)
    return;
  if (oneDriveItem.Type == OneDriveItemType.Folder)
  {
    try
    {
      Location.Forward = new Location
      {
        Id = oneDriveItem.Id,
        Back = Location
      };
      Location = Location.Forward;
      await LoadDataAsync(oneDriveItem.Id);
    }
    catch (Exception ex)
    {
      Logger.LogError(ex, ex.Message);
    }
  }
}
Listing 19-11

BaseFilesViewModel OnItemClick method implementation. Changes highlighted in bold

The next method that we will be porting over from the MyFilesViewModel to the BaseFilesViewModel is the LoadDataAsync() method. This performs the work to retrieve our data and render it onto the page. Since our BaseFilesViewModel now needs to handle various data loading strategies, we need to make some minor updates to it. Instead of invoking the IGraphFileService APIs directly, we will create a special protected abstract method that will invoke the IGraphFileService for us.

Note

An abstract method is a special method defined in an abstract class that defines a contract with any child class that inherits from that abstract class. This means the child classes that inherit from BaseFilesViewModel will need to write their own implementation for our abstract method.

The abstract method will be named GetGraphDataAsync() and will include the necessary properties to retrieve the data. See the abstract method definition in Listing 19-12.
protected abstract Task<IEnumerable<OneDriveItem>>
  GetGraphDataAsync(
    string pathId,
    Action<IEnumerable<OneDriveItem>, bool> callback,
    CancellationToken cancellationToken);
Listing 19-12

BaseFilesViewModel abstract method definition for GetGraphDataAsync()

Now that the abstract method GetGraphDataAsync() is defined, we can add our implementation for LoadDataAsync(). We do not need to worry about the implementation for the abstract method GetGraphDataAsync() since that will be handled in any child class implementation. We can assume it will take the parameters and return a value. See the LoadDataAsync() updated implementation in Listing 19-13, and the changes will be highlighted in bold.
CancellationTokenSource cancellationTokenSource;
CancellationToken cancellationToken;
TaskCompletionSource<bool> currentLoadDataTask;
protected virtual async Task LoadDataAsync(
  string pathId = null,
  Action presentationCallback = null)
{
  if (cancellationTokenSource != null &&
    !cancellationTokenSource.IsCancellationRequested)
  {
    cancellationTokenSource.Cancel();
    await currentLoadDataTask.Task;
  }
  currentLoadDataTask = new TaskCompletionSource<bool>(
    TaskCreationOptions.RunContinuationsAsynchronously);
  cancellationTokenSource = new CancellationTokenSource();
  cancellationToken = cancellationTokenSource.Token;
  try
  {
    IsStatusBarLoading = true;
    IEnumerable<OneDriveItem> data;
    Action<IEnumerable<OneDriveItem>, bool> updateFilesCallback =
      (items, isCached) => UpdateFiles(items, null, isCached);
    if (string.IsNullOrEmpty(pathId))
    {
      data = await GraphFileService.GetRootFilesAsync(
        updateFilesCallback, cancellationToken);
    }
    else
    {
      data = await GetGraphDataAsync(
        pathId, updateFilesCallback, cancellationToken);
    }
    UpdateFiles(data, presentationCallback);
  }
  catch (Exception ex)
  {
    Logger.LogError(ex, ex.Message);
  }
  finally
  {
    cancellationTokenSource = default;
    cancellationToken = default;
    IsStatusBarLoading = false;
    currentLoadDataTask.SetResult(true);
  }
}
Listing 19-13

BaseFilesViewModel LoadDataAsync method implementation. Changes highlighted in bold

The final method we need to port over from MyFilesViewModel to the BaseFilesViewModel is the UpdateFiles() method, which is a helper method used in the LoadDataAsync() method. See the implementation in Listing 19-14.
protected void UpdateFiles(
  IEnumerable<OneDriveItem> files,
  Action presentationCallback,
  bool isCached = false)
{
  if (files == null)
  {
    NoDataMessage = "Unable to retrieve data from API, " +
      "check network connection";
    Logger.LogInformation("No data retrieved from API, " +
      "ensure you have a stable internet connection";
  }
  else if (!files.Any())
  {
    NoDataMessage = "No files or folders";
  }
  FilesAndFolders = files.ToList();
  if (isCached)
  {
    presentationCallback?.Invoke();
  }
}
Listing 19-14

BaseFilesViewModel UpdateFiles method implementation

The abstract class BaseFilesViewModel is now implemented, and we will start adding implementations to the view models: MyFilesViewModel, RecentFilesViewModel, and SharedFilesViewModel.

Update MyFilesViewModel

The original implementation has been ported over to BaseFilesViewModel, and the MyFilesViewModel will shrink in size as most of the code is no longer needed. In this section we are going to start from the beginning and reimplement it. You can start by deleting the entire contents of MyFilesViewModel.

The first thing we need to do is add our class definition, which will now inherit from BaseFilesViewModel instead of ObservableObject . See Listing 19-15 for the MyFilesViewModel class definition.
public class MyFilesViewModel : BaseFilesViewModel, IInitialize
{
  // TODO – add implementation
}
Listing 19-15

MyFilesViewModel class definition

Just like the original implementation, we will need to inject the properties into the constructor that we want to resolve and use. The major difference is they are not stored in this class but as properties in the parent class BaseFilesViewModel. As you implement the constructor , you will need to invoke the base constructor and pass the parameters along. See the constructor code snippet in Listing 19-16.
public MyFilesViewModel(
  IGraphFileService graphFileService,
  ILogger<MyFilesViewModel> logger)
  : base(graphFileService, logger)
{
}
Listing 19-16

MyFilesViewModel constructor injection implementation

The MyFilesViewModel performs basic navigation forward and back, and that is not included in the parent class BaseFilesViewModel. Next, we will add our IRelayCommand properties and method implementations. See Listing 19-17 for navigation implementation.
public MyFilesViewModel(
  IGraphFileService graphFileService,
  ILogger<MyFilesViewModel> logger)
  : base(graphFileService, logger)
{
  Forward = new AsyncRelayCommand(OnForwardAsync,
    () => location.CanMoveForward);
  Back = new AsyncRelayCommand(OnBackAsync,
    () => location.CanMoveBack);
}
public IRelayCommand Forward { get; }
public IRelayCommand Back { get; }
Task OnForwardAsync()
{
  var forwardId = Location.Forward.Id;
  Location = Location.Forward;
  return LoadDataAsync(forwardId);
}
Task OnBackAsync()
{
  var backId = Location.Back.Id;
  Location = Location.Back;
  return LoadDataAsync(backId);
}
Listing 19-17

MyFilesViewModel navigation IRelayCommand and method implementations

When we implemented the BaseFilesViewModel, we defined the abstract method GetGraphDataAsync() , which we need to implement. This method will invoke the correct API on the IGraphFileService to retrieve the files for our page. See Listing 19-18 for the abstract method implementation.
protected override Task<IEnumerable<OneDriveItem>>
  GetGraphDataAsync(
    string pathId,
    Action<IEnumerable<OneDriveItem>, bool> callback,
    CancellationToken cancellationToken) =>
      GraphFileService.GetMyFilesAsync(
        pathId, callback, cancellationToken);
Listing 19-18

MyFilesViewModel GetGraphDataAsync method implementation

The method OnItemClick() uses x:Bind from the page, and it needs to have a reference here in the child class to be properly invoked from the XAML.

Note

We need to override the OnItemClick( ) method on Uno Platform targets. The code generation will fail to read the method in the parent classes.

To resolve this, we will create an override of the method OnItemClick from the parent class BaseFilesViewModel and just invoke the parent implementation. See the override implementation in Listing 19-19.
public override void OnItemClick(
  object sender, ItemClickEventArgs args) =>
    base.OnItemClick(sender, args);
Listing 19-19

MyFilesViewModel OnItemClick method override implementation

MyFilesViewModel is the only ViewModel that allows for navigation through the structure of OneDrive. We still have the IRelayCommand for both forward and back, and they are in this class. After the LoadDataAsync base implementation completes, we will need to use the NotifyCanExecuteChanged() method to check if any navigation action can occur. See the code snippet for method override in Listing 19-20.
protected override async Task LoadDataAsync(
  string pathId = null,
  Action presentationCallback = null)
{
  await base.LoadDataAsync(pathId, presentationCallback);
  Forward.NotifyCanExecuteChanged();
  Back.NotifyCanExecuteChanged();
}
Listing 19-20

MyFilesViewModel LoadDataAsync override for NotifyCanExecuteChanged

In the original MyFilesViewModel implementation, the class implemented IInitialize, and that code does not change at all. We already included the interface in the class definition. You can see this in Listing 19-15. Now, add the IInitialize implementation and invoke the LoadDataAsync() method. See Listing 19-21 for the InitializeAsync() method implementation.
public Task InitializeAsync() =>
  LoadDataAsync();
Listing 19-21

MyFilesViewModel IInitialize interface implementation

That completes our updates to the MyFilesViewModel. There is no need to change anything in the user interface.

Implement RecentFilesViewModel

The RecentFilesViewModel is a new implementation in this chapter and will be almost identical to the SharedFilesViewModel . This page does not allow any navigation and just displays a list of files that the user recently viewed.

To implement the RecentFilesViewModel, you will need to complete the following tasks:
  • Define the constructor and inject properties.

  • Override CurrentFolderPath to display the message “Recent Files.”

  • Override GetGraphDataAsync() to invoke the correct API.

  • Override the OnItemClick() method to invoke the base method.

  • Implement IInitialize.

See complete RecentFilesViewModel code in Listing 19-22.
public class RecentFilesViewModel :
  BaseFilesViewModel, IInitialize
{
  public RecentFilesViewModel(
    IGraphFileService graphFileService,
    ILogger<RecentFilesViewModel> logger)
    : base(graphFileService, logger)
  {
  }
  public override string CurrentFolderPath => "Recent Files";
  protected override Task<IEnumerable<OneDriveItem>>
    GetGraphDataAsync(
      string pathId,
      Action<IEnumerable<OneDriveItem>, bool> callback,
      CancellationToken cancellationToken) =>
        GraphFileService.GetRecentFilesAsync(
          callback, cancellationToken);
  public override void OnItemClick(
    object sender, ItemClickEventArgs args) =>
      base.OnItemClick(sender, args);
  public Task InitializeAsync() =>
    LoadDataAsync("RECENT");
}
Listing 19-22

RecentFilesViewModel complete implementation

Implement SharedFilesViewModel

The SharedFilesViewModel is a new implementation in this chapter and will be almost identical to the RecentFilesViewModel. This page does not allow any navigation and just displays a list of files shared to the user.

To implement the SharedFilesViewModel, you will need to complete the following tasks:
  • Define the constructor and inject properties.

  • Override CurrentFolderPath to display the message “Shared Files.”

  • Override GetGraphDataAsync() to invoke the correct API.

  • Override the OnItemClick() method to invoke the base method.

  • Implement IInitialize.

See complete SharedFilesViewModel code in Listing 19-23.
public class SharedFilesViewModel :
  BaseFilesViewModel, IInitialize
{
  public SharedFilesViewModel(
    IGraphFileService graphFileService,
    ILogger<SharedFilesViewModel> logger)
    : base(graphFileService, logger)
  {
  }
  public override string CurrentFolderPath => "Shared Files";
  protected override Task<IEnumerable<OneDriveItem>>
    GetGraphDataAsync(
      string pathId,
      Action<IEnumerable<OneDriveItem>, bool> callback,
      CancellationToken cancellationToken) =>
        GraphFileService.GetSharedFilesAsync(
          callback, cancellationToken);
  public override void OnItemClick(
    object sender, ItemClickEventArgs args) =>
      base.OnItemClick(sender, args);
  public Task InitializeAsync() =>
    LoadDataAsync("SHARED-WITH-ME");
}
Listing 19-23

SharedFilesViewModel complete implementation

Update the User Interface

The service layer changes and view model changes are complete, and we can start working on the user interface changes. The MyFilesPage.xaml and MyFilesPage.xaml.cs implementations are valid and do not need any changing. We only updated the MyFilesViewModel code to work with our new abstract class.

We do need to implement both RecentFilesPage and SharedFilesPage , which are going to be carbon copies of MyFilesPage for both the XAML and the code behind.

Both the RecentFilesPage and SharedFilesPage code behinds have the constructor defined. We will need to add the following to each code behind:
  • ViewModel reference for x:Bind usages

  • OnNavigatedTo override to invoke the IInitialize interface

See the code implementation for RecentFilesPage in Listing 19-24 and SharedFilesPage in Listing 19-25.
public sealed partial class RecentFilesPage : Page
{
  public RecentFilesPage()
  {
    this.InitializeComponent();
  }
  public RecentFilesViewModel ViewModel =>
    (RecentFilesViewModel)DataContext;
  protected override async void OnNavigatedTo(
    NavigationEventArgs e)
  {
    base.OnNavigatedTo(e);
    if (ViewModel is IInitialize initializeViewModel)
      await initializeViewModel.InitializeAsync();
  }
}
Listing 19-24

RecentFilesPage.xaml.cs code behind complete implementation

public sealed partial class SharedFilesPage : Page
{
  public SharedFilesPage()
  {
    this.InitializeComponent();
  }
  public SharedFilesViewModel ViewModel =>
    (SharedFilesViewModel)DataContext;
  protected override async void OnNavigatedTo(
    NavigationEventArgs e)
  {
    base.OnNavigatedTo(e);
    if (ViewModel is IInitialize initializeViewModel)
      await initializeViewModel.InitializeAsync();
  }
}
Listing 19-25

SharedFilesPage.xaml.cs code behind complete implementation

The implementations of both RecentFilesPage.xaml and SharedFilesPage.xaml are identical to MyFilesPage.xaml with the one change that the x:Class definition at the top must reference the correct class. For the remainder of this section, the code snippets will apply to both RecentFilesPage.xaml and SharedFilesPage.xaml.

To start our implementation of the RecentFilesPage.xaml and SharedFilesPage.xaml , you will need to update the xmlns section. See Listing 19-26 for RecentFilesPage.xaml and SharedFilesPage.xaml xmlns definitions.
xmlns:mvvm="using:UnoDrive.Mvvm"
mvvm:ViewModelLocator.AutoWireViewModel="True"
xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:skia="http://uno.ui/skia"
mc:Ignorable="d skia"
Listing 19-26

RecentFilesPage.xaml and SharedFilesPage.xaml xmlns definitions code snippet

Next, we can add the entire XAML needed to define the page. This can be copied from the MyFilesPage.xaml as it is the same XAML. See code in Listing 19-27.
<Grid x:Name="rootGrid">
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <Grid Grid.Row="0" Margin="0, 0, 0, 20">
    <Grid.ColumnDefinitions>
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <TextBox
      Grid.Column="0"
      Margin="10, 0, 0, 0"
      Padding="10, 6, 36, 5"
      IsReadOnly="True"
      IsFocusEngaged="False"
      IsEnabled="False"
      Foreground="Black"
      Background="#F2F2F2"
      Text="{Binding CurrentFolderPath}" />
    <not_skia:ProgressRing
      Grid.Column="0"
      Style="{StaticResource AddressBarProgressRing}"
      HorizontalAlignment="Right"
      Margin="0, 0, 10, 0"
      IsActive="{Binding IsStatusBarLoading}"
      Visibility="{Binding IsStatusBarLoading,
        Converter={StaticResource BoolToVisibilityConverter}}" />
    <skia:TextBlock
      Grid.Column="0"
      Text="Loading . . ."
      FontSize="12"
      Margin="0, 0, 10, 0"
      Foreground="Black"
      HorizontalAlignment="Right"
      VerticalAlignment="Center"
      Visibility="{Binding IsStatusBarLoading,
        Converter={StaticResource BoolToVisibilityConverter}}" />
  </Grid>
  <ScrollViewer Grid.Row="1">
    <StackPanel>
      <GridView
        ItemsSource="{Binding FilesAndFolders}"
        ItemClick="{x:Bind ViewModel.OnItemClick}"
        Visibility="{Binding IsPageEmpty, Converter=
          {StaticResource BoolNegationToVisibilityConverter}}"
        ScrollViewer.VerticalScrollMode="Enabled"
        ScrollViewer.VerticalScrollBarVisibility="Visible"
        ScrollViewer.HorizontalScrollMode="Disabled" />
      <TextBlock
        Text="No data found"
        Visibility="{Binding IsPageEmpty, Converter=
          {StaticResource BoolToVisibilityConverter}}" />
      <not_skia:ProgressRing
        Width="300"
        Height="300"
        IsActive="{Binding IsMainContentLoading}"
        Visibility="{Binding IsMainContentLoading, Converter=
          {StaticResource BoolToVisibilityConverter}}" />
      <skia:TextBlock
        Text="Loading . . ."
        FontSize="40"
        Foreground="Black"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Visibility="{Binding IsMainContentLoading, Converter=
          {StaticResource BoolToVisibilityConverter}}" />
    </StackPanel>
  </ScrollViewer>
</Grid>
Listing 19-27

RecentFilesPage.xaml and SharedFilesPage.xaml complete XAML

That completes our changes for the RecentFilesPage and SharedFilesPage . You can now launch the application and navigate to those pages, and it will render your recent files or shared files in a flat list. If you navigate to the original MyFilesPage , it will still work just as it has always done.

Conclusion

That completes all the final changes to our application. In this chapter we applied concepts we learned throughout this book to finish our implementation, which gives us a working OneDrive clone application built using Uno Platform targeting Windows, WASM, WPF, GTK (Linux), iOS, Android, and macOS!

If you had trouble following along with any of the code in this chapter, you can view the chapter 19 code available on GitHub: https://github.com/SkyeHoefling/UnoDrive/tree/main/Chapter%2019 .

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.247.196