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 all 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 public dynamic 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) }
}

}
20 changes: 17 additions & 3 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 @@ -108,7 +109,18 @@ enum SubMenuItems {
accessibilityDescription: "Keyboard"
)
}


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 Down Expand Up @@ -138,7 +150,8 @@ extension SubMenuItems {
CopyID(),

Separator(),


TogglePinToTop(),
ColdBoot(),
NoAudio(),
ToggleA11y(),
Expand All @@ -151,7 +164,8 @@ extension SubMenuItems {
CopyUDID(),

Separator(),


TogglePinToTop(),
Delete()
]
}
16 changes: 10 additions & 6 deletions MiniSim/Model/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ struct Device: Hashable, Codable {
var identifier: String?
var booted: Bool
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, identifier, booted, platform, displayName
case name, version, identifier, booted, platform, displayName, pinned
}

init(name: String, version: String? = nil, identifier: String?, booted: Bool = false, platform: Platform) {
Expand All @@ -35,6 +37,7 @@ struct Device: Hashable, Codable {
self.identifier = identifier
self.booted = booted
self.platform = platform
self.pinned = pinned
}

init(from decoder: Decoder) throws {
Expand All @@ -44,6 +47,7 @@ struct Device: Hashable, Codable {
identifier = try values.decode(String.self, forKey: .identifier)
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 @@ -219,18 +243,21 @@ extension DeviceService {
let identifierIdx = 4
let deviceStateIdx = 5
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.isEmpty {
osVersion = currentOs[currentOSIdx]
}
if let device = line.match("(.*?) (\\(([0-9.]+)\\) )?\\(([0-9A-F-]+)\\) (\\(.*?)\\)").first {
let deviceName = device[1].trimmingCharacters(in: .whitespacesAndNewlines)
devices.append(
Device(
name: device[deviceNameIdx].trimmingCharacters(in: .whitespacesAndNewlines),
version: osVersion,
identifier: device[identifierIdx],
booted: device[deviceStateIdx].contains("Booted"),
platform: .ios
platform: .ios,
pinned: pinnediOSSimulators.contains(deviceName)
)
)
}
Expand Down Expand Up @@ -312,6 +339,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.identifier else { return }
let result = !NSAlert.showQuestionDialog(
Expand Down Expand Up @@ -381,12 +411,13 @@ 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 { deviceName in
let adbId = try? ADB.getAdbId(for: deviceName, adbPath: adbPath)
return Device(name: deviceName, identifier: adbId, booted: adbId != nil, platform: .android)
return Device(name: deviceName, identifier: adbId, booted: adbId != nil, platform: .android, pinned: pinnedAndroidEmulators.contains(deviceName))
}
}

Expand Down Expand Up @@ -446,7 +477,10 @@ extension DeviceService {
case .copyName:
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,
let text = clipboard.string(forType: .string) else {
Expand Down