Opening a file from Toast Notification - c#

In my UWP app I am downloading a file and storing it in the location chosen by the user using FolderPicker. When the download has completed, I show a ToastNotification. I am using these two namespaces as shown in the docs.
using Microsoft.QueryStringDotNET; // for receciving arguments
using Microsoft.Toolkit.Uwp.Notifications;
the toast that I send has two buttons, 1) open the file 2) Dismiss.
I want to open the downloaded file when the user taps on the first button.
But from what I understand, toasts can only send string arguments to the application ( Correct me if wrong ). And in order to open a file, StorageFile object is needed ( path of StorageFile won't do ).
So is there any way to actually open the downloaded file from the toast ( using foreground or background activation ) ?
Code to download the file:
private async void DownloadButton_Click(object sender, RoutedEventArgs e)
{
StorageFolder selectedFolder;
try
{
selectedFolder = await ChooseFolderAsync();
}
catch
{
Toast.ShowToast("Something went wrong", ToastRow);
return;
}
Uri downloadLink = new Uri("ValidUri");
StorageFile destinationFile = await selectedFolder.CreateFileAsync(selectedAsset.name, CreationCollisionOption.GenerateUniqueName);
BackgroundDownloader downloader = new BackgroundDownloader();
downloader.SuccessToastNotification = handler.MakeToastWithButtons("Downloaded", selectedAsset.name, "Open", "Dismiss");
// downloader.SuccessToastNotification = handler.MakeToast("Downloaded", nameOfFile, string.Empty, 2);
DownloadOperation download = downloader.CreateDownload(downloadLink, destinationFile);
download.Priority = BackgroundTransferPriority.High;
download.CostPolicy = BackgroundTransferCostPolicy.Always;
var toast = handler.MakeToast("Downloading...", selectedAsset.name, selectedAsset.contentSize, 12);
toast.Group = "downloadStartedTag";
ToastNotificationManager.CreateToastNotifier().Show(toast);
Progress<DownloadOperation> progressCallback = new Progress<DownloadOperation>(handler.DownloadProgress);
try
{
await download.StartAsync().AsTask(cts.Token, progressCallback);
}
catch (Exception ex)
{
var errorCode = BackgroundTransferError.GetStatus(ex.HResult);
toast = handler.MakeToast("Download failed", selectedAsset.name, TextFormatter.CamelToHumanCase(errorCode.ToString()), 12);
toast.Group = "downloadFailedTag";
ToastNotificationManager.CreateToastNotifier().Show(toast);
return;
}
finally
{
ToastNotificationManager.History.Remove("downloadStartedTag");
}
}
Method that creates toast:
public ToastNotification MakeToastWithButtons(string heading, string line1, string button1, string button2)
{
ToastVisual visual = new ToastVisual()
{
BindingGeneric = new ToastBindingGeneric()
{
Children =
{
new AdaptiveText() {Text = heading},
new AdaptiveText() {Text = line1},
}
}
};
ToastActionsCustom actions = new ToastActionsCustom()
{
Buttons =
{
new ToastButton("Open", new QueryString()
{
{ "action", "open" }
//maybe file path can be given here in some argument
}.ToString())
{
ActivationType = ToastActivationType.Foreground
},
new ToastButton("Dismiss", new QueryString()
{
{ "action", "dismiss" }
//more details about the file can be given here
}.ToString())
{
ActivationType = ToastActivationType.Background
}
}
};
ToastContent toastContent = new ToastContent()
{
Visual = visual,
Actions = actions,
// Arguments when the user taps body of toast
Launch = new QueryString()
{
{ "action", "nothing" }
}.ToString()
};
// And create the toast notification
var toast = new ToastNotification(toastContent.GetXml());
toast.ExpirationTime = DateTime.Now.AddDays(2);
toast.Group = "DownloadCompleteGroup";
return toast;
}

One way to do this is to store the destinationFile created by the background download in the FutureAccessList, which will provide you with a token you can add to the payload data of the toast:
// using Windows.Storage.AccessCache;
string token = StorageApplicationPermissions.FutureAccessList.Add(destinationFile);
// add the token to your toast payload
Then when the user clicks the toast, you can redeem the token to get the file back:
var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(token);
Now you can do things with the file. Note that the token is good "forever" - as long as the file exists on disk, the token will work after your app restarts or even a reboot.
When you are done with the file (including if the download fails or the user cancels, etc.) you should remove the token from FutureAccessList so that the list doesn't fill up:
StorageApplicationPermissions.FutureAccessList.Remove(token);
This means you likely want to persist the token in your app's settings as well, just in case the user ignores the toast.

Related

"Attempt to present Xamarin_Forms_Platform_iOS_ModalWrapper whose view is not in the window hierarchy" error with UIImagePickerController

I have an Xamarin.Forms-based app which runs on Android and iOS. Right now, I am implementing the feature of selecting images from the camera roll and uploading it to our server. Therefore, I am writing platform-specific code for iOS, which is where the error occurs.
I am calling the UIImagePickerController from a platform-specific renderer for iOS. It opens normally. But when tapping on an image in the UIImagePickerController nothing happens, except Visual Studio showing a message in the debug console:
"Warning: Attempt to present Xamarin_Forms_Platform_iOS_ModalWrapper: 0x155a7ed00 on Xamarin_Forms_Platform_iOS_PlatformRenderer: 0x153ead6a0 whose view is not in the window hierarchy!"
I googled and found somebody writing a function called "GetVisibleViewController" which i adapted to my project (you can see it below). On the ViewController which that function returns, I call the PresentModalViewController() method. Unfortunately, it is not working either. It is not possible to select a photo.
private void ChoosePhoto()
{
_imagePicker = new UIImagePickerController()
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
MediaTypes = new string[] { UTType.Image }
};
_imagePicker.FinishedPickingMedia += delegate (object sender, UIImagePickerMediaPickedEventArgs e)
{
var fileName = eopAppLibrary.Tools.GetTimestampJpegFileName("ScanToEop_iOS");
var jpegImageData = e.OriginalImage.AsJPEG();
var jpegBytes = jpegImageData.ToArray();
Events.RaiseFilePreviewNeeded(this, jpegBytes, fileName);
};
_imagePicker.Canceled += delegate (object sender, EventArgs e)
{
_imagePicker.DismissModalViewController(true);
};
var viewController = GetVisibleViewController();
viewController.PresentModalViewController(_imagePicker, true);
}
UIViewController GetVisibleViewController(UIViewController controller = null)
{
controller = controller ?? UIApplication.SharedApplication.KeyWindow.RootViewController;
if (controller.PresentedViewController == null)
{
return controller;
}
if (controller.PresentedViewController is UINavigationController)
{
return ((UINavigationController)controller.PresentedViewController).VisibleViewController;
}
if (controller.PresentedViewController is UITabBarController)
{
return ((UITabBarController)controller.PresentedViewController).SelectedViewController;
}
return GetVisibleViewController(controller.PresentedViewController);
}
We had a similar issue and here is what we came up with:
var topViewController = UIApplication.SharedApplication.KeyWindow.RootViewController;
var controllerToPresentWith = topViewController.VisibleViewController();
controllerToPresentWith.PresentModalViewController(_imagePicker, true);
and then
...
public static UIViewController VisibleViewController(this UIViewController controller)
{
if (controller == null)
return null;
if (controller is UINavigationController navController)
{
return navController.VisibleViewController();
}
else if (controller is UITabBarController tabController)
{
tabController.SelectedViewController?.VisibleViewController();
}
else
{
var vc = controller.PresentedViewController?.VisibleViewController();
if (vc != null)
return vc;
}
return controller;
}
In the end, I implemented this by using James Montemagno's Media Plugin library (available over NuGet: https://www.nuget.org/packages/Xam.Plugin.Media) and Permissions Plugin (https://www.nuget.org/packages/Plugin.Permissions).
I wrote the following code for this:
private async Task ChoosePhoto()
{
var permission = await CheckCameraRollPermission();
if (permission == PermissionStatus.Granted)
{
await CrossMedia.Current.Initialize();
// Show image picker dialog
var file = await CrossMedia.Current.PickPhotoAsync(new Plugin.Media.Abstractions.PickMediaOptions()
{
ModalPresentationStyle = Plugin.Media.Abstractions.MediaPickerModalPresentationStyle.OverFullScreen
});
if (file != null)
{
// Image has been selected
using (var stream = file.GetStream())
{
using (var memoryStream = new System.IO.MemoryStream())
{
stream.CopyTo(memoryStream);
var fileBytes = memoryStream.ToArray();
// DO WHATEVER YOU WANT TO DO WITH THE SELECTED IMAGE AT THIS POINT
}
}
}
}
}
private async Task<PermissionStatus> CheckCameraRollPermission()
{
// Check permission for image library access
var permission = await PermissionsImplementation.Current.CheckPermissionStatusAsync(Permission.Photos);
if (permission != PermissionStatus.Granted)
{
// Permission has not been granted -> if permission has been requested before and the user did not grant it, show message and return the permission
var message = "";
switch (permission)
{
case PermissionStatus.Denied:
case PermissionStatus.Restricted:
message = "Unfortunately, you did not grant permission to access the camera roll. If you want to change this, you can do so in the system settings of your device.";
break;
default:
break;
}
if (!string.IsNullOrEmpty(message))
{
// Message available -> Display alert and return the permission
var alert = UIAlertController.Create("Permission not granted", message, UIAlertControllerStyle.Alert);
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
PresentViewController(alert, true, null);
return permission;
}
// In all other cases, request the permission
await PermissionsImplementation.Current.RequestPermissionsAsync(Permission.Photos);
// Check for permission one more time and return it
permission = await PermissionsImplementation.Current.CheckPermissionStatusAsync(Permission.Photos);
}
return permission;
}

