From df90123644626ae73fbee5efb890d7b407003312 Mon Sep 17 00:00:00 2001
From: Diogo Simao Marques <diogo.simaomarquespro@gmail.com>
Date: Mon, 4 Jul 2022 11:35:06 +0200
Subject: [PATCH 1/5] Podfile: Add Microsoft Graph SDK

Add of the Microsoft Graph SDK including a custom version of the
Microsoft Authentication Library in order to be able to build the
feature for iOS versions lower than iOS 13.0.

Add of MSAUTH queries schemes in order to allow making calls to the
Microsoft Authenticator.
---
 Podfile                        |  6 ++++++
 Podfile.lock                   | 32 ++++++++++++++++++++++++++++++--
 Sources/VLC for iOS-Info.plist |  9 +++++++++
 3 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/Podfile b/Podfile
index 143c40a24..520b30cf9 100644
--- a/Podfile
+++ b/Podfile
@@ -3,6 +3,7 @@ install! 'cocoapods', :deterministic_uuids => false
 inhibit_all_warnings!
 
 def shared_pods
+  use_modular_headers!
   pod 'XKKeychain', '~>1.0'
   pod 'box-ios-sdk-v2', :git => 'https://github.com/fkuehne/box-ios-sdk-v2.git', :commit => '08161e74' #has a our fixes
   pod 'CocoaHTTPServer', :git => 'https://github.com/fkuehne/CocoaHTTPServer.git' # has our fixes
@@ -30,6 +31,11 @@ target 'VLC-iOS' do
   target 'VLC-iOSTests' do
       inherit! :search_paths
   end
+
+  use_modular_headers!
+  pod 'MSGraphClientSDK'
+  pod 'MSGraphClientModels'
+  pod 'MSGraphMSALAuthProvider', :git => 'https://github.com/Mikanbu/msgraph-sdk-objc-auth'
 end
 
 target 'VLC-iOS-Screenshots' do
diff --git a/Podfile.lock b/Podfile.lock
index 64870c868..ab92d4521 100644
--- a/Podfile.lock
+++ b/Podfile.lock
@@ -40,6 +40,16 @@ PODS:
   - MetaDataFetcherKit (0.5.0):
     - AFNetworking (= 4.0.0)
   - MobileVLCKit (3.4.1b11)
+  - MSGraphClientModels (1.3.0)
+  - MSGraphClientSDK (1.0.0):
+    - MSGraphClientSDK/Authentication (= 1.0.0)
+    - MSGraphClientSDK/Common (= 1.0.0)
+  - MSGraphClientSDK/Authentication (1.0.0)
+  - MSGraphClientSDK/Common (1.0.0):
+    - MSGraphClientSDK/Authentication
+  - MSGraphMSALAuthProvider (0.2.0):
+    - MSGraphClientSDK (~> 1.0.0)
+    - VLC-MSAL
   - "NSData+Base64 (1.0.0)"
   - ObjectiveDropboxOfficial (6.3.2)
   - OBSlider (1.1.0)
@@ -65,6 +75,9 @@ PODS:
   - SimulatorStatusMagic (2.6)
   - SwiftLint (0.47.1)
   - TVVLCKit (3.4.1b11)
+  - VLC-MSAL (1.2.2):
+    - VLC-MSAL/app-lib (= 1.2.2)
+  - VLC-MSAL/app-lib (1.2.2)
   - VLCMediaLibraryKit (0.11.0b2):
     - MobileVLCKit
   - XKKeychain (1.0.1)
@@ -83,6 +96,9 @@ DEPENDENCIES:
   - MarqueeLabel (= 4.0.2)
   - MetaDataFetcherKit (~> 0.5.0)
   - MobileVLCKit (= 3.4.1b11)
+  - MSGraphClientModels
+  - MSGraphClientSDK
+  - MSGraphMSALAuthProvider (from `https://github.com/Mikanbu/msgraph-sdk-objc-auth`)
   - ObjectiveDropboxOfficial
   - OBSlider (= 1.1.0)
   - OneDriveSDK (from `https://code.videolan.org/fkuehne/onedrive-sdk-ios.git`, commit `810f82da`)
@@ -105,12 +121,15 @@ SPEC REPOS:
     - MarqueeLabel
     - MetaDataFetcherKit
     - MobileVLCKit
+    - MSGraphClientModels
+    - MSGraphClientSDK
     - "NSData+Base64"
     - ObjectiveDropboxOfficial
     - OBSlider
     - SimulatorStatusMagic
     - SwiftLint
     - TVVLCKit
+    - VLC-MSAL
     - VLCMediaLibraryKit
     - XKKeychain
 
@@ -126,6 +145,8 @@ EXTERNAL SOURCES:
   InAppSettingsKit:
     :commit: a429840
     :git: https://github.com/Mikanbu/InAppSettingsKit.git
+  MSGraphMSALAuthProvider:
+    :git: https://github.com/Mikanbu/msgraph-sdk-objc-auth
   OneDriveSDK:
     :commit: 810f82da
     :git: https://code.videolan.org/fkuehne/onedrive-sdk-ios.git
@@ -146,6 +167,9 @@ CHECKOUT OPTIONS:
   InAppSettingsKit:
     :commit: a429840
     :git: https://github.com/Mikanbu/InAppSettingsKit.git
+  MSGraphMSALAuthProvider:
+    :commit: f3e91a7e4d9ba82206c3c1d7e8d7def201507f67
+    :git: https://github.com/Mikanbu/msgraph-sdk-objc-auth
   OneDriveSDK:
     :commit: 810f82da
     :git: https://code.videolan.org/fkuehne/onedrive-sdk-ios.git
@@ -168,6 +192,9 @@ SPEC CHECKSUMS:
   MarqueeLabel: f762aa681ae201f66c1d511858b171f75d83013a
   MetaDataFetcherKit: c77fa3d0791d406a8cfab43464ed728437bd241b
   MobileVLCKit: 110f1ddc4175a85d911d09f70a58e3cf0c0d48d5
+  MSGraphClientModels: ffef6d0a1cdaa774e1689e38da35301cb74fc72a
+  MSGraphClientSDK: ffc07a58a838e0702c7bf2a856367035d4a335d7
+  MSGraphMSALAuthProvider: cdd3114898fbe43a6e3b4cc0ba7a032c2a1e65a8
   "NSData+Base64": 4e84902c4db907a15673474677e57763ef3903e4
   ObjectiveDropboxOfficial: 1b9ca679750487f770fda3f38c106d6e17fe2d73
   OBSlider: 490f108007bfdd5414a38650b211fe403a95b8a0
@@ -175,10 +202,11 @@ SPEC CHECKSUMS:
   SimulatorStatusMagic: 9f5294c12ca99c6cadb243a60362a66000df540c
   SwiftLint: f80f1be7fa96d30e0aa68e58d45d4ea1ccaac519
   TVVLCKit: 805100cd9b8c62440d0222f9a5696c950df9fd73
+  VLC-MSAL: 752652c8ae6aa352d6452baac202db6b62c98ba5
   VLCMediaLibraryKit: d7a3f1bbcccabddfc5faa010058f92bc96c34ad3
   XKKeychain: 852ef663c56a7194c73d3c68e8d9d4f07b121d4f
   xmlrpc: 2849d2a71cedfab68b1a8c82fb8373ce2ea0d0f3
 
-PODFILE CHECKSUM: 412675d649a1f3e178e8ee10d1c8eac7a9806d5f
+PODFILE CHECKSUM: 86045a8a46def41dac0cdac5b9ab7a6bc4acb144
 
-COCOAPODS: 1.11.2
+COCOAPODS: 1.11.3
diff --git a/Sources/VLC for iOS-Info.plist b/Sources/VLC for iOS-Info.plist
index e15d5c204..873e7ed2c 100644
--- a/Sources/VLC for iOS-Info.plist	
+++ b/Sources/VLC for iOS-Info.plist	
@@ -242,6 +242,12 @@
 				<string>com.googleusercontent.apps.CLIENT</string>
 			</array>
 		</dict>
+		<dict>
+			<key>CFBundleURLSchemes</key>
+			<array>
+				<string>msauth.com.example.vlc-ios</string>
+			</array>
+		</dict>
 	</array>
 	<key>CFBundleVersion</key>
 	<string>$(CURRENT_PROJECT_VERSION)</string>
@@ -253,6 +259,9 @@
 	<array>
 		<string>dbapi-2</string>
 		<string>dbapi-8-emm</string>
+		<string>msauth</string>
+		<string>msauthv2</string>
+		<string>msauthv3</string>
 	</array>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
-- 
GitLab


From 05d5ff1629b552ecc296b7f27794ba81da3470a8 Mon Sep 17 00:00:00 2001
From: Diogo Simao Marques <diogo.simaomarquespro@gmail.com>
Date: Mon, 4 Jul 2022 11:49:15 +0200
Subject: [PATCH 2/5] OneDrive: Remove the deprecated OneDriveSDK

Remove the old OneDrive SDK that is replaced by the Microsoft
Graph SDK.
---
 Podfile                                       |  1 -
 Podfile.lock                                  | 32 +------------------
 .../VLCServerListViewController.m             |  5 ---
 Sources/VLCCloudServicesTableViewController.m |  9 +-----
 Sources/VLCCloudStorageTableViewCell.h        |  8 +++--
 Sources/VLCCloudStorageTableViewCell.m        | 15 ++-------
 VLC.xcodeproj/project.pbxproj                 | 18 -----------
 7 files changed, 9 insertions(+), 79 deletions(-)

diff --git a/Podfile b/Podfile
index 520b30cf9..bd58da231 100644
--- a/Podfile
+++ b/Podfile
@@ -24,7 +24,6 @@ target 'VLC-iOS' do
   pod 'VLCMediaLibraryKit', '0.11.0b2'
   pod 'GTMAppAuth', '0.7.1'
   pod 'ADAL', :git => 'https://code.videolan.org/fkuehne/azure-activedirectory-library-for-objc.git', :commit => '348e94df'
-  pod 'OneDriveSDK', :git => 'https://code.videolan.org/fkuehne/onedrive-sdk-ios.git', :commit => '810f82da'
   pod 'MarqueeLabel', '4.0.2'
   pod 'ObjectiveDropboxOfficial'
 
diff --git a/Podfile.lock b/Podfile.lock
index ab92d4521..fe4ff43ad 100644
--- a/Podfile.lock
+++ b/Podfile.lock
@@ -16,7 +16,6 @@ PODS:
   - AFNetworking/UIKit (4.0.0):
     - AFNetworking/NSURLSession
   - AppAuth (0.95.1)
-  - Base32 (1.1.2)
   - box-ios-sdk-v2 (1.2.3):
     - box-ios-sdk-v2/no-arc (= 1.2.3)
   - box-ios-sdk-v2/no-arc (1.2.3)
@@ -53,25 +52,6 @@ PODS:
   - "NSData+Base64 (1.0.0)"
   - ObjectiveDropboxOfficial (6.3.2)
   - OBSlider (1.1.0)
-  - OneDriveSDK (1.3.0):
-    - OneDriveSDK/Auth (= 1.3.0)
-    - OneDriveSDK/Common (= 1.3.0)
-    - OneDriveSDK/Extensions (= 1.3.0)
-    - OneDriveSDK/Implementations (= 1.3.0)
-    - OneDriveSDK/OneDriveCoreSDK (= 1.3.0)
-  - OneDriveSDK/Auth (1.3.0):
-    - ADAL (~> 1.2.10)
-    - Base32 (~> 1.1)
-    - OneDriveSDK/Common
-  - OneDriveSDK/Common (1.3.0)
-  - OneDriveSDK/Extensions (1.3.0):
-    - OneDriveSDK/Auth
-    - OneDriveSDK/Implementations
-    - OneDriveSDK/OneDriveCoreSDK
-  - OneDriveSDK/Implementations (1.3.0):
-    - OneDriveSDK/Common
-  - OneDriveSDK/OneDriveCoreSDK (1.3.0):
-    - OneDriveSDK/Common
   - SimulatorStatusMagic (2.6)
   - SwiftLint (0.47.1)
   - TVVLCKit (3.4.1b11)
@@ -101,7 +81,6 @@ DEPENDENCIES:
   - MSGraphMSALAuthProvider (from `https://github.com/Mikanbu/msgraph-sdk-objc-auth`)
   - ObjectiveDropboxOfficial
   - OBSlider (= 1.1.0)
-  - OneDriveSDK (from `https://code.videolan.org/fkuehne/onedrive-sdk-ios.git`, commit `810f82da`)
   - SimulatorStatusMagic
   - SwiftLint (~> 0.47.1)
   - TVVLCKit (= 3.4.1b11)
@@ -113,7 +92,6 @@ SPEC REPOS:
   trunk:
     - AFNetworking
     - AppAuth
-    - Base32
     - GoogleAPIClientForREST
     - GRKArrayDiff
     - GTMAppAuth
@@ -147,9 +125,6 @@ EXTERNAL SOURCES:
     :git: https://github.com/Mikanbu/InAppSettingsKit.git
   MSGraphMSALAuthProvider:
     :git: https://github.com/Mikanbu/msgraph-sdk-objc-auth
-  OneDriveSDK:
-    :commit: 810f82da
-    :git: https://code.videolan.org/fkuehne/onedrive-sdk-ios.git
   xmlrpc:
     :commit: 3f8ce3a8
     :git: https://github.com/fkuehne/xmlrpc.git
@@ -170,9 +145,6 @@ CHECKOUT OPTIONS:
   MSGraphMSALAuthProvider:
     :commit: f3e91a7e4d9ba82206c3c1d7e8d7def201507f67
     :git: https://github.com/Mikanbu/msgraph-sdk-objc-auth
-  OneDriveSDK:
-    :commit: 810f82da
-    :git: https://code.videolan.org/fkuehne/onedrive-sdk-ios.git
   xmlrpc:
     :commit: 3f8ce3a8
     :git: https://github.com/fkuehne/xmlrpc.git
@@ -181,7 +153,6 @@ SPEC CHECKSUMS:
   ADAL: f2eb2158b9bcd2c8416ff2d026c176b98ea6651f
   AFNetworking: d9fdf484a3c723ec3c558a41cc5754c7e845ee77
   AppAuth: cd34aac431e5c77c46ec7f03935377faa8c5808b
-  Base32: 163c298644d184e89ca4e00a996bad6bf5166390
   box-ios-sdk-v2: 9423bd75373350ea40b92f3d2d6e89f81d96d634
   CocoaHTTPServer: 07df8b05a8bde406fe367d22c90a24a2fd4ca49f
   GoogleAPIClientForREST: 2abe83bae1085d28f94ccc30a49fc7950d721fb9
