Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: pin emulator to the top of the list #91

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions MiniSim/Extensions/UserDefaults+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ extension UserDefaults {
static let isOnboardingFinished = "isOnboardingFinished"
static let enableiOSSimulators = "enableiOSSimulators"
static let enableAndroidEmulators = "enableAndroidEmulators"
static let pinnediOSSimulators = "pinnediOSSimulators"
static let pinnedAndroidEmulators = "pinnedAndroidEmulators"
}

@objc dynamic public var androidHome: String? {
Expand Down Expand Up @@ -46,4 +48,15 @@ extension UserDefaults {
get { bool(forKey: Keys.enableAndroidEmulators) }
set { set(newValue, forKey: Keys.enableAndroidEmulators) }
}

public var pinnediOSSimulators: [String]? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can store everything under one key. This will simplify logic in DeviceService.

get { array(forKey: Keys.pinnediOSSimulators) as? [String] }
set { set(newValue, forKey: Keys.pinnediOSSimulators) }
}

public var pinnedAndroidEmulators: [String]? {
get { array(forKey: Keys.pinnedAndroidEmulators) as? [String] }
set { set(newValue, forKey: Keys.pinnedAndroidEmulators) }
}

}
14 changes: 14 additions & 0 deletions MiniSim/MenuItems/SubMenuItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ enum SubMenuItems {
case toggleA11y
case paste
case delete
case togglePinned
case customCommand = 200
}

Expand Down Expand Up @@ -109,6 +110,17 @@ enum SubMenuItems {
)
}

struct TogglePinToTop: SubMenuActionItem {
let title = NSLocalizedString("Pin/unpin to top", comment: "")
let tag = Tags.togglePinned.rawValue
let bootsDevice = false
let needBootedDevice = false
let image = NSImage(
systemSymbolName: "pin",
accessibilityDescription: "Pin or unpin to Top"
)
}