How to call a AVPlayer in XAMARIN iOS during notification instead of 30 second max duration notification sound?

I need to play a audio file which is 3 minutes length. But default notification sound does not play more than 30 seconds. So my idea is Calling a Avplayer
which will play my desired audio. But i do not know how to call this. Can any one please help me. I will be very grateful.
I am attaching my notification method here.
public void AVPlayer()
{
NSUrl songURL;
if (!MusicOn) return;
//Song url from your local Resource
songURL = new NSUrl("azan.wav");
NSError err;
player = new AVAudioPlayer(songURL, "Song", out err);
player.Volume = MusicVolume;
player.FinishedPlaying += delegate {
// backgroundMusic.Dispose();
player = null;
};
//Background Music play
player.Play();
}
public void CreateRequest(JamatTime jamat)
{
// Create action
var actionID = "pause";
var title = "PAUSE";
var action = UNNotificationAction.FromIdentifier(actionID, title, UNNotificationActionOptions.None);
// Create category
var categoryID = "message";
var actions = new UNNotificationAction[] { action };
var intentIDs = new string[] { };
var categoryOptions = new UNNotificationCategoryOptions[] { };
var category = UNNotificationCategory.FromIdentifier(categoryID, actions, intentIDs, UNNotificationCategoryOptions.None);
// Register category
var categories = new UNNotificationCategory[] { category };
UNUserNotificationCenter.Current.SetNotificationCategories(new NSSet<UNNotificationCategory>(categories));
// Rebuild notification
var content = new UNMutableNotificationContent();
content.Title = " Jamat Time alert";
content.Badge = 1;
content.CategoryIdentifier = "message";`enter code here`
content.Sound = UNNotificationSound.GetSound("sample.wav");
var times = new string[] { jamat.Asr, jamat.Dhuhr, jamat.Faijr, jamat.Ishaa, jamat.Jumah, jamat.Maghib };
int id = 0;
foreach (var time in times)
{
var ndate = DateTime.ParseExact(time, "h:mm tt", null);
var date = new NSDateComponents()
{
Calendar = NSCalendar.CurrentCalendar,
Hour = ndate.Hour,
Minute = ndate.Minute,
Second = 0
};
content.UserInfo = new NSDictionary<NSString, NSString>(
new NSString[] {
(NSString)"time1",
(NSString)"time2"
},
new NSString[] {
(NSString)DateTime.Now.ToString("h:mm tt"),
(NSString)time
});
var trigger = UNCalendarNotificationTrigger.CreateTrigger(date, true);
// ID of Notification to be updated
var request = UNNotificationRequest.FromIdentifier(id++.ToString(), content, trigger);
// Add to system to modify existing Notification
UNUserNotificationCenter.Current.AddNotificationRequest(request, (err1) =>
{
if (err1 != null)
{
Console.WriteLine("Error: {0}", err1);
}
Console.WriteLine($"Success: {request}");
});
}
}
You can't play an audio file instead of the UNNotificationSound.
There's no way to trigger the player's play method when the local notification comes. You could only configure the sound property using the code you post above. And the file should be embedded in the bundle resource.
It seems you are aware of UNNotificationSound: https://developer.apple.com/documentation/usernotifications/unnotificationsound?language=objc. But I still want to remind you of the file's format and length limitations.
Finally I have solved my problem.
When a notification fires then WillPresentNotification() method hits and I simply call the AVplayer there and perfectly working. If u want to play sound via UNNotificationSound then not possible because that is limited by 30 second duration..but problem this works only in foreground.