@@ -198,7 +169,6 @@ SPEC CHECKSUMS:
   "NSData+Base64": 4e84902c4db907a15673474677e57763ef3903e4
   ObjectiveDropboxOfficial: 1b9ca679750487f770fda3f38c106d6e17fe2d73
   OBSlider: 490f108007bfdd5414a38650b211fe403a95b8a0
-  OneDriveSDK: fbb41c73c36a23a580d172657c67c47dbb8f8e24
   SimulatorStatusMagic: 9f5294c12ca99c6cadb243a60362a66000df540c
   SwiftLint: f80f1be7fa96d30e0aa68e58d45d4ea1ccaac519
   TVVLCKit: 805100cd9b8c62440d0222f9a5696c950df9fd73
@@ -207,6 +177,6 @@ SPEC CHECKSUMS:
   XKKeychain: 852ef663c56a7194c73d3c68e8d9d4f07b121d4f
   xmlrpc: 2849d2a71cedfab68b1a8c82fb8373ce2ea0d0f3
 
-PODFILE CHECKSUM: 86045a8a46def41dac0cdac5b9ab7a6bc4acb144
+PODFILE CHECKSUM: 9d9a1c842ded6375a0275a2631e8f9a00222cccf
 
 COCOAPODS: 1.11.3
diff --git a/Sources/LocalNetworkConnectivity/VLCServerListViewController.m b/Sources/LocalNetworkConnectivity/VLCServerListViewController.m
index cdb2dcf3b..7cf4ca1ca 100644
--- a/Sources/LocalNetworkConnectivity/VLCServerListViewController.m
+++ b/Sources/LocalNetworkConnectivity/VLCServerListViewController.m
@@ -40,8 +40,6 @@
 #import "VLCWiFiUploadTableViewCell.h"
 
 #import "VLCBoxController.h"
-#import <OneDriveSDK.h>
-#import "VLCOneDriveConstants.h"
 #import "VLCDropboxConstants.h"
 
 #import "VLC-Swift.h"
@@ -287,9 +285,6 @@
 
     // Configure Dropbox
     [DBClientsManager setupWithAppKey:kVLCDropboxAppKey];
-
-    // Configure OneDrive
-    [ODClient setMicrosoftAccountAppId:kVLCOneDriveClientID scopes:@[@"onedrive.readwrite", @"offline_access"]];
 }
 
 - (void)boxSessionUpdated
diff --git a/Sources/VLCCloudServicesTableViewController.m b/Sources/VLCCloudServicesTableViewController.m
index 9725daa08..be751ff9c 100644
--- a/Sources/VLCCloudServicesTableViewController.m
+++ b/Sources/VLCCloudServicesTableViewController.m
@@ -16,8 +16,6 @@
 #import "VLCGoogleDriveTableViewController.h"
 #import "VLCBoxTableViewController.h"
 #import "VLCBoxController.h"
-#import "VLCOneDriveTableViewController.h"
-#import "VLCOneDriveController.h"
 #import "VLCDocumentPickerController.h"
 #import "VLCCloudServiceCell.h"
 
@@ -29,7 +27,6 @@
 @property (nonatomic) VLCDropboxTableViewController *dropboxTableViewController;
 @property (nonatomic) VLCGoogleDriveTableViewController *googleDriveTableViewController;
 @property (nonatomic) VLCBoxTableViewController *boxTableViewController;
-@property (nonatomic) VLCOneDriveTableViewController *oneDriveTableViewController;
 @property (nonatomic) VLCDocumentPickerController *documentPickerController;
 
 @end
@@ -41,13 +38,11 @@
 
     [self.tableView registerNib:[UINib nibWithNibName:@"VLCCloudServiceCell" bundle:nil] forCellReuseIdentifier:@"CloudServiceCell"];
     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(themeDidChange) name:kVLCThemeDidChangeNotification object:nil];
-    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(authenticationSessionsChanged:) name:VLCOneDriveControllerSessionUpdated object:nil];
     [self themeDidChange];
 
     self.dropboxTableViewController = [[VLCDropboxTableViewController alloc] initWithNibName:@"VLCCloudStorageTableViewController" bundle:nil];
     self.googleDriveTableViewController = [[VLCGoogleDriveTableViewController alloc] initWithNibName:@"VLCCloudStorageTableViewController" bundle:nil];
     self.boxTableViewController = [[VLCBoxTableViewController alloc] initWithNibName:@"VLCCloudStorageTableViewController" bundle:nil];
-    self.oneDriveTableViewController = [[VLCOneDriveTableViewController alloc] initWithNibName:@"VLCCloudStorageTableViewController" bundle:nil];
     self.documentPickerController = [VLCDocumentPickerController new];
 }
 
@@ -99,7 +94,6 @@
     int i = [[VLCDropboxController sharedInstance] isAuthorized] ? 1 : 0;
     i += [[VLCGoogleDriveController sharedInstance] isAuthorized] ? 1 : 0;
     i += [[VLCBoxController sharedInstance] isAuthorized] ? 1 : 0;
-    i += [[VLCOneDriveController sharedInstance] isAuthorized] ? 1 : 0;
     return i;
 }
 
@@ -165,7 +159,7 @@
         }
         case 3: {
             //OneDrive
-            BOOL isAuthorized = [[VLCOneDriveController sharedInstance] isAuthorized];
+            BOOL isAuthorized = NO;
             cell.icon.image = [UIImage imageNamed:@"OneDriveCell"];
             cell.cloudTitle.text = @"OneDrive";
             cell.cloudInformation.text = isAuthorized ? NSLocalizedString(@"LOGGED_IN", "") : NSLocalizedString(@"LOGIN", "");
@@ -211,7 +205,6 @@
             break;
         case 3:
             //OneDrive
-            [self.navigationController pushViewController:self.oneDriveTableViewController animated:YES];
             break;
         case 4:
             if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
diff --git a/Sources/VLCCloudStorageTableViewCell.h b/Sources/VLCCloudStorageTableViewCell.h
index 4c4ab3001..6d58c21b0 100644
--- a/Sources/VLCCloudStorageTableViewCell.h
+++ b/Sources/VLCCloudStorageTableViewCell.h
@@ -15,11 +15,13 @@
 #import <BoxSDK/BoxSDK.h>
 #if TARGET_OS_IOS
 #import "GTLRDrive.h"
-#import <OneDriveSDK/OneDriveSDK.h>
+#import <MSGraphClientModels/MSGraphDriveItem.h>
+#import <MSGraphClientModels/MSGraphAudio.h>
+#import <MSGraphClientModels/MSGraphVideo.h>
 #endif
 
 @class VLCNetworkImageView;
-@class ODItem;
+@class MSGraphDriveItem;
 
 @interface VLCCloudStorageTableViewCell : UITableViewCell
 
@@ -32,7 +34,7 @@
 @property (nonatomic, strong) IBOutlet UIButton *downloadButton;
 
 @property (nonatomic, retain) DBFILESMetadata *dropboxFile;
-@property (nonatomic, retain) ODItem *oneDriveFile;
+@property (nonatomic, retain) MSGraphDriveItem *oneDriveFile;
 @property (nonatomic, retain) BoxItem *boxFile;
 #if TARGET_OS_IOS
 @property (nonatomic, retain) GTLRDrive_File *driveFile;
diff --git a/Sources/VLCCloudStorageTableViewCell.m b/Sources/VLCCloudStorageTableViewCell.m
index 5f68c340a..e0c36f151 100644
--- a/Sources/VLCCloudStorageTableViewCell.m
+++ b/Sources/VLCCloudStorageTableViewCell.m
@@ -75,18 +75,7 @@
 
 - (void)loadThumbnail
 {
-    // The onedrive Api has no way to cancel a request and the ODThumbnail has no back reference to it's item
-    // so this might lead to wrong thumbnails if the cell is reused since we have no way of cancelling requests or check if the completion is still for the set item
-    ODDriveRequestBuilder *drive = [[ODClient loadCurrentClient] drive];
-    ODThumbnailRequest *request = [[[[drive items:_oneDriveFile.id] thumbnails:@"0"] medium] request];
-    __weak typeof(self) weakSelf = self;
-    [request getWithCompletion:^(ODThumbnail *response, NSError *error) {
-        if (error == nil && response.url) {// we don't care about errors for thumbnails
-            dispatch_async(dispatch_get_main_queue(), ^{
-                 [weakSelf.thumbnailView setImageWithURL:[NSURL URLWithString:response.url]];
-            });
-        }
-    }];
+    // Integrate the thumbnail generation for the OneDrive cells.
 }
 
 - (void)updateOneDriveDisplayAsItem
@@ -135,7 +124,7 @@
     _oneDriveFile.folder ? [self updateOneDriveDisplayAsFolder] : [self updateOneDriveDisplayAsItem];
 }
 
-- (void)setOneDriveFile:(ODItem *)oneDriveFile
+- (void)setOneDriveFile:(MSGraphDriveItem *)oneDriveFile
 {
     if (oneDriveFile != _oneDriveFile) {
         _oneDriveFile = oneDriveFile;
diff --git a/VLC.xcodeproj/project.pbxproj b/VLC.xcodeproj/project.pbxproj
index 53fee4918..3bdae99f4 100644
--- a/VLC.xcodeproj/project.pbxproj
+++ b/VLC.xcodeproj/project.pbxproj
@@ -132,8 +132,6 @@
 		7AC862A91765E9510011611A /* jquery.ui.widget.js in Resources */ = {isa = PBXBuildFile; fileRef = 7AC862A11765E90C0011611A /* jquery.ui.widget.js */; };
 		7D00161C177056B700649F27 /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 7D00161A17704DAC00649F27 /* main.js */; };
 		7D0C34E71BD951080058CD19 /* NSString+SupportedMedia.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D3784C5183A9972009EE944 /* NSString+SupportedMedia.m */; };
-		7D1052E91A4DCC1100295F08 /* VLCOneDriveTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D1052E81A4DCC1100295F08 /* VLCOneDriveTableViewController.m */; };
-		7D1052EE1A4DCD1E00295F08 /* VLCOneDriveController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D1052ED1A4DCD1E00295F08 /* VLCOneDriveController.m */; };
 		7D1329411BA1F10100BE647E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D1329401BA1F10100BE647E /* main.m */; };
 		7D1329441BA1F10100BE647E /* AppleTVAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D1329431BA1F10100BE647E /* AppleTVAppDelegate.m */; };
 		7D15168B194773630086FB8C /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D15168A194773630086FB8C /* MobileCoreServices.framework */; };
