Skip to content

Commit

Permalink
feat: add in-app feedback form (closes #145)
Browse files Browse the repository at this point in the history
  • Loading branch information
louis.pontoise committed Feb 5, 2020
1 parent 8a9264e commit a5b6db9
Show file tree
Hide file tree
Showing 17 changed files with 215 additions and 88 deletions.
84 changes: 52 additions & 32 deletions alt-tab-macos.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

20 changes: 15 additions & 5 deletions alt-tab-macos/ui/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ class App: NSApplication, NSApplicationDelegate, NSWindowDelegate {
static let version = "#VERSION#"
var statusItem: NSStatusItem?
var thumbnailsPanel: ThumbnailsPanel?
var preferencesPanel: PreferencesWindow?
var preferencesWindow: PreferencesWindow?
var feedbackWindow: FeedbackWindow?
var uiWorkShouldBeDone = true
var isFirstSummon = true
var appIsBeingUsed = false
Expand Down Expand Up @@ -56,11 +57,20 @@ class App: NSApplication, NSApplicationDelegate, NSWindowDelegate {

@objc
func showPreferencesPanel() {
if preferencesPanel == nil {
preferencesPanel = PreferencesWindow()
if preferencesWindow == nil {
preferencesWindow = PreferencesWindow()
}
Screen.repositionPanel(preferencesPanel!, Screen.preferred(), .appleCentered)
preferencesPanel?.show()
Screen.repositionPanel(preferencesWindow!, Screen.preferred(), .appleCentered)
preferencesWindow?.show()
}

@objc
func showFeedbackPanel() {
if feedbackWindow == nil {
feedbackWindow = FeedbackWindow()
}
Screen.repositionPanel(feedbackWindow!, Screen.preferred(), .appleCentered)
feedbackWindow?.show()
}

@objc
Expand Down
87 changes: 87 additions & 0 deletions alt-tab-macos/ui/FeedbackWindow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import Cocoa
import Foundation

class FeedbackWindow: NSWindow, NSTextViewDelegate {
var body: TextArea!
var email: TextArea!
var sendButton: NSButton!

override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) {
super.init(contentRect: .zero, styleMask: style, backing: backingStoreType, defer: flag)
setupWindow()
setupView()
}

func show() {
App.shared.activate(ignoringOtherApps: true)
makeKeyAndOrderFront(nil)
}

private func setupWindow() {
title = "Send feedback"
hidesOnDeactivate = false
isReleasedWhenClosed = false
styleMask.insert([.miniaturizable, .closable])
}

private func setupView() {
let intro = NSStackView(views: [
NSTextField(labelWithString: "Share improvement ideas, or report bugs."),
HyperlinkLabel("View existing tickets", "https://github.com/lwouis/alt-tab-macos/issues"),
])
intro.spacing = 4
sendButton = NSButton(title: "Send", target: nil, action: #selector(sendCallback))
sendButton.keyEquivalent = "\r"
sendButton.isEnabled = false
let buttons = NSStackView(views: [
NSButton(title: "Cancel", target: nil, action: #selector(cancelCallback)),
sendButton,
])
buttons.spacing = GridView.interPadding
body = TextArea(80, 20, "I think the app could be improved with…")
body.delegate = self
email = TextArea(80, 1.1, "Optional: email (if you want a reply)")
let view = GridView.make([
[intro],
[body],
[email],
[buttons],
])
view.cell(atColumnIndex: 0, rowIndex: 3).xPlacement = .trailing
view.fit()
setContentSize(view.fittingSize)
contentView = view
}

func textDidChange(_ notification: Notification) {
sendButton.isEnabled = !body.string.isEmpty
}

@objc
private func cancelCallback(senderControl: NSControl) {
close()
}

@objc
private func sendCallback(senderControl: NSControl) {
var request = URLRequest(url: URL(string: "https://api.github.com/repos/lwouis/alt-tab-macos/issues")!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
// access token of the alt-tab-macos-bot github account, with scope repo > public_repo
request.addValue("token 6ab65e11bb51e47835fe1f64970b1d7df0341653", forHTTPHeaderField: "Authorization")
let preamble = "_This issue was opened by a bot after a user submitted feedback through the in-app form. Here is what they wrote:_\n\n> "
let emailNote = !email.string.isEmpty ? "\n\nAuthor's email: " + email.string : ""
let parameters: [String: Any] = [
"title": "[In-app feedback]",
"body": preamble + body.string.replacingOccurrences(of: "\n", with: "\n> ") + emailNote
]
request.httpBody = try! JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
if error != nil || response == nil || (response as! HTTPURLResponse).statusCode != 201 {
debugPrint("HTTP call failed:", response, error)
}
}).resume()
close()
}
}
4 changes: 4 additions & 0 deletions alt-tab-macos/ui/Menubar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class Menubar {
withTitle: "Preferences…",
action: #selector(app.showPreferencesPanel),
keyEquivalent: ",")
item.menu!.addItem(
withTitle: "Send feedback…",
action: #selector(app.showFeedbackPanel),
keyEquivalent: ",")
item.menu!.addItem(
withTitle: "Quit",
action: #selector(NSApplication.terminate(_:)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class GridView {
gridView.column(at: gridView.numberOfColumns - 1).trailingPadding = padding
gridView.row(at: 0).topPadding = padding
gridView.row(at: gridView.numberOfRows - 1).bottomPadding = padding
gridView.fit()
return gridView
}
}
26 changes: 26 additions & 0 deletions alt-tab-macos/ui/generic-components/text/BaseLabel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Cocoa

class BaseLabel: NSTextView {
convenience init(_ text: String) {
self.init(frame: .zero)
textContainer!.size.width = 1000
string = text
setup()
}

convenience init(_ frame: NSRect, _ container: NSTextContainer?) {
self.init(frame: frame, textContainer: container)
setup()
}

private func setup() {
drawsBackground = true
backgroundColor = NSColor.blue
isSelectable = false
isEditable = false
enabledTextCheckingTypes = 0
layoutManager!.ensureLayout(for: textContainer!)
frame = layoutManager!.usedRect(for: textContainer!)
fit(frame.size.width, frame.size.height)
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Cocoa

class HyperlinkLabel: NSTextField {
convenience init(_ string: String, _ url: NSURL) {
convenience init(_ string: String, _ url: String) {
self.init(labelWithString: string)
isSelectable = true
allowsEditingTextAttributes = true
attributedStringValue = NSAttributedString(string: string, attributes: [
NSAttributedString.Key.link: url as Any,
NSAttributedString.Key.link: NSURL(string: url)!,
NSAttributedString.Key.font: NSFont.labelFont(ofSize: NSFont.systemFontSize),
])
}
Expand Down
16 changes: 16 additions & 0 deletions alt-tab-macos/ui/generic-components/text/TextArea.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Cocoa
import Foundation

class TextArea: NSTextView {
static let paddingX = CGFloat(5)
static let paddingY = CGFloat(10)
@objc var placeholderAttributedString: NSAttributedString?

convenience init(_ width: CGFloat, _ height: CGFloat, _ placeholder: String) {
self.init(frame: .zero)
font = NSFont.systemFont(ofSize: NSFont.systemFontSize)
textContainerInset = NSSize(width: TextArea.paddingX, height: TextArea.paddingY)
fit(font!.xHeight * width + TextArea.paddingX * 2, NSFont.systemFontSize * height + TextArea.paddingY * 2)
placeholderAttributedString = NSAttributedString(string: placeholder, attributes: [NSAttributedString.Key.foregroundColor: NSColor.gray])
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import Cocoa

class TextField: NSTextField, NSTextFieldDelegate {

var validationHandler: ((String)->Bool)?

public convenience init(_ value: String) {
convenience init(_ value: String) {
self.init(string: value)
usesSingleLineMode = true
font = .labelFont(ofSize: NSFont.systemFontSize)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@ class FontIcon: CellTitle {
static let sfSymbolCircledNumber10 = "􀓵"
static let sfSymbolCircledStart = "􀕬"

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

init(_ text: String, _ size: CGFloat, _ color: NSColor) {
convenience init(_ text: String, _ size: CGFloat, _ color: NSColor) {
// This helps SF symbols display vertically centered and not clipped at the bottom
super.init(size, 3)
self.init(size, 3)
string = text
font = NSFont(name: "SF Pro Text", size: size)
textColor = color
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,18 @@
import Cocoa

class BaseLabel: NSTextView {
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

init(_ text: String) {
super.init(frame: .zero)
_init()
string = text
}

override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) {
super.init(frame: frameRect, textContainer: container)
_init()
}

private func _init() {
drawsBackground = true
backgroundColor = .clear
isSelectable = false
isEditable = false
enabledTextCheckingTypes = 0
}
}

class CellTitle: BaseLabel {
let magicOffset: CGFloat

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var magicOffset = CGFloat(0)

init(_ size: CGFloat, _ magicOffset: CGFloat = 0) {
self.magicOffset = magicOffset
convenience init(_ size: CGFloat, _ magicOffset: CGFloat = 0) {
let textStorage = NSTextStorage()
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer()
textContainer.maximumNumberOfLines = 1
textContainer.lineFragmentPadding = 0
layoutManager.addTextContainer(textContainer)
super.init(frame: .zero, textContainer: textContainer)
self.init(NSRect.zero, textContainer)
self.magicOffset = magicOffset
textColor = Preferences.fontColor
shadow = CollectionViewItem.makeShadow(.darkGray)
defaultParagraphStyle = makeParagraphStyle(size)
Expand Down
4 changes: 0 additions & 4 deletions alt-tab-macos/ui/main-window/ThumbnailsPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ class ThumbnailsPanel: NSPanel, NSCollectionViewDataSource, NSCollectionViewDele
let cellId = NSUserInterfaceItemIdentifier("Cell")
var currentScreen: NSScreen?

override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) {
super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
}

convenience init(_ app: App) {
self.init()
self.app = app
Expand Down
2 changes: 1 addition & 1 deletion alt-tab-macos/ui/preferences-window/LabelAndControl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class LabelAndControl: NSObject {
if url == nil {
suffix = NSTextField(labelWithString: text)
} else {
suffix = HyperlinkLabel(text, NSURL(string: url!)!)
suffix = HyperlinkLabel(text, url!)
}
suffix.textColor = .gray
suffix.identifier = NSUserInterfaceItemIdentifier(controlName + ControlIdentifierDiscriminator.SUFFIX.rawValue)
Expand Down
5 changes: 3 additions & 2 deletions alt-tab-macos/ui/preferences-window/tabs/AboutTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ class AboutTab {
appInfo.spacing = GridView.interPadding
let view = GridView.make([
[appInfo],
[HyperlinkLabel("Source code repository", NSURL(string: "https://github.com/lwouis/alt-tab-macos")!)],
[HyperlinkLabel("Latest releases", NSURL(string: "https://github.com/lwouis/alt-tab-macos/releases")!)],
[HyperlinkLabel("Source code repository", "https://github.com/lwouis/alt-tab-macos")],
[HyperlinkLabel("Latest releases", "https://github.com/lwouis/alt-tab-macos/releases")],
])
view.fit()
return view
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class AppearanceTab {
view.column(at: 0).xPlacement = .trailing
view.rowAlignment = .lastBaseline
view.setRowsHeight(rowHeight)
view.fit()
return view
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ShortcutsTab {
view.column(at: 0).xPlacement = .trailing
view.rowAlignment = .lastBaseline
view.setRowsHeight(rowHeight)
view.fit()
return view
}
}

0 comments on commit a5b6db9

Please sign in to comment.