Download file through Angular and TypeScript

I'm having an issue in downloading a file in my Angular project. The problem is that when I try to navigate to the file's URL, the file does download successfully. But how can I implement the downloading function in Angular?
[VRoute("PassportAttachments/{id}", 1)]
[HttpGet]
[AllowAnonymous]
public HttpResponseMessage GetPassportAttachmentById(int individualId, [FromUri] int id = -1)
{
try
{
var attachment = _passportAttachmentManager.FindById(id);
string attachmentPath = HttpContext.Current.Server.MapPath(
string.Format(ConfigurationManager.AppSettings["IndividualPassportsPath"], individualId.ToString()) + attachment.FileName);
//string downloadUrl = Url.Content(attachmentPath).Replace("/Api/Contacts/PassportAttachments/~", "");
//var result = new { DownloadUrl = downloadUrl, AttachmentTitle = attachment.Title };
//return Ok(result);
if (File.Exists(attachmentPath))
return new FileContentResult(attachmentPath, attachment.Title, FileResultType.ImageContentResult);
else
return null;
}
catch (Exception ex)
{
Unit.Logger.Error(ex, ToString(), ActionContext.ActionArguments.ToList());
return null;
//return NotFound();
}
}
FileContentResult constructor:
public FileContentResult(string FilePath, string ResponeFileName, FileResultType fileResultType) : base(HttpStatusCode.OK)
{
var stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
base.Content = new StreamContent(stream);
base.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachments") { FileName = ResponeFileName };
switch (fileResultType)
{
case FileResultType.ZipContentResult:
base.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
break;
case FileResultType.ExcelContentResult:
base.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
break;
case FileResultType.PDFContentResult:
base.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
break;
case FileResultType.ImageContentResult:
base.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
break;
}
}
Now like I said, when I type the URL which downloads the file by myself (hence the AllowAnonymous) everything works fine. But function should I use or write to download the file using TypeScript
public DownloadAttachments(): void {
if (this.SelectedPassportAttachments != null && this.SelectedPassportAttachments.length > 0) {
if (this.SelectedPassportAttachments.length == 1) {
this.service.DownloadSinglePassportAttachment(this.SelectedPassportAttachments[0].Id, this.IndividualId).subscribe((file: any) => {
// download file (function)
});
}
else {
this.service.DownloadMultiplePassportAttachment(this.IndividualId, this.SelectedPassportAttachments.map(pa => pa.Id), this.SelectedPassportNumber).subscribe();
}
}
}
Since you are using a Content-Disposition header, the browser will automatically trigger a download dialog when it attempts to load the URL.
So you can either just navigate to the download location, or open the download location in a separate window (which will automatically close in most browsers when the download dialog appears):
// navigate to the URL:
window.location.href = downloadUrl;
// or open a new window
window.open(downloadUrl);
Note that opening a window will be blocked by popup blockers if you run window.open outside from mouse events (for example button clicks). You can avoid that by opening the window first when the download button is clicked, and then change the URL later. Something like this:
downloadAttachment() {
const downloadWindow = window.open('about:blank');
this.service.GetDownloadUrl(…).subscribe(downloadUrl => {
downloadWindow.location.href = downloadUrl;
});
}

