AeThex-OS/android/app/src/main/java/com/aethex/os/ProjectsActivity.java
MrPiglr b3c308b2c8 Add functional marketplace modules, bottom nav bar, root terminal, arcade games
- ModuleManager: Central tracking for installed marketplace modules
- DataAnalyzerWidget: Real-time CPU/RAM/Battery/Storage widget (unlocked by Data Analyzer module)
- BottomNavBar: Navigation bar for Projects/Chat/Marketplace/Settings
- RootShell: Real root command execution utility
- TerminalActivity: Full root shell with neofetch, sysinfo, real Linux commands
- Terminal Pro module: Adds aliases (ll, la, h), command history
- ArcadeActivity + SnakeGame: Pixel Arcade module unlocks retro games
- fade_in/fade_out animations for smooth transitions
2026-02-18 22:03:50 -07:00

824 lines
34 KiB
Java

package com.aethex.os;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* Projects — Interactive Kanban-style project tracking board.
* Supports create, edit, delete, and status cycling.
*/
public class ProjectsActivity extends AppCompatActivity {
private ThemeManager themeManager;
private Typeface displayFont;
private Typeface monoFont;
private SharedPreferences prefs;
private LinearLayout projectListContainer;
private static final String PREFS_NAME = "aethex_projects";
private static final String KEY_PROJECTS = "projects_json";
private static final String[] STATUSES = {"Planned", "In Progress", "Review", "Complete"};
private static final String[] STATUS_COLORS = {"#66FFFFFF", "#06B6D4", "#FBBF24", "#22C55E"};
private List<ProjectItem> projects = new ArrayList<>();
static class ProjectItem {
String name;
String description;
int statusIndex; // 0=Planned, 1=In Progress, 2=Review, 3=Complete
ProjectItem(String name, String description, int statusIndex) {
this.name = name;
this.description = description;
this.statusIndex = statusIndex;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app);
hideSystemUI();
themeManager = new ThemeManager(this);
displayFont = themeManager.getDisplayFont(this);
monoFont = themeManager.getMonoFont(this);
prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
SoundManager.getInstance().play(SoundManager.Sound.OPEN);
TextView title = findViewById(R.id.app_title);
title.setText("Projects");
TextView nameDisplay = findViewById(R.id.app_name_display);
findViewById(R.id.app_back).setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLOSE);
finish();
overridePendingTransition(R.anim.scale_in, R.anim.slide_down_out);
});
LinearLayout content = (LinearLayout) nameDisplay.getParent();
content.removeAllViews();
content.setGravity(Gravity.TOP);
content.setPadding(0, 0, 0, 0);
loadProjects();
buildProjectsUI(content);
View root = findViewById(R.id.app_root);
root.setAlpha(0f);
root.animate().alpha(1f).setDuration(300).start();
// Add bottom navigation bar
BottomNavBar.attach(this, (ViewGroup) root, BottomNavBar.TAB_PROJECTS);
}
private void buildProjectsUI(LinearLayout parent) {
int primaryColor = themeManager.getPrimaryColor(this);
// Header row with title + add button
LinearLayout headerRow = new LinearLayout(this);
headerRow.setOrientation(LinearLayout.HORIZONTAL);
headerRow.setGravity(Gravity.CENTER_VERTICAL);
headerRow.setPadding(dpToPx(16), dpToPx(14), dpToPx(16), dpToPx(10));
TextView header = new TextView(this);
header.setText("PROJECT BOARD");
header.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
header.setTextColor(Color.parseColor("#66FFFFFF"));
header.setTypeface(monoFont);
header.setLetterSpacing(0.15f);
header.setLayoutParams(new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
headerRow.addView(header);
// Project count
TextView countView = new TextView(this);
countView.setText(projects.size() + " projects");
countView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
countView.setTextColor(Color.parseColor("#4DFFFFFF"));
countView.setTypeface(monoFont);
countView.setTag("project_count");
LinearLayout.LayoutParams cvP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
cvP.setMarginEnd(dpToPx(10));
countView.setLayoutParams(cvP);
headerRow.addView(countView);
// Add button
TextView addBtn = new TextView(this);
addBtn.setText("+ NEW");
addBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
addBtn.setTextColor(primaryColor);
addBtn.setTypeface(monoFont, Typeface.BOLD);
addBtn.setLetterSpacing(0.1f);
addBtn.setPadding(dpToPx(12), dpToPx(6), dpToPx(12), dpToPx(6));
GradientDrawable addBg = new GradientDrawable();
addBg.setCornerRadius(dpToPx(6));
addBg.setStroke(dpToPx(1), primaryColor);
addBg.setColor(Color.parseColor("#0D000000"));
addBtn.setBackground(addBg);
addBtn.setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
showCreateProjectOverlay();
});
headerRow.addView(addBtn);
parent.addView(headerRow);
// Status filter row
LinearLayout filterRow = new LinearLayout(this);
filterRow.setOrientation(LinearLayout.HORIZONTAL);
filterRow.setPadding(dpToPx(16), 0, dpToPx(16), dpToPx(10));
filterRow.setGravity(Gravity.CENTER_VERTICAL);
for (int i = 0; i < STATUSES.length; i++) {
final int statusIdx = i;
int count = 0;
for (ProjectItem p : projects) if (p.statusIndex == statusIdx) count++;
TextView chip = new TextView(this);
chip.setText(STATUSES[i] + " " + count);
chip.setTextSize(TypedValue.COMPLEX_UNIT_SP, 9);
chip.setTextColor(Color.parseColor(STATUS_COLORS[i]));
chip.setTypeface(monoFont);
chip.setPadding(dpToPx(8), dpToPx(4), dpToPx(8), dpToPx(4));
GradientDrawable chipBg = new GradientDrawable();
chipBg.setCornerRadius(dpToPx(4));
chipBg.setColor(Color.parseColor("#0D000000"));
chip.setBackground(chipBg);
LinearLayout.LayoutParams chipP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
chipP.setMarginEnd(dpToPx(6));
chip.setLayoutParams(chipP);
filterRow.addView(chip);
}
parent.addView(filterRow);
// Divider
View div = new View(this);
LinearLayout.LayoutParams divP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, dpToPx(1));
div.setLayoutParams(divP);
div.setBackgroundColor(Color.parseColor("#1AFFFFFF"));
parent.addView(div);
// Scrollable project list
ScrollView scroll = new ScrollView(this);
scroll.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
scroll.setOverScrollMode(View.OVER_SCROLL_NEVER);
projectListContainer = new LinearLayout(this);
projectListContainer.setOrientation(LinearLayout.VERTICAL);
projectListContainer.setPadding(dpToPx(16), dpToPx(10), dpToPx(16), dpToPx(16));
refreshProjectList();
scroll.addView(projectListContainer);
parent.addView(scroll);
}
private void refreshProjectList() {
projectListContainer.removeAllViews();
if (projects.isEmpty()) {
TextView empty = new TextView(this);
empty.setText("No projects yet.\nTap + NEW to create one.");
empty.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
empty.setTextColor(Color.parseColor("#4DFFFFFF"));
empty.setTypeface(monoFont);
empty.setGravity(Gravity.CENTER);
empty.setPadding(0, dpToPx(40), 0, 0);
projectListContainer.addView(empty);
return;
}
for (int i = 0; i < projects.size(); i++) {
projectListContainer.addView(buildProjectCard(i));
}
// Update count
View root = findViewById(R.id.app_root);
if (root != null) {
TextView countView = root.findViewWithTag("project_count");
if (countView != null) {
countView.setText(projects.size() + " projects");
}
}
}
private View buildProjectCard(int index) {
ProjectItem project = projects.get(index);
int statusColor = Color.parseColor(STATUS_COLORS[project.statusIndex]);
LinearLayout card = new LinearLayout(this);
card.setOrientation(LinearLayout.HORIZONTAL);
card.setGravity(Gravity.CENTER_VERTICAL);
LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
cardParams.bottomMargin = dpToPx(8);
card.setLayoutParams(cardParams);
card.setPadding(dpToPx(16), dpToPx(14), dpToPx(12), dpToPx(14));
GradientDrawable bg = new GradientDrawable();
bg.setCornerRadius(dpToPx(12));
bg.setColor(Color.parseColor("#0D0F172A"));
bg.setStroke(dpToPx(1), Color.parseColor("#1AFFFFFF"));
card.setBackground(bg);
// Status dot — tap to cycle status
View dot = new View(this);
int dotSize = dpToPx(10);
LinearLayout.LayoutParams dotParams = new LinearLayout.LayoutParams(dotSize, dotSize);
dotParams.setMarginEnd(dpToPx(12));
dot.setLayoutParams(dotParams);
GradientDrawable dotBg = new GradientDrawable();
dotBg.setShape(GradientDrawable.OVAL);
dotBg.setColor(statusColor);
dot.setBackground(dotBg);
dot.setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
project.statusIndex = (project.statusIndex + 1) % STATUSES.length;
saveProjects();
// Rebuild entire UI to update filter counts
LinearLayout content = (LinearLayout) projectListContainer.getParent().getParent();
content.removeAllViews();
buildProjectsUI(content);
AeThexToast.show(this, project.name + "" + STATUSES[project.statusIndex], AeThexToast.Type.INFO);
});
card.addView(dot);
// Info column
LinearLayout info = new LinearLayout(this);
info.setOrientation(LinearLayout.VERTICAL);
info.setLayoutParams(new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
TextView name = new TextView(this);
name.setText(project.name);
name.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13);
name.setTextColor(Color.parseColor("#CCFFFFFF"));
name.setTypeface(displayFont);
info.addView(name);
if (project.description != null && !project.description.isEmpty()) {
TextView desc = new TextView(this);
desc.setText(project.description);
desc.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
desc.setTextColor(Color.parseColor("#4DFFFFFF"));
desc.setTypeface(monoFont);
desc.setMaxLines(1);
LinearLayout.LayoutParams dP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
dP.topMargin = dpToPx(2);
desc.setLayoutParams(dP);
info.addView(desc);
}
TextView status = new TextView(this);
status.setText(STATUSES[project.statusIndex]);
status.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
status.setTextColor(statusColor);
status.setTypeface(monoFont);
LinearLayout.LayoutParams sParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
sParams.topMargin = dpToPx(4);
status.setLayoutParams(sParams);
info.addView(status);
card.addView(info);
// Edit button
TextView editBtn = new TextView(this);
editBtn.setText("");
editBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
editBtn.setTextColor(Color.parseColor("#66FFFFFF"));
editBtn.setPadding(dpToPx(8), dpToPx(4), dpToPx(8), dpToPx(4));
editBtn.setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
showEditProjectOverlay(index);
});
card.addView(editBtn);
// Delete button
TextView delBtn = new TextView(this);
delBtn.setText("");
delBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
delBtn.setTextColor(Color.parseColor("#EF4444"));
delBtn.setPadding(dpToPx(8), dpToPx(4), dpToPx(8), dpToPx(4));
delBtn.setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
showDeleteConfirmation(index);
});
card.addView(delBtn);
// Tap card for detail toast
card.setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
String detail = project.name + "\n" + STATUSES[project.statusIndex];
if (project.description != null && !project.description.isEmpty()) {
detail += "\n" + project.description;
}
AeThexToast.show(this, detail, AeThexToast.Type.INFO);
});
return card;
}
private void showCreateProjectOverlay() {
showProjectFormOverlay(-1, "", "", 0);
}
private void showEditProjectOverlay(int index) {
ProjectItem p = projects.get(index);
showProjectFormOverlay(index, p.name, p.description, p.statusIndex);
}
private void showProjectFormOverlay(int editIndex, String currentName, String currentDesc, int currentStatus) {
boolean isEdit = editIndex >= 0;
int primaryColor = themeManager.getPrimaryColor(this);
FrameLayout root = findViewById(R.id.app_root);
FrameLayout scrim = new FrameLayout(this);
scrim.setTag("project_form_overlay");
scrim.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
scrim.setBackgroundColor(Color.parseColor("#CC000000"));
LinearLayout panel = new LinearLayout(this);
panel.setOrientation(LinearLayout.VERTICAL);
FrameLayout.LayoutParams pp = new FrameLayout.LayoutParams(
dpToPx(300), ViewGroup.LayoutParams.WRAP_CONTENT);
pp.gravity = Gravity.CENTER;
panel.setLayoutParams(pp);
panel.setPadding(dpToPx(20), dpToPx(20), dpToPx(20), dpToPx(20));
GradientDrawable panelBg = new GradientDrawable();
panelBg.setCornerRadius(dpToPx(16));
panelBg.setColor(Color.parseColor("#E6111827"));
panelBg.setStroke(dpToPx(1), Color.parseColor("#33FFFFFF"));
panel.setBackground(panelBg);
// Title
TextView title = new TextView(this);
title.setText(isEdit ? "EDIT PROJECT" : "NEW PROJECT");
title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
title.setTextColor(Color.parseColor("#66FFFFFF"));
title.setTypeface(monoFont);
title.setLetterSpacing(0.15f);
LinearLayout.LayoutParams tP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
tP.bottomMargin = dpToPx(16);
title.setLayoutParams(tP);
panel.addView(title);
// Name label
TextView nameLabel = new TextView(this);
nameLabel.setText("PROJECT NAME");
nameLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 9);
nameLabel.setTextColor(Color.parseColor("#66FFFFFF"));
nameLabel.setTypeface(monoFont);
panel.addView(nameLabel);
// Name input
EditText nameInput = new EditText(this);
nameInput.setText(currentName);
nameInput.setTextColor(Color.WHITE);
nameInput.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
nameInput.setTypeface(monoFont);
nameInput.setSingleLine(true);
nameInput.setHint("Enter project name");
nameInput.setHintTextColor(Color.parseColor("#33FFFFFF"));
nameInput.setPadding(dpToPx(12), dpToPx(10), dpToPx(12), dpToPx(10));
nameInput.setShowSoftInputOnFocus(false);
GradientDrawable niBg = new GradientDrawable();
niBg.setCornerRadius(dpToPx(8));
niBg.setColor(Color.parseColor("#0DFFFFFF"));
niBg.setStroke(dpToPx(1), Color.parseColor("#1AFFFFFF"));
nameInput.setBackground(niBg);
LinearLayout.LayoutParams niP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
niP.topMargin = dpToPx(4);
niP.bottomMargin = dpToPx(14);
nameInput.setLayoutParams(niP);
nameInput.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) AeThexKeyboard.attachToEditText(this, nameInput);
});
panel.addView(nameInput);
// Description label
TextView descLabel = new TextView(this);
descLabel.setText("DESCRIPTION");
descLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 9);
descLabel.setTextColor(Color.parseColor("#66FFFFFF"));
descLabel.setTypeface(monoFont);
panel.addView(descLabel);
// Description input
EditText descInput = new EditText(this);
descInput.setText(currentDesc);
descInput.setTextColor(Color.WHITE);
descInput.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
descInput.setTypeface(monoFont);
descInput.setMaxLines(3);
descInput.setHint("Optional description");
descInput.setHintTextColor(Color.parseColor("#33FFFFFF"));
descInput.setPadding(dpToPx(12), dpToPx(10), dpToPx(12), dpToPx(10));
descInput.setShowSoftInputOnFocus(false);
GradientDrawable diBg = new GradientDrawable();
diBg.setCornerRadius(dpToPx(8));
diBg.setColor(Color.parseColor("#0DFFFFFF"));
diBg.setStroke(dpToPx(1), Color.parseColor("#1AFFFFFF"));
descInput.setBackground(diBg);
LinearLayout.LayoutParams diP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
diP.topMargin = dpToPx(4);
diP.bottomMargin = dpToPx(14);
descInput.setLayoutParams(diP);
descInput.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) AeThexKeyboard.attachToEditText(this, descInput);
});
panel.addView(descInput);
// Status selector (only for edit)
final int[] selectedStatus = {currentStatus};
if (isEdit) {
TextView statusLabel = new TextView(this);
statusLabel.setText("STATUS");
statusLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 9);
statusLabel.setTextColor(Color.parseColor("#66FFFFFF"));
statusLabel.setTypeface(monoFont);
panel.addView(statusLabel);
LinearLayout statusRow = new LinearLayout(this);
statusRow.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams srP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
srP.topMargin = dpToPx(6);
srP.bottomMargin = dpToPx(16);
statusRow.setLayoutParams(srP);
TextView[] statusChips = new TextView[STATUSES.length];
for (int i = 0; i < STATUSES.length; i++) {
final int idx = i;
statusChips[i] = new TextView(this);
statusChips[i].setText(STATUSES[i]);
statusChips[i].setTextSize(TypedValue.COMPLEX_UNIT_SP, 9);
statusChips[i].setTypeface(monoFont);
statusChips[i].setPadding(dpToPx(8), dpToPx(6), dpToPx(8), dpToPx(6));
statusChips[i].setGravity(Gravity.CENTER);
LinearLayout.LayoutParams scP = new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
scP.setMarginEnd(dpToPx(4));
statusChips[i].setLayoutParams(scP);
updateStatusChipStyle(statusChips[i], idx, selectedStatus[0] == idx);
statusChips[i].setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
selectedStatus[0] = idx;
for (int j = 0; j < statusChips.length; j++) {
updateStatusChipStyle(statusChips[j], j, j == idx);
}
});
statusRow.addView(statusChips[i]);
}
panel.addView(statusRow);
}
// Button row
LinearLayout btnRow = new LinearLayout(this);
btnRow.setOrientation(LinearLayout.HORIZONTAL);
btnRow.setGravity(Gravity.END);
// Cancel
TextView cancelBtn = new TextView(this);
cancelBtn.setText("CANCEL");
cancelBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
cancelBtn.setTextColor(Color.parseColor("#66FFFFFF"));
cancelBtn.setTypeface(monoFont);
cancelBtn.setPadding(dpToPx(16), dpToPx(10), dpToPx(16), dpToPx(10));
cancelBtn.setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
AeThexKeyboard.dismissKeyboard(this);
dismissOverlay("project_form_overlay");
});
btnRow.addView(cancelBtn);
// Save
TextView saveBtn = new TextView(this);
saveBtn.setText(isEdit ? "SAVE" : "CREATE");
saveBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
saveBtn.setTextColor(Color.WHITE);
saveBtn.setTypeface(monoFont, Typeface.BOLD);
saveBtn.setGravity(Gravity.CENTER);
saveBtn.setPadding(dpToPx(20), dpToPx(10), dpToPx(20), dpToPx(10));
GradientDrawable saveBg = new GradientDrawable();
saveBg.setCornerRadius(dpToPx(8));
saveBg.setColor(primaryColor);
saveBtn.setBackground(saveBg);
LinearLayout.LayoutParams svP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
svP.setMarginStart(dpToPx(8));
saveBtn.setLayoutParams(svP);
saveBtn.setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
String name = nameInput.getText().toString().trim();
if (name.isEmpty()) {
AeThexToast.show(this, "Project name required", AeThexToast.Type.INFO);
return;
}
String desc = descInput.getText().toString().trim();
if (isEdit) {
ProjectItem p = projects.get(editIndex);
p.name = name;
p.description = desc;
p.statusIndex = selectedStatus[0];
} else {
projects.add(new ProjectItem(name, desc, 0));
}
saveProjects();
AeThexKeyboard.dismissKeyboard(this);
dismissOverlay("project_form_overlay");
// Rebuild UI
LinearLayout content = (LinearLayout) projectListContainer.getParent().getParent();
content.removeAllViews();
buildProjectsUI(content);
AeThexToast.show(this,
isEdit ? "Project updated" : "Project created",
AeThexToast.Type.SUCCESS);
});
btnRow.addView(saveBtn);
panel.addView(btnRow);
scrim.addView(panel);
scrim.setAlpha(0f);
root.addView(scrim);
scrim.animate().alpha(1f).setDuration(200).start();
nameInput.requestFocus();
nameInput.postDelayed(() -> AeThexKeyboard.attachToEditText(this, nameInput), 200);
}
private void updateStatusChipStyle(TextView chip, int statusIdx, boolean selected) {
int color = Color.parseColor(STATUS_COLORS[statusIdx]);
GradientDrawable bg = new GradientDrawable();
bg.setCornerRadius(dpToPx(6));
if (selected) {
bg.setColor(Color.argb(40, Color.red(color), Color.green(color), Color.blue(color)));
bg.setStroke(dpToPx(1), color);
chip.setTextColor(color);
} else {
bg.setColor(Color.parseColor("#0DFFFFFF"));
bg.setStroke(dpToPx(1), Color.parseColor("#1AFFFFFF"));
chip.setTextColor(Color.parseColor("#4DFFFFFF"));
}
chip.setBackground(bg);
}
private void showDeleteConfirmation(int index) {
int primaryColor = themeManager.getPrimaryColor(this);
FrameLayout root = findViewById(R.id.app_root);
FrameLayout scrim = new FrameLayout(this);
scrim.setTag("project_delete_overlay");
scrim.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
scrim.setBackgroundColor(Color.parseColor("#CC000000"));
LinearLayout panel = new LinearLayout(this);
panel.setOrientation(LinearLayout.VERTICAL);
panel.setGravity(Gravity.CENTER);
FrameLayout.LayoutParams pp = new FrameLayout.LayoutParams(
dpToPx(260), ViewGroup.LayoutParams.WRAP_CONTENT);
pp.gravity = Gravity.CENTER;
panel.setLayoutParams(pp);
panel.setPadding(dpToPx(20), dpToPx(20), dpToPx(20), dpToPx(20));
GradientDrawable panelBg = new GradientDrawable();
panelBg.setCornerRadius(dpToPx(16));
panelBg.setColor(Color.parseColor("#E6111827"));
panelBg.setStroke(dpToPx(1), Color.parseColor("#33FFFFFF"));
panel.setBackground(panelBg);
// Warning icon
TextView icon = new TextView(this);
icon.setText("");
icon.setTextSize(TypedValue.COMPLEX_UNIT_SP, 28);
icon.setGravity(Gravity.CENTER);
panel.addView(icon);
// Message
TextView msg = new TextView(this);
msg.setText("Delete \"" + projects.get(index).name + "\"?");
msg.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
msg.setTextColor(Color.WHITE);
msg.setTypeface(displayFont);
msg.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams mP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mP.topMargin = dpToPx(10);
mP.bottomMargin = dpToPx(6);
msg.setLayoutParams(mP);
panel.addView(msg);
TextView sub = new TextView(this);
sub.setText("This action cannot be undone.");
sub.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
sub.setTextColor(Color.parseColor("#66FFFFFF"));
sub.setTypeface(monoFont);
sub.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams sP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
sP.bottomMargin = dpToPx(16);
sub.setLayoutParams(sP);
panel.addView(sub);
// Button row
LinearLayout btnRow = new LinearLayout(this);
btnRow.setOrientation(LinearLayout.HORIZONTAL);
btnRow.setGravity(Gravity.CENTER);
TextView cancelBtn = new TextView(this);
cancelBtn.setText("CANCEL");
cancelBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
cancelBtn.setTextColor(Color.parseColor("#66FFFFFF"));
cancelBtn.setTypeface(monoFont);
cancelBtn.setPadding(dpToPx(20), dpToPx(10), dpToPx(20), dpToPx(10));
cancelBtn.setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
dismissOverlay("project_delete_overlay");
});
btnRow.addView(cancelBtn);
TextView delBtn = new TextView(this);
delBtn.setText("DELETE");
delBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
delBtn.setTextColor(Color.WHITE);
delBtn.setTypeface(monoFont, Typeface.BOLD);
delBtn.setGravity(Gravity.CENTER);
delBtn.setPadding(dpToPx(20), dpToPx(10), dpToPx(20), dpToPx(10));
GradientDrawable delBg = new GradientDrawable();
delBg.setCornerRadius(dpToPx(8));
delBg.setColor(Color.parseColor("#EF4444"));
delBtn.setBackground(delBg);
LinearLayout.LayoutParams dbP = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
dbP.setMarginStart(dpToPx(10));
delBtn.setLayoutParams(dbP);
delBtn.setOnClickListener(v -> {
SoundManager.getInstance().play(SoundManager.Sound.CLICK);
String removedName = projects.get(index).name;
projects.remove(index);
saveProjects();
dismissOverlay("project_delete_overlay");
// Rebuild UI
LinearLayout content = (LinearLayout) projectListContainer.getParent().getParent();
content.removeAllViews();
buildProjectsUI(content);
AeThexToast.show(this, removedName + " deleted", AeThexToast.Type.INFO);
});
btnRow.addView(delBtn);
panel.addView(btnRow);
scrim.addView(panel);
scrim.setAlpha(0f);
root.addView(scrim);
scrim.animate().alpha(1f).setDuration(200).start();
}
private void dismissOverlay(String tag) {
FrameLayout root = findViewById(R.id.app_root);
View overlay = root.findViewWithTag(tag);
if (overlay != null) {
overlay.animate().alpha(0f).setDuration(150).withEndAction(() -> {
root.removeView(overlay);
}).start();
}
}
// ── Persistence ──
private void loadProjects() {
projects.clear();
String json = prefs.getString(KEY_PROJECTS, null);
if (json != null) {
try {
JSONArray arr = new JSONArray(json);
for (int i = 0; i < arr.length(); i++) {
JSONObject obj = arr.getJSONObject(i);
projects.add(new ProjectItem(
obj.getString("name"),
obj.optString("desc", ""),
obj.getInt("status")
));
}
} catch (Exception ignored) {}
}
// Seed default projects if none exist
if (projects.isEmpty()) {
projects.add(new ProjectItem("AeThex Mobile", "Android native client", 1));
projects.add(new ProjectItem("Security Audit", "Q1 security review", 2));
projects.add(new ProjectItem("Data Pipeline", "Streaming analytics pipeline", 3));
projects.add(new ProjectItem("UI Redesign", "Glass morphism theme system", 1));
projects.add(new ProjectItem("API Gateway", "REST & WebSocket endpoints", 0));
saveProjects();
}
}
private void saveProjects() {
try {
JSONArray arr = new JSONArray();
for (ProjectItem p : projects) {
JSONObject obj = new JSONObject();
obj.put("name", p.name);
obj.put("desc", p.description);
obj.put("status", p.statusIndex);
arr.put(obj);
}
prefs.edit().putString(KEY_PROJECTS, arr.toString()).apply();
} catch (Exception ignored) {}
}
@SuppressWarnings("deprecation")
@Override
public void onBackPressed() {
FrameLayout root = findViewById(R.id.app_root);
// Check for overlays
View formOverlay = root.findViewWithTag("project_form_overlay");
if (formOverlay != null) {
AeThexKeyboard.dismissKeyboard(this);
dismissOverlay("project_form_overlay");
return;
}
View delOverlay = root.findViewWithTag("project_delete_overlay");
if (delOverlay != null) {
dismissOverlay("project_delete_overlay");
return;
}
SoundManager.getInstance().play(SoundManager.Sound.CLOSE);
super.onBackPressed();
overridePendingTransition(R.anim.scale_in, R.anim.slide_down_out);
}
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) hideSystemUI();
}
private void hideSystemUI() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
final WindowInsetsController c = getWindow().getInsetsController();
if (c != null) {
c.hide(WindowInsets.Type.systemBars());
c.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
} else {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN);
}
}
}