Initial commit: Spotify MC client-side Fabric mod for Minecraft 1.21.1

This commit is contained in:
jkortis committed 2026-09-30 23:12:47 -04:00
commit 32f8428ddb
32 files changed
+3643

No files matched your search

@@ -0,0 +1,254 @@
package com.spotifymc.config;
import com.spotifymc.auth.SpotifyAuthManager;
import com.spotifymc.playback.SpotifyPlaybackManager;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text;
/**
* Clean in-game configuration screen for managing Spotify authentication and HUD options.
*/
public class SpotifyConfigScreen extends Screen {
private final Screen parent;
private final SpotifyConfig config;
private final SpotifyAuthManager authManager;
private final SpotifyPlaybackManager playbackManager;
private TextFieldWidget clientIdField;
private ButtonWidget connectButton;
public SpotifyConfigScreen(Screen parent, SpotifyConfig config, SpotifyAuthManager authManager, SpotifyPlaybackManager playbackManager) {
super(Text.translatable("spotifymc.config.title"));
this.parent = parent;
this.config = config;
this.authManager = authManager;
this.playbackManager = playbackManager;
}
@Override
protected void init() {
int centerX = this.width / 2;
int startY = 36;
int colWidth = 150;
int rowHeight = 24;
// 1. Client ID input
this.clientIdField = new TextFieldWidget(this.textRenderer, centerX - 150, startY + 14, 300, 20, Text.translatable("spotifymc.config.client_id"));
this.clientIdField.setMaxLength(64);
this.clientIdField.setText(config.getClientId());
this.clientIdField.setChangedListener(text -> {
config.setClientId(text);
config.save();
});
this.addDrawableChild(this.clientIdField);
// 2. Auth Action Button (Connect / Disconnect)
int authBtnY = startY + 40;
this.connectButton = ButtonWidget.builder(
Text.literal(authManager.isAuthenticated() ? "Disconnect" : "Connect Spotify"),
btn -> {
if (authManager.isAuthenticated()) {
authManager.logout();
updateAuthButton();
} else {
config.setClientId(clientIdField.getText());
config.save();
btn.active = false;
btn.setMessage(Text.literal("Opening browser..."));
authManager.startPkceLoginFlow().whenComplete((res, err) -> {
if (this.client != null) {
this.client.execute(this::updateAuthButton);
}
});
}
}
).dimensions(centerX - 100, authBtnY, 200, 20).build();
this.addDrawableChild(this.connectButton);
// 3. Left Column: Toggles
int optionsY = authBtnY + 38;
// HUD Enabled toggle
this.addDrawableChild(ButtonWidget.builder(
getHudEnabledText(),
btn -> {
config.setHudEnabled(!config.isHudEnabled());
config.save();
btn.setMessage(getHudEnabledText());
}
).dimensions(centerX - colWidth - 5, optionsY, colWidth, 20).build());
// Show Album Art toggle
this.addDrawableChild(ButtonWidget.builder(
getAlbumArtText(),
btn -> {
config.setShowAlbumArt(!config.isShowAlbumArt());
config.save();
btn.setMessage(getAlbumArtText());
}
).dimensions(centerX + 5, optionsY, colWidth, 20).build());
// Auto Hide toggle
this.addDrawableChild(ButtonWidget.builder(
getAutoHideText(),
btn -> {
config.setAutoHideWhenInactive(!config.isAutoHideWhenInactive());
config.save();
btn.setMessage(getAutoHideText());
}
).dimensions(centerX - colWidth - 5, optionsY + rowHeight, colWidth, 20).build());
// Anchor Cycle button
this.addDrawableChild(ButtonWidget.builder(
getAnchorText(),
btn -> {
SpotifyConfig.Anchor[] anchors = SpotifyConfig.Anchor.values();
int nextIdx = (config.getAnchor().ordinal() + 1) % anchors.length;
config.setAnchor(anchors[nextIdx]);
config.save();
btn.setMessage(getAnchorText());
}
).dimensions(centerX + 5, optionsY + rowHeight, colWidth, 20).build());
// 4. Sliders (Scale, Opacity, Polling Rate)
int slidersY = optionsY + (rowHeight * 2);
// Scale Slider (0.5x to 2.0x)
double initialScaleProgress = (config.getHudScale() - 0.5f) / 1.5f;
this.addDrawableChild(new SliderWidget(centerX - colWidth - 5, slidersY, colWidth, 20, getScaleText(), initialScaleProgress) {
@Override
protected void updateMessage() {
this.setMessage(getScaleText());
}
@Override
protected void applyValue() {
float val = 0.5f + (float) this.value * 1.5f;
config.setHudScale(Math.round(val * 10.0f) / 10.0f);
config.save();
}
});
// Opacity Slider (20% to 100%)
double initialOpacityProgress = (config.getHudOpacity() - 0.2f) / 0.8f;
this.addDrawableChild(new SliderWidget(centerX + 5, slidersY, colWidth, 20, getOpacityText(), initialOpacityProgress) {
@Override
protected void updateMessage() {
this.setMessage(getOpacityText());
}
@Override
protected void applyValue() {
float val = 0.2f + (float) this.value * 0.8f;
config.setHudOpacity(Math.round(val * 100.0f) / 100.0f);
config.save();
}
});
// Polling Rate Slider (1s to 10s)
double initialPollProgress = (config.getPollingIntervalSec() - 1.0) / 9.0;
this.addDrawableChild(new SliderWidget(centerX - 100, slidersY + rowHeight, 200, 20, getPollRateText(), initialPollProgress) {
@Override
protected void updateMessage() {
this.setMessage(getPollRateText());
}
@Override
protected void applyValue() {
int sec = 1 + (int) Math.round(this.value * 9.0);
config.setPollingIntervalSec(sec);
config.save();
}
});
// Done button
this.addDrawableChild(ButtonWidget.builder(
Text.translatable("spotifymc.config.done"),
btn -> this.close()
).dimensions(centerX - 100, this.height - 28, 200, 20).build());
updateAuthButton();
}
private void updateAuthButton() {
if (connectButton == null) return;
connectButton.active = true;
if (authManager.isAuthenticated()) {
connectButton.setMessage(Text.translatable("spotifymc.config.btn_disconnect"));
} else if (authManager.isAuthenticating()) {
connectButton.setMessage(Text.translatable("spotifymc.config.auth_status.waiting"));
connectButton.active = false;
} else {
connectButton.setMessage(Text.translatable("spotifymc.config.btn_connect"));
}
}
private Text getHudEnabledText() {
return Text.literal("HUD Overlay: " + (config.isHudEnabled() ? "§aON" : "§cOFF"));
}
private Text getAlbumArtText() {
return Text.literal("Album Art: " + (config.isShowAlbumArt() ? "§aON" : "§cOFF"));
}
private Text getAutoHideText() {
return Text.literal("Auto-Hide Paused: " + (config.isAutoHideWhenInactive() ? "§aON" : "§cOFF"));
}
private Text getAnchorText() {
return Text.literal("Anchor: §e" + config.getAnchor().name());
}
private Text getScaleText() {
return Text.literal(String.format("HUD Scale: %.1fx", config.getHudScale()));
}
private Text getOpacityText() {
return Text.literal(String.format("Opacity: %d%%", Math.round(config.getHudOpacity() * 100.0f)));
}
private Text getPollRateText() {
return Text.literal("Update Interval: " + config.getPollingIntervalSec() + "s");
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
super.render(context, mouseX, mouseY, delta);
int centerX = this.width / 2;
// Title
context.drawCenteredTextWithShadow(this.textRenderer, this.title, centerX, 12, 0xFFFFFF);
// Client ID label & hint
context.drawTextWithShadow(this.textRenderer, "Spotify Developer Client ID:", centerX - 150, 36, 0xAAAAAA);
// Status badge
String statusText;
int statusColor;
if (authManager.isAuthenticated()) {
statusText = "● Connected";
statusColor = 0x1DB954;
} else if (authManager.isAuthenticating()) {
statusText = "○ Waiting for authorization...";
statusColor = 0xFFD700;
} else {
statusText = "○ Not Connected";
statusColor = 0xFF5555;
}
context.drawCenteredTextWithShadow(this.textRenderer, statusText, centerX, 66, statusColor);
}
@Override
public void close() {
config.save();
if (this.client != null) {
this.client.setScreen(this.parent);
}
}
}