Skip to content

Commit 9dfaa51

Browse files
Adding changes to DocumentViewerDemo sample
1 parent 3a6e934 commit 9dfaa51

File tree

11 files changed

+583
-33
lines changed

11 files changed

+583
-33
lines changed

DocumentViewerDemo/FileService.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using System.Xml.Linq;
7+
8+
namespace DocumentViewerDemo
9+
{
10+
/// <summary>
11+
/// Provides the functionality to open and save PDF files.
12+
/// </summary>
13+
public partial class FileService
14+
{
15+
16+
/// <summary>
17+
/// Saves the PDF stream with given file name using platform specific file saving APIs.
18+
/// </summary>
19+
/// <param name="fileName">The file name with which the PDF needs to be saved.</param>
20+
/// <param name="fileStream">The stream of the PDF to be saved.</param>
21+
/// <returns></returns>
22+
public async static Task<string?> SaveAsAsync(string fileName, Stream fileStream)
23+
{
24+
return await PlatformSaveAsAsync(fileName, fileStream);
25+
}
26+
27+
/// <summary>
28+
/// Writes the given stream to the specified file path.
29+
/// </summary>
30+
/// <param name="stream">The stream of the PDF file to be written.</param>
31+
/// <param name="filePath">The file path to which the PDF file needs to be written.</param>
32+
/// <returns></returns>
33+
static async Task WriteStream(Stream stream, string? filePath)
34+
{
35+
if (!string.IsNullOrEmpty(filePath))
36+
{
37+
await using var fileStream = new FileStream(filePath, FileMode.OpenOrCreate);
38+
fileStream.SetLength(0);
39+
if (stream.CanSeek)
40+
{
41+
stream.Seek(0, SeekOrigin.Begin);
42+
}
43+
44+
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
45+
}
46+
}
47+
}
48+
}

DocumentViewerDemo/MainPage.xaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<tabView:SfTabView IndicatorBackground="{DynamicResource PrimaryLight}"
1616
IndicatorPlacement="Bottom"
1717
TabBarPlacement="Top"
18+
TabWidthMode="SizeToContent"
1819
Loaded="SfTabView_Loaded"
1920
>
2021
<tabView:SfTabItem Header="PDF_Succinctly.pdf" x:Name="tab1">
@@ -23,11 +24,11 @@
2324
</tabView:SfTabItem>
2425
<tabView:SfTabItem Header="Autumn Leaves.jpg" x:Name="tab3">
2526
</tabView:SfTabItem>
26-
<tabView:SfTabItem Header="InputTemplate.xlsx" x:Name="tab4">
27+
<tabView:SfTabItem Header="Template.pptx" x:Name="tab4">
2728
</tabView:SfTabItem>
28-
<tabView:SfTabItem Header="Input.xps" x:Name="tab5">
29+
<tabView:SfTabItem Header="InputTemplate.xlsx" x:Name="tab5">
2930
</tabView:SfTabItem>
30-
<tabView:SfTabItem Header="Template.pptx" x:Name="tab6">
31+
<tabView:SfTabItem Header="Input.xps" x:Name="tab6">
3132
</tabView:SfTabItem>
3233
</tabView:SfTabView>
3334
</Grid>

DocumentViewerDemo/MainPage.xaml.cs

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Syncfusion.Maui.PdfViewer;
1+
using Syncfusion.Maui.Core;
2+
using Syncfusion.Maui.PdfViewer;
23