@@ -657,11 +655,6 @@
 		7AC862A01765E90C0011611A /* jquery.iframe-transport.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "jquery.iframe-transport.js"; sourceTree = "<group>"; };
 		7AC862A11765E90C0011611A /* jquery.ui.widget.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.ui.widget.js; sourceTree = "<group>"; };
 		7D00161A17704DAC00649F27 /* main.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = "<group>"; };
-		7D1052E71A4DCC1100295F08 /* VLCOneDriveTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCOneDriveTableViewController.h; path = Sources/VLCOneDriveTableViewController.h; sourceTree = SOURCE_ROOT; };
-		7D1052E81A4DCC1100295F08 /* VLCOneDriveTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCOneDriveTableViewController.m; path = Sources/VLCOneDriveTableViewController.m; sourceTree = SOURCE_ROOT; };
-		7D1052EB1A4DCC4B00295F08 /* VLCOneDriveConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = VLCOneDriveConstants.h; path = Sources/VLCOneDriveConstants.h; sourceTree = SOURCE_ROOT; };
-		7D1052EC1A4DCD1E00295F08 /* VLCOneDriveController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCOneDriveController.h; path = Sources/VLCOneDriveController.h; sourceTree = SOURCE_ROOT; };
-		7D1052ED1A4DCD1E00295F08 /* VLCOneDriveController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCOneDriveController.m; path = Sources/VLCOneDriveController.m; sourceTree = SOURCE_ROOT; };
 		7D13293D1BA1F10100BE647E /* VLC-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "VLC-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		7D1329401BA1F10100BE647E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
 		7D1329421BA1F10100BE647E /* AppleTVAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppleTVAppDelegate.h; sourceTree = "<group>"; };
@@ -888,8 +881,6 @@
 		7DF383B81BF21E4400D71A5C /* VLCPlayerControlWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCPlayerControlWebSocket.m; path = SharedSources/VLCPlayerControlWebSocket.m; sourceTree = SOURCE_ROOT; };
 		7DF383BF1BF231BC00D71A5C /* VLCCloudStorageTVViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCCloudStorageTVViewController.h; sourceTree = "<group>"; };
 		7DF383C01BF231BC00D71A5C /* VLCCloudStorageTVViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCCloudStorageTVViewController.m; sourceTree = "<group>"; };
-		7DF383C91BF2498800D71A5C /* VLCOneDriveCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCOneDriveCollectionViewController.h; path = SharedSources/Clouds/VLCOneDriveCollectionViewController.h; sourceTree = SOURCE_ROOT; };
-		7DF383CA1BF2498800D71A5C /* VLCOneDriveCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCOneDriveCollectionViewController.m; path = SharedSources/Clouds/VLCOneDriveCollectionViewController.m; sourceTree = SOURCE_ROOT; };
 		7DF383CE1BF24BB100D71A5C /* VLCBoxCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCBoxCollectionViewController.h; path = SharedSources/Clouds/VLCBoxCollectionViewController.h; sourceTree = SOURCE_ROOT; };
 		7DF383CF1BF24BB100D71A5C /* VLCBoxCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCBoxCollectionViewController.m; path = SharedSources/Clouds/VLCBoxCollectionViewController.m; sourceTree = SOURCE_ROOT; };
 		7DF7CA0617650C2A00C61739 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
@@ -1345,13 +1336,6 @@
 		7D1052EA1A4DCC1700295F08 /* OneDrive */ = {
 			isa = PBXGroup;
 			children = (
-				7D1052EB1A4DCC4B00295F08 /* VLCOneDriveConstants.h */,
-				7D1052E71A4DCC1100295F08 /* VLCOneDriveTableViewController.h */,
-				7D1052E81A4DCC1100295F08 /* VLCOneDriveTableViewController.m */,
-				7DF383C91BF2498800D71A5C /* VLCOneDriveCollectionViewController.h */,
-				7DF383CA1BF2498800D71A5C /* VLCOneDriveCollectionViewController.m */,
-				7D1052EC1A4DCD1E00295F08 /* VLCOneDriveController.h */,
-				7D1052ED1A4DCD1E00295F08 /* VLCOneDriveController.m */,
 			);
 			name = OneDrive;
 			sourceTree = "<group>";
@@ -3361,12 +3345,10 @@
 				DD3EFEEE1BDEBA3800B68579 /* VLCServerListViewController.m in Sources */,
 				8DE187832105DB2E00A091D2 /* AudioViewController.swift in Sources */,
 				7DB0CB42247FCBCE00077592 /* VLCLocalNetworkServiceBrowserUPnP.m in Sources */,
-				7D1052E91A4DCC1100295F08 /* VLCOneDriveTableViewController.m in Sources */,
 				7D30F3D7183AB2F100FFC021 /* VLCNetworkListCell.m in Sources */,
 				44C8BBB124AF36F4003F8940 /* ActionSheetSpecifier.swift in Sources */,
 				44B5822024E434FD001A2583 /* MediaGridCollectionCell.swift in Sources */,
 				8D6E1588223BBAF600077DD3 /* MiniPlayer.swift in Sources */,
-				7D1052EE1A4DCD1E00295F08 /* VLCOneDriveController.m in Sources */,
 				414396C22023316C005E3FAF /* AppearanceManager.swift in Sources */,
 				7D30F3DC183AB2F900FFC021 /* VLCNetworkLoginViewController.m in Sources */,
 				91F8BF1827318D890058D9DE /* StoreProductCollectionViewCell.swift in Sources */,
-- 
GitLab


From 7939ab86983e543d4421790f078741b9d85056bb Mon Sep 17 00:00:00 2001
From: Diogo Simao Marques <diogo.simaomarquespro@gmail.com>
Date: Mon, 4 Jul 2022 11:16:27 +0200
Subject: [PATCH 3/5] GraphViewController: Add of the MSGraph authentication
 for OneDrive access

Microsoft Graph is now used to allow the user to access the OneDrive
cloud service.

The user is now able to login into a personal or professional account,
navigate through the folders, play the medias displayed and download them.
---
 GraphTableViewController.swift                | 290 ++++++++
 GraphViewController.swift                     | 617 ++++++++++++++++++
 Sources/VLCCloudServicesTableViewController.m |   6 +-
 Sources/VLCCloudStorageTableViewCell.m        |   6 +-
 VLC.xcodeproj/project.pbxproj                 | 258 ++++----
 vlc-ios/VLC-iOS-Bridging-Header.h             |   1 +
 6 files changed, 1054 insertions(+), 124 deletions(-)
 create mode 100644 GraphTableViewController.swift
 create mode 100644 GraphViewController.swift

diff --git a/GraphTableViewController.swift b/GraphTableViewController.swift
new file mode 100644
index 000000000..d2bc41a05
--- /dev/null
+++ b/GraphTableViewController.swift
@@ -0,0 +1,290 @@
+/*****************************************************************************
+ * GraphTableViewController.swift
+ *
+ * Copyright © 2022 VLC authors and VideoLAN
+ *
+ * Authors: Diogo Simao Marques <diogo.simaomarquespro@gmail.com>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+import UIKit
+import MSGraphClientModels
+
+@objc (VLCGraphTableViewController)
+class GraphTableViewController: VLCCloudStorageTableViewController {
+    // MARK: - Properties
+    private lazy var graphViewController: GraphViewController = {
+        let graphViewController = GraphViewController.sharedObject
+        graphViewController.setPresentingViewController(with: self)
+        graphViewController.delegate = self
+        return graphViewController
+    }()
+
+    // MARK: - Init
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        prepareMSGraphControllerIfNeeded()
+
+        navigationItem.titleView = UIImageView(image: UIImage(named: "OneDriveWhite"))
+        cloudStorageLogo.image = UIImage(named: "OneDriveWhite")
+        cloudStorageLogo.sizeToFit()
+        cloudStorageLogo.center = view.center
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = false
+        }
+
+        updateViewAfterSessionChange()
+        authorizationInProgress = false
+        prepareMSGraphControllerIfNeeded()
+        tableView.dataSource = self
+    }
+
+    // MARK: - Overriding methods
+    override func goBack() {
+        let currentItemID = graphViewController.currentItem?.entityId
+
+        if let currentItemID = currentItemID,
+           let rootItemID = graphViewController.getRootItemID(),
+           currentItemID != rootItemID {
+            if graphViewController.parentItem == nil
+                || rootItemID == graphViewController.parentItem?.entityId {
+                graphViewController.currentItem = nil
+            } else {
+                graphViewController.currentItem = graphViewController.parentItem
+                graphViewController.loadParentItem()
+            }
+
+            if let itemName = graphViewController.currentItem?.name {
+                title = itemName
+            }
+
+            activityIndicator.startAnimating()
+            graphViewController.loadCurrentItem()
+        } else {
+            navigationController?.popViewController(animated: true)
+        }
+    }
+
+    override func playAllAction(_ sender: Any!) {
+        let mediaList = createMediaList()
+        streamMediaList(mediaList: mediaList)
+    }
+
+    override func loginAction(_ sender: Any!) {
+        if !graphViewController.isAuthorized {
+            authorizationInProgress = true
+            graphViewController.loginWithViewController(presentingViewController: self)
+        } else {
+            graphViewController.logout()
+        }
+    }
+
+    override func sessionWasUpdated() {
+        DispatchQueue.main.async {
+            self.updateViewAfterSessionChange()
+            self.mediaListUpdated()
+        }
+    }
+
+    override func operationWithProgressInformationStarted() {
+        super.operationWithProgressInformationStarted()
+    }
+
+    override func operationWithProgressInformationStopped() {
+        super.operationWithProgressInformationStopped()
+    }
+
+    override func currentProgressInformation(_ progress: CGFloat) {
+        super.currentProgressInformation(progress)
+    }
+
+    // MARK: - Private helpers
+    private func prepareMSGraphControllerIfNeeded() {
+        if controller == nil {
+            controller = graphViewController
+        }
+    }
+
+    private func streamMediaList(mediaList: VLCMediaList) {
+///        Chunk of code to replace when the .isEmpty property will be available on VLCKit.
+//        if mediaList.isEmpty {
+        if mediaList.count < 1 {
+            return
+        }
+
+        let vpc: PlaybackService = PlaybackService.sharedInstance()
+        vpc.playMediaList(mediaList, firstIndex: 0, subtitlesFilePath: nil)
+    }
+
+    private func addMedia(to mediaList: VLCMediaList, itemName: String?, downloadUrl: String) {
+        let url = URL(string: downloadUrl)
+        if let url = url {
+            let media: VLCMedia = graphViewController.setMediaNameMetadata(VLCMedia(url: url), withName: itemName)
+            mediaList.add(media)
+        }
+    }
+
+    private func createMediaListWithMSGraphDriveItem(item: MSGraphDriveItem? = nil) -> VLCMediaList {
+        let folderItems = graphViewController.getCurrentListFiles()
+        let mediaList: VLCMediaList = VLCMediaList()
+        let downloadUrlDictionary = graphViewController.getDownloadUrlDictionary()
+
+        if let item = item,
+           let downloadUrl = downloadUrlDictionary[item] {
+            addMedia(to: mediaList, itemName: item.name, downloadUrl: downloadUrl)
+        }
+
+        for folderItem in folderItems {
+            if folderItem.folder != nil || folderItem == item {
+                continue
+            }
+
+            if let downloadUrl = downloadUrlDictionary[folderItem] {
+                addMedia(to: mediaList, itemName: folderItem.name, downloadUrl: downloadUrl)
+            }
+        }
+
+        return mediaList
+    }
+
+    private func createMediaList() -> VLCMediaList {
+        return createMediaListWithMSGraphDriveItem()
+    }
+}
+
+// MARK: - VLCCloudStorageTableViewCellProtocol
+extension GraphTableViewController: VLCCloudStorageTableViewCellProtocol {
+    func triggerDownload(for cell: VLCCloudStorageTableViewCell!) {
+        let indexPath: IndexPath? = tableView.indexPath(for: cell)
+        let currentItems: [MSGraphDriveItem] = graphViewController.getCurrentListFiles()
+
+        guard let indexPath = indexPath,
+              indexPath.row < currentItems.count else {
+            preconditionFailure("GraphTableViewController: Invalid range.")
+        }
+
+        let selectedItem: MSGraphDriveItem = currentItems[indexPath.row]
+        var selectedItemName: String = ""
+        if let name = selectedItem.name {
+            selectedItemName = name
+        }
+
+        if selectedItem.size < UIDevice.current.freeDiskSpace.int64Value {
+            let alertController = UIAlertController(title: NSLocalizedString("DROPBOX_DOWNLOAD", comment: ""),
+                                                    message: String(format: NSLocalizedString("DROPBOX_DL_LONG", comment: ""),
+                                                                    selectedItemName, UIDevice.current.model),
+                                                    preferredStyle: .alert)
+
+            let downloadAction = UIAlertAction(title: NSLocalizedString("BUTTON_DOWNLOAD", comment: ""),
+                                               style: .default,
+                                               handler: { _ in
+                self.graphViewController.startDownloadingDriveItem(item: selectedItem)
+            })
+
+            let cancelAction = UIAlertAction(title: NSLocalizedString("BUTTON_CANCEL", comment: ""),
+                                             style: .cancel,
+                                             handler: nil)
+
+            alertController.addAction(downloadAction)
+            alertController.addAction(cancelAction)
+            present(alertController, animated: true)
+        } else {
+            let alertController = UIAlertController(title: NSLocalizedString("DISK_FULL", comment: ""),
+                                                    message: String(format: NSLocalizedString("DISK_FULL_FORMAT", comment: ""),
+                                                                    selectedItemName, UIDevice.current.model),
+                                                    preferredStyle: .alert)
+
+            let okAction = UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""),
+                                         style: .cancel,
+                                         handler: nil)
+
+            alertController.addAction(okAction)
+            present(alertController, animated: true)
+        }
+    }
+}
+
+// MARK: - UITableViewDataSource
+extension GraphTableViewController: UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return graphViewController.getCurrentListFiles().count
+    }
+
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cellIdentifier: String = "OneDriveCell"
+
+        var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
+
+        if cell == nil {
+            cell = VLCCloudStorageTableViewCell(reuseIdentifier: cellIdentifier)
+        }
+
+        if let cell = cell as? VLCCloudStorageTableViewCell,
+           indexPath.row < graphViewController.getCurrentListFiles().count {
+            cell.delegate = self
+            cell.oneDriveFile = graphViewController.getCurrentListFiles()[indexPath.row]
+            cell.titleLabel.text = graphViewController.getCurrentListFiles()[indexPath.row].name
+            return cell
+        }
+
+        return VLCCloudStorageTableViewCell(reuseIdentifier: cellIdentifier)
+    }
+}
+
+// MARK: - UITableViewDelegate
+extension GraphTableViewController: UITableViewDelegate {
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        let currentItems = graphViewController.getCurrentListFiles()
+        let row = indexPath.row
+
+        guard row < currentItems.count else {
+            return
+        }
+
+        let selectedItem: MSGraphDriveItem = currentItems[row]
+
+        if selectedItem.folder != nil {
+            self.activityIndicator.startAnimating()
+            graphViewController.parentItem = graphViewController.currentItem
+            graphViewController.currentItem = selectedItem
+            graphViewController.loadCurrentItem()
+            self.title = selectedItem.name
+            tableView.reloadData()
+        } else {
+            let downloadUrlDictionary = graphViewController.getDownloadUrlDictionary()
+            if let downloadUrl = downloadUrlDictionary[selectedItem] {
+                var mediaList: VLCMediaList
+                let url = URL(string: downloadUrl)
+                var mediaToPlay: VLCMedia = VLCMedia(url: url!)
+                mediaToPlay = graphViewController.setMediaNameMetadata(mediaToPlay, withName: selectedItem.name)
+
+                if !UserDefaults.standard.bool(forKey: kVLCAutomaticallyPlayNextItem) {
+                    mediaList = VLCMediaList(array: [mediaToPlay])
+                } else {
+                    mediaList = createMediaListWithMSGraphDriveItem(item: selectedItem)
+                }
+                streamMediaList(mediaList: mediaList)
+            } else {
+                let alertController = UIAlertController(title: NSLocalizedString("ERROR", comment: ""),
+                                                        message: NSLocalizedString("ONEDRIVE_MEDIA_WITHOUT_URL", comment: ""),
+                                                        preferredStyle: .alert)
+
+                let okAction = UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""),
+                                             style: .cancel,
+                                             handler: nil)
+
+                alertController.addAction(okAction)
+                present(alertController, animated: true)
+            }
+        }
+
+        self.tableView.deselectRow(at: indexPath, animated: false)
+    }
+}
diff --git a/GraphViewController.swift b/GraphViewController.swift
new file mode 100644
index 000000000..c3e319a7a
--- /dev/null
+++ b/GraphViewController.swift
@@ -0,0 +1,617 @@
+/*****************************************************************************
+ * GraphViewController.swift
+ *
+ * Copyright © 2022 VLC authors and VideoLAN
+ *
+ * Authors: Diogo Simao Marques <diogo.simaomarquespro@gmail.com>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+import UIKit
+import MSAL
+import MSGraphClientSDK
+import MSGraphClientModels
+import MSGraphMSALAuthProvider
+
+@objc (VLCGraphViewController)
+class GraphViewController: VLCCloudStorageController {
+    // MARK: - Properties
+    private let kClientID = "c7fbceea-ef4a-4a2e-9e54-72a3c333aa0b"
+    private let kRedirectUri = "msauth.com.example.vlc-ios://auth"
+    private let kAuthority = "https://login.microsoftonline.com/common"
+    private let kGraphEndpoint = "https://graph.microsoft.com/"
+
+    private let kScopes: [String] = ["user.read", "files.read"]
+
+    private var accessToken = String()
+    private var applicationContext: MSALPublicClientApplication?
+    private var webViewParameters: MSALWebviewParameters?
+
+    private var currentAccount: MSALAccount?
+
+    private var authenticationProvider: MSAuthenticationProvider?
+
+    lazy var currentItem: MSGraphDriveItem? = nil
+    lazy var parentItem: MSGraphDriveItem? = nil
+    private lazy var currentItems: [MSGraphDriveItem] = []
+    private lazy var downloadUrlDictionary: [MSGraphDriveItem: String] = [:]
+    private lazy var rootItemID: String? = nil
+    private var presentingViewController: UIViewController?
+
+    private lazy var pendingDownloads: [MSGraphDriveItem] = []
+    private var downloadInProgress: Bool = false
+    private var progress: Progress = Progress()
+
+    @objc static var sharedObject: GraphViewController = {
+        let sharedInstance = GraphViewController()
+        return sharedInstance
+    }()
+
+    // MARK: - Overriding properties
+    override var currentListFiles: [Any]! {
+        get { return currentItems }
+    }
+
+    override var isAuthorized: Bool {
+        get { return super.isAuthorized }
+        set { super.isAuthorized = newValue }
+    }
+
+    override var canPlayAll: Bool {
+        get { return !currentItems.isEmpty }
+    }
+
+    // MARK: - Overriding methods
+    override func logout() {
+        guard let applicationContext = applicationContext else {
+            return
+        }
+
+        guard let currentAccount = currentAccount else {
+            return
+        }
+
+        do {
+            let signoutParameters = MSALSignoutParameters(webviewParameters: webViewParameters!)
+            signoutParameters.signoutFromBrowser = false
+
+            applicationContext.signout(with: currentAccount, signoutParameters: signoutParameters, completionBlock: { (success, error) in
+                if let error = error {
+                    preconditionFailure("GraphViewController: Couldn't sign out account: \(error).")
+                }
+
+                self.accessToken = ""
+                self.updateCurrentAccount(account: nil)
+            })
+        }
+
+        delegate.mediaListUpdated()
+        if let presentingViewController = presentingViewController {
+            presentingViewController.navigationController?.popViewController(animated: true)
+        }
+
+        resetSession()
+    }
+
+    override func requestDirectoryListing(atPath path: String!) {
+        loadCurrentItem()
+    }
+
+    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
+        DispatchQueue.main.async {
+            if let progress: Progress = object as? Progress {
+                let expectedDowloadSize = CGFloat(progress.totalUnitCount)
+                let percentage = (progress.fractionCompleted * expectedDowloadSize) / 100
+                self.delegate.currentProgressInformation?(percentage)
+            }
+        }
+    }
+
+    // MARK: - Public helpers
+    func getCurrentListFiles() -> [MSGraphDriveItem] {
+        return currentItems
+    }
+
+    func getDownloadUrlDictionary() -> [MSGraphDriveItem: String] {
+        return downloadUrlDictionary
+    }
+
+    func getRootItemID() -> String? {
+        return rootItemID
+    }
+
+    func setPresentingViewController(with viewController: UIViewController) {
+        presentingViewController = viewController
+    }
+
+    // MARK: - Private helpers
+    private func resetSession() {
+        parentItem = nil
+        currentItem = nil
+        rootItemID = nil
+        isAuthorized = false
+        currentItems.removeAll()
+        downloadUrlDictionary.removeAll()
+        pendingDownloads.removeAll()
+    }
+
+    private func checkFileExtension(for item: MSGraphDriveItem) -> Bool {
+        if item.folder != nil || item.audio != nil || item.video != nil {
+            return true
+        } else if let itemName = item.name as? NSString,
+           itemName.isSupportedMediaFormat() {
+            return true
+        } else {
+            return false
+        }
+    }
+
+    @objc private func sessionWasUpdated() {
+        delegate.responds(to: #selector(sessionWasUpdated))
+        delegate.perform(#selector(sessionWasUpdated))
+    }
+
+    private func platformViewDidLoadSetup() {
+        NotificationCenter.default.addObserver(self,
+                                               selector: #selector(appCameToForeGround(notification:)),
+                                               name: UIApplication.willEnterForegroundNotification,
+                                               object: nil)
+    }
+
+    @objc private func appCameToForeGround(notification: Notification) {
+        loadCurrentAccount()
+    }
+
+    // MARK: - Authentication
+    func loginWithViewController(presentingViewController: UIViewController) {
+        self.presentingViewController = presentingViewController
+
+        if #available(iOS 11.0, *) {
+            UINavigationBar.appearance().prefersLargeTitles = false
+        }
+
+        do {
+            try initMSAL()
+        } catch let error {
+            preconditionFailure("GraphViewController: Unable to create the application context: \(error).")
+        }
+
+        webViewParameters = MSALWebviewParameters(authPresentationViewController: presentingViewController)
+
+        loadCurrentAccount()
+        platformViewDidLoadSetup()
+        callGraphAPI()
+    }
+
+    private func initMSAL() throws {
+        guard let authorityURL = URL(string: kAuthority) else {
+            preconditionFailure("GraphViewController: Unable to create authority URL.")
+        }
+
+        let authority = try MSALAADAuthority(url: authorityURL)
+        let msalConfiguration = MSALPublicClientApplicationConfig(clientId: kClientID, redirectUri: kRedirectUri, authority: authority)
+        applicationContext = try MSALPublicClientApplication(configuration: msalConfiguration)
+    }
+
+    private func callGraphAPI() {
+        loadCurrentAccount { (account) in
+            guard let currentAccount = account else {
+                self.acquireTokenInteractively()
+                return
+            }
+
+            self.acquireTokenSilently(currentAccount)
+        }
+    }
+
+    private func acquireTokenInteractively() {
+        guard let applicationContext = applicationContext else {
+            return
+        }
+
+        guard let webViewParameters = webViewParameters else {
+            return
+        }
+
+        let parameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: webViewParameters)
+        parameters.promptType = .selectAccount
+
+        applicationContext.acquireToken(with: parameters) { (result, error) in
+            if let error = error {
+                preconditionFailure("GraphViewController: Couldn't acquire token: \(error).")
+            }
+
+            guard let result = result else {
+                preconditionFailure("GraphViewController: Couldn't retrieve token result.")
+            }
+
+            self.accessToken = result.accessToken
+            self.updateCurrentAccount(account: result.account)
+            self.load()
+        }
+    }
+
+    private func acquireTokenSilently(_ account: MSALAccount) {
+        guard let applicationContext = applicationContext else {
+            return
+        }
+
+        let parameters = MSALSilentTokenParameters(scopes: kScopes, account: account)
+        applicationContext.acquireTokenSilent(with: parameters) { (result, error) in
+            if let error = error {
+                let nsError = error as NSError
+                if nsError.domain == MSALErrorDomain {
+                    if nsError.code == MSALError.interactionRequired.rawValue {
+                        DispatchQueue.main.async {
+                            self.acquireTokenInteractively()
+                        }
+
+                        return
+                    }
+                }
+
+                return
+            }
+
+            guard let result = result else {
+                preconditionFailure("GraphViewController: Couldn't retrieve token result.")
+            }
+
+            self.accessToken = result.accessToken
+            self.load()
+        }
+    }
+
+    private func getGraphEndpoint() -> String {
+        if let currentItem = currentItem,
+           let rootItemID = rootItemID {
+            return kGraphEndpoint + "v1.0/drives/\(rootItemID)/items/\(currentItem.entityId)/children"
+        }
+
+        return kGraphEndpoint + "v1.0/me/drive/root/children"
+    }
+
+    typealias AccountCompletion = (MSALAccount?) -> Void
+
+    private func loadCurrentAccount(completion: AccountCompletion? = nil) {
+        guard let applicationContext = applicationContext else {
+            return
+        }
+
+        let msalParameters = MSALParameters()
+        msalParameters.completionBlockQueue = DispatchQueue.main
+
+        applicationContext.getCurrentAccount(with: msalParameters, completionBlock: { (currentAccount, previousAccount, error) in
+            if let error = error {
+                preconditionFailure("GraphViewController: Couldn't acquire current account: \(error).")
+            }
+
+            if let currentAccount = currentAccount {
+                self.updateCurrentAccount(account: currentAccount)
+
+                if let completion = completion {
+                    completion(self.currentAccount)
+                }
+
+                return
+            }
+
+            self.accessToken = ""
+            self.updateCurrentAccount(account: nil)
+
+            if let completion = completion {
+                completion(nil)
+            }
+        })
+    }
+
+    private func updateCurrentAccount(account: MSALAccount?) {
+        currentAccount = account
+    }
+
+    private func authSuccess() {
+        isAuthorized = true
+        DispatchQueue.main.async {
+            self.sessionWasUpdated()
+        }
+    }
+
+    private func authFailed() {
+        isAuthorized = false
+        DispatchQueue.main.async {
+            self.sessionWasUpdated()
+        }
+    }
+
+    // MARK: - Content load
+    func loadParentItem() {
+        guard let applicationContext = applicationContext else {
+            return
+        }
+
+        guard let parentId = parentItem?.parentReference?.itemReferenceId else {
+            currentItem = nil
+            parentItem = nil
+            return
+        }
+
+        var rootID: String = "root"
+        if let rootItemID = rootItemID {
+            rootID = rootItemID
+        }
+
+        let stringUrl: String = kGraphEndpoint + "v1.0/drives/\(rootID)/items/\(parentId)"
+        let url = URL(string: stringUrl)
+
+        guard let url = url else {
+            return
+        }
+
+        let urlRequest = NSMutableURLRequest(url: url)
+        urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
+        urlRequest.httpMethod = "GET"
+
+        let parameters = MSALAuthenticationProviderOptions(scopes: kScopes)
+
+        authenticationProvider = MSALAuthenticationProvider(publicClientApplication: applicationContext, andOptions: parameters)
+        let httpClient: MSHTTPClient = MSClientFactory.createHTTPClient(with: authenticationProvider)
+
+        var driveItem: MSGraphDriveItem? = nil
+
+        let meDataTask: MSURLSessionDataTask = httpClient.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
+            if error != nil {
+                self.authFailed()
+                return
+            }
+
+            guard let data = data else {
+                preconditionFailure("GraphViewController: Couldn't retrieve data.")
+            }
+
+            driveItem = try! MSGraphDriveItem(data: data)
+            self.parentItem = driveItem
+        })
+        meDataTask.execute()
+    }
+
+    func loadCurrentItem() {
+        let itemID: String
+        if let currentItem = currentItem {
+            itemID = currentItem.entityId
+        } else {
+            itemID = "root"
+        }
+
+        guard let rootItemID = rootItemID else {
+            return
+        }
+
+        let stringUrl: String = kGraphEndpoint + "v1.0/drives/\(rootItemID)/items/\(itemID)/children"
+        let url = URL(string: stringUrl)
+
+        guard let url = url else {
+            return
+        }
+
+        let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: url)
+        urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
+        urlRequest.httpMethod = "GET"
+
+        load(with: urlRequest)
+    }
+
+    private func load(with request: NSMutableURLRequest? = nil) {
+        guard let applicationContext = applicationContext else {
+            return
+        }
+
+        let parameters = MSALAuthenticationProviderOptions(scopes: kScopes)
+
+        authenticationProvider = MSALAuthenticationProvider(publicClientApplication: applicationContext, andOptions: parameters)
+        let httpClient: MSHTTPClient = MSClientFactory.createHTTPClient(with: authenticationProvider)
+
+        let urlRequest: NSMutableURLRequest
+        if let request = request {
+            urlRequest = request
+        } else {
+            let url = URL(string: getGraphEndpoint())
+
+            guard let url = url else {
+                return
+            }
+
+            urlRequest = NSMutableURLRequest(url: url)
+            urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
+            urlRequest.httpMethod = "GET"
+        }
+
+        let meDataTask: MSURLSessionDataTask = httpClient.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
+            if error != nil {
+                self.authFailed()
+                return
+            }
+
+            guard let data = data else {
+                preconditionFailure("GraphViewController: Couldn't retrieve data.")
+            }
+
+            let collection: MSCollection? = try? MSCollection(data: data)
+
+            guard let collection = collection else {
+                return
+            }
+
+            guard let collectionValue = collection.value else {
+                return
+            }
+
+            self.currentItems.removeAll()
+
+            for object in collectionValue {
+                let driveItem: MSGraphDriveItem? = MSGraphDriveItem(dictionary: object as? [AnyHashable: Any])
+                if let driveItem = driveItem,
+                   self.checkFileExtension(for: driveItem) {
+                    self.currentItems.append(driveItem)
+                    self.getDownloadURL(for: driveItem)
+
+                    if self.rootItemID == nil {
+                        self.rootItemID = driveItem.parentReference?.driveId
+                    }
+                }
+            }
+
+            self.authSuccess()
+        })
+        meDataTask.execute()
+    }
+
+    // MARK: - Download
+    func startDownloadingDriveItem(item: MSGraphDriveItem) {
+        pendingDownloads.append(item)
+        triggerNextDownload()
+    }
+
+    private func triggerNextDownload() {
+        if !pendingDownloads.isEmpty && !downloadInProgress {
+            downloadInProgress = true
+            guard let firstItem = pendingDownloads.first else {
+                return
+            }
+
+            downloadDriveItem(item: firstItem)
+        }
+    }
+
+    private func downloadStarted() {
+        delegate.operationWithProgressInformationStarted?()
+    }
+
+    private func downloadEnded() {
+        delegate.operationWithProgressInformationStopped?()
+        downloadInProgress = false
+        pendingDownloads.remove(at: 0)
+        triggerNextDownload()
+    }
+
+    private func downloadDriveItem(item: MSGraphDriveItem) {
+        guard let downloadUrl = downloadUrlDictionary[item] else {
+            return
+        }
+
+        let url = URL(string: downloadUrl)
+
+        guard let url = url else {
+            return
+        }
+
+        downloadStarted()
+
+        loadData(item: item, url: url) { (data, error) in
+            self.downloadEnded()
+        }
+    }
+
+    private func loadData(item: MSGraphDriveItem, url: URL, completion: @escaping (Data?, Error?) -> Void) {
+        let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
+        let fileCachePath = documentDirectory.appendingPathComponent(item.name!, isDirectory: true)
+
+        if FileManager().fileExists(atPath: fileCachePath.path) {
+            downloadEnded()
+            return
+        }
+
+        if let data = try? Data(contentsOf: fileCachePath) {
+            completion(data, nil)
+            return
+        }
+
+        download(url: url, toFile: fileCachePath, item: item) { (error) in
+            let data = try? Data(contentsOf: fileCachePath)
+            completion(data, error)
+        }
+    }
+
+    private func download(url: URL, toFile file: URL, item: MSGraphDriveItem, completion: @escaping (Error?) -> Void) {
+        let task = URLSession.shared.downloadTask(with: url) { (tempUrl, response, error) in
+            guard let tempUrl = tempUrl else {
+                completion(error)
+                return
+            }
+
+            do {
+                if FileManager.default.fileExists(atPath: file.path) {
+                    try FileManager.default.removeItem(at: file)
+                }
+
+                try FileManager.default.copyItem(at: tempUrl, to: file)
+
+                completion(nil)
+            }
+
+            catch let fileError {
+                completion(fileError)
+            }
+        }
+
+        if #available(iOS 11.0, *) {
+            task.progress.totalUnitCount = item.size
+            showProgress(progress: task.progress)
+        }
+        task.resume()
+    }
+
+    private func showProgress(progress: Progress) {
+        self.progress = progress
+        progress.addObserver(self, forKeyPath: NSStringFromSelector(#selector(getter: self.progress.fractionCompleted)), options: .init(rawValue: 0), context: nil)
+    }
+
+    private func getDownloadURL(for driveItem: MSGraphDriveItem) {
+        guard let applicationContext = applicationContext else {
+            return
+        }
+
+        downloadUrlDictionary.removeAll()
+
+        let driveItemID: String = driveItem.entityId
+
+        let stringUrl: String = kGraphEndpoint + "v1.0/drive/items/\(driveItemID)/?select=id,@microsoft.graph.downloadUrl"
+        let url = URL(string: stringUrl)
+
+        guard let url = url else {
+            return
+        }
+
+        let urlRequest = NSMutableURLRequest(url: url)
+        urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
+        urlRequest.httpMethod = "GET"
+
+        let parameters = MSALAuthenticationProviderOptions(scopes: kScopes)
+
+        authenticationProvider = MSALAuthenticationProvider(publicClientApplication: applicationContext, andOptions: parameters)
+        let httpClient: MSHTTPClient = MSClientFactory.createHTTPClient(with: authenticationProvider)
+
+        let meDataTask: MSURLSessionDataTask = httpClient.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
+            if error != nil {
+                self.authFailed()
+                return
+            }
+
+            guard let data = data else {
+                preconditionFailure("GraphViewController: Couldn't retrieve data.")
+            }
+
+            guard let result = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: Any] else {
+                return
+            }
+
+            guard let url = result["@microsoft.graph.downloadUrl"] as? String else {
+                self.downloadUrlDictionary[driveItem] = nil
+                return
+            }
+
+            self.downloadUrlDictionary.updateValue(url, forKey: driveItem)
+        })
+        meDataTask.execute()
+    }
+}
diff --git a/Sources/VLCCloudServicesTableViewController.m b/Sources/VLCCloudServicesTableViewController.m
index be751ff9c..9076395a1 100644
--- a/Sources/VLCCloudServicesTableViewController.m
+++ b/Sources/VLCCloudServicesTableViewController.m
@@ -27,6 +27,7 @@
 @property (nonatomic) VLCDropboxTableViewController *dropboxTableViewController;
 @property (nonatomic) VLCGoogleDriveTableViewController *googleDriveTableViewController;
 @property (nonatomic) VLCBoxTableViewController *boxTableViewController;
+@property (nonatomic) VLCGraphTableViewController *graphTableViewController;
 @property (nonatomic) VLCDocumentPickerController *documentPickerController;
 
 @end
@@ -43,6 +44,7 @@
     self.dropboxTableViewController = [[VLCDropboxTableViewController alloc] initWithNibName:@"VLCCloudStorageTableViewController" bundle:nil];
     self.googleDriveTableViewController = [[VLCGoogleDriveTableViewController alloc] initWithNibName:@"VLCCloudStorageTableViewController" bundle:nil];
     self.boxTableViewController = [[VLCBoxTableViewController alloc] initWithNibName:@"VLCCloudStorageTableViewController" bundle:nil];
+    self.graphTableViewController = [[VLCGraphTableViewController alloc] initWithNibName:@"VLCCloudStorageTableViewController" bundle:nil];
     self.documentPickerController = [VLCDocumentPickerController new];
 }
 
@@ -94,6 +96,7 @@
     int i = [[VLCDropboxController sharedInstance] isAuthorized] ? 1 : 0;
     i += [[VLCGoogleDriveController sharedInstance] isAuthorized] ? 1 : 0;
     i += [[VLCBoxController sharedInstance] isAuthorized] ? 1 : 0;
+    i += [[VLCGraphViewController sharedObject] isAuthorized] ? 1 : 0;
     return i;
 }
 
@@ -159,7 +162,7 @@
         }
         case 3: {
             //OneDrive
-            BOOL isAuthorized = NO;
+            BOOL isAuthorized = [VLCGraphViewController.sharedObject isAuthorized];
             cell.icon.image = [UIImage imageNamed:@"OneDriveCell"];
             cell.cloudTitle.text = @"OneDrive";
             cell.cloudInformation.text = isAuthorized ? NSLocalizedString(@"LOGGED_IN", "") : NSLocalizedString(@"LOGIN", "");
@@ -205,6 +208,7 @@
             break;
         case 3:
             //OneDrive
+            [self.navigationController pushViewController:self.graphTableViewController animated:YES];
             break;
         case 4:
             if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
diff --git a/Sources/VLCCloudStorageTableViewCell.m b/Sources/VLCCloudStorageTableViewCell.m
index e0c36f151..67c105db4 100644
--- a/Sources/VLCCloudStorageTableViewCell.m
+++ b/Sources/VLCCloudStorageTableViewCell.m
@@ -81,17 +81,17 @@
 - (void)updateOneDriveDisplayAsItem
 {
     int64_t duration = 0;
-    NSString *title = self.oneDriveFile.name;
+    NSString *title = _oneDriveFile.name;
     NSMutableString *subtitle = [[NSMutableString alloc] init];
 
     _downloadButton.hidden = NO;
     _titleLabel.text = title;
 
-    if (_oneDriveFile.audio) {
+    if (_oneDriveFile.audio || title.isSupportedAudioMediaFormat) {
         _thumbnailView.image = [UIImage imageNamed:@"audio"];
         duration = _oneDriveFile.audio.duration;
         [self loadThumbnail];
-    } else if (_oneDriveFile.video) {
+    } else if (_oneDriveFile.video || title.isSupportedMediaFormat) {
         _thumbnailView.image = [UIImage imageNamed:@"movie"];
         duration = _oneDriveFile.video.duration;
         [self loadThumbnail];
diff --git a/VLC.xcodeproj/project.pbxproj b/VLC.xcodeproj/project.pbxproj
index 3bdae99f4..9475eee06 100644
--- a/VLC.xcodeproj/project.pbxproj
+++ b/VLC.xcodeproj/project.pbxproj
@@ -7,8 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		14903A768F4E959C0828288C /* libPods-VLC-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 52DDBB38DF22E6BBD19C38C8 /* libPods-VLC-tvOS.a */; };
-		226083AA75B10C9AA36C939A /* libPods-VLC-iOS-Screenshots.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D5BE7EF6F028FE82FA278E5E /* libPods-VLC-iOS-Screenshots.a */; };
+		13925DC647EC27EEB2818FD2 /* libPods-VLC-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B0E3BE184ED7B95399C04EB6 /* libPods-VLC-tvOS.a */; };
 		2629C888223991A400F8D080 /* VLCMediaThumbnailerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2629C887223991A400F8D080 /* VLCMediaThumbnailerCache.swift */; };
 		2663ACB222497D21000FBB95 /* VLCNetworkLoginTVViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2663ACB122497D21000FBB95 /* VLCNetworkLoginTVViewController.swift */; };
 		2663ACB422497D47000FBB95 /* VLCNetworkLoginTVViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2663ACB322497D47000FBB95 /* VLCNetworkLoginTVViewController.xib */; };
