Xamarin iOS post image to Instagram - c#

Goal: Using an iOS native method, push a user made picture onto their Instagram feed in C#.
public bool ShareImage(byte[] imageByte)
{
bool result = false;
//string fileName = "xxx.png";
//string path = Path.Combine(FileSystem.CacheDirectory, fileName);
//File.WriteAllBytes(path, imageByte);
NSData data = NSData.FromArray(imageByte);
UIImage image = UIImage.LoadFromData(data);
//NSUrl url = NSUrl.FromString($"instagram://library?AssetPath={path}"); // Only opens
NSUrl url = NSUrl.FromString($"instagram://library?LocalIdentifier={1}");
if (UIApplication.SharedApplication.CanOpenUrl(url))
{
UIApplication.SharedApplication.OpenUrl(url);
}
return result;
}
As far as I'm aware the way I have to do this is to save my image to the device and get a Local Identifier for this.
I have constructed this from the snippets of objective-c code I have seen. Sharing photos only works with the native app installed, as I have learnt from my trials to get the facebook module working.
Edit: Using PHAssetChangeRequest from the iOS Photos namespace does not work.
A collegue has pointed out to me about the possibility of saving then using a photo picker to get the PHAsset for the Local Identifier. But this is an extra step I do not want the users of the App to go through. Better to just remove Instagram support as I just can go through the generic share method as shown below. The disadvantage of this method is that the user has then to pick the medium to share over.
public async void ShareImage(byte[] imageByte)
{
string fileName = "xxx.png";
string path = Path.Combine(FileSystem.CacheDirectory, fileName);
File.WriteAllBytes(path, imageByte);
await Share.RequestAsync(
new ShareFileRequest()
{
File = new ShareFile(path),
Title = "xxx"
}
);
}
Edit 2
Tried a different way using UIDocumentInteractionController but it is showing nothing and not posting, it is not throwing any exceptions to give me any clues as to what I'm doing wrong.
public bool ShareImage(byte[] imageByte)
{
bool result = false;
string fileName = "xxx.igo";
string path = Path.Combine(FileSystem.CacheDirectory, fileName);
File.WriteAllBytes(path, imageByte);
//string caption = "xxx";
string uti = "com.instagram.exclusivegram";
UIImageView view = new UIImageView();
//UIDocumentInteractionController controller = UIDocumentInteractionController.FromUrl(NSUrl.FromString(path));
UIDocumentInteractionController controller = new UIDocumentInteractionController
{
//controller.Url = NSUrl.FromString(path);
Url = NSUrl.FromFilename(path),
Uti = uti
};
//CoreGraphics.CGRect viewDimensions = new CoreGraphics.CGRect(0, 0, 200, 100);
_ = controller.PresentOpenInMenu(CoreGraphics.CGRect.Empty, view, true);
//_ = controller.PresentOpenInMenu(viewDimensions, view, true);
return result;
}
Edit 3
Using
UIView view = new UIImageView();
if (UIApplication.SharedApplication.KeyWindow.RootViewController is UIViewController uiController && uiController.View != null)
{
view = uiController.View;
}
I was able to get the possible share options to show. I was logged in to Instagram using the native App, but the post did not show.

Change
Url = NSUrl.FromFilename(path)
to
Url = new NSUrl(path,false)
The second way is pointing to the correct file path not the file name .
Change
controller.PresentOpenInMenu(CoreGraphics.CGRect.Empty, view, true);
to
controller.PresentOptionsMenu(UIScreen.MainScreen.Bounds, (UIApplication.SharedApplication.Delegate as AppDelegate).Window, true);
Show UIDocumentInteractionController inside current Window and set the proper frame.

Related

Rally C#: Is it possible to create a new user story along with an attachment? (Avoiding to query for the user-story reference)

