PageRenderTime 285ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/CodeHub.iOS/Views/Issues/IssueView.cs

https://gitlab.com/jslee1/CodeHub
C# | 251 lines | 211 code | 39 blank | 1 comment | 37 complexity | be708bcc0fe1bf8b48a9aec725c58257 MD5 | raw file
  1. using System;
  2. using CodeHub.Core.ViewModels.Issues;
  3. using UIKit;
  4. using CodeHub.iOS.ViewControllers;
  5. using CodeHub.iOS.Utilities;
  6. using CodeHub.iOS.DialogElements;
  7. using System.Linq;
  8. using System.Collections.Generic;
  9. using Humanizer;
  10. using CodeHub.iOS.ViewControllers.Repositories;
  11. using CodeHub.iOS.Services;
  12. using System.Reactive.Linq;
  13. using ReactiveUI;
  14. using CodeHub.iOS.WebViews;
  15. using System.Threading.Tasks;
  16. namespace CodeHub.iOS.Views.Issues
  17. {
  18. public class IssueView : PrettyDialogViewController
  19. {
  20. private readonly HtmlElement _descriptionElement = new HtmlElement("description");
  21. private readonly HtmlElement _commentsElement = new HtmlElement("comments");
  22. private StringElement _milestoneElement;
  23. private StringElement _assigneeElement;
  24. private StringElement _labelsElement;
  25. private StringElement _addCommentElement;
  26. private SplitButtonElement _split = new SplitButtonElement();
  27. private SplitButtonElement.Button _splitButton1;
  28. private SplitButtonElement.Button _splitButton2;
  29. public new IssueViewModel ViewModel
  30. {
  31. get { return (IssueViewModel) base.ViewModel; }
  32. set { base.ViewModel = value; }
  33. }
  34. public override void ViewDidLoad()
  35. {
  36. base.ViewDidLoad();
  37. _splitButton1 = _split.AddButton("Comments", "-");
  38. _splitButton2 = _split.AddButton("Participants", "-");
  39. Title = "Issue #" + ViewModel.Id;
  40. HeaderView.SetImage(null, Images.Avatar);
  41. HeaderView.Text = Title;
  42. Appeared.Take(1)
  43. .Select(_ => Observable.Timer(TimeSpan.FromSeconds(0.2f)))
  44. .Switch()
  45. .ObserveOn(RxApp.MainThreadScheduler)
  46. .Select(_ => ViewModel.Bind(x => x.IsClosed, true).Where(x => x.HasValue).Select(x => x.Value))
  47. .Switch()
  48. .Subscribe(x =>
  49. {
  50. HeaderView.SubImageView.TintColor = x ? UIColor.FromRGB(0xbd, 0x2c, 0) : UIColor.FromRGB(0x6c, 0xc6, 0x44);
  51. HeaderView.SetSubImage((x ? Octicon.IssueClosed :Octicon.IssueOpened).ToImage());
  52. });
  53. _milestoneElement = new StringElement("Milestone", "No Milestone", UITableViewCellStyle.Value1) {Image = Octicon.Milestone.ToImage() };
  54. _assigneeElement = new StringElement("Assigned", "Unassigned", UITableViewCellStyle.Value1) {Image = Octicon.Person.ToImage() };
  55. _labelsElement = new StringElement("Labels", "None", UITableViewCellStyle.Value1) {Image = Octicon.Tag.ToImage() };
  56. _addCommentElement = new StringElement("Add Comment") { Image = Octicon.Pencil.ToImage() };
  57. var actionButton = new UIBarButtonItem(UIBarButtonSystemItem.Action) { Enabled = false };
  58. NavigationItem.RightBarButtonItem = actionButton;
  59. ViewModel.Bind(x => x.IsModifying).SubscribeStatus("Loading...");
  60. ViewModel.Bind(x => x.Issue).Subscribe(x =>
  61. {
  62. _assigneeElement.Value = x.Assignee != null ? x.Assignee.Login : "Unassigned";
  63. _milestoneElement.Value = x.Milestone != null ? x.Milestone.Title : "No Milestone";
  64. _labelsElement.Value = x.Labels.Count == 0 ? "None" : string.Join(", ", x.Labels.Select(i => i.Name));
  65. var model = new DescriptionModel(ViewModel.MarkdownDescription, (int)UIFont.PreferredSubheadline.PointSize, true);
  66. var markdown = new MarkdownView { Model = model };
  67. var html = markdown.GenerateString();
  68. _descriptionElement.SetValue(string.IsNullOrEmpty(ViewModel.MarkdownDescription) ? null : html);
  69. HeaderView.Text = x.Title;
  70. HeaderView.SubText = "Updated " + x.UpdatedAt.Humanize();
  71. HeaderView.SetImage(x.User?.AvatarUrl, Images.Avatar);
  72. RefreshHeaderView();
  73. Render();
  74. });
  75. ViewModel.BindCollection(x => x.Comments).Subscribe(_ => RenderComments());
  76. ViewModel.BindCollection(x => x.Events).Subscribe(_ => RenderComments());
  77. ViewModel.Bind(x => x.ShouldShowPro).Subscribe(x => {
  78. if (x) this.ShowPrivateView();
  79. });
  80. OnActivation(d =>
  81. {
  82. d(_milestoneElement.Clicked.BindCommand(ViewModel.GoToMilestoneCommand));
  83. d(_assigneeElement.Clicked.BindCommand(ViewModel.GoToAssigneeCommand));
  84. d(_labelsElement.Clicked.BindCommand(ViewModel.GoToLabelsCommand));
  85. d(_addCommentElement.Clicked.Subscribe(_ => AddCommentTapped()));
  86. d(_descriptionElement.UrlRequested.BindCommand(ViewModel.GoToUrlCommand));
  87. d(_commentsElement.UrlRequested.BindCommand(ViewModel.GoToUrlCommand));
  88. d(actionButton.GetClickedObservable().Subscribe(ShowExtraMenu));
  89. d(HeaderView.Clicked.BindCommand(ViewModel.GoToOwner));
  90. d(ViewModel.Bind(x => x.IsCollaborator, true).Subscribe(x => {
  91. foreach (var i in new [] { _assigneeElement, _milestoneElement, _labelsElement })
  92. i.Accessory = x ? UITableViewCellAccessory.DisclosureIndicator : UITableViewCellAccessory.None;
  93. }));
  94. d(ViewModel.Bind(x => x.IsLoading).Subscribe(x => actionButton.Enabled = !x));
  95. });
  96. }
  97. private static string CreateEventBody(string eventType, string commitId)
  98. {
  99. commitId = commitId ?? string.Empty;
  100. var smallCommit = commitId;
  101. if (string.IsNullOrEmpty(smallCommit))
  102. smallCommit = "Unknown";
  103. else if (smallCommit.Length > 7)
  104. smallCommit = commitId.Substring(0, 7);
  105. if (eventType == "closed")
  106. return "<p><span class=\"label label-danger\">Closed</span> this issue.</p>";
  107. if (eventType == "reopened")
  108. return "<p><span class=\"label label-success\">Reopened</span> this issue.</p>";
  109. if (eventType == "merged")
  110. return "<p><span class=\"label label-info\">Merged</span> commit " + smallCommit + "</p>";
  111. if (eventType == "referenced")
  112. return "<p><span class=\"label label-default\">Referenced</span> commit " + smallCommit + "</p>";
  113. return string.Empty;
  114. }
  115. public void RenderComments()
  116. {
  117. var comments = ViewModel.Comments
  118. .Select(x => new Comment(x.User.AvatarUrl, x.User.Login, ViewModel.ConvertToMarkdown(x.Body), x.CreatedAt))
  119. .Concat(ViewModel.Events.Select(x => new Comment(x.Actor.AvatarUrl, x.Actor.Login, CreateEventBody(x.Event, x.CommitId), x.CreatedAt)))
  120. .Where(x => !string.IsNullOrEmpty(x.Body))
  121. .OrderBy(x => x.Date)
  122. .ToList();
  123. var commentModel = new CommentModel(comments, (int)UIFont.PreferredSubheadline.PointSize);
  124. var razorView = new CommentsView { Model = commentModel };
  125. var html = razorView.GenerateString();
  126. InvokeOnMainThread(() => {
  127. _commentsElement.SetValue(!comments.Any() ? null : html);
  128. Render();
  129. });
  130. }
  131. protected virtual void Render()
  132. {
  133. //Wait for the issue to load
  134. if (ViewModel.Issue == null)
  135. return;
  136. var participants = ViewModel.Events.Select(x => x.Actor.Login).Distinct().Count();
  137. _splitButton1.Text = ViewModel.Issue.Comments.ToString();
  138. _splitButton2.Text = participants.ToString();
  139. ICollection<Section> sections = new LinkedList<Section>();
  140. sections.Add(new Section { _split });
  141. var secDetails = new Section();
  142. if (_descriptionElement.HasValue)
  143. secDetails.Add(_descriptionElement);
  144. secDetails.Add(_assigneeElement);
  145. secDetails.Add(_milestoneElement);
  146. secDetails.Add(_labelsElement);
  147. sections.Add(secDetails);
  148. var commentsSection = new Section();
  149. if (_commentsElement.HasValue)
  150. commentsSection.Add(_commentsElement);
  151. commentsSection.Add(_addCommentElement);
  152. sections.Add(commentsSection);
  153. Root.Reset(sections);
  154. }
  155. void AddCommentTapped()
  156. {
  157. var composer = new MarkdownComposerViewController();
  158. composer.NewComment(this, async (text) => {
  159. var hud = this.CreateHud();
  160. hud.Show("Posting Comment...");
  161. if (await ViewModel.AddComment(text))
  162. composer.CloseComposer();
  163. hud.Hide();
  164. composer.EnableSendButton = true;
  165. });
  166. }
  167. public override UIView InputAccessoryView
  168. {
  169. get
  170. {
  171. var u = new UIView(new CoreGraphics.CGRect(0, 0, 320f, 27)) { BackgroundColor = UIColor.White };
  172. return u;
  173. }
  174. }
  175. private void ShowExtraMenu(UIBarButtonItem item)
  176. {
  177. if (ViewModel.Issue == null)
  178. return;
  179. var sheet = new UIActionSheet();
  180. var editButton = ViewModel.IsCollaborator ? sheet.AddButton("Edit") : -1;
  181. var openButton = ViewModel.IsCollaborator ? sheet.AddButton(ViewModel.Issue.State == "open" ? "Close" : "Open") : -1;
  182. var commentButton = sheet.AddButton("Comment");
  183. var shareButton = sheet.AddButton("Share");
  184. var showButton = sheet.AddButton("Show in GitHub");
  185. var cancelButton = sheet.AddButton("Cancel");
  186. sheet.CancelButtonIndex = cancelButton;
  187. sheet.Dismissed += (s, e) =>
  188. {
  189. BeginInvokeOnMainThread(() =>
  190. {
  191. if (e.ButtonIndex == editButton)
  192. ViewModel.GoToEditCommand.Execute(null);
  193. else if (e.ButtonIndex == openButton)
  194. ViewModel.ToggleStateCommand.Execute(null);
  195. else if (e.ButtonIndex == shareButton)
  196. AlertDialogService.ShareUrl(ViewModel.Issue?.HtmlUrl, NavigationItem.RightBarButtonItem);
  197. else if (e.ButtonIndex == showButton)
  198. ViewModel.GoToUrlCommand.Execute(ViewModel.Issue.HtmlUrl);
  199. else if (e.ButtonIndex == commentButton)
  200. AddCommentTapped();
  201. });
  202. sheet.Dispose();
  203. };
  204. sheet.ShowFrom(item, true);
  205. }
  206. public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
  207. {
  208. Task.WhenAll(_descriptionElement.ForceResize(), _commentsElement.ForceResize())
  209. .ToBackground(() => Root.Reload(new [] { _commentsElement, _descriptionElement }));
  210. }
  211. }
  212. }