@@ -97,6 +96,7 @@
 		44C8BBB224AF36F4003F8940 /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44C8BBAC24AF36F4003F8940 /* SettingsCell.swift */; };
 		44C8BBB324AF36F4003F8940 /* SettingsHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44C8BBAD24AF36F4003F8940 /* SettingsHeaderFooterView.swift */; };
 		44C8BBB524AF51FE003F8940 /* NSObject+SettingsReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44C8BBB424AF51FD003F8940 /* NSObject+SettingsReader.swift */; };
+		4B079F7D58CA85ECBFEE7115 /* libPods-VLC-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F4D7CA6C946BEC69172FE01C /* libPods-VLC-iOS.a */; };
 		597B403F2625E85000C0D81E /* SliderInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597B403E2625E85000C0D81E /* SliderInfoView.swift */; };
 		6B4E33D11BF2A39400A35255 /* playerControl.css in Resources */ = {isa = PBXBuildFile; fileRef = 6B4E33CF1BF2A39400A35255 /* playerControl.css */; };
 		6B4E33D21BF2A39400A35255 /* playerControl.js in Resources */ = {isa = PBXBuildFile; fileRef = 6B4E33D01BF2A39400A35255 /* playerControl.js */; };
@@ -281,7 +281,6 @@
 		7DF90B4A1BE7A8110059C0E3 /* IASKSettingsReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DF90B471BE7A8110059C0E3 /* IASKSettingsReader.m */; };
 		7DF90B4B1BE7A8110059C0E3 /* IASKSpecifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DF90B491BE7A8110059C0E3 /* IASKSpecifier.m */; };
 		7DF9352F1958AB0600E60FD4 /* UIColor+Presets.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DF9352E1958AB0600E60FD4 /* UIColor+Presets.m */; };