Please be patient I have already gone through the example from: How to Add an Attachment to a User Story using Rally REST .NET
However the sample code requires a query to identify the newest user story reference. The sample illustrates how to add an attachment to an existing user story. I would like to accomplish: Creating a new user story along with an attachment.
Here is my method.
public void createUsWithAttachment(string workspace, string project,
string userStoryName, string userStoryDescription){
//Authenticate with Rally
this.EnsureRallyIsAuthenticated();
//UserStory Setup
DynamicJsonObject toCreate = new DynamicJsonObject();
toCreate[RallyField.workSpace] = workspace;
toCreate[RallyField.project] = project;
toCreate[RallyField.name] = userStoryName;
toCreate[RallyField.description] = userStoryDescription;
//get the image reference - assume that this is where the image lives after being downloaded from Outlook
String imageFilePath = "C:\\Users\\secret\\...";
String imageFileName = "file.png";
String fullImageFile = imageFilePath + imageFileName;
Image myImage = Image.FromFile(fullImageFile);
// Convert Image to Base64 format
string imageBase64String = imageToBase64(myImage, System.Drawing.Imaging.ImageFormat.Png);
// Length calculated from Base64String converted back
var imageNumberBytes = Convert.FromBase64String(imageBase64String).Length;
// DynamicJSONObject for AttachmentContent
DynamicJsonObject myAttachmentContent = new DynamicJsonObject();
myAttachmentContent["Content"] = imageBase64String;
try
{
//create user story
CreateResult createUserStory = _api.Create(RallyField.hierarchicalRequirement, toCreate);
//create attachment
CreateResult myAttachmentContentCreateResult = _api.Create("AttachmentContent", myAttachmentContent);
String myAttachmentContentRef = myAttachmentContentCreateResult.Reference;
Console.WriteLine("Created: " + myAttachmentContentRef);
// DynamicJSONObject for Attachment Container
DynamicJsonObject myAttachment = new DynamicJsonObject();
//Note the below commented line
/*myAttachment["Artifact"] = ;*/
myAttachment["Content"] = myAttachmentContentRef;
myAttachment["Name"] = "AttachmentFromREST.png";
myAttachment["Description"] = "Attachment Desc";
myAttachment["ContentType"] = "image/png";
myAttachment["Size"] = imageNumberBytes;
//create attachment
CreateResult myAttachmentCreateResult = _api.Create("Attachment", myAttachment);
}
catch (WebException e)
{
Console.WriteLine(e.Message);
}
}
In the above commented line I need to get a reference to the created user story which I can do, but that would require another method to fetch the user story which I have.
However I am wondering if this can be accomplished a different way similar to how we can create a user story with the dynamicJsonObject staging.
I was thinking something like this would work, but I am having a tough time.
DynamicJsonObject toCreate = new DynamicJsonObject();
toCreate[RallyField.workSpace] = workspace;
toCreate[RallyField.project] = project;
toCreate[RallyField.name] = userStoryName;
toCreate[RallyField.description] = userStoryDescription;
toCreate["Content"] = imageToBase64;
CreateResult createUserStory = _api.Create(RallyField.hierarchicalRequirement, toCreate);
My above assumption is not going as planned and I was wondering if there is a way to create a new user story with an attachment, without having to query for the userstory reference as neatly illustrated in the example from the link provided.
Thanks and credit to the author from the example in the link.
You should already have what you need to associate it from the create result:
myAttachment["Artifact"] = createUserStory.Reference;

Xamarin.forms how to hold image on same page after navigating another page

