Skip to content

Commit

Permalink
Merge pull request #19 from GrioSF/sutro-adequations
Browse files Browse the repository at this point in the history
Sutro adequations
  • Loading branch information
hgarcia-grio authored Sep 6, 2019
2 parents 390ec7d + 41eb001 commit e158112
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 57 deletions.
2 changes: 1 addition & 1 deletion BugSnap Demo App/BugSnap Demo App/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>1.0.4</string>
<key>CFBundleVersion</key>
<string>9</string>
<string>10</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
Expand Down
4 changes: 2 additions & 2 deletions BugSnap.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pod::Spec.new do |spec|
#

spec.name = "BugSnap"
spec.version = "1.0.5"
spec.version = "1.0.6"
spec.summary = "POD that allows to snapshot or record the screen after a shake gesture, annotate the image and upload it to JIRA"

# This description is used to generate tags and improve search results.
Expand Down Expand Up @@ -72,7 +72,7 @@ Pod::Spec.new do |spec|
# Supports git, hg, bzr, svn and HTTP.
#

spec.source = { :git => "https://github.com/GrioSF/bugsnap-iOS.git", :tag => "v1.0.5" }
spec.source = { :git => "https://github.com/GrioSF/bugsnap-iOS.git", :tag => "v1.0.6" }


# ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
Expand Down
14 changes: 11 additions & 3 deletions BugSnap/BugSnap.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
7FC50C68230F080C0066A8C3 /* JIRAIssueCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JIRAIssueCreator.swift; sourceTree = "<group>"; };
7FD2312722E8B0F100292997 /* UIApplication+Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Logging.swift"; sourceTree = "<group>"; };
7FD2312922E8FA8D00292997 /* FileManager+Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Util.swift"; sourceTree = "<group>"; };
7FE43E1622F1050D000F8ABC /* BugSnap.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = BugSnap.podspec; path = ../../BugSnap.podspec; sourceTree = "<group>"; };
7FE43E1622F1050D000F8ABC /* BugSnap.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = BugSnap.podspec; path = ../../BugSnap.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
7FEB197A22BC050300E9C8B5 /* UIColor+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helpers.swift"; sourceTree = "<group>"; };
7FEB197C22BC264800E9C8B5 /* PaddedTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddedTextField.swift; sourceTree = "<group>"; };
7FEB197E22BD91A400E9C8B5 /* AutocompleteTextFieldViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteTextFieldViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -280,6 +280,7 @@
7F0BCF9F22B04F9B006B4F26 /* ViewControllers */ = {
isa = PBXGroup;
children = (
7FBE149A2322C4E4006661BB /* UserFeedbackFlow */,
7F66ABEF22C5183400806661 /* JIRA Forms */,
7FB62E2E22B980360007A02E /* JIRA Data Selectors */,
7FB62E1B22B7D4500007A02E /* Annotation Editor Menues */,
Expand All @@ -288,8 +289,6 @@
7F66ABF722C5336D00806661 /* ScrolledViewController.swift */,
7F4C8E0122F89445001EAD96 /* CaptureOptionSheetViewController.swift */,
7FEB52FC22FC5ECC000498BC /* OptionSelectorPopupViewController.swift */,
7F2DBE47230C9852002E5011 /* FeedbackCardViewController.swift */,
7FC50C66230EED190066A8C3 /* FeedbackCaptureViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
Expand Down Expand Up @@ -426,6 +425,15 @@
path = JIRA;
sourceTree = "<group>";
};
7FBE149A2322C4E4006661BB /* UserFeedbackFlow */ = {
isa = PBXGroup;
children = (
7F2DBE47230C9852002E5011 /* FeedbackCardViewController.swift */,
7FC50C66230EED190066A8C3 /* FeedbackCaptureViewController.swift */,
);
path = UserFeedbackFlow;
sourceTree = "<group>";
};
7FEBAC4D22B17FF70022158D /* CustomViews */ = {
isa = PBXGroup;
children = (
Expand Down
2 changes: 1 addition & 1 deletion BugSnap/BugSnap/Networking/JIRA-API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public class JIRARestAPI : NSObject {
*/
func allProjects( completion : @escaping ([JIRA.Project]?)->Void) {

var request = URLRequest(url: URL(string: "rest/api/3/project/search?startAt=0&maxResults=10&orderBy=name", relativeTo: serverURL)!)
var request = URLRequest(url: URL(string: "rest/api/3/project/search?startAt=0&maxResults=50&orderBy=name", relativeTo: serverURL)!)
request.httpMethod = "GET"

let urlSession = URLSession(configuration: sessionConfiguration)
Expand Down
34 changes: 30 additions & 4 deletions BugSnap/BugSnap/Utilities/JIRAIssueCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,23 @@ public class JIRAIssueCreator: NSObject {
/// The text to set in the issue
private var issueText : String!

/// The description to set in the issue
private var descriptionText : String!

/// The snapshot to set in the issue
private var snapshot : UIImage!
private var snapshot : UIImage? = nil

/// The video url to set as an attachment
private var videoURL : URL? = nil

/// MARK: - Facing API


@objc public func createIssue( text : String, snapshot : UIImage ) {
@objc public func createIssue( text : String, description : String, snapshot : UIImage? = nil , videoURL : URL? = nil ) {
issueText = text
descriptionText = description
self.snapshot = snapshot
self.videoURL = videoURL
selectProject()
}

Expand All @@ -58,6 +66,19 @@ public class JIRAIssueCreator: NSObject {
}
}

/**
Uploads the video result of the annotated screen recording.
- Parameter issue: The issue created previously with the summary and description provided by the UI.
*/
private func uploadScreenRecording( issue : JIRA.Object ) {
loadingViewController?.message = "Uploading video..."
JIRARestAPI.sharedInstance.attach(fileURL: videoURL!, mimeType: .mp4Video, issue: issue) { [weak self] (_, errors) in

guard let strongSelf = self else { return }
strongSelf.handleAttachmentResponse(issue: issue, errors: errors, caller: strongSelf.uploadScreenRecording(issue:))
}
}

private func uploadLogs( issue : JIRA.Object ) {
guard let data = UIApplication.lastLogs() else {

Expand Down Expand Up @@ -123,7 +144,7 @@ public class JIRAIssueCreator: NSObject {
$0.value = value
} else if ($0.key ?? "") == "description" {
let value = JIRA.IssueField.Value()
value.stringValue = issueText
value.stringValue = descriptionText
$0.value = value
} else if ($0.key ?? "") == "environment" {
let value = JIRA.IssueField.Value()
Expand Down Expand Up @@ -159,7 +180,12 @@ public class JIRAIssueCreator: NSObject {
return
}

uploadImage(issue: issueObject)
if snapshot != nil {
uploadImage(issue: issueObject)
} else if videoURL != nil {
uploadScreenRecording(issue: issueObject)
}

}

private func handleAttachmentResponse( issue : JIRA.Object , errors : [String]? , caller : @escaping (JIRA.Object)->Void ) {
Expand Down
33 changes: 28 additions & 5 deletions BugSnap/BugSnap/Utilities/UIApplication+ScreenRecording.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ fileprivate var _recordingIndicatorWindow = "_recordingIndicatorWindow"
/// A key for storing the file for saving the video
fileprivate var _avassetwriterfile = "_avassetwriterfile"

/// A key for storing whether the screen recording has been stopped
fileprivate var _recordingstopped = "_recordingStoppedKey"

/// Extension to support screen recording
extension UIApplication {

Expand All @@ -37,6 +40,20 @@ extension UIApplication {
}
}

/// Flag to know whether screen recording has been stopped. This should be only modified from the main thread
var wasScreenRecordingStopped: Bool {
get {
guard let number = objc_getAssociatedObject(self, &_recordingstopped) as? NSNumber else { return false }
return number.boolValue
}
set(newVal) {
let number = NSNumber(booleanLiteral: newVal)
willChangeValue(forKey: "wasScreenRecordingStopped")
objc_setAssociatedObject(self, &_recordingstopped, number, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
didChangeValue(forKey: "wasScreenRecordingStopped")
}
}

/// The video writer
var videoWriter : VideoFileWriter? {
get {
Expand Down Expand Up @@ -83,17 +100,22 @@ extension UIApplication {

let videoWriter = VideoFileWriter()
self.videoWriter = videoWriter
wasScreenRecordingStopped = false

RPScreenRecorder.shared().delegate = self
RPScreenRecorder.shared().isMicrophoneEnabled = false
RPScreenRecorder.shared().startCapture(handler: { [weak self] (buffer, bufferType, error) in
guard error == nil else { return }

self?.isScreenRecording = true
if self?.screenRecordingIndicator == nil {
DispatchQueue.main.async {
let window = UIView.addRecordingIndicator()
self?.screenRecordingIndicator = window
DispatchQueue.main.async {

if !(self?.wasScreenRecordingStopped ?? true) {
self?.isScreenRecording = true
if self?.screenRecordingIndicator == nil {

let window = UIView.addRecordingIndicator()
self?.screenRecordingIndicator = window
}
}
}

Expand All @@ -114,6 +136,7 @@ extension UIApplication {
let loading = UIViewController.topMostViewController?.presentLoading(message: "Stopping capture...")
RPScreenRecorder.shared().stopCapture { [weak self] (error) in
DispatchQueue.main.async {
self?.wasScreenRecordingStopped = true
self?.showEndCapture(loading: loading!, error: error)
RPScreenRecorder.shared().delegate = nil
}
Expand Down
20 changes: 14 additions & 6 deletions BugSnap/BugSnap/Utilities/UIApplication+Snap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ public extension UIApplication {

guard Thread.current.isMainThread,
applicationState != .background else {
NSLog("Tried to enable shake gesture on either a secondary thread or in a non active state")
NSLog("Tried to call shake gesture on either a secondary thread or in a non active state")
return
}

// check whether we're already recording
if isScreenRecording {
doEndCapture()
// Check whether the screen recorder is available
} else if RPScreenRecorder.shared().isAvailable && !userFeedbackFlow {
} else if RPScreenRecorder.shared().isAvailable {
askSnapAction()
// Otherwise defaults to the snapshot action
} else {
Expand All @@ -118,12 +118,18 @@ public extension UIApplication {

// MARK: - Support

private func askSnapAction() {
@objc func askSnapAction() {
guard let topMost = UIViewController.topMostViewController else {
NSLog("Couldn't find either the key window or the top most view controller")
return
}

// Stop screen recording if it's recording already
guard !isScreenRecording else {
doEndCapture()
return
}

let optionSheetController = CaptureOptionSheetViewController()
optionSheetController.optionSelectionHandler = {
[weak self] (option) in
Expand Down Expand Up @@ -158,7 +164,7 @@ public extension UIApplication {
[weak self] (image) in

if self?.userFeedbackFlow ?? false {
self?.startUserFeedbackFlow(image: image!)
UIViewController.topMostViewController?.startJIRACapture(snapshot: image)
} else {
self?.startQAFlow(image: image)
}
Expand All @@ -168,15 +174,17 @@ public extension UIApplication {
/**
Starts the flow with the capture card in order to have some sort of automated user feedback
- Parameter image: The image captured
- Parameter url: The URL for the video recording.
*/
private func startUserFeedbackFlow( image : UIImage? ) {
private func startUserFeedbackFlow( image : UIImage?, url : URL? = nil ) {
guard let topMost = UIViewController.topMostViewController else {
return
}
let loading = topMost.presentLoading(message: "Loading image...")

let controller = FeedbackCardViewController()
let controller = FeedbackCaptureViewController()
controller.snapshot = image
controller.videoURL = url
controller.modalPresentationStyle = .overCurrentContext

loading.dismiss(animated: true, completion: {
Expand Down
39 changes: 38 additions & 1 deletion BugSnap/BugSnap/Utilities/UIViewController+JIRAIssueFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,48 @@ public extension UIViewController {

/**
Starts the flow for capturing the issue with a snapshot or a video URL (that can be captured with the framework too).
This method presents the JIRALoginViewController to validate the credentials and then JIRAIssueFormViewController to finalize the capture of the information related to the issue. After the information is validated it can be uploaded to JIRA.

- Parameter snapshot: The snapshot to be uploaded to JIRA as attachement
- Parameter videoURL: The videoURL (in the local file system, ideally in the caches directory) to be uploaded in JIRA as an attachement.
*/
@objc func startJIRACapture( snapshot : UIImage? = nil, videoURL : URL? = nil) {
if UIApplication.shared.userFeedbackFlow {
startUserFeedbackFlow(snapshot: snapshot, videoURL: videoURL)
} else {
startJIRAUserFeedbackFlow(snapshot: snapshot, videoURL: videoURL)
}
}

/**
Starts the flow for user feedback with a card being presented.
- Parameter snapshot: The snapshot to be uploaded to JIRA as attachement
- Parameter videoURL: The videoURL (in the local file system, ideally in the caches directory) to be uploaded in JIRA as an attachement.
*/
private func startUserFeedbackFlow( snapshot : UIImage? = nil, videoURL : URL? = nil ) {

guard let topMost = UIViewController.topMostViewController else {
return
}
let loading = topMost.presentLoading(message: "Loading image...")

let controller = FeedbackCaptureViewController()
controller.snapshot = snapshot
controller.videoURL = videoURL
controller.modalPresentationStyle = .overCurrentContext

loading.dismiss(animated: true, completion: {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
topMost.present(controller, animated: true, completion: nil)
})
})
}

/**
This method presents the JIRALoginViewController to validate the credentials and then JIRAIssueFormViewController to finalize the capture of the information related to the issue. After the information is validated it can be uploaded to JIRA.
- Parameter snapshot: The image captured from the screen
- Parameter videoURL: The video URL for the video screen recording.
*/
private func startJIRAUserFeedbackFlow( snapshot : UIImage? = nil, videoURL : URL? = nil) {
let jiraLoginViewController = JIRALoginViewController()
jiraLoginViewController.modalPresentationStyle = .overCurrentContext
jiraLoginViewController.modalTransitionStyle = .coverVertical
Expand Down
Loading

0 comments on commit e158112

Please sign in to comment.