-		81334053F6D89AB90E14A1C3 /* libPods-VLC-iOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 91CCB170FBC97B6B93B99E0A /* libPods-VLC-iOSTests.a */; };
 		8D0E75CB24D1D870000D5267 /* VideoPlayerViewController+ControlsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D0E75CA24D1D870000D5267 /* VideoPlayerViewController+ControlsDelegate.swift */; };
 		8D144D6322298E8E00984C46 /* AudioMiniPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D144D6222298E8E00984C46 /* AudioMiniPlayer.swift */; };
 		8D15A1ED22A9603300CFA758 /* ActionSheetSortSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D15A1EC22A9603300CFA758 /* ActionSheetSortSectionHeader.swift */; };
@@ -331,8 +330,8 @@
 		9B9231C4185A703700F89498 /* VLCNetworkLoginViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B9231C3185A703700F89498 /* VLCNetworkLoginViewController.xib */; };
 		9BADAF45185FBD9D00108BD8 /* VLCFrostedGlasView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BADAF44185FBD9D00108BD8 /* VLCFrostedGlasView.m */; };
 		9BE4D1CE183D76950006346C /* VLCCloudStorageTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D3784B0183A990F009EE944 /* VLCCloudStorageTableViewCell.m */; };
-		A483C473E441E7BA7B31BF76 /* libPods-VLC-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CE6CC455148EC65B1D66CC66 /* libPods-VLC-iOS.a */; };
 		A79246C8170F11DF0036AAF2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A79246C6170F11DF0036AAF2 /* Localizable.strings */; };
+		ABCDCCB0B59453B72E6C4787 /* libPods-VLC-iOS-Screenshots.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4307771A200A7301C243AE51 /* libPods-VLC-iOS-Screenshots.a */; };
 		CAA0B0ED2072651000B9274E /* VLCTestMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAF76D8F20709C4100E2AD7B /* VLCTestMenu.swift */; };
 		CAA0B0F02072651A00B9274E /* XCUIElement+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAF76D9320709C9500E2AD7B /* XCUIElement+Helpers.swift */; };
 		CAA0B0F52072686E00B9274E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A79246C6170F11DF0036AAF2 /* Localizable.strings */; };
@@ -348,7 +347,9 @@
 		D6E034ED1CC284FC0037F516 /* VLCStreamingHistoryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D6E034EC1CC284FC0037F516 /* VLCStreamingHistoryCell.m */; };
 		D902E21527F4ADF900DC2B41 /* ArtistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D902E21427F4ADF900DC2B41 /* ArtistViewController.swift */; };
 		D92C186527CD317D0034058F /* ChapterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D92C186427CD317D0034058F /* ChapterView.swift */; };
+		D96ACA962832836C00DD1A3E /* GraphViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D96ACA952832836C00DD1A3E /* GraphViewController.swift */; };
 		D96C9EBC28105F5B005F13BB /* AlbumHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D96C9EBB28105F5B005F13BB /* AlbumHeader.swift */; };
+		D98EE7452846183F00E6B02D /* GraphTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D98EE7442846183F00E6B02D /* GraphTableViewController.swift */; };
 		D9AFDCA527D6383F002326CC /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9AFDCA427D6383F002326CC /* BookmarksView.swift */; };
 		DD13A37B1BEE2FAA00A35554 /* VLCMaskView.m in Sources */ = {isa = PBXBuildFile; fileRef = DD13A37A1BEE2FAA00A35554 /* VLCMaskView.m */; };
 		DD1B31F41BF637D500A369B6 /* VLCPlaybackInfoTracksTVViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1B31F21BF637D500A369B6 /* VLCPlaybackInfoTracksTVViewController.m */; };
@@ -437,6 +438,7 @@
 		E08AD1712304DDE800E023B9 /* MediaPlayerActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08AD1702304DDE800E023B9 /* MediaPlayerActionSheet.swift */; };
 		E0C04F951A25B4410080331A /* VLCDocumentPickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C04F941A25B4410080331A /* VLCDocumentPickerController.m */; };
 		E0F8980422D399E10061DB2F /* MediaScrubProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0F8980322D399E10061DB2F /* MediaScrubProgressBar.swift */; };