I have uploaded image on my profile page and I want to hold that image until I logout in xamarin forms.
My image will be lost if I select another page so I want to hold it until I log out.
var profile = new Image { };
profile.Source = "profile.png";
profile.HorizontalOptions = LayoutOptions.StartAndExpand;
profile.VerticalOptions = LayoutOptions.StartAndExpand;
var profiletap = new TapGestureRecognizer();
profiletap.Tapped += async (s, e) =>
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
im = ImageSource.FromStream(() =>
{
var stream = file.GetStream();
//file.Dispose();
return stream;
});
profile.Source = im;
// await Navigation.PushModalAsync(new PhotoPage(im));
};
profile.GestureRecognizers.Add(profiletap);
Pages do not get destroyed when navigating to another page and coming back, which is why a page's constructor only gets executed the first time it is shown. So I am not sure what you mean when you say you want to hold that image.
Having said that, you could always assign the entire profile variable to a static global variable in your App class like below so that it stays the same no matter what. Then you would have to assign/initialize the global variable at the correct time.
But again, I am not sure if that is necessary, so you might try to explain more what the issue actually is:
In the App class:
public class App : Application {
public static Image ProfileImage = new Image {
Source = "profile.png",
HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalOptions = LayoutOptions.StartAndExpand
};
....
}
Then in your page:
public class ProfilePage : ContentPage {
public ProfilePage() {
....
App.ProfileImage.GestureRecognizers.Add(profiletap);
}
}
Edit: See my answer here for an example of using a plugin to allow the user to choose a photo from their device's camera roll. Once you have the photo path, you can simply use HttpClient to send the image and a base64 string. There are plenty of other example online about how to do that.
Edit #2: After this line of your code:
var file = await CrossMedia.Current.PickPhotoAsync();
You now have the file and the path in file variable. So currently all you are doing is showing the image using ImageSource.FromStream but in order to keep showing the image when you return to the page, you need to also save the image to the device. In order to do that, you will need to write platform specific code in each project and reference that in your shared code. Something like this:
In your iOS and Android project, create a new file (FileHelper_Android.cs and FileHelper_iOS.cs for example) and add the following (the same code can be added to both iOS and Android files, just change the name of the class and file:
using ....;
[assembly: Dependency(typeof(FileHelper_Android))]
namespace YourNamespace.Droid{
/// <summary>
/// Responsible for working with files on an Android device.
/// </summary>
internal class FileHelper_Android : IFileHelper {
#region Constructor
public FileHelper_Android() { }
#endregion
public string CopyFile(string sourceFile, string destinationFilename, bool overwrite = true) {
if(!File.Exists(sourceFile)) { return string.Empty; }
string fullFileLocation = Path.Combine(Environment.GetFolderPath (Environment.SpecialFolder.Personal), destinationFilename);
File.Copy(sourceFile, fullFileLocation, overwrite);
return fullFileLocation;
}
}
}
Do the same on iOS and just change the file name. Now in your shared project you need to create IFileHelper.cs like so:
public interface IFileHelper {
string CopyFile(string sourceFile, string destinationFilename, bool overwrite = true);
}
Finally, in your page you would write the following:
_fileHelper = _fileHelper ?? DependencyService.Get<IFileHelper>();
profiletap.Tapped += async (s, e) =>
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
profile.Source = im;
imageName = "SomeUniqueFileName" + DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss-tt");
filePath = _fileHelper.CopyFile(file.Path, imageName);
im = ImageSource.FromFile(filePath)
// await Navigation.PushModalAsync(new PhotoPage(im));
};
Above, once the user chooses the file, we copy that file locally and the we also set your im variable to the new local file path, which gets returned from the IFileHelper.CopyFile method.
You still need to handle the case when the user comes back to the page or turns the app off and on again. In that situation, you need to load the saved image path. I would suggest either saving the image path into the DB, unless the user will only ever have a single profile image, then you could always just load that same path and filename. Let me know if you still have issues.

Show loading screen before dynamically created PDF