Windows 10 Toast Notification Visuals and Actions not working

I'm writing a c# console application that sends toast notifications in VS2017 on a Windows 10 machine. When I run my code and have only defined adaptive text content for my notification it runs correctly and displays the desired text. If I add actions, it no longer displays the adaptive text I defined ("title" and "content" below), nor does it have the option to perform said actions. If I try to add additional visuals such as a logo or picture, they do not appear but the adaptive text is unaffected.
string category = "Notifications";
string title = "New Test Notification!;
string content = "Testing push notifications!";
ToastContent toastContent = new ToastContent()
{
Visual = new ToastVisual()
{
BindingGeneric = new ToastBindingGeneric()
{
Children =
{
new AdaptiveText()
{
Text = title
},
new AdaptiveText()
{
Text = content
}
},
/*AppLogoOverride = new ToastGenericAppLogo()
{
Source = "https://unsplash.it/64?image=883",
HintCrop = ToastGenericAppLogoCrop.Circle
}*/
}
},
/*Actions = new ToastActionsCustom()
{
Buttons =
{
new ToastButton("check", "check")
{
ImageUri = "check.png"
},
new ToastButton("cancel", "cancel")
{
ImageUri = "cancel.png"
}
}
}*/
};
string toastXml = toastContent.GetContent();
XmlDocument doc = new XmlDocument();
doc.LoadXml(toastXml);
var toast = new ToastNotification(doc);
ToastNotificationManager.CreateToastNotifier(category).Show(toast);
Result when above code is run with or without the first block of commented code:
Result when code is run with the second block of commented code:

Windows Phone Push Notification By Azure Notification Hub

I am android app developer .Now, I am developing app for windows phone . I am integrating push notification in windows phone first time by Azure notification Hub. I want help for notification Hub. How to integrate? How to send notification to selected device not to all?
I was integrated notification but after some time notification is stop. I have done with many notification hub credentials. Now notification is coming.
private async void Application_Launching(object sender, LaunchingEventArgs e)
{
StorageFile MyDBFile = null;
try
{
// Read the db file from DB path
MyDBFile = await StorageFile.GetFileFromPathAsync(DB_PATH);
}
catch (FileNotFoundException)
{
if (MyDBFile == null)
{
// Copy file from installation folder to local folder.
IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication();
// Create a stream for the file in the installation folder.
using (Stream input = Application.GetResourceStream(new Uri(DbConst.DATABASE, UriKind.Relative)).Stream)
{
// Create a stream for the new file in the local folder.
using (IsolatedStorageFileStream output = iso.CreateFile(DB_PATH))
{
// Initialize the buffer.
byte[] readBuffer = new byte[4096];
int bytesRead = -1;
// Copy the file from the installation folder to the local folder.
while ((bytesRead = input.Read(readBuffer, 0, readBuffer.Length)) > 0)
{
output.Write(readBuffer, 0, bytesRead);
}
}
}
}
}
var channel = HttpNotificationChannel.Find("MyPushChannel");
if (channel == null)
{
channel = new HttpNotificationChannel("MyPushChannel");
channel.Open();
channel.BindToShellToast();
}
channel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(async (o, args) =>
{
//var hub = new NotificationHub("<hub name>", "<connection string>");
var hub = new NotificationHub("mnokhgjhjhjbohkjkl", "Endpoint=sb://connect-hub.servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=RM6jjnbjnbjAnjhttD4yxqnknknklmkmnkkmkkkmmkbnl5rSk=");
Registration x= await hub.RegisterNativeAsync(args.ChannelUri.ToString());
//mChennelURI = x.ChannelUri;
Debug.WriteLine("Chennel URI: " + x.ChannelUri);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
System.Diagnostics.Debug.WriteLine(args.ChannelUri.ToString());
});
});
channel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(async (o,args) =>
{
// Error handling logic for your particular application would be here.
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
// Show the notification since toasts aren't
// displayed when the app is currently running.
MessageBox.Show(String.Format("A push notification {0} error occurred. {1} ({2}) {3}", args.ErrorType, args.Message, args.ErrorCode,args.ErrorAdditionalData));
});
});
// Handle the event that is raised when a toast is received.
channel.ShellToastNotificationReceived +=new EventHandler<NotificationEventArgs>((o, args) =>
{
string message = "";
foreach (string key in args.Collection.Keys)
{
message += args.Collection[key] + ": ";
}
//string[] separators = { "#"};
// string[] words = message.Split(separators, StringSplitOptions.RemoveEmptyEntries);
// string type = words[0];
// string title = words[1];
// string body = words[2];
// string attach = words[3];
//string date = words[4];
//string time = words[5];
//string msdId = words[6];
//string groupIDS = words[7];
//foreach (var word in words)
//Console.WriteLine(word);
Console.WriteLine("Push notification : " + message);
Debug.WriteLine("Push notification : " + message);
Debugger.Log(1, "Push", "Push notification : " + message);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
// Show the notification since toasts aren't
// displayed when the app is currently running.
MessageBox.Show(message);
//<Temp>
// DatabaseHelperClass dbHelper = new DatabaseHelperClass();
// dbHelper.Insert(new Msg(msdId, groupIDS, type, title, body, attach, date, time, Constants.NO));
//</Temp>
});
});
}

Categories

Resources