+		FA22019D4109D19B4299773C /* libPods-VLC-iOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 939888242E2DC6874C59DCD6 /* libPods-VLC-iOSTests.a */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -464,6 +466,7 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
+		027E1E187A0A43CF5CFAECEC /* Pods-VLC-iOS.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS.distribution.xcconfig"; path = "Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS.distribution.xcconfig"; sourceTree = "<group>"; };
 		1ACB60BB25275E5700405250 /* VLCFirstStepsBaseViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = VLCFirstStepsBaseViewController.h; path = Sources/VLCFirstStepsBaseViewController.h; sourceTree = "<group>"; };
 		1ACB60BC25275E5700405250 /* VLCFirstStepsBaseViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = VLCFirstStepsBaseViewController.m; path = Sources/VLCFirstStepsBaseViewController.m; sourceTree = "<group>"; };
 		2629C886223991A300F8D080 /* VLC-tvOS-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VLC-tvOS-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -473,9 +476,8 @@
 		26F1BFCF1A770408001DF30C /* libMediaVLC.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = libMediaVLC.xml; sourceTree = "<group>"; };
 		29125E5417492219003F03E5 /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = "<group>"; };
 		2915544217490D4A00B86CAD /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
-		2B1BC34A3BC4BCE7588BD68A /* Pods-VLC-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS.release.xcconfig"; sourceTree = "<group>"; };
-		3B75A76DF6589FE678357D42 /* Pods-VLC-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS.debug.xcconfig"; sourceTree = "<group>"; };
-		3E95DC30A81B2AF9F7442E00 /* Pods-VLC-iOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOSTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-iOSTests/Pods-VLC-iOSTests.debug.xcconfig"; sourceTree = "<group>"; };
+		2E995114543D1E41A5A82486 /* Pods-VLC-iOS-Screenshots.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS-Screenshots.release.xcconfig"; path = "Target Support Files/Pods-VLC-iOS-Screenshots/Pods-VLC-iOS-Screenshots.release.xcconfig"; sourceTree = "<group>"; };
+		3B79FA55420524A988373783 /* Pods-VLC-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS.release.xcconfig"; path = "Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS.release.xcconfig"; sourceTree = "<group>"; };
 		4072A776258791B8006EA498 /* VideoFiltersView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = VideoFiltersView.xib; sourceTree = "<group>"; };
 		4072A77D25879577006EA498 /* VideoFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoFiltersView.swift; sourceTree = "<group>"; };
 		407EB586257A418900686B1F /* SleepTimerView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SleepTimerView.xib; sourceTree = "<group>"; };
@@ -608,6 +610,7 @@
 		41EC28E42136DD41004BCF0F /* MovieCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieCollectionViewCell.swift; sourceTree = "<group>"; };
 		41EC28E82136DE46004BCF0F /* MovieCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MovieCollectionViewCell.xib; sourceTree = "<group>"; };
 		41FCD2F720B565B500660AAB /* VLCAlertViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VLCAlertViewController.swift; sourceTree = "<group>"; };
+		4307771A200A7301C243AE51 /* libPods-VLC-iOS-Screenshots.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VLC-iOS-Screenshots.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		4342C3C227474CA000E52334 /* SortedMediaFiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortedMediaFiles.swift; sourceTree = "<group>"; };
 		444E5BF924C6081A0003B69C /* PasscodeLockController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockController.swift; sourceTree = "<group>"; };
 		444E5BFF24C719480003B69C /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = "<group>"; };
@@ -620,11 +623,9 @@
 		44C8BBAC24AF36F4003F8940 /* SettingsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = "<group>"; };
 		44C8BBAD24AF36F4003F8940 /* SettingsHeaderFooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsHeaderFooterView.swift; sourceTree = "<group>"; };
 		44C8BBB424AF51FD003F8940 /* NSObject+SettingsReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSObject+SettingsReader.swift"; path = "Sources/Extensions/NSObject+SettingsReader.swift"; sourceTree = "<group>"; };
-		52DDBB38DF22E6BBD19C38C8 /* libPods-VLC-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VLC-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
-		5671052B495534848FA467EE /* Pods-VLC-iOS-Screenshots.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS-Screenshots.release.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-iOS-Screenshots/Pods-VLC-iOS-Screenshots.release.xcconfig"; sourceTree = "<group>"; };
+		535ECB057D81248C91FA25C4 /* Pods-VLC-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS.debug.xcconfig"; path = "Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS.debug.xcconfig"; sourceTree = "<group>"; };
+		57087A12E77ACEB9D1D30E33 /* Pods-VLC-tvOS.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-tvOS.distribution.xcconfig"; path = "Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS.distribution.xcconfig"; sourceTree = "<group>"; };
 		597B403E2625E85000C0D81E /* SliderInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderInfoView.swift; sourceTree = "<group>"; };
-		5B343E4F4D971F5A169EB864 /* Pods-VLC-iOS.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS.distribution.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS.distribution.xcconfig"; sourceTree = "<group>"; };
-		65EC8984D840047F50AA00A1 /* libPods-VLC-iOSUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VLC-iOSUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		6B4E33CF1BF2A39400A35255 /* playerControl.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = playerControl.css; path = Resources/web/playerControl.css; sourceTree = SOURCE_ROOT; };
 		6B4E33D01BF2A39400A35255 /* playerControl.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = playerControl.js; path = Resources/web/playerControl.js; sourceTree = SOURCE_ROOT; };
 		6C5B0C9C27A43098005AE25B /* PlaybackServiceAdjustFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PlaybackServiceAdjustFilter.swift; path = Sources/PlaybackServiceAdjustFilter.swift; sourceTree = SOURCE_ROOT; };
@@ -649,7 +650,7 @@
 		6DE36F2A2719D7CB00558199 /* UIDevice+VLC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+VLC.swift"; sourceTree = "<group>"; };
 		6DE63F3E270FD3D1008A011D /* VLCPlayerDisplayController+MiniPlayerDraggingDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VLCPlayerDisplayController+MiniPlayerDraggingDelegate.swift"; sourceTree = "<group>"; };
 		6DE9183C25F65C390039AFA7 /* UIScrollView+flashScrollIndicatorsIfNeeded.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIScrollView+flashScrollIndicatorsIfNeeded.swift"; path = "Sources/Extensions/UIScrollView+flashScrollIndicatorsIfNeeded.swift"; sourceTree = "<group>"; };
-		703A80CCC005093CCDFBECBF /* Pods-VLC-iOS-Screenshots.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS-Screenshots.distribution.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-iOS-Screenshots/Pods-VLC-iOS-Screenshots.distribution.xcconfig"; sourceTree = "<group>"; };
+		723659CB3834E1B30918E514 /* Pods-VLC-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS.debug.xcconfig"; sourceTree = "<group>"; };
 		7AC8629B1765DC560011611A /* style.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = style.css; sourceTree = "<group>"; };
 		7AC8629F1765E90C0011611A /* jquery.fileupload.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.fileupload.js; sourceTree = "<group>"; };
 		7AC862A01765E90C0011611A /* jquery.iframe-transport.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "jquery.iframe-transport.js"; sourceTree = "<group>"; };
@@ -891,7 +892,6 @@
 		7DF90B491BE7A8110059C0E3 /* IASKSpecifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = IASKSpecifier.m; path = Pods/InAppSettingsKit/InAppSettingsKit/Models/IASKSpecifier.m; sourceTree = SOURCE_ROOT; };
 		7DF9352D1958AB0600E60FD4 /* UIColor+Presets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor+Presets.h"; path = "Sources/Extensions/UIColor+Presets.h"; sourceTree = SOURCE_ROOT; };
 		7DF9352E1958AB0600E60FD4 /* UIColor+Presets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIColor+Presets.m"; path = "Sources/Extensions/UIColor+Presets.m"; sourceTree = SOURCE_ROOT; };
-		7FC9CCF39DD8843873A42D34 /* Pods-VLC-iOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOSTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-iOSTests/Pods-VLC-iOSTests.release.xcconfig"; sourceTree = "<group>"; };
 		8D0E75CA24D1D870000D5267 /* VideoPlayerViewController+ControlsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VideoPlayerViewController+ControlsDelegate.swift"; sourceTree = "<group>"; };
 		8D144D6222298E8E00984C46 /* AudioMiniPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMiniPlayer.swift; sourceTree = "<group>"; };
 		8D15A1EC22A9603300CFA758 /* ActionSheetSortSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetSortSectionHeader.swift; sourceTree = "<group>"; };
@@ -935,9 +935,9 @@
 		918C8E10279065C6002B3CD6 /* TitleSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleSelectionView.swift; sourceTree = "<group>"; };
 		91B85AEE2731A4C00010F137 /* StoreProductCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = StoreProductCollectionViewCell.xib; path = Resources/Xib/StoreProductCollectionViewCell.xib; sourceTree = "<group>"; };
 		91C1BB7F25EFD7A40096F97E /* ColorThemeExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorThemeExtension.swift; sourceTree = "<group>"; };
-		91CCB170FBC97B6B93B99E0A /* libPods-VLC-iOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VLC-iOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		91D33C7225E6B6E700CC49BE /* VideoPlayerInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerInfoView.swift; sourceTree = "<group>"; };
 		91F8BF1727318D890058D9DE /* StoreProductCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreProductCollectionViewCell.swift; sourceTree = "<group>"; };
+		939888242E2DC6874C59DCD6 /* libPods-VLC-iOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VLC-iOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		9B088306183D7BEC004B5C2A /* VLCCloudStorageTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCCloudStorageTableViewController.h; path = Sources/VLCCloudStorageTableViewController.h; sourceTree = SOURCE_ROOT; };
 		9B088307183D7BEC004B5C2A /* VLCCloudStorageTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCCloudStorageTableViewController.m; path = Sources/VLCCloudStorageTableViewController.m; sourceTree = SOURCE_ROOT; };
 		9B9231C3185A703700F89498 /* VLCNetworkLoginViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = VLCNetworkLoginViewController.xib; path = Resources/VLCNetworkLoginViewController.xib; sourceTree = SOURCE_ROOT; };
@@ -946,8 +946,12 @@
 		A7035BBD174519600057DFA7 /* iTunesArtwork */ = {isa = PBXFileReference; lastKnownFileType = file; path = iTunesArtwork; sourceTree = "<group>"; };
 		A79246C7170F11DF0036AAF2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; };
 		A79246C9170F11E40036AAF2 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
-		AC40202FFE42CEDCEB37E50D /* Pods-VLC-tvOS.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-tvOS.distribution.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS.distribution.xcconfig"; sourceTree = "<group>"; };
-		C6872E7B396534F3DAF4E48F /* Pods-VLC-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS.release.xcconfig"; sourceTree = "<group>"; };
+		AF116147FD13655FDCF10359 /* Pods-VLC-iOSTests.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOSTests.distribution.xcconfig"; path = "Target Support Files/Pods-VLC-iOSTests/Pods-VLC-iOSTests.distribution.xcconfig"; sourceTree = "<group>"; };
+		B0E3BE184ED7B95399C04EB6 /* libPods-VLC-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VLC-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		B1CA1021683B1DE3C1E367A6 /* Pods-VLC-iOS-Screenshots.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS-Screenshots.debug.xcconfig"; path = "Target Support Files/Pods-VLC-iOS-Screenshots/Pods-VLC-iOS-Screenshots.debug.xcconfig"; sourceTree = "<group>"; };
+		BB4AA60FE9D8D55360CC4ED3 /* Pods-VLC-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-tvOS.release.xcconfig"; path = "Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS.release.xcconfig"; sourceTree = "<group>"; };
+		C177035ADC636FBBDB2F537A /* Pods-VLC-iOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOSTests.debug.xcconfig"; path = "Target Support Files/Pods-VLC-iOSTests/Pods-VLC-iOSTests.debug.xcconfig"; sourceTree = "<group>"; };
+		C3E0FA5F7E6B347A85A3E252 /* Pods-VLC-iOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOSTests.release.xcconfig"; path = "Target Support Files/Pods-VLC-iOSTests/Pods-VLC-iOSTests.release.xcconfig"; sourceTree = "<group>"; };
 		CA6FB8222074601900FC9BF2 /* VLC-iOS-Screenshots-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VLC-iOS-Screenshots-Bridging-Header.h"; sourceTree = "<group>"; };
 		CAA0B0F620726A0E00B9274E /* TestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHelper.swift; sourceTree = "<group>"; };
 		CAD925782075536300F88496 /* Screenshot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Screenshot.swift; sourceTree = "<group>"; };
@@ -967,15 +971,14 @@
 		CC87148317A56C85003C7383 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = ca; path = ca.lproj/Localizable.strings; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; };
 		CCAF837E17DE46D800E3578F /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
 		CCE2A22D17A5859E00D9EAAD /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
-		CE6CC455148EC65B1D66CC66 /* libPods-VLC-iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VLC-iOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		D0AF091E220A741300076D07 /* VLCCloudSortingSpecifierManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VLCCloudSortingSpecifierManager.swift; path = Sources/VLCCloudSortingSpecifierManager.swift; sourceTree = "<group>"; };
-		D581CD3C269D82BD6A25E543 /* Pods-VLC-iOS-Screenshots.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS-Screenshots.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-iOS-Screenshots/Pods-VLC-iOS-Screenshots.debug.xcconfig"; sourceTree = "<group>"; };
-		D5BE7EF6F028FE82FA278E5E /* libPods-VLC-iOS-Screenshots.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VLC-iOS-Screenshots.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		D6E034EB1CC284FC0037F516 /* VLCStreamingHistoryCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCStreamingHistoryCell.h; path = Sources/VLCStreamingHistoryCell.h; sourceTree = SOURCE_ROOT; };
 		D6E034EC1CC284FC0037F516 /* VLCStreamingHistoryCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCStreamingHistoryCell.m; path = Sources/VLCStreamingHistoryCell.m; sourceTree = SOURCE_ROOT; };
 		D902E21427F4ADF900DC2B41 /* ArtistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistViewController.swift; sourceTree = "<group>"; };
 		D92C186427CD317D0034058F /* ChapterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChapterView.swift; sourceTree = "<group>"; };
+		D96ACA952832836C00DD1A3E /* GraphViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphViewController.swift; sourceTree = "<group>"; };
 		D96C9EBB28105F5B005F13BB /* AlbumHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumHeader.swift; sourceTree = "<group>"; };
+		D98EE7442846183F00E6B02D /* GraphTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphTableViewController.swift; sourceTree = "<group>"; };
 		D9AFDCA427D6383F002326CC /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; };
 		DD13A3791BEE2FAA00A35554 /* VLCMaskView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCMaskView.h; path = "UI Elements/VLCMaskView.h"; sourceTree = "<group>"; };
 		DD13A37A1BEE2FAA00A35554 /* VLCMaskView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCMaskView.m; path = "UI Elements/VLCMaskView.m"; sourceTree = "<group>"; };
@@ -1104,8 +1107,8 @@
 		E0C04F931A25B4410080331A /* VLCDocumentPickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCDocumentPickerController.h; path = Sources/VLCDocumentPickerController.h; sourceTree = SOURCE_ROOT; };
 		E0C04F941A25B4410080331A /* VLCDocumentPickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCDocumentPickerController.m; path = Sources/VLCDocumentPickerController.m; sourceTree = SOURCE_ROOT; };
 		E0F8980322D399E10061DB2F /* MediaScrubProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaScrubProgressBar.swift; sourceTree = "<group>"; };
-		E39E49F085F7C54D2DB79534 /* Pods-VLC-iOSTests.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOSTests.distribution.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-iOSTests/Pods-VLC-iOSTests.distribution.xcconfig"; sourceTree = "<group>"; };
-		F25DF68DCD1A8FA89D1933B3 /* Pods-VLC-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-tvOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS.debug.xcconfig"; sourceTree = "<group>"; };
+		F4D7CA6C946BEC69172FE01C /* libPods-VLC-iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VLC-iOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		FC34D45714F6ABBFF4645E35 /* Pods-VLC-iOS-Screenshots.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-iOS-Screenshots.distribution.xcconfig"; path = "Target Support Files/Pods-VLC-iOS-Screenshots/Pods-VLC-iOS-Screenshots.distribution.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -1113,7 +1116,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				226083AA75B10C9AA36C939A /* libPods-VLC-iOS-Screenshots.a in Frameworks */,
+				ABCDCCB0B59453B72E6C4787 /* libPods-VLC-iOS-Screenshots.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1121,7 +1124,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				81334053F6D89AB90E14A1C3 /* libPods-VLC-iOSTests.a in Frameworks */,
+				FA22019D4109D19B4299773C /* libPods-VLC-iOSTests.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1136,7 +1139,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				14903A768F4E959C0828288C /* libPods-VLC-tvOS.a in Frameworks */,
+				13925DC647EC27EEB2818FD2 /* libPods-VLC-tvOS.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1167,13 +1170,32 @@
 				7D94FCDF16DE7D1000F2623B /* UIKit.framework in Frameworks */,
 				7D94FCE116DE7D1000F2623B /* Foundation.framework in Frameworks */,
 				7D94FCE316DE7D1000F2623B /* CoreGraphics.framework in Frameworks */,
