Skip to content

Commit

Permalink
Support bug reporting via email (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
levinli303 authored Jul 25, 2023
1 parent 22a3a66 commit c7aba96
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 10 deletions.
8 changes: 8 additions & 0 deletions CelestiaFoundation/Bundle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,12 @@ public extension Bundle {
}
return current
}()

var shortVersion: String {
return infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
}

var build: String {
return infoDictionary?["CFBundleVersion"] as? String ?? ""
}
}
4 changes: 2 additions & 2 deletions CelestiaUI/Addon/Models/ResourceItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
import Foundation

public struct ResourceItem: Codable {
let name: String
public let name: String
let description: String
let type: String?
let id: String
public let id: String
let image: URL?
let item: URL
let authors: [String]?
Expand Down
2 changes: 1 addition & 1 deletion CelestiaUI/Addon/Models/ResourceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public final class ResourceManager: @unchecked Sendable {
return addonDirectory.appendingPathComponent(identifier)
}

func installedResources() -> [ResourceItem] {
public func installedResources() -> [ResourceItem] {
guard let addonDirectory = extraAddonDirectory else { return [] }
var items = [ResourceItem]()
let fm = FileManager.default
Expand Down
5 changes: 2 additions & 3 deletions CelestiaUI/Settings/AboutViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// of the License, or (at your option) any later version.
//

import CelestiaFoundation
import UIKit

public final class AboutViewController: BaseTableViewController {
Expand Down Expand Up @@ -41,9 +42,7 @@ public final class AboutViewController: BaseTableViewController {
private func loadContents() {
var totalItems = [[TextItem]]()

let shortVersion = bundle.infoDictionary!["CFBundleShortVersionString"] as! String
let buildNumber = bundle.infoDictionary!["CFBundleVersion"] as! String
let versionItem = TextItem.short(title: CelestiaString("Version", comment: ""), detail: "\(shortVersion)(\(buildNumber))")
let versionItem = TextItem.short(title: CelestiaString("Version", comment: ""), detail: "\(bundle.shortVersion)(\(bundle.build))")

totalItems.append([versionItem])

Expand Down
4 changes: 2 additions & 2 deletions MobileCelestia.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1545,7 +1545,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.15;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SHARED_BUILD_NUMBER = 332;
SHARED_BUILD_NUMBER = 333;
SHARED_BUILD_VERSION = 1.5.24;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand Down Expand Up @@ -1602,7 +1602,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
MACOSX_DEPLOYMENT_TARGET = 10.15;
SDKROOT = iphoneos;
SHARED_BUILD_NUMBER = 332;
SHARED_BUILD_NUMBER = 333;
SHARED_BUILD_VERSION = 1.5.24;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
Expand Down
19 changes: 18 additions & 1 deletion MobileCelestia/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ enum MenuBarAction: Hashable, Equatable {
case showInstalledAddons
case addBookmark
case organizeBookmarks
case reportBug
case suggestFeature
}

let newURLOpenedNotificationName = Notification.Name("NewURLOpenedNotificationName")
Expand Down Expand Up @@ -112,6 +114,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
.selector(#selector(showInstalledAddons)),
.selector(#selector(addBookmark)),
.selector(#selector(organizeBookmarks)),
.selector(#selector(reportBug)),
.selector(#selector(suggestFeature)),
]

var window: UIWindow?
Expand Down Expand Up @@ -430,11 +434,16 @@ extension AppDelegate {
MenuActionContext(title: CelestiaString("Delete Other Views", comment: ""), action: #selector(deleteOtherViews), input: "d", modifierFlags: .control),
]), atStartOfMenu: .view)


let runDemoMenu = createMenuItem(identifierSuffix: "help.demo", action: MenuActionContext(title: CelestiaString("Run Demo", comment: ""), action: #selector(runDemo), input: "d"))
builder.insertChild(runDemoMenu, atEndOfMenu: .help)
let openGLMenu = createMenuItem(identifierSuffix: "help.opengl", action: MenuActionContext(title: CelestiaString("OpenGL Info", comment: ""), action: #selector(showOpenGLInfo)))
builder.insertSibling(openGLMenu, afterMenu: runDemoMenu.identifier)

let feedbackActionMenu = createMenuItemGroup(identifierSuffix: "feedback", actions: [
MenuActionContext(title: CelestiaString("Report a Bug", comment: ""), action: #selector(reportBug)),
MenuActionContext(title: CelestiaString("Suggest a Feature", comment: ""), action: #selector(suggestFeature)),
])
builder.insertSibling(feedbackActionMenu, afterMenu: openGLMenu.identifier)
}

private struct MenuActionContext {
Expand Down Expand Up @@ -614,6 +623,14 @@ extension AppDelegate {
@objc private func organizeBookmarks() {
NotificationCenter.default.post(name: menuBarActionNotificationName, object: nil, userInfo: [menuBarActionNotificationKey: MenuBarAction.organizeBookmarks])
}

@objc private func reportBug() {
NotificationCenter.default.post(name: menuBarActionNotificationName, object: nil, userInfo: [menuBarActionNotificationKey: MenuBarAction.reportBug])
}

@objc private func suggestFeature() {
NotificationCenter.default.post(name: menuBarActionNotificationName, object: nil, userInfo: [menuBarActionNotificationKey: MenuBarAction.suggestFeature])
}
}

#if targetEnvironment(macCatalyst)
Expand Down
134 changes: 134 additions & 0 deletions MobileCelestia/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import CelestiaCore
import CelestiaFoundation
import CelestiaUI
import LinkPresentation
import MessageUI
import UniformTypeIdentifiers
import UIKit

Expand All @@ -24,6 +25,11 @@ class MainViewController: UIViewController {
case loaded
}

private enum Constants {
static let feedbackEmailAddress = "[email protected]"
static let feedbackGitHubLink = URL(string: "https://celestia.mobi/feedback")!
}

private(set) var celestiaController: CelestiaViewController!
private lazy var loadingController = LoadingViewController()
private lazy var actionViewController: ToolbarViewController = {
Expand Down Expand Up @@ -417,6 +423,10 @@ extension MainViewController {
}
case .organizeBookmarks:
presentFavorite(.bookmarks)
case .reportBug:
reportBug()
case .suggestFeature:
suggestFeature()
}
}

Expand Down Expand Up @@ -620,7 +630,125 @@ extension MainViewController: CelestiaControllerDelegate {
]
let url = components.url!
UIApplication.shared.open(url, options: [:], completionHandler: nil)
case .feedback:
sendFeedback()
}
}

private func sendFeedback() {
showSelection(nil, options: [
CelestiaString("Report a Bug", comment: ""),
CelestiaString("Suggest a Feature", comment: ""),
], source: nil) { [weak self] index in
guard let self, let index else { return }
if index == 1 {
self.suggestFeature()
} else {
self.reportBug()
}
}
}

private func reportBug() {
guard MFMailComposeViewController.canSendMail() else {
reportBugSuggestFeatureFallback()
return
}
Task {
do {
try await reportBugAsync()
} catch {
reportBugSuggestFeatureFallback()
}
}
}

private func reportBugAsync() async throws {
let parentPath = (NSTemporaryDirectory() as NSString).appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(atPath: parentPath, withIntermediateDirectories: true)

let screenshotPath = (parentPath as NSString).appendingPathComponent("screenshot.png")
let executor = self.executor
let (renderInfo, url, screenshotSuccess) = await executor.get { core in
executor.makeRenderContextCurrent()
core.draw()
return (core.renderInfo, core.currentURL, core.screenshot(to: screenshotPath, type: .PNG))
}
let imageData = screenshotSuccess ? try? Data(contentsOf: URL(fileURLWithPath: screenshotPath)) : nil
let addonInfo = resourceManager.installedResources().map { "\($0.name)/\($0.id)" }.joined(separator: "\n")

let vc = MFMailComposeViewController()
vc.mailComposeDelegate = self
vc.setToRecipients([Constants.feedbackEmailAddress])
vc.setSubject(CelestiaString("Bug report for Celestia", comment: ""))
vc.setMessageBody(CelestiaString("Please describe the issue and repro steps, if known.", comment: ""), isHTML: false)
if let imageData {
vc.addAttachmentData(imageData, mimeType: "image/png", fileName: "screenshot.png")
}
if let urlInfoData = url.data(using: .utf8) {
vc.addAttachmentData(urlInfoData, mimeType: "text/plain", fileName: "urlinfo.txt")
}
if let renderInfoData = renderInfo.data(using: .utf8) {
vc.addAttachmentData(renderInfoData, mimeType: "text/plain", fileName: "renderinfo.txt")
}
if let addonInfoData = addonInfo.data(using: .utf8) {
vc.addAttachmentData(addonInfoData, mimeType: "text/plain", fileName: "addoninfo.txt")
}
let bundle = Bundle.app
let device = UIDevice.current

var sysName = utsname()
uname(&sysName)
let machineMirror = Mirror(reflecting: sysName.machine)
let model = machineMirror.children.reduce(into: String()) { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return }
identifier += String(UnicodeScalar(UInt8(value)))
}

#if targetEnvironment(macCatalyst)
let os = "Mac"
#else
let os: String
if #available(iOS 14, *), ProcessInfo.processInfo.isiOSAppOnMac {
os = "Mac (iOS)"
} else {
os = "iOS"
}
#endif
#if arch(x86_64)
let arch = "x86_64"
#elseif arch(arm64)
let arch = "arm64"
#endif
let systemInfo =
"""
Application Version: \(bundle.shortVersion)(\(bundle.build))
Operating System: \(os)
Operating System Version: \(device.systemVersion)
Operating System Architecture \(arch)
Device Model: \(model)
"""
if let systemInfoData = systemInfo.data(using: .utf8) {
vc.addAttachmentData(systemInfoData, mimeType: "text/plain", fileName: "systeminfo.txt")
}
presentAfterDismissCurrent(vc, animated: true)
}

private func suggestFeature() {
guard MFMailComposeViewController.canSendMail() else {
reportBugSuggestFeatureFallback()
return
}
let vc = MFMailComposeViewController()
vc.mailComposeDelegate = self
vc.setToRecipients([Constants.feedbackEmailAddress])
vc.setSubject(CelestiaString("Feature suggestion for Celestia", comment: ""))
vc.setMessageBody(CelestiaString("Please describe the feature you want to see in Celestia.", comment: ""), isHTML: false)
presentAfterDismissCurrent(vc, animated: true)
}

private func reportBugSuggestFeatureFallback() {
UIApplication.shared.open(Constants.feedbackGitHubLink, options: [:], completionHandler: nil)
}

private func presentShare() {
Expand Down Expand Up @@ -1306,3 +1434,9 @@ extension MarkerRepresentation {
}
}
}

extension MainViewController: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true)
}
}
7 changes: 6 additions & 1 deletion MobileCelestia/Toolbar/ToolbarViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ enum AppToolbarAction: String {
case paperplane
case speedometer
case newsarchive
case feedback

static var persistentAction: [[AppToolbarAction]] {
return [[.setting], [.share, .search, .home, .paperplane], [.camera, .time, .script, .speedometer], [.browse, .favorite, .event], [.addons, .download, .newsarchive], [.help]]
return [[.setting], [.share, .search, .home, .paperplane], [.camera, .time, .script, .speedometer], [.browse, .favorite, .event], [.addons, .download, .newsarchive], [.feedback, .help]]
}
}

Expand Down Expand Up @@ -253,6 +254,8 @@ extension AppToolbarAction: ToolbarAction {
return UIImage(systemName: "speedometer")
case .newsarchive:
return UIImage(systemName: "newspaper") ?? UIImage(named: "toolbar_newsarchive")
case .feedback:
return UIImage(systemName: "exclamationmark.bubble")
}
}
}
Expand Down Expand Up @@ -292,6 +295,8 @@ extension AppToolbarAction {
return CelestiaString("Speed Control", comment: "")
case .newsarchive:
return CelestiaString("News Archive", comment: "")
case .feedback:
return CelestiaString("Send Feedback", comment: "")
}
}
}

0 comments on commit c7aba96

Please sign in to comment.