mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-17 22:07:20 +00:00
- 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
824 lines
34 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|