I have a view that is, instead of returning a View(), is returning a dynamically created PDF and then showing the PDF in a new tab. I'm not saving the PDF anywhere, or storing it anywhere. What I would like to do is have a loading screen show up while the PDF is being created. Can this be done?
public ActionResult SolicitorActionReport_Load(SolicitorActionParamsViewModel viewModel) {
var cultivationModel = new CultivationModel(viewModel, ConstituentRepository, CampaignRepository);
var cultivationData = cultivationModel.GetCultivationActivityData();
var reportParamModel = new List<ReportParamModel>
{new ReportParamModel {AgencyName = SelectedUserAgency.AgencyName, StartDate = viewModel.StartDate, EndDate = viewModel.EndDate}};
var reportToRun = "ActionDateCultivationReport";
if (viewModel.SortActionBy == SolicitorActionReportSortType.Constituent) {
reportToRun = "ConstituentCultivationReport";
} else if (viewModel.SortActionBy == SolicitorActionReportSortType.Solicitor) {
reportToRun = "SolicitorCultivationReport";
}
return FileContentPdf("Constituent", reportToRun, cultivationData, reportParamModel, new List<FundraisingAppealMassSummary>(), new List<FundraisingAppealPortfolioSummary>());
}
public FileContentResult FileContentPdf(string folder, string reportName, object dataSet,object reportParamModel,object appealMassDataSet, object appealPortfolioDataSet) {
var localReport = new LocalReport();
localReport.ReportPath = Server.MapPath("~/bin/Reports/" + folder + "/rpt" + reportName + ".rdlc");
var reportDataSource = new ReportDataSource(reportName + "DataSet", dataSet);
var reportParamsDataSource = new ReportDataSource("ReportParamModelDataSet", reportParamModel);
var reportParamsDataSourceMass = new ReportDataSource("FundraisingAppealMassSummaryDataSet", appealMassDataSet);
var reportParamsDataSourcePortfolio = new ReportDataSource("FundraisingAppealPortfolioSummaryDataSet", appealPortfolioDataSet);
#region Setting ReportViewControl
localReport.DataSources.Add(reportDataSource);
localReport.DataSources.Add(reportParamsDataSource);
localReport.DataSources.Add(reportParamsDataSourceMass);
localReport.DataSources.Add(reportParamsDataSourcePortfolio);
localReport.SubreportProcessing += (s, e) => { e.DataSources.Add(reportDataSource); };
string reportType = "pdf";
string mimeType;
string encoding;
string fileNameExtension;
//The DeviceInfo settings should be changed based on the reportType
//http://msdn2.microsoft.com/en-us/library/ms155397.aspx
string deviceInfo = "<DeviceInfo><OutputFormat>PDF</OutputFormat></DeviceInfo>";
Warning[] warnings;
string[] streams;
byte[] renderedBytes;
//Render the report
renderedBytes = localReport.Render(reportType, deviceInfo, out mimeType, out encoding, out fileNameExtension, out streams, out warnings);
#endregion
return File(renderedBytes, mimeType);
}
I'm not saving the PDF anywhere, or storing it anywhere. What I would like to do is have a loading screen show up while the PDF is being created. Can this be done?
Short Answer
No, not in a new tab.
The main problem with what you're trying to do is the lack of power you have when it comes to controlling the browser. Specifically, when you tell an anchor to open its hyperlink in a new tab (ie target="_blank"). There are hacky ways around this that generally are just going to frustrate your user because you're changing behavior that they might be dependent/relying on.
Workaround
You can get very close to your desired outcome by using this jQuery File Download plugin (view a demo). Basically, it manipulates an iframe to queue a download. This makes it possible to show a loading div while also keeping the user on the active page (not directing them to another tab). Then, the user can click the downloaded PDF which will most-likely open in a new tab (view compatible browsers here).
If you decide to use this plugin, here are the steps to applying it:
Download the plugin js source and include it in your Scripts.
Include the FileDownloadAttribute class provided in the plugin MVC Demo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class FileDownloadAttribute: ActionFilterAttribute
{
public FileDownloadAttribute(string cookieName = "fileDownload", string cookiePath = "/")
{
CookieName = cookieName;
CookiePath = cookiePath;
}
public string CookieName { get; set; }
public string CookiePath { get; set; }
/// <summary>
/// If the current response is a FileResult (an MVC base class for files) then write a
/// cookie to inform jquery.fileDownload that a successful file download has occured
/// </summary>
/// <param name="filterContext"></param>
private void CheckAndHandleFileResult(ActionExecutedContext filterContext)
{
var httpContext = filterContext.HttpContext;
var response = httpContext.Response;
if (filterContext.Result is FileResult)
//jquery.fileDownload uses this cookie to determine that a file download has completed successfully
response.AppendCookie(new HttpCookie(CookieName, "true") { Path = CookiePath });
else
//ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
if (httpContext.Request.Cookies[CookieName] != null)
{
response.AppendCookie(new HttpCookie(CookieName, "true") { Expires = DateTime.Now.AddYears(-1), Path = CookiePath });
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
CheckAndHandleFileResult(filterContext);
base.OnActionExecuted(filterContext);
}
}
github source
Apply the FileDownload attribute to your ActionResult method:
[FileDownload]
public ActionResult SolicitorActionReport_Load(SolicitorActionParamsViewModel viewModel) {
...
return FileContentPdf("Constituent", reportToRun, cultivationData, reportParamModel, new List<FundraisingAppealMassSummary>(), new List<FundraisingAppealPortfolioSummary>());
}
Include the necessary markup in the View to which you'll be linking to the report:
<a class="report-download" href="/Route/To/SolicitorActionReport">Download PDF</a>
Attach an event handler to the report-download anchor:
$(document).on("click", "a.report-download", function () {
$.fileDownload($(this).prop('href'), {
preparingMessageHtml: "We are preparing your report, please wait...",
failMessageHtml: "There was a problem generating your report, please try again."
});
return false; //this is critical to stop the click event which will trigger a normal file download!
});
You can view working demos at http://jqueryfiledownload.apphb.com/. There is also a demo that uses pre-styled jQuery UI modals to "prettify" the user experience.
You can also download the demo ASP.NET MVC solution from johnculviner / jquery.fileDownload github to see all of this working.
I think you have two choices:
Redirect to a "loading" page with fancy GIF spinners, then direct the request to the PDF (this would work if the PDF take a little server time to generate - the visitor would be looking at a loading page while waiting for next page to load)
or
Use an iFrame: load a page that has an iframe. This page can overlay a spinning GIF and loading message while the iFrame loads the PDF itself. Note: you could make the iframe 100% width and height

CanvasAuthorize C# SDK Infinite Loop

I'm looking to try and get an MVC3 Canvas app working with the Facebook C# SDK, but am struggling to allow permissions - Below is my code, and when I open the app I get the 'Allow / Deny' dialog but when I click allow I get redirected to my app and the same dialog appears again (And again and so on no matter how many times I click allow)?
I guess I am missing something obvious... If I take the user_groups permission out it works fine, I just can't access the persons groups.
[CanvasAuthorize(Permissions = "user_groups")]
public class HomeController : Controller
{
public ActionResult Index()
{
IFacebookApplication settings = FacebookApplication.Current;
if (settings != null)
{
//CanvasPage = settings.CanvasPage;
//AppId = settings.AppId;
}
FacebookWebContext facebookContext = FacebookWebContext.Current;
FacebookSignedRequest signedRequest = facebookContext.SignedRequest;
var client = new FacebookWebClient(facebookContext.AccessToken);
dynamic me = client.Get("me");
var friends = client.Get("me/friends");
var groups = client.Get("me/groups");
ViewBag.Name = me.name;
ViewBag.Id = me.id;
JavaScriptSerializer sr = new JavaScriptSerializer();
var fbFriends = sr.Deserialize<FBFriends>(friends.ToString());
ViewData["friends"] = fbFriends.data;
return View("Friends");
}
}
Any help, tips or code samples greatly appreciated.
make sure u have set the appid and appsecret correctly.
download the source code and checkout the "samples" folder, there are a bunch of asp.net mvc samples.

Publishing stream appears with no images - Facebook

I am building a small facebook application using the Facebook developer toolkit 3.0 in c#. I had this piece of code that used to work a week ago. From some reason, when I try publishing my feed it appears without an image and without a Flash movie. Here is my code:
string userName = FacebookAPI.Users.GetInfo().name;
string message = "";
attachment attach = new attachment();
attach.href = string.Format(#"http://apps.facebook.com/{0}/?uId={1}&uName={2}", Consts.APP_NAME, ViewerUserId, userName);
attachment_media_flash flash = new attachment_media_flash();
flash.type = attachment_media_type.flash;
flash.expanded_width = 320;
flash.expanded_height = 260;
flash.height = 100;
flash.width = 130;
message = "";
attach.name = ""
attach.caption = "this works";
attach.description = ""
flash.imgsrc = string.Format(#"{0}/flash/Pinguin.JPG", Consts.DOMAIN_NAME);
flash.swfsrc = string.Format(#"{0}/flash/pinguin.swf", Consts.DOMAIN_NAME);
List<attachment_media> attach_media_list = new List<attachment_media>();
attach_media_list.Add(flash);
attach.media = attach_media_list;
/* action links */
List<action_link> actionlink = new List<action_link>();
action_link al1 = new action_link();
al1.href = string.Format(#"http://apps.facebook.com/{0}", Consts.APP_NAME);
al1.text = "myApp";
actionlink.Add(al1);
FacebookAPI.Stream.Publish(message, attach, actionlink, null , Convert.ToInt64 (ViewerUserId));
Make sure that:
1: The URL you pass is not the one that of the FB app, like http://apps.fb.co/whatever/image.jpg;
it should be your domain URL, like http://www.example.com/images/myimage.jpg.
2: You need to pass the full URL of the image including the protocol (http:// / https://), like this:
http://www.example.com/images/myimage.jpg
The following is wrong:
www.example.com/images/myimage.jpg

Categories