34
namespace DocumentViewerDemo
45
{
@@ -15,6 +16,12 @@ public MainPage()
1516
{
1617
InitializeComponent();
1718

19+
AddSaveOptionToolbarItems(pdfViewer);
20+
AddSaveOptionToolbarItems(pdfViewer1);
21+
AddSaveOptionToolbarItems(pdfViewer2);
22+
AddSaveOptionToolbarItems(pdfViewer3);
23+
AddSaveOptionToolbarItems(pdfViewer4);
24+
AddSaveOptionToolbarItems(pdfViewer5);
1825
// Set the zoom mode of three PDF viewers to fit the width of the container
1926
pdfViewer.ZoomMode = ZoomMode.FitToWidth;
2027
pdfViewer1.ZoomMode = ZoomMode.FitToWidth;
@@ -24,8 +31,72 @@ public MainPage()
2431
pdfViewer5.ZoomMode = ZoomMode.FitToWidth;
2532
}
2633

34+
void AddSaveOptionToolbarItems(SfPdfViewer pdfViewer)
35+
{
36+
37+
// Create new save button
38+
Button fileSaveButton = new Button
39+
{
40+
Text = "\ue75f", // Set button text
41+
FontSize = 24, // Set button text font size
42+
FontFamily = "MauiMaterialAssets", // Set button text font family
43+
BackgroundColor = Colors.Transparent, // Set background for the button
44+
BorderColor = Colors.Transparent, // Set border color for the button
45+
TextColor = Color.FromArgb("#49454F"), // Set button text color
46+
CornerRadius = 5 // Set corner radius of the button
47+
};
48+
49+
// Subscription of click event for the save button
50+
fileSaveButton.Clicked += FileSaveButton_Clicked;
51+
52+
// Set the tooltip text on hover
53+
ToolTipProperties.SetText(fileSaveButton, "Save");
54+
55+
#if !WINDOWS && !MACCATALYST
56+
// Inserting save file option button as toolbar item in the top toolbar for the mobile platform.
57+
pdfViewer?.Toolbars?.GetByName("TopToolbar")?.Items?.Insert(1, new Syncfusion.Maui.PdfViewer.ToolbarItem(fileSaveButton, "FileSaveButton"));
58+
#else
59+
// Inserting save file option button as toolbar item in the top toolbar for the desktop platform.
60+
pdfViewer?.Toolbars?.GetByName("PrimaryToolbar")?.Items?.Insert(1, new Syncfusion.Maui.PdfViewer.ToolbarItem(fileSaveButton, "FileSaveButton"));
61+
#endif
62+
}
63+
64+
private async void FileSaveButton_Clicked(object? sender, EventArgs e)
65+
{
66+
// Create a new memory stream to hold the saved PDF document
67+
Stream savedStream = new MemoryStream();
68+
69+
// Asynchronously save the current document content into the memory stream
70+
await pdfViewer.SaveDocumentAsync(savedStream);
71+
72+
try
73+
{
74+
savedStream.Position = 0;
75+
76+
// Open the file explorer using the file picker, select a location to save the PDF, save the file to the chosen path, and retrieve the saved file location for display.
77+
string? filePath = await FileService.SaveAsAsync("Saved.pdf", savedStream);
78+
79+
// Safely storing the main page of the application.
80+
var mainPage = Application.Current?.Windows[0].Page;
81+
82+
if (mainPage != null)
83+
// Display the saved file path.
84+
await mainPage.DisplayAlert("File saved", $"The file is saved to {filePath}", "OK");
85+
}
86+
catch (Exception exception)
87+
{
88+
// Safely storing the main page of the application.
89+
var mainPage = Application.Current?.Windows[0].Page;
90+
91+
if (mainPage != null)
92+
// Display the error message when the file is not saved.
93+
await mainPage.DisplayAlert("Error", $"The file is not saved. {exception.Message}", "OK");
94+
}
95+
}
96+
2797
private void SfTabView_Loaded(System.Object sender, System.EventArgs e)
2898
{
99+
29100
// Ensure the viewModel is not null before proceeding
30101
if (viewModel?.PDFDocuments != null)
31102
{
@@ -51,25 +122,25 @@ private void SfTabView_Loaded(System.Object sender, System.EventArgs e)
51122
}
52123

53124

54-
// Check if the fourth tab's header matches "InputTemplate.xlsx"
55-
if (tab4.Header.Equals("InputTemplate.xlsx"))
125+
// Check if the fourth tab's header matches "Template.pptx"
126+
if (tab4.Header.Equals("Template.pptx"))
56127
{
57-
pdfViewer3.DocumentSource = viewModel.ConvertXlsxToPdf(viewModel.PDFDocuments[3]); // Assign the stream to the "DocumentSource" property of the PdfViewer control
128+
pdfViewer3.DocumentSource = viewModel.ConvertPptToPdf(viewModel.PDFDocuments[3]); // Assign the stream to the "DocumentSource" property of the PdfViewer control
58129
tab4.Content = pdfViewer3; // Set the content of tab1 to the pdfViewer1.
59130
}
60131

61132

62-
// Check if the fifth tab's header matches "Input.xps"
63-
if (tab5.Header.Equals("Input.xps"))
133+
// Check if the fifth tab's header matches "InputTemplate.xlsx"
134+
if (tab5.Header.Equals("InputTemplate.xlsx"))
64135
{
65-
pdfViewer4.DocumentSource = viewModel.ConvertXpsToPdf(viewModel.PDFDocuments[4]); // Assign the stream to the "DocumentSource" property of the PdfViewer control
136+
pdfViewer4.DocumentSource = viewModel.ConvertXlsxToPdf(viewModel.PDFDocuments[4]); // Assign the stream to the "DocumentSource" property of the PdfViewer control
66137
tab5.Content = pdfViewer4; // Set the content of tab1 to the pdfViewer1.
67138
}
68139

69-
// Check if the sixth tab's header matches "Template.pptx"
70-
if (tab6.Header.Equals("Template.pptx"))
140+
// Check if the sixth tab's header matches "Input.xps"
141+
if (tab6.Header.Equals("Input.xps"))
71142
{
72-
pdfViewer5.DocumentSource = viewModel.ConvertPptToPdf(viewModel.PDFDocuments[5]); // Assign the stream to the "DocumentSource" property of the PdfViewer control
143+
pdfViewer5.DocumentSource = viewModel.ConvertXpsToPdf(viewModel.PDFDocuments[5]); // Assign the stream to the "DocumentSource" property of the PdfViewer control
73144
tab6.Content = pdfViewer5; // Set the content of tab1 to the pdfViewer1.
74145
}
75146
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace DocumentViewerDemo
2+
{
3+
static class AndroidDirectoryConstants
4+
{
5+
public const string PrimaryStorage = "primary";
6+
public const string Storage = "storage";
7+
public const string ExternalStorageBaseUrl = "content://com.android.externalstorage.documents/document/primary%3A";
8+
}
9+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using Uri = Android.Net.Uri;
2+
using Application = Android.App.Application;
3+
using Environment = Android.OS.Environment;
4+
using Android.Content;
5+
using Android.Webkit;
6+
using Android.Provider;
7+
using System.Web;
8+
using Java.IO;
9+
10+
namespace DocumentViewerDemo
11+
{
12+
public partial class FileService
13+
{
14+
internal static async Task<string?> PlatformSaveAsAsync(string fileName, Stream stream)
15+
{
16+
Uri? filePath = null;
17+
CancellationToken cancellationToken = CancellationToken.None;
18+
19+
if (!OperatingSystem.IsAndroidVersionAtLeast(33))
20+
{
21+
var status = await Permissions.RequestAsync<Permissions.StorageWrite>().WaitAsync(cancellationToken).ConfigureAwait(false);
22+
if (status is not PermissionStatus.Granted)
23+
{
24+
throw new PermissionException("Storage permission is not granted.");
25+
}
26+
}
27+
28+
var intent = new Intent(Intent.ActionCreateDocument);
29+
30+
intent.AddCategory(Intent.CategoryOpenable);
31+
intent.SetType(MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(fileName)) ?? "application/pdf");
32+
intent.PutExtra(Intent.ExtraTitle, fileName);
33+
await IntermediateActivity.StartAsync(intent, 2001, onResult: OnResult).WaitAsync(cancellationToken).ConfigureAwait(false);
34+
35+
if (filePath is null)
36+
{
37+
throw new Exception("User canceled or error in saving.");
38+
}
39+
40+
return await SaveDocument(filePath, stream, cancellationToken).ConfigureAwait(false);
41+
42+
void OnResult(Intent resultIntent)
43+
{
44+
filePath = resultIntent.Data;
45+
}
46+
}
47+
48+
static async Task<string?> SaveDocument(Uri uri, Stream stream, CancellationToken cancellationToken)
49+
{
50+
using var parcelFileDescriptor = Application.Context.ContentResolver?.OpenFileDescriptor(uri, "wt");
51+
using var fileOutputStream = new FileOutputStream(parcelFileDescriptor?.FileDescriptor);
52+
await using var memoryStream = new MemoryStream();
53+
await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
54+
await fileOutputStream.WriteAsync(memoryStream.ToArray()).WaitAsync(cancellationToken).ConfigureAwait(false);
55+
56+
return ConvertToPhysicalPath(uri);
57+
}
58+
59+
static string? ConvertToPhysicalPath(Uri uri)
60+
{
61+
const string uriSchemeFolder = "content";
62+
if (uri.Scheme is null || !uri.Scheme.Equals(uriSchemeFolder, StringComparison.OrdinalIgnoreCase))
63+
{
64+
return null;
65+
}
66+
67+
if (uri.PathSegments?.Count < 2)
68+
{
69+
return null;
70+
}
71+
72+
// Example path would be /tree/primary:DCIM, or /tree/SDCare:DCIM
73+
var path = uri.PathSegments?[1];
74+
75+
if (path is null)
76+
{
77+
return null;
78+
}
79+
80+
var pathSplit = path.Split(':');
81+
if (pathSplit.Length < 2)
82+
{
83+
if (pathSplit.Length == 1)
84+
{
85+
//If there is no `:` in the path, assume it's a top-level directory like Downloads
86+
return $"{Environment.ExternalStorageDirectory?.Path}/Downloads";
87+
}
88+
return null;
89+
}
90+
91+
// Primary is the device's internal storage, and anything else is an SD card or other external storage
92+
if (pathSplit[0].Equals(AndroidDirectoryConstants.PrimaryStorage, StringComparison.OrdinalIgnoreCase))
93+
{
94+
// Example for internal path /storage/emulated/0/DCIM
95+
return $"{Environment.ExternalStorageDirectory?.Path}/{pathSplit[1]}";
96+
}
97+
98+
// Example for external path /storage/1B0B-0B1C/DCIM
99+
return $"/{AndroidDirectoryConstants.Storage}/{pathSplit[0]}/{pathSplit[1]}";
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)