Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
60631e2
feat: remove bg image, add force landscape toggle + custom bg prefs
MaxJubayerYT May 30, 2026
930aaa5
feat: add Palette API colour theming — presets + extract from bg image
MaxJubayerYT May 30, 2026
462dc9e
feat: working colour themes via setTheme() + ?attr/ — 6 presets + Pal…
MaxJubayerYT May 30, 2026
2204d3e
fix: LauncherActivity.java has a leftover call to ThemeManager.applyT…
MaxJubayerYT May 30, 2026
ce18b73
Fix: mine_button_background.xml not rendering
MaxJubayerYT May 30, 2026
2bd67da
fix: button colour, drawable theme attrs, full colour propagation
MaxJubayerYT May 30, 2026
fc54982
fix: theme all hardcoded R.color references in mcAccountSpinner + Tex…
MaxJubayerYT May 30, 2026
075c988
feat: background gradient toggle — per-theme .Gradient style variants
MaxJubayerYT May 31, 2026
6f695ea
fix: bgMainDrawable was in attrs.xml twice
MaxJubayerYT May 31, 2026
49494a1
fix: gradient everywhere, settings bg, right pane no-wallpaper, fulls…
MaxJubayerYT May 31, 2026
0819c2a
fix: gradient everywhere, settings bg, right pane no-wallpaper, fulls…
MaxJubayerYT May 31, 2026
529c494
fix: fullscreen landscape-only via theme, crash on copper+landscape, …
MaxJubayerYT May 31, 2026
d93e74f
fix: no commit message
MaxJubayerYT May 31, 2026
a7cf26f
fix: gradient toggle off = plain dark, on = themed colour
MaxJubayerYT May 31, 2026
69d0b03
fix: colour leaks, gradient toggle fully controls all backgrounds
MaxJubayerYT Jun 1, 2026
b397aea
fix: Wrong bg colors
MaxJubayerYT Jun 1, 2026
f717d45
fix: misc
MaxJubayerYT Jun 1, 2026
ffce950
fix: remove blank line between ImageView attrs causing XML parse error
MaxJubayerYT Jun 1, 2026
d50c38f
fix: notification bar showing orange tint
MaxJubayerYT Jun 1, 2026
5270fe1
fix: sidebar colour, divider, slider thumbs, scrollbar, rename colorA…
MaxJubayerYT Jun 1, 2026
4bd26f1
fix: control editor panel visibility, slider thumb colour
MaxJubayerYT Jun 1, 2026
ccba2eb
fix: all toggles/switches use light blue (#64B5F6)
MaxJubayerYT Jun 1, 2026
04015ba
remove: generate theme from background button
MaxJubayerYT Jun 1, 2026
fa4e528
fix: toggles sync with theme accent colour
MaxJubayerYT Jun 1, 2026
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
3 changes: 2 additions & 1 deletion app_pojavlauncher/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ dependencies {
implementation 'commons-codec:commons-codec:1.15'
// implementation 'com.wu-man:android-bsf-api:3.1.3'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.palette:palette:1.0.0'
//implementation 'androidx.core:core:1.7.0'
implementation 'androidx.drawerlayout:drawerlayout:1.2.0'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
Expand Down Expand Up @@ -237,4 +238,4 @@ dependencies {

implementation "net.java.dev.jna:jna:5.13.0@aar"

}
}
4 changes: 2 additions & 2 deletions app_pojavlauncher/src/main/java/com/kdt/mcgui/MineButton.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void init() {
getContext(), R.font.noto_sans_bold));
setBackground(ResourcesCompat.getDrawable(
getResources(),
R.drawable.mine_button_background, null));
R.drawable.mine_button_background, getContext().getTheme()));
setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(R.dimen._13ssp));
setTextColor(Color.WHITE);
Expand All @@ -41,4 +41,4 @@ public void init() {
return false;
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr, in
private String mText = "";

private void init(){
setProgressDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.view_text_progressbar, null));
setProgressDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.view_text_progressbar, getContext().getTheme()));
setProgress(35);
mTextPaint = new Paint();
mTextPaint.setColor(Color.WHITE);
Expand Down Expand Up @@ -65,4 +65,4 @@ public final void setText(String text){
public final void setTextPadding(int padding){
mTextPadding = padding;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
import java.util.List;
import java.util.Objects;

import android.util.TypedValue;

import fr.spse.extended_view.ExtendedTextView;

public class mcAccountSpinner extends AppCompatSpinner implements AdapterView.OnItemSelectedListener {
Expand Down Expand Up @@ -125,7 +127,7 @@ public mcAccountSpinner(@NonNull Context context, @Nullable AttributeSet attrs)

/* Triggered when we need to do microsoft login */
private final ExtraListener<Uri> mMicrosoftLoginListener = (key, value) -> {
mLoginBarPaint.setColor(getResources().getColor(R.color.minebutton_color));
mLoginBarPaint.setColor(getThemeColor(net.kdt.pojavlaunch.R.attr.copperAccent));
new MicrosoftBackgroundLogin(false, value.getQueryParameter("code")).performLogin(
mProgressListener, mDoneListener, mErrorListener);
return false;
Expand All @@ -151,8 +153,8 @@ public mcAccountSpinner(@NonNull Context context, @Nullable AttributeSet attrs)
@SuppressLint("ClickableViewAccessibility")
private void init(){
// Set visual properties
setBackgroundColor(getResources().getColor(R.color.background_status_bar));
mLoginBarPaint.setColor(getResources().getColor(R.color.minebutton_color));
setBackgroundColor(getThemeColor(net.kdt.pojavlaunch.R.attr.colorBgStatusBar));
mLoginBarPaint.setColor(getThemeColor(net.kdt.pojavlaunch.R.attr.copperAccent));
mLoginBarPaint.setStrokeWidth(getResources().getDimensionPixelOffset(R.dimen._2sdp));

// Set behavior
Expand Down Expand Up @@ -284,7 +286,7 @@ private void performLogin(MinecraftAccount minecraftAccount){
}
if(minecraftAccount.isLocal()) return;

mLoginBarPaint.setColor(getResources().getColor(R.color.minebutton_color));
mLoginBarPaint.setColor(getThemeColor(net.kdt.pojavlaunch.R.attr.copperAccent));
if(minecraftAccount.isMicrosoft){
if(System.currentTimeMillis() > minecraftAccount.expiresAt){
// Perform login only if needed
Expand Down Expand Up @@ -420,6 +422,10 @@ private void showDeleteDialog(Context context, int position) {
}
}



}
/** Resolve a theme colour attribute (e.g. R.attr.copperAccent) to an int colour. */
private int getThemeColor(int attr) {
TypedValue tv = new TypedValue();
getContext().getTheme().resolveAttribute(attr, tv, true);
return tv.data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,12 @@ public boolean setFullscreen() {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Apply saved colour theme before layout inflation
setTheme(net.kdt.pojavlaunch.theme.ThemeManager.getSavedTheme());
// Apply force-landscape preference before layout inflation
if (LauncherPreferences.DEFAULT_PREF.getBoolean("force_landscape", false)) {
setRequestedOrientation(android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
}
setContentView(R.layout.activity_pojav_launcher);
FragmentManager fragmentManager = getSupportFragmentManager();
// If we don't have a back stack root yet...
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package net.kdt.pojavlaunch.fragments;

import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand All @@ -10,13 +13,18 @@
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;

import java.io.File;

/**
* Default content of the right pane in landscape two-pane mode.
* Shows the cat wallpaper, Wiki and Discord buttons at the top.
* Shows a custom background (if set), otherwise a plain transparent pane.
* Wiki and Discord buttons are pinned at the top.
*/
public class RightPaneHomeFragment extends Fragment {

public static final String TAG = "RightPaneHomeFragment";
/** File path where the custom launcher background image is stored. */
public static final String CUSTOM_BG_PATH = Tools.DIR_DATA + "/custom_launcher_bg";

public RightPaneHomeFragment() {
super(R.layout.fragment_right_pane_home);
Expand All @@ -29,5 +37,43 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat

view.findViewById(R.id.discord_button_pane).setOnClickListener(
v -> Tools.openURL(requireActivity(), getString(R.string.discord_invite)));

loadBackground(view);
}

/**
* Called after saving or removing a custom background so the pane
* refreshes without needing a full fragment recreate.
*/
public void reloadBackground() {
View v = getView();
if (v != null) loadBackground(v);
}

private void loadBackground(@NonNull View view) {
ImageView wallpaper = view.findViewById(R.id.right_pane_wallpaper);
File bgFile = new File(CUSTOM_BG_PATH);
if (bgFile.exists()) {
Drawable d = Drawable.createFromPath(bgFile.getAbsolutePath());
if (d != null) {
wallpaper.setImageDrawable(d);
wallpaper.setScaleType(ImageView.ScaleType.CENTER_CROP);
wallpaper.setBackground(null);
wallpaper.setVisibility(View.VISIBLE);
return;
}
}
// No custom bg — show the gradient drawable as the pane background if gradient is on,
// otherwise stay transparent (root fragment_launcher bg shows through).
wallpaper.setImageDrawable(null);
TypedValue tv = new TypedValue();
view.getContext().getTheme().resolveAttribute(R.attr.bgMainDrawable, tv, true);
if (tv.resourceId != 0) {
wallpaper.setBackgroundResource(tv.resourceId);
wallpaper.setVisibility(View.VISIBLE);
} else {
wallpaper.setBackground(null);
wallpaper.setVisibility(View.GONE);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,143 @@
package net.kdt.pojavlaunch.prefs.screens;

import android.app.AlertDialog;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.SwitchPreferenceCompat;

import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.fragments.MainMenuFragment;
import net.kdt.pojavlaunch.fragments.RightPaneHomeFragment;
import net.kdt.pojavlaunch.theme.ThemeManager;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;

public class LauncherPreferenceExperimentalFragment extends LauncherPreferenceFragment {

private final ActivityResultLauncher<String> mImagePickerLauncher =
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri != null) copyImageToBgFile(uri);
});

@Override
public void onCreatePreferences(Bundle b, String str) {
addPreferencesFromResource(R.xml.pref_experimental);
setupForceLandscape();
setupCustomBackground();
setupColourTheme();
}

// ── Force landscape ───────────────────────────────────────────────────────

private void setupForceLandscape() {
SwitchPreferenceCompat pref = requirePreference("force_landscape", SwitchPreferenceCompat.class);
pref.setOnPreferenceChangeListener((preference, newValue) -> {
boolean force = Boolean.TRUE.equals(newValue);
requireActivity().setRequestedOrientation(
force ? SCREEN_ORIENTATION_SENSOR_LANDSCAPE : SCREEN_ORIENTATION_UNSPECIFIED);
return true;
});

SwitchPreferenceCompat gradientPref = requirePreference("enable_bg_gradient", SwitchPreferenceCompat.class);
gradientPref.setOnPreferenceChangeListener((preference, newValue) -> {
// Save explicitly before recreate — the framework saves after listener returns
net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF.edit()
.putBoolean(ThemeManager.KEY_GRADIENT, Boolean.TRUE.equals(newValue))
.commit(); // commit() not apply() — must be synchronous before recreate
requireActivity().recreate();
return true;
});
}

// ── Custom background ─────────────────────────────────────────────────────

private void setupCustomBackground() {
requirePreference("set_custom_launcher_bg").setOnPreferenceClickListener(p -> {
mImagePickerLauncher.launch("image/*");
return true;
});

requirePreference("remove_custom_launcher_bg").setOnPreferenceClickListener(p -> {
File bgFile = new File(RightPaneHomeFragment.CUSTOM_BG_PATH);
if (bgFile.exists()) bgFile.delete();
notifyHomeFragmentBgChanged();
toast(R.string.preference_custom_bg_removed);
return true;
});
}

private void copyImageToBgFile(@NonNull Uri uri) {
File bgFile = new File(RightPaneHomeFragment.CUSTOM_BG_PATH);
try (InputStream in = requireContext().getContentResolver().openInputStream(uri);
OutputStream out = new FileOutputStream(bgFile)) {
if (in == null) throw new Exception("Cannot open URI");
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) out.write(buf, 0, len);
notifyHomeFragmentBgChanged();
toast(R.string.preference_custom_bg_set_success);
} catch (Exception e) {
if (bgFile.exists()) bgFile.delete();
toast(R.string.preference_custom_bg_error);
}
}

// ── Colour theme ──────────────────────────────────────────────────────────

private void setupColourTheme() {
// Button 1: preset picker + reset
requirePreference("colour_theme_presets").setOnPreferenceClickListener(p -> {
showPresetDialog();
return true;
});
}

private void showPresetDialog() {
ThemeManager.Preset[] presets = ThemeManager.PRESETS;
String[] labels = new String[presets.length + 1];
for (int i = 0; i < presets.length; i++) labels[i] = presets[i].name;
labels[presets.length] = getString(R.string.preference_colour_reset);

new AlertDialog.Builder(requireContext())
.setTitle(R.string.preference_colour_presets_title)
.setItems(labels, (dialog, which) -> {
if (which < presets.length) {
ThemeManager.applyPreset(presets[which]);
} else {
ThemeManager.resetToDefault();
}
requireActivity().recreate();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}

// ── Helpers ───────────────────────────────────────────────────────────────

private void toast(int resId) {
Toast.makeText(requireContext(), resId, Toast.LENGTH_SHORT).show();
}

private void notifyHomeFragmentBgChanged() {
MainMenuFragment mmf = (MainMenuFragment) requireActivity()
.getSupportFragmentManager()
.findFragmentByTag("ROOT");
if (mmf == null) return;
RightPaneHomeFragment home = (RightPaneHomeFragment) mmf
.getChildFragmentManager()
.findFragmentByTag(RightPaneHomeFragment.TAG);
if (home != null) home.reloadBackground();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class LauncherPreferenceFragment extends PreferenceFragmentCompat impleme

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
view.setBackgroundColor(getResources().getColor(R.color.background_app));
net.kdt.pojavlaunch.theme.ThemeManager.applyToPrefView(view);
super.onViewCreated(view, savedInstanceState);
}

Expand Down Expand Up @@ -84,4 +84,4 @@ protected <T extends Preference> T requirePreference(CharSequence key, Class<T>
if(preferenceClass.isInstance(preference)) return (T)preference;
throw new IllegalStateException("Preference "+key+" is not an instance of "+preferenceClass.getSimpleName());
}
}
}
Loading
Loading