-				A483C473E441E7BA7B31BF76 /* libPods-VLC-iOS.a in Frameworks */,
+				4B079F7D58CA85ECBFEE7115 /* libPods-VLC-iOS.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		023340A3B4C320149F526087 /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				535ECB057D81248C91FA25C4 /* Pods-VLC-iOS.debug.xcconfig */,
+				3B79FA55420524A988373783 /* Pods-VLC-iOS.release.xcconfig */,
+				027E1E187A0A43CF5CFAECEC /* Pods-VLC-iOS.distribution.xcconfig */,
+				B1CA1021683B1DE3C1E367A6 /* Pods-VLC-iOS-Screenshots.debug.xcconfig */,
+				2E995114543D1E41A5A82486 /* Pods-VLC-iOS-Screenshots.release.xcconfig */,
+				FC34D45714F6ABBFF4645E35 /* Pods-VLC-iOS-Screenshots.distribution.xcconfig */,
+				C177035ADC636FBBDB2F537A /* Pods-VLC-iOSTests.debug.xcconfig */,
+				C3E0FA5F7E6B347A85A3E252 /* Pods-VLC-iOSTests.release.xcconfig */,
+				AF116147FD13655FDCF10359 /* Pods-VLC-iOSTests.distribution.xcconfig */,
+				723659CB3834E1B30918E514 /* Pods-VLC-tvOS.debug.xcconfig */,
+				BB4AA60FE9D8D55360CC4ED3 /* Pods-VLC-tvOS.release.xcconfig */,
+				57087A12E77ACEB9D1D30E33 /* Pods-VLC-tvOS.distribution.xcconfig */,
+			);
+			path = Pods;
+			sourceTree = "<group>";
+		};
 		29125E5317492219003F03E5 /* web */ = {
 			isa = PBXGroup;
 			children = (
@@ -1501,6 +1523,8 @@
 				D0AF091E220A741300076D07 /* VLCCloudSortingSpecifierManager.swift */,
 				4184AA131A5492070063DF5A /* VLCCloudStorageController.h */,
 				4184AA141A5492070063DF5A /* VLCCloudStorageController.m */,
+				D96ACA952832836C00DD1A3E /* GraphViewController.swift */,
+				D98EE7442846183F00E6B02D /* GraphTableViewController.swift */,
 			);
 			name = "Cloud Integration";
 			path = ..;
@@ -1626,8 +1650,8 @@
 				4133CADD22CCB0620047A4EC /* VLC-iOSScreenShots */,
 				7D94FCDD16DE7D1000F2623B /* Frameworks */,
 				7D94FCDC16DE7D1000F2623B /* Products */,
-				A77803FFDA800BD6A3DAED0C /* Pods */,
 				412A87D01F3B89B000640BB7 /* Recovered References */,
+				023340A3B4C320149F526087 /* Pods */,
 			);
 			sourceTree = "<group>";
 			usesTabs = 0;
@@ -1670,11 +1694,10 @@
 				7D94FCDE16DE7D1000F2623B /* UIKit.framework */,
 				7D94FCE016DE7D1000F2623B /* Foundation.framework */,
 				7D94FCE216DE7D1000F2623B /* CoreGraphics.framework */,
-				CE6CC455148EC65B1D66CC66 /* libPods-VLC-iOS.a */,
-				91CCB170FBC97B6B93B99E0A /* libPods-VLC-iOSTests.a */,
-				65EC8984D840047F50AA00A1 /* libPods-VLC-iOSUITests.a */,
-				52DDBB38DF22E6BBD19C38C8 /* libPods-VLC-tvOS.a */,
-				D5BE7EF6F028FE82FA278E5E /* libPods-VLC-iOS-Screenshots.a */,
+				F4D7CA6C946BEC69172FE01C /* libPods-VLC-iOS.a */,
+				4307771A200A7301C243AE51 /* libPods-VLC-iOS-Screenshots.a */,
+				939888242E2DC6874C59DCD6 /* libPods-VLC-iOSTests.a */,
+				B0E3BE184ED7B95399C04EB6 /* libPods-VLC-tvOS.a */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -2178,25 +2201,6 @@
 			name = GoogleDrive;
 			sourceTree = "<group>";
 		};
