package de.winniepat.citrus.gui; import de.winniepat.citrus.managers.ProfileManager; import de.winniepat.citrus.managers.ProfileManager.PlayerProfile; import de.winniepat.citrus.managers.ProfileManager.SearchResult; import de.winniepat.citrus.managers.WallpaperManager; import de.winniepat.citrus.managers.ConfigManager; import de.winniepat.citrus.utils.texture.CdnTextureDrawable; import icyllis.modernui.animation.*; import icyllis.modernui.core.Context; import icyllis.modernui.fragment.Fragment; import icyllis.modernui.graphics.drawable.*; import icyllis.modernui.mc.*; import icyllis.modernui.text.InputFilter; import icyllis.modernui.text.TextPaint; import icyllis.modernui.util.DataSet; import icyllis.modernui.view.*; import icyllis.modernui.widget.*; import net.minecraft.client.MinecraftClient; import org.jetbrains.annotations.*; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class ProfileScreen extends Fragment implements ScreenCallback { private LinearLayout mContentContainer; private LinearLayout mSearchResultsContainer; private EditText mSearchInput; private boolean mIsEditMode = false; private PlayerProfile mCurrentProfile; private UUID mViewingUuid; private CdnTextureDrawable mBackground1Drawable; private final List mStaggeredViews = new ArrayList<>(); private EditText mBioEdit; private EditText mDiscordEdit; private EditText mTwitterEdit; private EditText mYoutubeEdit; private EditText mTwitchEdit; private EditText mInstagramEdit; private EditText mGithubEdit; private EditText mWebsiteEdit; private float getGuiScale() { return (float) MinecraftClient.getInstance().getWindow().getScaleFactor(); } private void setFixedTextSize(TextView tv, float px) { float scale = getGuiScale(); tv.setTextSize(px / scale); } @Override public View onCreateView(@NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable DataSet savedInstanceState) { Context context = requireContext(); FrameLayout root = new FrameLayout(context); root.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT )); FrameLayout backgroundView = new FrameLayout(context); backgroundView.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT )); backgroundView.setScaleX(1.1f); backgroundView.setScaleY(1.1f); View backgroundView1 = new View(context); backgroundView1.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT )); backgroundView.addView(backgroundView1); root.addView(backgroundView); mBackground1Drawable = new CdnTextureDrawable(); backgroundView1.setBackground(mBackground1Drawable); String currentWallpaper = ConfigManager.config.mainmenuWallpaper; if (currentWallpaper == null || currentWallpaper.isEmpty()) { currentWallpaper = "default.png"; } WallpaperManager.loadWallpaperWithData(currentWallpaper, imageData -> { MuiModApi.postToUiThread(() -> mBackground1Drawable.setImageData(imageData)); }); ObjectAnimator bgPulseX = ObjectAnimator.ofFloat(backgroundView, View.SCALE_X, 1.1f, 1.15f); ObjectAnimator bgPulseY = ObjectAnimator.ofFloat(backgroundView, View.SCALE_Y, 1.1f, 1.15f); bgPulseX.setDuration(15000); bgPulseY.setDuration(15000); bgPulseX.setRepeatCount(ValueAnimator.INFINITE); bgPulseY.setRepeatCount(ValueAnimator.INFINITE); bgPulseX.setRepeatMode(ValueAnimator.REVERSE); bgPulseY.setRepeatMode(ValueAnimator.REVERSE); bgPulseX.start(); bgPulseY.start(); FrameLayout contentWrapper = new FrameLayout(context); contentWrapper.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT )); root.addView(contentWrapper); LinearLayout leftPanel = new LinearLayout(context); leftPanel.setOrientation(LinearLayout.VERTICAL); FrameLayout.LayoutParams leftPanelLp = new FrameLayout.LayoutParams( 320, ViewGroup.LayoutParams.WRAP_CONTENT ); leftPanelLp.gravity = Gravity.CENTER_VERTICAL; leftPanelLp.setMargins(60, 0, 0, 0); leftPanel.setLayoutParams(leftPanelLp); TextView title = new TextView(context); title.setText("PROFILES"); setFixedTextSize(title, 96); title.getPaint().setTextStyle(TextPaint.BOLD); title.setTextColor(0xFFFFFFFF); leftPanel.addView(title); mStaggeredViews.add(title); TextView subtitle = new TextView(context); subtitle.setText("View and edit player profiles"); setFixedTextSize(subtitle, 32); subtitle.setTextColor(0xCCFFFFFF); LinearLayout.LayoutParams subtitleLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); subtitleLp.setMargins(0, 0, 0, dp(30)); subtitle.setLayoutParams(subtitleLp); leftPanel.addView(subtitle); mStaggeredViews.add(subtitle); LinearLayout searchSection = new LinearLayout(context); searchSection.setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams searchSectionLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); searchSectionLp.setMargins(0, 0, 0, dp(20)); searchSection.setLayoutParams(searchSectionLp); TextView searchLabel = new TextView(context); searchLabel.setText("Search Players"); setFixedTextSize(searchLabel, 28); searchLabel.setTextColor(0x88FFFFFF); searchLabel.setPadding(0, 0, 0, dp(8)); searchSection.addView(searchLabel); mSearchInput = new EditText(context); mSearchInput.setHint("Enter player name..."); setFixedTextSize(mSearchInput, 32); mSearchInput.setTextColor(0xFFFFFFFF); mSearchInput.setHintTextColor(0x66FFFFFF); mSearchInput.setPadding(dp(16), dp(12), dp(16), dp(12)); mSearchInput.setSingleLine(true); ShapeDrawable inputBg = new ShapeDrawable(); inputBg.setShape(ShapeDrawable.RECTANGLE); inputBg.setColor(0x26FFFFFF); inputBg.setCornerRadius(dp(10)); mSearchInput.setBackground(inputBg); LinearLayout.LayoutParams inputLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); inputLp.setMargins(0, 0, 0, dp(8)); mSearchInput.setLayoutParams(inputLp); searchSection.addView(mSearchInput); leftPanel.addView(searchSection); mStaggeredViews.add(searchSection); addNavButton(leftPanel, "Search", "multiplayer_icon", v -> performSearch()); addNavButton(leftPanel, "My Profile", "settings_icon", v -> loadOwnProfile()); addNavButton(leftPanel, "Back to Menu", "door_icon", v -> { MinecraftClient.getInstance().execute(() -> { MinecraftClient.getInstance().setScreen(MuiModApi.get().createScreen(new MainMenuScreen(), null, null)); }); }); contentWrapper.addView(leftPanel); LinearLayout rightPanel = new LinearLayout(context); rightPanel.setOrientation(LinearLayout.VERTICAL); FrameLayout.LayoutParams rightPanelLp = new FrameLayout.LayoutParams( 500, 500 ); rightPanelLp.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT; rightPanelLp.setMargins(0, 0, 60, 0); rightPanel.setLayoutParams(rightPanelLp); mSearchResultsContainer = new LinearLayout(context); mSearchResultsContainer.setOrientation(LinearLayout.VERTICAL); mSearchResultsContainer.setVisibility(View.GONE); LinearLayout.LayoutParams resultsLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); resultsLp.setMargins(0, 0, 0, dp(12)); mSearchResultsContainer.setLayoutParams(resultsLp); ShapeDrawable resultsBg = new ShapeDrawable(); resultsBg.setShape(ShapeDrawable.RECTANGLE); resultsBg.setColor(0x33000000); resultsBg.setCornerRadius(dp(16)); mSearchResultsContainer.setBackground(resultsBg); mSearchResultsContainer.setPadding(dp(16), dp(16), dp(16), dp(16)); rightPanel.addView(mSearchResultsContainer); LinearLayout profilePanel = new LinearLayout(context); profilePanel.setOrientation(LinearLayout.VERTICAL); profilePanel.setPadding(dp(16), dp(16), dp(16), dp(16)); LinearLayout.LayoutParams profilePanelLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f ); profilePanel.setLayoutParams(profilePanelLp); ShapeDrawable panelBg = new ShapeDrawable(); panelBg.setShape(ShapeDrawable.RECTANGLE); panelBg.setColor(0x33000000); panelBg.setCornerRadius(dp(16)); profilePanel.setBackground(panelBg); TextView panelTitle = new TextView(context); panelTitle.setText("PROFILE"); setFixedTextSize(panelTitle, 48); panelTitle.getPaint().setTextStyle(TextPaint.BOLD); panelTitle.setTextColor(0xFF4a90d9); panelTitle.setGravity(Gravity.CENTER); LinearLayout.LayoutParams panelTitleLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); panelTitleLp.setMargins(0, 0, 0, dp(12)); panelTitle.setLayoutParams(panelTitleLp); profilePanel.addView(panelTitle); ScrollView scrollView = new ScrollView(context); LinearLayout.LayoutParams scrollLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f ); scrollView.setLayoutParams(scrollLp); scrollView.setScrollBarSize(dp(6)); mContentContainer = new LinearLayout(context); mContentContainer.setOrientation(LinearLayout.VERTICAL); scrollView.addView(mContentContainer); profilePanel.addView(scrollView); rightPanel.addView(profilePanel); mStaggeredViews.add(rightPanel); contentWrapper.addView(rightPanel); TextView infoText = new TextView(context); infoText.setText("Share your profile with friends • Add your socials to connect"); setFixedTextSize(infoText, 24); infoText.setTextColor(0x66FFFFFF); FrameLayout.LayoutParams infoLp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); infoLp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; infoLp.setMargins(0, 0, 0, dp(20)); infoText.setLayoutParams(infoLp); contentWrapper.addView(infoText); mStaggeredViews.add(infoText); loadOwnProfile(); animateEntrance(); return root; } private void addNavButton(LinearLayout parent, String text, String iconPath, View.OnClickListener listener) { Context context = requireContext(); FrameLayout btnWrapper = new FrameLayout(context); LinearLayout.LayoutParams wrapperLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 56 ); wrapperLp.setMargins(0, 0, 0, 8); btnWrapper.setLayoutParams(wrapperLp); ShapeDrawable btnBg = new ShapeDrawable(); btnBg.setShape(ShapeDrawable.RECTANGLE); btnBg.setColor(0x1AFFFFFF); btnBg.setCornerRadius(12); btnWrapper.setBackground(btnBg); LinearLayout content = new LinearLayout(context); content.setOrientation(LinearLayout.HORIZONTAL); content.setGravity(Gravity.CENTER_VERTICAL); content.setPadding(20, 0, 20, 0); ImageDrawable icon = new ImageDrawable("citrus", "startscreen/" + iconPath + ".png"); icon.setBounds(0, 0, 24, 24); TextView tv = new TextView(context); tv.setText(text); setFixedTextSize(tv, 36); tv.setTextColor(0xFFFFFFFF); tv.setCompoundDrawables(icon, null, null, null); tv.setCompoundDrawablePadding(16); content.addView(tv); btnWrapper.addView(content); btnWrapper.setClickable(true); btnWrapper.setFocusable(true); btnWrapper.setOnClickListener(listener); btnWrapper.setOnHoverListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.TRANSLATION_X, 0f, (float) dp(15)); animator.setDuration(250); animator.setInterpolator(TimeInterpolator.DECELERATE); animator.start(); btnBg.setColor(0x33FFFFFF); return true; } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.TRANSLATION_X, (float) dp(15), 0f); animator.setDuration(250); animator.setInterpolator(TimeInterpolator.DECELERATE); animator.start(); btnBg.setColor(0x1AFFFFFF); return true; } return false; }); parent.addView(btnWrapper); mStaggeredViews.add(btnWrapper); } private void animateEntrance() { long delay = 100; for (View v : mStaggeredViews) { v.setAlpha(0); float startX = -dp(40); ViewGroup.LayoutParams lp = v.getLayoutParams(); if (lp instanceof FrameLayout.LayoutParams flp) { if ((flp.gravity & Gravity.RIGHT) == Gravity.RIGHT) { startX = dp(40); } } v.setTranslationX(startX); ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f); ObjectAnimator trans = ObjectAnimator.ofFloat(v, View.TRANSLATION_X, startX, 0f); AnimatorSet set = new AnimatorSet(); set.playTogether(alpha, trans); set.setDuration(800); set.setStartDelay(delay); set.setInterpolator(TimeInterpolator.DECELERATE); set.start(); delay += 60; } } private void loadOwnProfile() { mSearchResultsContainer.setVisibility(View.GONE); showLoading(); UUID uuid = MinecraftClient.getInstance().getSession().getUuidOrNull(); if (uuid == null) { showError("Not logged in"); return; } mViewingUuid = uuid; ProfileManager.getOwnProfile().thenAccept(profile -> { MuiModApi.postToUiThread(() -> { if (profile != null) { mCurrentProfile = profile; showProfileView(profile, true); } else { mCurrentProfile = new PlayerProfile( uuid.toString(), MinecraftClient.getInstance().getSession().getUsername() ); showProfileEdit(mCurrentProfile); } }); }); } private void loadProfile(UUID uuid) { mSearchResultsContainer.setVisibility(View.GONE); showLoading(); mViewingUuid = uuid; UUID ownUuid = MinecraftClient.getInstance().getSession().getUuidOrNull(); boolean isOwn = ownUuid != null && ownUuid.equals(uuid); ProfileManager.getProfile(uuid).thenAccept(profile -> { MuiModApi.postToUiThread(() -> { if (profile != null) { mCurrentProfile = profile; showProfileView(profile, isOwn); } else { showError("Profile not found"); } }); }); } private void performSearch() { String query = mSearchInput.getText().toString().trim(); if (query.isEmpty()) return; mSearchResultsContainer.removeAllViews(); mSearchResultsContainer.setVisibility(View.VISIBLE); TextView searching = new TextView(requireContext()); searching.setText("Searching..."); setFixedTextSize(searching, 28); searching.setTextColor(0x88FFFFFF); mSearchResultsContainer.addView(searching); ProfileManager.searchProfiles(query).thenAccept(results -> { MuiModApi.postToUiThread(() -> { mSearchResultsContainer.removeAllViews(); if (results.isEmpty()) { TextView noResults = new TextView(requireContext()); noResults.setText("No profiles found"); setFixedTextSize(noResults, 28); noResults.setTextColor(0x88FFFFFF); mSearchResultsContainer.addView(noResults); } else { TextView resultsLabel = new TextView(requireContext()); resultsLabel.setText("Results (" + results.size() + ")"); setFixedTextSize(resultsLabel, 32); resultsLabel.getPaint().setTextStyle(TextPaint.BOLD); resultsLabel.setTextColor(0xFFFFD700); resultsLabel.setPadding(0, 0, 0, dp(10)); mSearchResultsContainer.addView(resultsLabel); for (SearchResult result : results) { mSearchResultsContainer.addView(createSearchResultCard(result)); } } }); }); } private View createSearchResultCard(SearchResult result) { Context context = requireContext(); LinearLayout card = new LinearLayout(context); card.setOrientation(LinearLayout.HORIZONTAL); card.setGravity(Gravity.CENTER_VERTICAL); card.setPadding(dp(12), dp(10), dp(12), dp(10)); LinearLayout.LayoutParams cardLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); cardLp.setMargins(0, 0, 0, dp(8)); card.setLayoutParams(cardLp); ShapeDrawable cardBg = new ShapeDrawable(); cardBg.setShape(ShapeDrawable.RECTANGLE); cardBg.setColor(0x20FFFFFF); cardBg.setCornerRadius(dp(10)); card.setBackground(cardBg); card.setClickable(true); card.setOnClickListener(v -> { try { UUID uuid = UUID.fromString(result.uuid); loadProfile(uuid); } catch (Exception ignored) {} }); card.setOnHoverListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { cardBg.setColor(0x33FFFFFF); } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { cardBg.setColor(0x20FFFFFF); } return false; }); TextView nameTv = new TextView(context); nameTv.setText(result.playerName); setFixedTextSize(nameTv, 30); nameTv.setTextColor(0xFFFFFFFF); LinearLayout.LayoutParams nameLp = new LinearLayout.LayoutParams( 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f ); nameTv.setLayoutParams(nameLp); card.addView(nameTv); if (result.hasSocials) { TextView socialsTag = new TextView(context); socialsTag.setText("Socials"); setFixedTextSize(socialsTag, 22); socialsTag.setTextColor(0xFF5cb85c); socialsTag.setPadding(dp(8), dp(4), dp(8), dp(4)); ShapeDrawable tagBg = new ShapeDrawable(); tagBg.setShape(ShapeDrawable.RECTANGLE); tagBg.setColor(0x335cb85c); tagBg.setCornerRadius(dp(4)); socialsTag.setBackground(tagBg); card.addView(socialsTag); } return card; } private void showLoading() { mContentContainer.removeAllViews(); LinearLayout loadingContainer = new LinearLayout(requireContext()); loadingContainer.setOrientation(LinearLayout.VERTICAL); loadingContainer.setGravity(Gravity.CENTER); loadingContainer.setPadding(dp(20), dp(60), dp(20), dp(60)); TextView loading = new TextView(requireContext()); loading.setText("Loading profile..."); setFixedTextSize(loading, 32); loading.setTextColor(0x88FFFFFF); loading.setGravity(Gravity.CENTER); loadingContainer.addView(loading); mContentContainer.addView(loadingContainer); } private void showError(String message) { mContentContainer.removeAllViews(); LinearLayout errorContainer = new LinearLayout(requireContext()); errorContainer.setOrientation(LinearLayout.VERTICAL); errorContainer.setGravity(Gravity.CENTER); errorContainer.setPadding(dp(20), dp(60), dp(20), dp(60)); TextView error = new TextView(requireContext()); error.setText(message); setFixedTextSize(error, 32); error.setTextColor(0xFFFF6B6B); error.setGravity(Gravity.CENTER); errorContainer.addView(error); mContentContainer.addView(errorContainer); } private void showProfileView(PlayerProfile profile, boolean isOwn) { mIsEditMode = false; mContentContainer.removeAllViews(); Context context = requireContext(); LinearLayout header = new LinearLayout(context); header.setOrientation(LinearLayout.HORIZONTAL); header.setGravity(Gravity.CENTER_VERTICAL); LinearLayout.LayoutParams headerLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); headerLp.setMargins(0, 0, 0, dp(16)); header.setLayoutParams(headerLp); TextView nameTv = new TextView(context); nameTv.setText(profile.playerName); setFixedTextSize(nameTv, 42); nameTv.getPaint().setTextStyle(TextPaint.BOLD); nameTv.setTextColor(0xFFFFFFFF); LinearLayout.LayoutParams nameLp = new LinearLayout.LayoutParams( 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f ); nameTv.setLayoutParams(nameLp); header.addView(nameTv); if (isOwn) { TextView editBtn = createSmallActionButton(context, "Edit", 0xFF4a90d9); editBtn.setOnClickListener(v -> showProfileEdit(profile)); header.addView(editBtn); } mContentContainer.addView(header); if (profile.bio != null && !profile.bio.trim().isEmpty()) { mContentContainer.addView(createSectionLabel(context, "About")); TextView bioTv = new TextView(context); bioTv.setText(profile.bio); setFixedTextSize(bioTv, 28); bioTv.setTextColor(0xCCFFFFFF); bioTv.setPadding(0, 0, 0, dp(16)); mContentContainer.addView(bioTv); } boolean hasSocials = profile.hasSocialLinks(); if (hasSocials) { mContentContainer.addView(createSectionLabel(context, "Social Links")); LinearLayout socialsGrid = new LinearLayout(context); socialsGrid.setOrientation(LinearLayout.VERTICAL); if (!isEmpty(profile.discord)) { socialsGrid.addView(createSocialLink(context, "Discord", profile.discord, 0xFF5865F2)); } if (!isEmpty(profile.twitter)) { socialsGrid.addView(createSocialLink(context, "Twitter/X", profile.twitter, 0xFF1DA1F2)); } if (!isEmpty(profile.youtube)) { socialsGrid.addView(createSocialLink(context, "YouTube", profile.youtube, 0xFFFF0000)); } if (!isEmpty(profile.twitch)) { socialsGrid.addView(createSocialLink(context, "Twitch", profile.twitch, 0xFF9146FF)); } if (!isEmpty(profile.instagram)) { socialsGrid.addView(createSocialLink(context, "Instagram", profile.instagram, 0xFFE1306C)); } if (!isEmpty(profile.github)) { socialsGrid.addView(createSocialLink(context, "GitHub", profile.github, 0xFFFFFFFF)); } if (!isEmpty(profile.website)) { socialsGrid.addView(createSocialLink(context, "Website", profile.website, 0xFF4a90d9)); } mContentContainer.addView(socialsGrid); } if (!hasSocials && (profile.bio == null || profile.bio.trim().isEmpty())) { LinearLayout emptyContainer = new LinearLayout(context); emptyContainer.setOrientation(LinearLayout.VERTICAL); emptyContainer.setGravity(Gravity.CENTER); emptyContainer.setPadding(dp(16), dp(40), dp(16), dp(40)); TextView emptyTitle = new TextView(context); emptyTitle.setText(isOwn ? "Profile Empty" : "No Info"); setFixedTextSize(emptyTitle, 32); emptyTitle.setTextColor(0x88FFFFFF); emptyTitle.setGravity(Gravity.CENTER); emptyContainer.addView(emptyTitle); TextView emptySubtitle = new TextView(context); emptySubtitle.setText(isOwn ? "Click 'Edit' to add your info!" : "This player hasn't added info yet."); setFixedTextSize(emptySubtitle, 24); emptySubtitle.setTextColor(0x55FFFFFF); emptySubtitle.setGravity(Gravity.CENTER); emptySubtitle.setPadding(0, dp(4), 0, 0); emptyContainer.addView(emptySubtitle); mContentContainer.addView(emptyContainer); } } private void showProfileEdit(PlayerProfile profile) { mIsEditMode = true; mContentContainer.removeAllViews(); Context context = requireContext(); TextView headerTv = new TextView(context); headerTv.setText("Edit Your Profile"); setFixedTextSize(headerTv, 36); headerTv.getPaint().setTextStyle(TextPaint.BOLD); headerTv.setTextColor(0xFFFFFFFF); headerTv.setPadding(0, 0, 0, dp(16)); mContentContainer.addView(headerTv); mContentContainer.addView(createInputLabel(context, "Bio")); mBioEdit = createEditText(context, profile.bio, "Tell others about yourself...", false); mBioEdit.setMinLines(2); mBioEdit.setMaxLines(4); mBioEdit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(500)}); mContentContainer.addView(mBioEdit); mContentContainer.addView(createSectionLabel(context, "Social Links")); mContentContainer.addView(createInputLabel(context, "Discord")); mDiscordEdit = createEditText(context, profile.discord, "username", true); mContentContainer.addView(mDiscordEdit); mContentContainer.addView(createInputLabel(context, "Twitter/X")); mTwitterEdit = createEditText(context, profile.twitter, "@username", true); mContentContainer.addView(mTwitterEdit); mContentContainer.addView(createInputLabel(context, "YouTube")); mYoutubeEdit = createEditText(context, profile.youtube, "Channel URL", true); mContentContainer.addView(mYoutubeEdit); mContentContainer.addView(createInputLabel(context, "Twitch")); mTwitchEdit = createEditText(context, profile.twitch, "username", true); mContentContainer.addView(mTwitchEdit); mContentContainer.addView(createInputLabel(context, "Instagram")); mInstagramEdit = createEditText(context, profile.instagram, "@username", true); mContentContainer.addView(mInstagramEdit); mContentContainer.addView(createInputLabel(context, "GitHub")); mGithubEdit = createEditText(context, profile.github, "username", true); mContentContainer.addView(mGithubEdit); mContentContainer.addView(createInputLabel(context, "Website")); mWebsiteEdit = createEditText(context, profile.website, "https://...", true); mContentContainer.addView(mWebsiteEdit); LinearLayout buttonRow = new LinearLayout(context); buttonRow.setOrientation(LinearLayout.HORIZONTAL); buttonRow.setGravity(Gravity.CENTER); LinearLayout.LayoutParams buttonRowLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); buttonRowLp.setMargins(0, dp(16), 0, 0); buttonRow.setLayoutParams(buttonRowLp); TextView cancelBtn = createSmallActionButton(context, "Cancel", 0xFF666666); cancelBtn.setOnClickListener(v -> showProfileView(profile, true)); LinearLayout.LayoutParams cancelLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); cancelLp.setMargins(0, 0, dp(10), 0); cancelBtn.setLayoutParams(cancelLp); buttonRow.addView(cancelBtn); TextView saveBtn = createSmallActionButton(context, "Save", 0xFF5cb85c); saveBtn.setOnClickListener(v -> saveProfile()); buttonRow.addView(saveBtn); mContentContainer.addView(buttonRow); } private void saveProfile() { PlayerProfile updated = new PlayerProfile(); updated.uuid = mCurrentProfile.uuid; updated.playerName = MinecraftClient.getInstance().getSession().getUsername(); updated.bio = mBioEdit.getText().toString().trim(); updated.discord = mDiscordEdit.getText().toString().trim(); updated.twitter = mTwitterEdit.getText().toString().trim(); updated.youtube = mYoutubeEdit.getText().toString().trim(); updated.twitch = mTwitchEdit.getText().toString().trim(); updated.instagram = mInstagramEdit.getText().toString().trim(); updated.github = mGithubEdit.getText().toString().trim(); updated.website = mWebsiteEdit.getText().toString().trim(); showLoading(); ProfileManager.updateProfile(updated).thenAccept(error -> { MuiModApi.postToUiThread(() -> { if (error == null) { mCurrentProfile = updated; ProfileManager.invalidateCache(UUID.fromString(updated.uuid)); showProfileView(updated, true); } else { showError("Failed to save: " + error); } }); }); } private View createSocialLink(Context context, String platform, String value, int color) { LinearLayout row = new LinearLayout(context); row.setOrientation(LinearLayout.HORIZONTAL); row.setGravity(Gravity.CENTER_VERTICAL); row.setPadding(dp(10), dp(8), dp(10), dp(8)); LinearLayout.LayoutParams rowLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); rowLp.setMargins(0, 0, 0, dp(6)); row.setLayoutParams(rowLp); ShapeDrawable rowBg = new ShapeDrawable(); rowBg.setShape(ShapeDrawable.RECTANGLE); rowBg.setColor(0x20FFFFFF); rowBg.setCornerRadius(dp(8)); row.setBackground(rowBg); View colorIndicator = new View(context); colorIndicator.setLayoutParams(new LinearLayout.LayoutParams(dp(4), dp(20))); ShapeDrawable indicatorBg = new ShapeDrawable(); indicatorBg.setShape(ShapeDrawable.RECTANGLE); indicatorBg.setColor(color); indicatorBg.setCornerRadius(dp(2)); colorIndicator.setBackground(indicatorBg); row.addView(colorIndicator); TextView platformTv = new TextView(context); platformTv.setText(platform); setFixedTextSize(platformTv, 26); platformTv.getPaint().setTextStyle(TextPaint.BOLD); platformTv.setTextColor(0xFFFFFFFF); platformTv.setPadding(dp(10), 0, dp(8), 0); row.addView(platformTv); TextView valueTv = new TextView(context); valueTv.setText(value); setFixedTextSize(valueTv, 24); valueTv.setTextColor(0xCCFFFFFF); LinearLayout.LayoutParams valueLp = new LinearLayout.LayoutParams( 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f ); valueTv.setLayoutParams(valueLp); row.addView(valueTv); TextView copyBtn = new TextView(context); copyBtn.setText("Copy"); setFixedTextSize(copyBtn, 22); copyBtn.setTextColor(0xFF4a90d9); copyBtn.setPadding(dp(8), dp(4), dp(8), dp(4)); copyBtn.setClickable(true); copyBtn.setOnClickListener(v -> { MinecraftClient.getInstance().keyboard.setClipboard(value); copyBtn.setText("✓"); v.postDelayed(() -> copyBtn.setText("Copy"), 2000); }); row.addView(copyBtn); return row; } private TextView createSmallActionButton(Context context, String text, int color) { TextView btn = new TextView(context); btn.setText(text); setFixedTextSize(btn, 26); btn.setTextColor(0xFFFFFFFF); btn.setPadding(dp(14), dp(8), dp(14), dp(8)); btn.setGravity(Gravity.CENTER); ShapeDrawable bg = new ShapeDrawable(); bg.setShape(ShapeDrawable.RECTANGLE); bg.setColor(color); bg.setCornerRadius(dp(8)); btn.setBackground(bg); btn.setClickable(true); btn.setOnHoverListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { bg.setColor(lightenColor(color, 0.2f)); } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { bg.setColor(color); } return false; }); return btn; } private TextView createSectionLabel(Context context, String text) { TextView label = new TextView(context); label.setText(text); setFixedTextSize(label, 30); label.getPaint().setTextStyle(TextPaint.BOLD); label.setTextColor(0xFFFFFFFF); label.setPadding(0, dp(8), 0, dp(8)); return label; } private TextView createInputLabel(Context context, String text) { TextView label = new TextView(context); label.setText(text); setFixedTextSize(label, 24); label.setTextColor(0xAAFFFFFF); label.setPadding(0, dp(6), 0, dp(4)); return label; } private EditText createEditText(Context context, String value, String hint, boolean singleLine) { EditText edit = new EditText(context); if (value != null) edit.setText(value); edit.setHint(hint); setFixedTextSize(edit, 26); edit.setTextColor(0xFFFFFFFF); edit.setHintTextColor(0x66FFFFFF); edit.setPadding(dp(12), dp(10), dp(12), dp(10)); edit.setSingleLine(singleLine); ShapeDrawable bg = new ShapeDrawable(); bg.setShape(ShapeDrawable.RECTANGLE); bg.setColor(0x26FFFFFF); bg.setCornerRadius(dp(8)); edit.setBackground(bg); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); lp.setMargins(0, 0, 0, dp(4)); edit.setLayoutParams(lp); return edit; } private boolean isEmpty(String s) { return s == null || s.trim().isEmpty(); } private int lightenColor(int color, float factor) { int a = (color >> 24) & 0xFF; int r = Math.min(255, (int) (((color >> 16) & 0xFF) * (1 + factor))); int g = Math.min(255, (int) (((color >> 8) & 0xFF) * (1 + factor))); int b = Math.min(255, (int) ((color & 0xFF) * (1 + factor))); return (a << 24) | (r << 16) | (g << 8) | b; } private int dp(int value) { return (int) (value * getContext().getResources().getDisplayMetrics().density); } @Override public boolean isPauseScreen() { return false; } @Override public boolean shouldBlurBackground() { return false; } @Override public boolean hasDefaultBackground() { return false; } }