struct Delete: SubMenuActionItem {
let title = NSLocalizedString("Delete simulator", comment: "")
let tag = Tags.delete.rawValue
Expand All @@ -128,6 +140,7 @@ extension SubMenuItems {

Separator(),

TogglePinToTop(),
ColdBoot(),
NoAudio(),
ToggleA11y(),
Expand All @@ -140,6 +153,7 @@ extension SubMenuItems {

Separator(),

TogglePinToTop(),
Delete()
]
}
14 changes: 9 additions & 5 deletions MiniSim/Model/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,33 @@ struct Device: Hashable, Codable {
var ID: String?
var booted: Bool = false
var platform: Platform
var pinned: Bool

var displayName: String {
let pinIcon = pinned ? " 📌" : ""
switch platform {
case .ios:
if let version {
return "\(name) - (\(version))"
return "\(name) - (\(version))" + pinIcon
}
return name
return name + pinIcon

case .android:
return name
return name + pinIcon
}
}

enum CodingKeys: String, CodingKey {
case name, version, ID, booted, platform, displayName
case name, version, ID, booted, platform, displayName, pinned
}

init(name: String, version: String? = nil, ID: String?, booted: Bool = false, platform: Platform) {
init(name: String, version: String? = nil, ID: String?, booted: Bool = false, platform: Platform, pinned: Bool = false) {
self.name = name
self.version = version
self.ID = ID
self.booted = booted
self.platform = platform
self.pinned = pinned
}

init(from decoder: Decoder) throws {
Expand All @@ -44,6 +47,7 @@ struct Device: Hashable, Codable {
ID = try values.decode(String.self, forKey: .ID)
booted = try values.decode(Bool.self, forKey: .booted)
platform = try values.decode(Platform.self, forKey: .platform)
pinned = try values.decode(Bool.self, forKey: .pinned)
}

func encode(to encoder: Encoder) throws {
Expand Down
40 changes: 37 additions & 3 deletions MiniSim/Service/DeviceService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,30 @@ class DeviceService: DeviceServiceProtocol {
}
}
}

static func togglePinned(device: Device) {
var pinnedDevices: [String]?
switch device.platform {
case .android:
pinnedDevices = UserDefaults.standard.pinnedAndroidEmulators
var dedupedPinnedDevices = Set(pinnedDevices ?? [])
if dedupedPinnedDevices.contains(device.name) {
dedupedPinnedDevices.remove(device.name)
} else {
dedupedPinnedDevices.insert(device.name)
}
UserDefaults.standard.pinnedAndroidEmulators = Array(dedupedPinnedDevices)
case .ios:
pinnedDevices = UserDefaults.standard.pinnediOSSimulators
var dedupedPinnedDevices = Set(pinnedDevices ?? [])
if dedupedPinnedDevices.contains(device.name) {
dedupedPinnedDevices.remove(device.name)
} else {
dedupedPinnedDevices.insert(device.name)
}
UserDefaults.standard.pinnediOSSimulators = Array(dedupedPinnedDevices)
}
}
}

// MARK: iOS Methods
Expand All @@ -216,18 +240,21 @@ extension DeviceService {
static private func parseIOSDevices(result: [String]) -> [Device] {
var devices: [Device] = []
var osVersion = ""
let pinnediOSSimulators: [String] = UserDefaults.standard.pinnediOSSimulators ?? []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say it's quite invasive to the parsing function. Maybe you can move it to getIOSDevices?

result.forEach { line in
if let currentOs = line.match("-- (.*?) --").first, currentOs.count > 0 {
osVersion = currentOs[1]
}
if let device = line.match("(.*?) (\\(([0-9.]+)\\) )?\\(([0-9A-F-]+)\\) (\\(.*?)\\)").first {
let deviceName = device[1].trimmingCharacters(in: .whitespacesAndNewlines)
devices.append(
Device(
name: device[1].trimmingCharacters(in: .whitespacesAndNewlines),
name: deviceName,
version: osVersion,
ID: device[4],
booted: device[5].contains("Booted"),
platform: .ios
platform: .ios,
pinned: pinnediOSSimulators.contains(deviceName)
)
)
}
Expand Down Expand Up @@ -295,6 +322,9 @@ extension DeviceService {
NSPasteboard.general.copyToPasteboard(text: deviceID)
DeviceService.showSuccessMessage(title: "Device ID copied to clipboard!", message: deviceID)
}
case .togglePinned:
DeviceService.togglePinned(device: device)

case .delete:
guard let deviceID = device.ID else { return }
if !NSAlert.showQuestionDialog(title: "Are you sure?", message: "Are you sure you want to delete this Simulator?") {
Expand Down Expand Up @@ -366,10 +396,11 @@ extension DeviceService {
let adbPath = try ADB.getAdbPath()
let output = try shellOut(to: emulatorPath, arguments: ["-list-avds"])
let splitted = output.components(separatedBy: "\n")
let pinnedAndroidEmulators: [String] = UserDefaults.standard.pinnedAndroidEmulators ?? []

return splitted.filter({ !$0.isEmpty }).map {
let adbId = try? ADB.getAdbId(for: $0, adbPath: adbPath)
return Device(name: $0, ID: adbId, booted: adbId != nil, platform: .android)
return Device(name: $0, ID: adbId, booted: adbId != nil, platform: .android, pinned: pinnedAndroidEmulators.contains($0))
}
}

Expand Down Expand Up @@ -420,6 +451,9 @@ extension DeviceService {
NSPasteboard.general.copyToPasteboard(text: device.name)
DeviceService.showSuccessMessage(title: "Device name copied to clipboard!", message: device.name)

case .togglePinned:
DeviceService.togglePinned(device: device)

case .paste:
guard let clipboard = NSPasteboard.general.pasteboardItems?.first?.string(forType: .string) else { break }
try DeviceService.sendText(device: device, text: clipboard)
Expand Down