-		A77803FFDA800BD6A3DAED0C /* Pods */ = {
-			isa = PBXGroup;
-			children = (
-				3B75A76DF6589FE678357D42 /* Pods-VLC-iOS.debug.xcconfig */,
-				C6872E7B396534F3DAF4E48F /* Pods-VLC-iOS.release.xcconfig */,
-				5B343E4F4D971F5A169EB864 /* Pods-VLC-iOS.distribution.xcconfig */,
-				F25DF68DCD1A8FA89D1933B3 /* Pods-VLC-tvOS.debug.xcconfig */,
-				2B1BC34A3BC4BCE7588BD68A /* Pods-VLC-tvOS.release.xcconfig */,
-				AC40202FFE42CEDCEB37E50D /* Pods-VLC-tvOS.distribution.xcconfig */,
-				3E95DC30A81B2AF9F7442E00 /* Pods-VLC-iOSTests.debug.xcconfig */,
-				7FC9CCF39DD8843873A42D34 /* Pods-VLC-iOSTests.release.xcconfig */,
-				E39E49F085F7C54D2DB79534 /* Pods-VLC-iOSTests.distribution.xcconfig */,
-				D581CD3C269D82BD6A25E543 /* Pods-VLC-iOS-Screenshots.debug.xcconfig */,
-				5671052B495534848FA467EE /* Pods-VLC-iOS-Screenshots.release.xcconfig */,
-				703A80CCC005093CCDFBECBF /* Pods-VLC-iOS-Screenshots.distribution.xcconfig */,
-			);
-			name = Pods;
-			sourceTree = "<group>";
-		};
 		A7924697170F0ED20036AAF2 /* iOS Resources */ = {
 			isa = PBXGroup;
 			children = (
@@ -2481,7 +2485,7 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 4133CAE622CCB0620047A4EC /* Build configuration list for PBXNativeTarget "VLC-iOS-Screenshots" */;
 			buildPhases = (
-				2E5934DFBFA4E9DF220B9070 /* [CP] Check Pods Manifest.lock */,
+				249843156C1F5E72AAC05696 /* [CP] Check Pods Manifest.lock */,
 				4133CAD822CCB0620047A4EC /* Sources */,
 				4133CAD922CCB0620047A4EC /* Frameworks */,
 				4133CADA22CCB0620047A4EC /* Resources */,
@@ -2500,7 +2504,7 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 41533C98211338D600EC3ABA /* Build configuration list for PBXNativeTarget "VLC-iOSTests" */;
 			buildPhases = (
-				16C1AB96786C00EC189F241D /* [CP] Check Pods Manifest.lock */,
+				DFC9085B196928CE07634712 /* [CP] Check Pods Manifest.lock */,
 				41533C8D211338D500EC3ABA /* Sources */,
 				41533C8E211338D500EC3ABA /* Frameworks */,
 				41533C8F211338D500EC3ABA /* Resources */,
@@ -2537,12 +2541,12 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 7D1329541BA1F10100BE647E /* Build configuration list for PBXNativeTarget "VLC-tvOS" */;
 			buildPhases = (
-				CEAC60B72FF5CB91256BC99B /* [CP] Check Pods Manifest.lock */,
+				6B1272663E377D4BC4DCBF9D /* [CP] Check Pods Manifest.lock */,
 				7D1329391BA1F10100BE647E /* Sources */,
 				7D13293A1BA1F10100BE647E /* Frameworks */,
 				7D13293B1BA1F10100BE647E /* Resources */,
-				0452CD613294910CB73B58CB /* [CP] Copy Pods Resources */,
-				08BEF713BB055DD740241EF9 /* [CP] Embed Pods Frameworks */,
+				982755253ECF96B8E3B17FEE /* [CP] Embed Pods Frameworks */,
+				9FC273B6B91160C54B8FDFE5 /* [CP] Copy Pods Resources */,
 			);
 			buildRules = (
 			);
@@ -2557,14 +2561,14 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 7D94FD0A16DE7D1100F2623B /* Build configuration list for PBXNativeTarget "VLC-iOS" */;
 			buildPhases = (
-				23B425DEB65EA5C88D873ED2 /* [CP] Check Pods Manifest.lock */,
+				23B51B8827E6794878250849 /* [CP] Check Pods Manifest.lock */,
 				41F7087C1F37900A00953630 /* Generate Localized Strings */,
 				0A9D17D120C4A14700AA4A8C /* Run SwiftLint Script */,
 				7D94FCD716DE7D1000F2623B /* Sources */,
 				7D94FCD816DE7D1000F2623B /* Frameworks */,
 				7D94FCD916DE7D1000F2623B /* Resources */,
-				9706D52A0CDF446E336826A6 /* [CP] Copy Pods Resources */,
-				3696DF9BCC8B754F06D345D9 /* [CP] Embed Pods Frameworks */,
+				F03D8E7BCA1DC4981CDB963A /* [CP] Embed Pods Frameworks */,
+				A7E516CDB189F9DFEC0DA54B /* [CP] Copy Pods Resources */,
 			);
 			buildRules = (
 			);
@@ -2834,93 +2838,79 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
-		0452CD613294910CB73B58CB /* [CP] Copy Pods Resources */ = {
+		0A9D17D120C4A14700AA4A8C /* Run SwiftLint Script */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			inputPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/XKKeychain-tvOS/XKKeychain.bundle",
 			);
-			name = "[CP] Copy Pods Resources";
+			name = "Run SwiftLint Script";
 			outputPaths = (
-				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/XKKeychain.bundle",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS-resources.sh\"\n";
-			showEnvVarsInLog = 0;
+			shellScript = "${PODS_ROOT}/SwiftLint/swiftlint\n";
 		};
-		08BEF713BB055DD740241EF9 /* [CP] Embed Pods Frameworks */ = {
+		23B51B8827E6794878250849 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
+			inputFileListPaths = (
+			);
 			inputPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS-frameworks.sh",
-				"${PODS_XCFRAMEWORKS_BUILD_DIR}/TVVLCKit/TVVLCKit.framework/TVVLCKit",
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
 			);
-			name = "[CP] Embed Pods Frameworks";
 			outputPaths = (
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TVVLCKit.framework",
+				"$(DERIVED_FILE_DIR)/Pods-VLC-iOS-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS-frameworks.sh\"\n";
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
-		0A9D17D120C4A14700AA4A8C /* Run SwiftLint Script */ = {
+		249843156C1F5E72AAC05696 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
-			inputPaths = (
-			);
-			name = "Run SwiftLint Script";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "${PODS_ROOT}/SwiftLint/swiftlint\n";
-		};
-		16C1AB96786C00EC189F241D /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
+			inputFileListPaths = (
 			);
 			inputPaths = (
 				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
 				"${PODS_ROOT}/Manifest.lock",
 			);
 			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
 			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-VLC-iOSTests-checkManifestLockResult.txt",
+				"$(DERIVED_FILE_DIR)/Pods-VLC-iOS-Screenshots-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
-		23B425DEB65EA5C88D873ED2 /* [CP] Check Pods Manifest.lock */ = {
+		41F7087C1F37900A00953630 /* Generate Localized Strings */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
 			);
-			name = "[CP] Check Pods Manifest.lock";
+			name = "Generate Localized Strings";
 			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-VLC-iOS-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
+			shellScript = "python3 Tools/update_strings.py Resources/en.lproj/Localizable.strings\n";
 		};
-		2E5934DFBFA4E9DF220B9070 /* [CP] Check Pods Manifest.lock */ = {
+		6B1272663E377D4BC4DCBF9D /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -2935,48 +2925,50 @@
 			outputFileListPaths = (
 			);
 			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-VLC-iOS-Screenshots-checkManifestLockResult.txt",
+				"$(DERIVED_FILE_DIR)/Pods-VLC-tvOS-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
-		3696DF9BCC8B754F06D345D9 /* [CP] Embed Pods Frameworks */ = {
+		982755253ECF96B8E3B17FEE /* [CP] Embed Pods Frameworks */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			inputPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS-frameworks.sh",
-				"${PODS_XCFRAMEWORKS_BUILD_DIR}/MobileVLCKit/MobileVLCKit.framework/MobileVLCKit",
-				"${PODS_XCFRAMEWORKS_BUILD_DIR}/VLCMediaLibraryKit/VLCMediaLibraryKit.framework/VLCMediaLibraryKit",
+				"${PODS_ROOT}/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS-frameworks.sh",
+				"${PODS_XCFRAMEWORKS_BUILD_DIR}/TVVLCKit/TVVLCKit.framework/TVVLCKit",
 			);
 			name = "[CP] Embed Pods Frameworks";
 			outputPaths = (
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MobileVLCKit.framework",
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VLCMediaLibraryKit.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TVVLCKit.framework",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS-frameworks.sh\"\n";
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS-frameworks.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
-		41F7087C1F37900A00953630 /* Generate Localized Strings */ = {
+		9FC273B6B91160C54B8FDFE5 /* [CP] Copy Pods Resources */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			inputPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS-resources.sh",
+				"${PODS_CONFIGURATION_BUILD_DIR}/XKKeychain-tvOS/XKKeychain.bundle",
 			);
-			name = "Generate Localized Strings";
+			name = "[CP] Copy Pods Resources";
 			outputPaths = (
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/XKKeychain.bundle",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "python3 Tools/update_strings.py Resources/en.lproj/Localizable.strings\n";
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS-resources.sh\"\n";
+			showEnvVarsInLog = 0;
 		};
-		9706D52A0CDF446E336826A6 /* [CP] Copy Pods Resources */ = {
+		A7E516CDB189F9DFEC0DA54B /* [CP] Copy Pods Resources */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -3000,24 +2992,48 @@
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS-resources.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
-		CEAC60B72FF5CB91256BC99B /* [CP] Check Pods Manifest.lock */ = {
+		DFC9085B196928CE07634712 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
+			inputFileListPaths = (
+			);
 			inputPaths = (
 				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
 				"${PODS_ROOT}/Manifest.lock",
 			);
 			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
 			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-VLC-tvOS-checkManifestLockResult.txt",
+				"$(DERIVED_FILE_DIR)/Pods-VLC-iOSTests-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
+		F03D8E7BCA1DC4981CDB963A /* [CP] Embed Pods Frameworks */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS-frameworks.sh",
+				"${PODS_XCFRAMEWORKS_BUILD_DIR}/MobileVLCKit/MobileVLCKit.framework/MobileVLCKit",
+				"${PODS_XCFRAMEWORKS_BUILD_DIR}/VLCMediaLibraryKit/VLCMediaLibraryKit.framework/VLCMediaLibraryKit",
+			);
+			name = "[CP] Embed Pods Frameworks";
+			outputPaths = (
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MobileVLCKit.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VLCMediaLibraryKit.framework",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VLC-iOS/Pods-VLC-iOS-frameworks.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -3169,6 +3185,7 @@
 				4090705925A60341004667E7 /* OptionsNavigationBar.swift in Sources */,
 				444E5C0024C719480003B69C /* AboutController.swift in Sources */,
 				41D7DD2720C3060300AD94F6 /* PagerStripViewController.swift in Sources */,
+				D98EE7452846183F00E6B02D /* GraphTableViewController.swift in Sources */,
 				413EC98B201B4F2C00BF412F /* PresentationTheme.swift in Sources */,
 				DDF908D01CF4CCAA00108B70 /* VLCNetworkLoginViewButtonCell.m in Sources */,
 				DD3EFF3D1BDEBCE500B68579 /* VLCLocalNetworkServiceBrowserHTTP.m in Sources */,
@@ -3353,6 +3370,7 @@
 				7D30F3DC183AB2F900FFC021 /* VLCNetworkLoginViewController.m in Sources */,
 				91F8BF1827318D890058D9DE /* StoreProductCollectionViewCell.swift in Sources */,
 				91D33C7325E6B6E700CC49BE /* VideoPlayerInfoView.swift in Sources */,
+				D96ACA962832836C00DD1A3E /* GraphViewController.swift in Sources */,
 				8DE18890210B53E000A091D2 /* TrackModel.swift in Sources */,
 				8DE18898210F144B00A091D2 /* GenreModel.swift in Sources */,
 				7D310885245F1EE100BC6D79 /* VLCMediaFileDownloader.m in Sources */,
@@ -3513,7 +3531,7 @@
 /* Begin XCBuildConfiguration section */
 		4133CAE322CCB0620047A4EC /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = D581CD3C269D82BD6A25E543 /* Pods-VLC-iOS-Screenshots.debug.xcconfig */;
+			baseConfigurationReference = B1CA1021683B1DE3C1E367A6 /* Pods-VLC-iOS-Screenshots.debug.xcconfig */;
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -3549,7 +3567,7 @@
 		};
 		4133CAE422CCB0620047A4EC /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 5671052B495534848FA467EE /* Pods-VLC-iOS-Screenshots.release.xcconfig */;
+			baseConfigurationReference = 2E995114543D1E41A5A82486 /* Pods-VLC-iOS-Screenshots.release.xcconfig */;
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -3585,7 +3603,7 @@
 		};
 		4133CAE522CCB0620047A4EC /* Distribution */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 703A80CCC005093CCDFBECBF /* Pods-VLC-iOS-Screenshots.distribution.xcconfig */;
+			baseConfigurationReference = FC34D45714F6ABBFF4645E35 /* Pods-VLC-iOS-Screenshots.distribution.xcconfig */;
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -3621,7 +3639,7 @@
 		};
 		41533C99211338D600EC3ABA /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 3E95DC30A81B2AF9F7442E00 /* Pods-VLC-iOSTests.debug.xcconfig */;
+			baseConfigurationReference = C177035ADC636FBBDB2F537A /* Pods-VLC-iOSTests.debug.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_ANALYZER_NONNULL = YES;
@@ -3658,7 +3676,7 @@
 		};
 		41533C9A211338D600EC3ABA /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 7FC9CCF39DD8843873A42D34 /* Pods-VLC-iOSTests.release.xcconfig */;
+			baseConfigurationReference = C3E0FA5F7E6B347A85A3E252 /* Pods-VLC-iOSTests.release.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_ANALYZER_NONNULL = YES;
@@ -3696,7 +3714,7 @@
 		};
 		41533C9B211338D600EC3ABA /* Distribution */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = E39E49F085F7C54D2DB79534 /* Pods-VLC-iOSTests.distribution.xcconfig */;
+			baseConfigurationReference = AF116147FD13655FDCF10359 /* Pods-VLC-iOSTests.distribution.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_ANALYZER_NONNULL = YES;
@@ -3884,7 +3902,7 @@
 		};
 		7D1329511BA1F10100BE647E /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = F25DF68DCD1A8FA89D1933B3 /* Pods-VLC-tvOS.debug.xcconfig */;
+			baseConfigurationReference = 723659CB3834E1B30918E514 /* Pods-VLC-tvOS.debug.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
@@ -3929,7 +3947,7 @@
 		};
 		7D1329521BA1F10100BE647E /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 2B1BC34A3BC4BCE7588BD68A /* Pods-VLC-tvOS.release.xcconfig */;
+			baseConfigurationReference = BB4AA60FE9D8D55360CC4ED3 /* Pods-VLC-tvOS.release.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
@@ -3974,7 +3992,7 @@
 		};
 		7D1329531BA1F10100BE647E /* Distribution */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = AC40202FFE42CEDCEB37E50D /* Pods-VLC-tvOS.distribution.xcconfig */;
+			baseConfigurationReference = 57087A12E77ACEB9D1D30E33 /* Pods-VLC-tvOS.distribution.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
@@ -4123,7 +4141,7 @@
 		};
 		7D94FD0B16DE7D1100F2623B /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 3B75A76DF6589FE678357D42 /* Pods-VLC-iOS.debug.xcconfig */;
+			baseConfigurationReference = 535ECB057D81248C91FA25C4 /* Pods-VLC-iOS.debug.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
 				ARCHS = (
@@ -4171,7 +4189,7 @@
 		};
 		7D94FD0C16DE7D1100F2623B /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = C6872E7B396534F3DAF4E48F /* Pods-VLC-iOS.release.xcconfig */;
+			baseConfigurationReference = 3B79FA55420524A988373783 /* Pods-VLC-iOS.release.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
 				ARCHS = (
@@ -4268,7 +4286,7 @@
 		};
 		A7035BC0174519E40057DFA7 /* Distribution */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 5B343E4F4D971F5A169EB864 /* Pods-VLC-iOS.distribution.xcconfig */;
+			baseConfigurationReference = 027E1E187A0A43CF5CFAECEC /* Pods-VLC-iOS.distribution.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
 				ARCHS = (
diff --git a/vlc-ios/VLC-iOS-Bridging-Header.h b/vlc-ios/VLC-iOS-Bridging-Header.h
index d807b78bf..9c4801624 100644
--- a/vlc-ios/VLC-iOS-Bridging-Header.h
+++ b/vlc-ios/VLC-iOS-Bridging-Header.h
@@ -26,6 +26,7 @@
 #import "VLCMediaFileDiscoverer.h"
 #import "VLCMigrationViewController.h"
 #import "VLCCloudStorageTableViewController.h"
+#import "VLCCloudStorageTableViewCell.h"
 #import "VLCMetadata.h"
 #import "VLCPlayerDisplayController.h"
 #import "NSString+SupportedMedia.h"
-- 
GitLab


From 8cdc3e16e9a89e35fe9cfe6a921f799d81ce8bc1 Mon Sep 17 00:00:00 2001
From: Diogo Simao Marques <diogo.simaomarquespro@gmail.com>
Date: Fri, 1 Jul 2022 13:43:11 +0200
Subject: [PATCH 4/5] GraphViewController: Display the media title when
 downloading

The title of the media that is currently being downloaded is displayed
to let the user know which one is being downloaded.
---
 GraphTableViewController.swift               | 4 ++++
 GraphViewController.swift                    | 1 +
 Resources/en.lproj/Localizable.strings       | 1 +
 Sources/VLCCloudStorageController.h          | 1 +
 Sources/VLCCloudStorageTableViewController.m | 5 +++++
 Sources/VLCProgressView.h                    | 1 +
 Sources/VLCProgressView.m                    | 7 +++++++
 7 files changed, 20 insertions(+)

diff --git a/GraphTableViewController.swift b/GraphTableViewController.swift
index d2bc41a05..793f4b02c 100644
--- a/GraphTableViewController.swift
+++ b/GraphTableViewController.swift
@@ -105,6 +105,10 @@ class GraphTableViewController: VLCCloudStorageTableViewController {
         super.currentProgressInformation(progress)
     }
 
+    override func updateProgressLabel(_ mediaName: String!) {
+        super.updateProgressLabel(mediaName)
+    }
+
     // MARK: - Private helpers
     private func prepareMSGraphControllerIfNeeded() {
         if controller == nil {
diff --git a/GraphViewController.swift b/GraphViewController.swift
index c3e319a7a..d702208e3 100644
--- a/GraphViewController.swift
+++ b/GraphViewController.swift
@@ -557,6 +557,7 @@ class GraphViewController: VLCCloudStorageController {
         if #available(iOS 11.0, *) {
             task.progress.totalUnitCount = item.size
             showProgress(progress: task.progress)
+            delegate.updateProgressLabel?(item.name!)
         }
         task.resume()
     }
diff --git a/Resources/en.lproj/Localizable.strings b/Resources/en.lproj/Localizable.strings
index 740594855..019cd0c3c 100644
--- a/Resources/en.lproj/Localizable.strings
+++ b/Resources/en.lproj/Localizable.strings
@@ -154,6 +154,7 @@
 "ONE_FILE" = "1 file";
 "NO_FILES" = "No supported files";
 "DOWNLOADING" = "Downloading...";
+"DOWNLOADING_MEDIA" = "Downloading: %@";
 "REMAINING_TIME" = "Remaining time: %@";
 "BIOMETRIC_UNLOCK" = "Unlock Media Library\nCancel to enter Passcode";
 
diff --git a/Sources/VLCCloudStorageController.h b/Sources/VLCCloudStorageController.h
index 1cb8e82b3..3f4e80212 100644
--- a/Sources/VLCCloudStorageController.h
+++ b/Sources/VLCCloudStorageController.h
@@ -22,6 +22,7 @@ typedef NS_ENUM (NSInteger, VLCCloudSortingCriteria) {
 - (void)operationWithProgressInformationStarted;
 - (void)currentProgressInformation:(CGFloat)progress;
 - (void)updateRemainingTime:(NSString *)time;
+- (void)updateProgressLabel:(NSString *)mediaName;
 - (void)operationWithProgressInformationStopped;
 - (void)numberOfFilesWaitingToBeDownloadedChanged;
 - (void)sessionWasUpdated;
diff --git a/Sources/VLCCloudStorageTableViewController.m b/Sources/VLCCloudStorageTableViewController.m
index 2a1b6df71..8bb6fa959 100644
--- a/Sources/VLCCloudStorageTableViewController.m
+++ b/Sources/VLCCloudStorageTableViewController.m
@@ -211,6 +211,11 @@ typedef NS_ENUM(NSInteger, VLCToolbarStyle) {
     [_progressView updateTime:time];
 }
 
+-(void)updateProgressLabel:(NSString *)mediaName
+{
+    [_progressView updateProgressLabel:mediaName];
+}
+
 - (void)currentProgressInformation:(CGFloat)progress
 {
     [_progressView.progressBar setProgress:progress animated:YES];
diff --git a/Sources/VLCProgressView.h b/Sources/VLCProgressView.h
index 1aabee9dc..7edb471b7 100644
--- a/Sources/VLCProgressView.h
+++ b/Sources/VLCProgressView.h
@@ -18,5 +18,6 @@
 @property(nonatomic) UILabel *progressLabel;
 
 - (void)updateTime:(NSString *)time;
+- (void)updateProgressLabel:(NSString *)mediaName;
 
 @end
diff --git a/Sources/VLCProgressView.m b/Sources/VLCProgressView.m
index aadebe83d..eec3ef186 100644
--- a/Sources/VLCProgressView.m
+++ b/Sources/VLCProgressView.m
@@ -49,4 +49,11 @@
     [self.progressLabel setFrame:CGRectMake(self.progressLabel.frame.origin.x, self.progressLabel.frame.origin.y, size.width, size.height)];
 }
 
+- (void)updateProgressLabel:(NSString *)mediaName
+{
+    [self.progressLabel setText:[NSString stringWithFormat:NSLocalizedString(@"DOWNLOADING_MEDIA", nil), mediaName]];
+    CGSize size = [self.progressLabel.text sizeWithAttributes:self.progressLabel.font.fontDescriptor.fontAttributes];
+    [self.progressLabel setFrame:CGRectMake(self.progressLabel.frame.origin.x, self.progressLabel.frame.origin.y, size.width, size.height)];
+}
+
 @end
-- 
GitLab


From 75aab6d734d933c232b7a49f739f9eb0bd4556a9 Mon Sep 17 00:00:00 2001
From: Diogo Simao Marques <diogo.simaomarquespro@gmail.com>
Date: Fri, 1 Jul 2022 14:35:05 +0200
Subject: [PATCH 5/5] VLCCloudStorageTableViewController: Fix updating loop and
 thread issue

The CloudStorageTableViewController does not create a loop trying to
update the content of the view when there are no items to be displayed
anymore.

The adding and removing views calls are now dispatched to the main
queue to avoid crashes.
---
 Sources/VLCCloudStorageTableViewController.m | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/Sources/VLCCloudStorageTableViewController.m b/Sources/VLCCloudStorageTableViewController.m
index 8bb6fa959..0aca7bf9a 100644
--- a/Sources/VLCCloudStorageTableViewController.m
+++ b/Sources/VLCCloudStorageTableViewController.m
@@ -203,7 +203,9 @@ typedef NS_ENUM(NSInteger, VLCToolbarStyle) {
             [items addObjectsFromArray:@[_numberOfFilesBarButtonItem, [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]]];
             break;
     }
-    [self setToolbarItems:items animated:YES];
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self setToolbarItems:items animated:YES];
+    });
  }
 
 - (void)updateRemainingTime:(NSString *)time
@@ -269,7 +271,9 @@ typedef NS_ENUM(NSInteger, VLCToolbarStyle) {
     }
     if (_authorizationInProgress || [self.controller isAuthorized]) {
         if (self.loginToCloudStorageView.superview) {
-            [self.loginToCloudStorageView removeFromSuperview];
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [self.loginToCloudStorageView removeFromSuperview];
+            });
         }
     }
     if (![self.controller isAuthorized]) {
@@ -292,10 +296,6 @@ typedef NS_ENUM(NSInteger, VLCToolbarStyle) {
     if (self.currentPath == nil) {
         self.currentPath = @"";
     }
-
-    if ([self.controller.currentListFiles count] == 0) {
-        [self requestInformationForCurrentPath];
-    }
 }
 
 - (void)logout
-- 
GitLab