Skip to content

Commit

Permalink
Improve UI
Browse files Browse the repository at this point in the history
Notably, Radio won't enter an infinite loop if the user closes Discord. It'll still be running, but just sending useless data (since the socket is closed).
  • Loading branch information
kyleerhabor committed Jun 8, 2023
1 parent 10520e4 commit 1f609dd
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 49 deletions.
8 changes: 0 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ You can either download one of the [releases](https://github.com/KyleErhabor/Rad

## Limitations

### Discord Client

Currently, Radio expects that Discord is running throughout its entire execute. This has some unfortunate consequences:
- If you quit Discord while Radio is running, it'll begin using excess resources.
- If you start Radio before Discord has started or initialized, it won't be able to send rich presence.

In both these cases, you should quit Radio and start it after Discord.

### Artwork

Discord uses Activity Asset Images for images displayed in Rich Presence. These must either be URLs to images on the web or IDs for asset images uploaded to the Developer Portal. This is troublesome, as album artwork in Doppler is image data and does not correspond to any URL. To circumvent this limitation, Radio broadcasts an ID associated with the song album, where the album artist and album name are separated by a space and normalized. For example, "Rocket" from Susumu Hirasawa's error CD album is broadcasted as `susumu_hirasawa_error_cd`. With this composition, an image associated with an album can be uploaded to the Developer Portal.
Expand Down
22 changes: 11 additions & 11 deletions Radio.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
0012B46D2A0AA96200C5C0DE /* DiscordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = 0012B46C2A0AA96200C5C0DE /* DiscordRPC */; };
0012B46F2A0AE4E900C5C0DE /* DIscord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0012B46E2A0AE4E900C5C0DE /* DIscord.swift */; };
0012B46F2A0AE4E900C5C0DE /* Discord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0012B46E2A0AE4E900C5C0DE /* Discord.swift */; };
001C03752A321726005014B9 /* DiscordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = 001C03742A321726005014B9 /* DiscordRPC */; };
0060CFC72A0DC53400AFA58F /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 0060CFC62A0DC53400AFA58F /* LICENSE */; };
00E007B42A09CA49006FEE5C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E007B32A09CA49006FEE5C /* ContentView.swift */; };
00E007B62A09CA4D006FEE5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 00E007B52A09CA4D006FEE5C /* Assets.xcassets */; };
Expand All @@ -30,7 +30,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
0012B46E2A0AE4E900C5C0DE /* DIscord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DIscord.swift; sourceTree = "<group>"; };
0012B46E2A0AE4E900C5C0DE /* Discord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Discord.swift; sourceTree = "<group>"; };
005E5E092A102045007A809C /* example.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = example.png; sourceTree = "<group>"; };
0060CFC02A0DBFC700AFA58F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
0060CFC62A0DC53400AFA58F /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
Expand All @@ -48,7 +48,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0012B46D2A0AA96200C5C0DE /* DiscordRPC in Frameworks */,
001C03752A321726005014B9 /* DiscordRPC in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -87,7 +87,7 @@
isa = PBXGroup;
children = (
00E007B12A09CA49006FEE5C /* RadioApp.swift */,
0012B46E2A0AE4E900C5C0DE /* DIscord.swift */,
0012B46E2A0AE4E900C5C0DE /* Discord.swift */,
00E007B32A09CA49006FEE5C /* ContentView.swift */,
00E007B52A09CA4D006FEE5C /* Assets.xcassets */,
00E007BA2A09CA4D006FEE5C /* Radio.entitlements */,
Expand Down Expand Up @@ -133,7 +133,7 @@
);
name = Radio;
packageProductDependencies = (
0012B46C2A0AA96200C5C0DE /* DiscordRPC */,
001C03742A321726005014B9 /* DiscordRPC */,
);
productName = Radio;
productReference = 00E007AE2A09CA49006FEE5C /* Radio.app */;
Expand Down Expand Up @@ -164,7 +164,7 @@
);
mainGroup = 00E007A52A09CA49006FEE5C;
packageReferences = (
0012B46B2A0AA96200C5C0DE /* XCRemoteSwiftPackageReference "DiscordRPC" */,
001C03732A321726005014B9 /* XCRemoteSwiftPackageReference "DiscordRPC" */,
);
productRefGroup = 00E007AF2A09CA49006FEE5C /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -194,7 +194,7 @@
buildActionMask = 2147483647;
files = (
00E007B42A09CA49006FEE5C /* ContentView.swift in Sources */,
0012B46F2A0AE4E900C5C0DE /* DIscord.swift in Sources */,
0012B46F2A0AE4E900C5C0DE /* Discord.swift in Sources */,
00E0088B2A09E5F0006FEE5C /* RadioApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -399,7 +399,7 @@
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
0012B46B2A0AA96200C5C0DE /* XCRemoteSwiftPackageReference "DiscordRPC" */ = {
001C03732A321726005014B9 /* XCRemoteSwiftPackageReference "DiscordRPC" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/KyleErhabor/DiscordRPC";
requirement = {
Expand All @@ -410,9 +410,9 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
0012B46C2A0AA96200C5C0DE /* DiscordRPC */ = {
001C03742A321726005014B9 /* DiscordRPC */ = {
isa = XCSwiftPackageProductDependency;
package = 0012B46B2A0AA96200C5C0DE /* XCRemoteSwiftPackageReference "DiscordRPC" */;
package = 001C03732A321726005014B9 /* XCRemoteSwiftPackageReference "DiscordRPC" */;
productName = DiscordRPC;
};
/* End XCSwiftPackageProductDependency section */
Expand Down
63 changes: 34 additions & 29 deletions Radio/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct ContentView: View {
@AppStorage("displayArtwork") private var displayArtwork = false

@State private var clientId: String = ""
@State private var displayArtworkInfo = false

let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
Expand All @@ -26,45 +27,49 @@ struct ContentView: View {

var body: some View {
Form {
HStack {
Spacer()

Text("Radio")
.bold()

Text("")
.foregroundColor(.secondary)

Text("Broadcast your currently playing song in Doppler to Discord.")
.font(.title)
LabeledContent("Client ID") {
TextField("Client ID", text: $clientId, prompt: Text(defaultClientId))
.labelsHidden()
.fontDesign(.monospaced)
}.onChange(of: clientId) { id in
guard !id.isEmpty else {
appClientId = defaultClientId

return
}

Spacer()
}.font(.largeTitle)
appClientId = id
}

// TODO: Figure out how to monospace the value but not the label.
TextField("Client ID", text: $clientId, prompt: Text(defaultClientId))
.onChange(of: clientId) { id in
guard !id.isEmpty else {
appClientId = defaultClientId
LabeledContent("Refresh Rate") {
Slider(value: $refreshRate, in: 1...10, step: 1) {
Text(formatter.string(from: refreshRate) ?? "")
}
}

return
}
LabeledContent("Display Artwork") {
Toggle("Display Artwork", isOn: $displayArtwork)
.labelsHidden()
.toggleStyle(.switch)

appClientId = id
Button {
displayArtworkInfo = true
} label: {
Image(systemName: "questionmark")
}
.clipShape(Circle())
.popover(isPresented: $displayArtworkInfo, arrowEdge: .trailing) {
VStack {
Text("To use this feature, please read the guide here:")

HStack {
Slider(value: $refreshRate, in: 1...10, step: 1) {
Text("Refresh Rate")
}
let url = "https://github.com/KyleErhabor/Radio#artwork"

if let s = formatter.string(from: refreshRate) {
Text(s)
Link(url, destination: .init(string: url)!)
.focusable(false)
}.padding()
}
}

Toggle("Display Artwork", isOn: $displayArtwork)

Section {
// Empty
} footer: {
Expand Down
2 changes: 1 addition & 1 deletion Radio/DIscord.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// DIscord.swift
// Discord.swift
// Radio
//
// Created by Kyle Erhabor on 5/9/23.
Expand Down
40 changes: 40 additions & 0 deletions Radio/Discord.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Discord.swift
// Radio
//
// Created by Kyle Erhabor on 5/9/23.
//

import Darwin
import DiscordRPC

struct RequestSetActivity: Encodable {
let cmd: CommandType = .setActivity
let nonce: String
let args: RequestSetActivityArgs
}

struct RequestSetActivityArgs: Encodable {
let pid = getpid()
let activity: Activity
}

struct Activity: Encodable {
let details: String?
let state: String?
let assets: ActivityAssets?
}

struct ActivityAssets: Encodable {
let largeImage: String?
let largeText: String?
let smallImage: String?
let smallText: String?

enum CodingKeys: String, CodingKey {
case largeImage = "large_image"
case largeText = "large_text"
case smallImage = "small_image"
case smallText = "small_text"
}
}

0 comments on commit 1f609dd

Please sign in to comment.