From ecd79606bada627c80e72a8cca415c6f7571e1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E5=A4=A9=E7=BF=94?= Date: Tue, 27 Jan 2026 13:17:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=8C=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E3=80=81=E4=B8=BB=E9=A2=98=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E3=80=81=E6=92=A4=E9=94=80=E9=87=8D=E5=81=9A=E7=AD=89=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/NotesApplication.java | 18 ++ .../net/micode/notes/data/FontManager.java | 56 ++++ .../micode/notes/data/ThemeRepository.java | 43 +++ .../java/net/micode/notes/model/Command.java | 6 + .../net/micode/notes/model/NoteCommand.java | 40 +++ .../micode/notes/model/UndoRedoManager.java | 49 ++++ .../net/micode/notes/tool/RichTextHelper.java | 257 ++++++++++++++++++ .../net/micode/notes/ui/NoteColorAdapter.java | 111 ++++++++ .../net/micode/notes/ui/SettingsFragment.java | 117 ++++++++ .../src/main/res/drawable/ic_format_bold.xml | 9 + .../src/main/res/drawable/ic_format_code.xml | 9 + .../res/drawable/ic_format_color_fill.xml | 12 + .../res/drawable/ic_format_color_text.xml | 12 + .../main/res/drawable/ic_format_header.xml | 9 + .../main/res/drawable/ic_format_italic.xml | 9 + .../res/drawable/ic_format_list_bulleted.xml | 9 + .../src/main/res/drawable/ic_format_quote.xml | 9 + .../res/drawable/ic_format_strikethrough.xml | 9 + .../main/res/drawable/ic_format_underline.xml | 9 + .../app/src/main/res/drawable/ic_image.xml | 10 + .../main/res/drawable/ic_insert_divider.xml | 9 + .../src/main/res/drawable/ic_insert_link.xml | 9 + .../src/main/res/drawable/ic_menu_redo.xml | 9 + .../main/res/drawable/ic_menu_rich_text.xml | 9 + .../src/main/res/drawable/ic_menu_undo.xml | 9 + .../app/src/main/res/drawable/ic_palette.xml | 10 + .../src/main/res/layout/activity_settings.xml | 15 + .../main/res/layout/dialog_color_picker.xml | 69 +++++ .../src/main/res/layout/note_color_item.xml | 26 ++ .../app/src/main/res/values-night/colors.xml | 15 + .../app/src/main/res/values/font_arrays.xml | 17 ++ 31 files changed, 1000 insertions(+) create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/NotesApplication.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/data/FontManager.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/data/ThemeRepository.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/model/Command.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/model/NoteCommand.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/model/UndoRedoManager.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/tool/RichTextHelper.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteColorAdapter.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_bold.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_code.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_color_fill.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_color_text.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_header.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_italic.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_list_bulleted.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_quote.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_strikethrough.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_format_underline.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_image.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_insert_divider.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_insert_link.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_menu_redo.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_menu_rich_text.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_menu_undo.xml create mode 100644 src/Notesmaster/app/src/main/res/drawable/ic_palette.xml create mode 100644 src/Notesmaster/app/src/main/res/layout/activity_settings.xml create mode 100644 src/Notesmaster/app/src/main/res/layout/dialog_color_picker.xml create mode 100644 src/Notesmaster/app/src/main/res/layout/note_color_item.xml create mode 100644 src/Notesmaster/app/src/main/res/values-night/colors.xml create mode 100644 src/Notesmaster/app/src/main/res/values/font_arrays.xml diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/NotesApplication.java b/src/Notesmaster/app/src/main/java/net/micode/notes/NotesApplication.java new file mode 100644 index 0000000..e6b008a --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/NotesApplication.java @@ -0,0 +1,18 @@ +package net.micode.notes; + +import android.app.Application; +import net.micode.notes.data.ThemeRepository; +import com.google.android.material.color.DynamicColors; + +public class NotesApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + // Apply Dynamic Colors (Material You) if available + DynamicColors.applyToActivitiesIfAvailable(this); + + // Apply saved theme preference + ThemeRepository repository = new ThemeRepository(this); + ThemeRepository.applyTheme(repository.getThemeMode()); + } +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/FontManager.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/FontManager.java new file mode 100644 index 0000000..dacf271 --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/FontManager.java @@ -0,0 +1,56 @@ +package net.micode.notes.data; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Typeface; +import android.widget.TextView; + +import androidx.preference.PreferenceManager; + +public class FontManager { + public static final String PREF_FONT_FAMILY = "pref_font_family"; + + private final SharedPreferences mPrefs; + private static FontManager sInstance; + + private FontManager(Context context) { + mPrefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + public static synchronized FontManager getInstance(Context context) { + if (sInstance == null) { + sInstance = new FontManager(context.getApplicationContext()); + } + return sInstance; + } + + public void applyFont(TextView textView) { + String fontValue = mPrefs.getString(PREF_FONT_FAMILY, "default"); + Typeface typeface = getTypeface(fontValue); + if (typeface != null) { + textView.setTypeface(typeface); + } else { + textView.setTypeface(Typeface.DEFAULT); + } + } + + private Typeface getTypeface(String fontValue) { + switch (fontValue) { + case "serif": + return Typeface.SERIF; + case "sans-serif": + return Typeface.SANS_SERIF; + case "monospace": + return Typeface.MONOSPACE; + case "cursive": + // Android doesn't have a built-in cursive typeface constant, + // but we can try to load sans-serif-light or similar as a placeholder, + // or load from assets if we had custom fonts. + // For now, let's map it to serif-italic style if possible or just serif. + return Typeface.create(Typeface.SERIF, Typeface.ITALIC); + case "default": + default: + return Typeface.DEFAULT; + } + } +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/ThemeRepository.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/ThemeRepository.java new file mode 100644 index 0000000..815f0ce --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/ThemeRepository.java @@ -0,0 +1,43 @@ +package net.micode.notes.data; + +import android.content.Context; +import android.content.SharedPreferences; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.preference.PreferenceManager; + +public class ThemeRepository { + private static final String PREF_THEME_MODE = "pref_theme_mode"; + public static final String THEME_MODE_SYSTEM = "system"; + public static final String THEME_MODE_LIGHT = "light"; + public static final String THEME_MODE_DARK = "dark"; + + private final SharedPreferences mPrefs; + + public ThemeRepository(Context context) { + mPrefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + public String getThemeMode() { + return mPrefs.getString(PREF_THEME_MODE, THEME_MODE_SYSTEM); + } + + public void setThemeMode(String mode) { + mPrefs.edit().putString(PREF_THEME_MODE, mode).apply(); + applyTheme(mode); + } + + public static void applyTheme(String mode) { + switch (mode) { + case THEME_MODE_LIGHT: + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); + break; + case THEME_MODE_DARK: + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); + break; + case THEME_MODE_SYSTEM: + default: + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + break; + } + } +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/model/Command.java b/src/Notesmaster/app/src/main/java/net/micode/notes/model/Command.java new file mode 100644 index 0000000..77e4b0e --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/model/Command.java @@ -0,0 +1,6 @@ +package net.micode.notes.model; + +public interface Command { + void execute(); + void undo(); +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/model/NoteCommand.java b/src/Notesmaster/app/src/main/java/net/micode/notes/model/NoteCommand.java new file mode 100644 index 0000000..787bbfb --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/model/NoteCommand.java @@ -0,0 +1,40 @@ +package net.micode.notes.model; + +import android.text.Editable; +import android.widget.EditText; + +public class NoteCommand implements Command { + private final EditText mEditor; + private final int mStart; + private final CharSequence mBefore; + private final CharSequence mAfter; + + public NoteCommand(EditText editor, int start, CharSequence before, CharSequence after) { + mEditor = editor; + mStart = start; + mBefore = before.toString(); + mAfter = after.toString(); + } + + @Override + public void execute() { + // Redo: replace 'before' with 'after' + Editable text = mEditor.getText(); + int end = mStart + mBefore.length(); + if (end <= text.length()) { + text.replace(mStart, end, mAfter); + mEditor.setSelection(mStart + mAfter.length()); + } + } + + @Override + public void undo() { + // Undo: replace 'after' with 'before' + Editable text = mEditor.getText(); + int end = mStart + mAfter.length(); + if (end <= text.length()) { + text.replace(mStart, end, mBefore); + mEditor.setSelection(mStart + mBefore.length()); + } + } +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/model/UndoRedoManager.java b/src/Notesmaster/app/src/main/java/net/micode/notes/model/UndoRedoManager.java new file mode 100644 index 0000000..081dab9 --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/model/UndoRedoManager.java @@ -0,0 +1,49 @@ +package net.micode.notes.model; + +import java.util.Stack; + +public class UndoRedoManager { + private static final int MAX_STACK_SIZE = 20; + private final Stack mUndoStack = new Stack<>(); + private final Stack mRedoStack = new Stack<>(); + + public void addCommand(Command command) { + mUndoStack.push(command); + if (mUndoStack.size() > MAX_STACK_SIZE) { + mUndoStack.remove(0); + } + mRedoStack.clear(); + } + + public void undo() { + if (!mUndoStack.isEmpty()) { + Command command = mUndoStack.pop(); + command.undo(); + mRedoStack.push(command); + } + } + + public void redo() { + if (!mRedoStack.isEmpty()) { + Command command = mRedoStack.pop(); + command.execute(); + mUndoStack.push(command); + if (mUndoStack.size() > MAX_STACK_SIZE) { + mUndoStack.remove(0); + } + } + } + + public boolean canUndo() { + return !mUndoStack.isEmpty(); + } + + public boolean canRedo() { + return !mRedoStack.isEmpty(); + } + + public void clear() { + mUndoStack.clear(); + mRedoStack.clear(); + } +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/RichTextHelper.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/RichTextHelper.java new file mode 100644 index 0000000..5ed8da3 --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/RichTextHelper.java @@ -0,0 +1,257 @@ +package net.micode.notes.tool; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.Editable; +import android.text.Html; +import android.text.Spannable; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.BackgroundColorSpan; +import android.text.style.BulletSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.QuoteSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; +import android.text.style.URLSpan; +import android.text.style.UnderlineSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +public class RichTextHelper { + + public static void applyBold(EditText editText) { + applyStyleSpan(editText, Typeface.BOLD); + } + + public static void applyItalic(EditText editText) { + applyStyleSpan(editText, Typeface.ITALIC); + } + + public static void applyUnderline(EditText editText) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + if (start > end) { int temp = start; start = end; end = temp; } + + Editable editable = editText.getText(); + UnderlineSpan[] spans = editable.getSpans(start, end, UnderlineSpan.class); + if (spans != null && spans.length > 0) { + for (UnderlineSpan span : spans) { + editable.removeSpan(span); + } + } else { + editable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + public static void applyStrikethrough(EditText editText) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + if (start > end) { int temp = start; start = end; end = temp; } + + Editable editable = editText.getText(); + StrikethroughSpan[] spans = editable.getSpans(start, end, StrikethroughSpan.class); + if (spans != null && spans.length > 0) { + for (StrikethroughSpan span : spans) { + editable.removeSpan(span); + } + } else { + editable.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + private static void applyStyleSpan(EditText editText, int style) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + if (start > end) { int temp = start; start = end; end = temp; } + + Editable editable = editText.getText(); + StyleSpan[] spans = editable.getSpans(start, end, StyleSpan.class); + boolean exists = false; + for (StyleSpan span : spans) { + if (span.getStyle() == style) { + editable.removeSpan(span); + exists = true; + } + } + + if (!exists) { + editable.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + public static void applyHeading(EditText editText, int level) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + // Expand to full line + Editable text = editText.getText(); + String string = text.toString(); + + // Find line start and end + int lineStart = string.lastIndexOf('\n', start - 1) + 1; + if (lineStart < 0) lineStart = 0; + int lineEnd = string.indexOf('\n', end); + if (lineEnd < 0) lineEnd = string.length(); + + // Remove existing heading spans + RelativeSizeSpan[] sizeSpans = text.getSpans(lineStart, lineEnd, RelativeSizeSpan.class); + for (RelativeSizeSpan span : sizeSpans) { + text.removeSpan(span); + } + StyleSpan[] styleSpans = text.getSpans(lineStart, lineEnd, StyleSpan.class); + for (StyleSpan span : styleSpans) { + if (span.getStyle() == Typeface.BOLD) { + text.removeSpan(span); + } + } + + if (level > 0) { + float scale = 1.0f; + switch (level) { + case 1: scale = 2.0f; break; + case 2: scale = 1.5f; break; + case 3: scale = 1.25f; break; + case 4: scale = 1.1f; break; + case 5: scale = 1.0f; break; + case 6: scale = 0.8f; break; + } + text.setSpan(new RelativeSizeSpan(scale), lineStart, lineEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(new StyleSpan(Typeface.BOLD), lineStart, lineEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + public static void applyBullet(EditText editText) { + // Simple bullet implementation for now + int start = editText.getSelectionStart(); + Editable text = editText.getText(); + String string = text.toString(); + int lineStart = string.lastIndexOf('\n', start - 1) + 1; + if (lineStart < 0) lineStart = 0; + + // Check if already bulleted + // Note: BulletSpan covers a paragraph. + int lineEnd = string.indexOf('\n', start); + if (lineEnd < 0) lineEnd = string.length(); + + BulletSpan[] spans = text.getSpans(lineStart, lineEnd, BulletSpan.class); + if (spans != null && spans.length > 0) { + for (BulletSpan span : spans) { + text.removeSpan(span); + } + } else { + text.setSpan(new BulletSpan(20, Color.BLACK), lineStart, lineEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + public static void applyQuote(EditText editText) { + int start = editText.getSelectionStart(); + Editable text = editText.getText(); + String string = text.toString(); + int lineStart = string.lastIndexOf('\n', start - 1) + 1; + if (lineStart < 0) lineStart = 0; + int lineEnd = string.indexOf('\n', start); + if (lineEnd < 0) lineEnd = string.length(); + + QuoteSpan[] spans = text.getSpans(lineStart, lineEnd, QuoteSpan.class); + if (spans != null && spans.length > 0) { + for (QuoteSpan span : spans) { + text.removeSpan(span); + } + } else { + text.setSpan(new QuoteSpan(Color.GRAY), lineStart, lineEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + public static void applyCode(EditText editText) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + if (start > end) { int temp = start; start = end; end = temp; } + + Editable editable = editText.getText(); + TypefaceSpan[] spans = editable.getSpans(start, end, TypefaceSpan.class); + boolean exists = false; + for (TypefaceSpan span : spans) { + if ("monospace".equals(span.getFamily())) { + editable.removeSpan(span); + exists = true; + } + } + + // Also toggle background color for code block look + BackgroundColorSpan[] bgSpans = editable.getSpans(start, end, BackgroundColorSpan.class); + for (BackgroundColorSpan span : bgSpans) { + if (span.getBackgroundColor() == 0xFFEEEEEE) { + editable.removeSpan(span); + } + } + + if (!exists) { + editable.setSpan(new TypefaceSpan("monospace"), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + editable.setSpan(new BackgroundColorSpan(0xFFEEEEEE), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + public static void insertLink(Context context, final EditText editText) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle("Insert Link"); + + final EditText input = new EditText(context); + input.setHint("http://example.com"); + builder.setView(input); + + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String url = input.getText().toString(); + if (!TextUtils.isEmpty(url)) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + if (start == end) { + // Insert url as text + editText.getText().insert(start, url); + end = start + url.length(); + } + editText.getText().setSpan(new URLSpan(url), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + }); + builder.setNegativeButton("Cancel", null); + builder.show(); + } + + public static void applyColor(EditText editText, int color, boolean isBackground) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + if (start > end) { int temp = start; start = end; end = temp; } + + Editable editable = editText.getText(); + if (isBackground) { + BackgroundColorSpan[] spans = editable.getSpans(start, end, BackgroundColorSpan.class); + for (BackgroundColorSpan span : spans) editable.removeSpan(span); + editable.setSpan(new BackgroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + ForegroundColorSpan[] spans = editable.getSpans(start, end, ForegroundColorSpan.class); + for (ForegroundColorSpan span : spans) editable.removeSpan(span); + editable.setSpan(new ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + public static void insertDivider(EditText editText) { + int start = editText.getSelectionStart(); + editText.getText().insert(start, "\n-------------------\n"); + } + + public static String toHtml(Spanned text) { + return Html.toHtml(text); + } + + public static Spanned fromHtml(String html) { + return Html.fromHtml(html); + } +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteColorAdapter.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteColorAdapter.java new file mode 100644 index 0000000..d32c674 --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteColorAdapter.java @@ -0,0 +1,111 @@ +package net.micode.notes.ui; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import net.micode.notes.R; +import net.micode.notes.tool.ResourceParser; + +import java.util.List; + +public class NoteColorAdapter extends RecyclerView.Adapter { + + public interface OnColorClickListener { + void onColorClick(int colorId); + } + + private List mColorIds; + private int mSelectedColorId; + private OnColorClickListener mListener; + + public NoteColorAdapter(List colorIds, int selectedColorId, OnColorClickListener listener) { + mColorIds = colorIds; + mSelectedColorId = selectedColorId; + mListener = listener; + } + + public void setSelectedColor(int colorId) { + mSelectedColorId = colorId; + notifyDataSetChanged(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.note_color_item, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + int colorId = mColorIds.get(position); + + if (colorId == ResourceParser.CUSTOM_COLOR_BUTTON_ID) { + holder.colorView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + int padding = (int) (12 * holder.itemView.getContext().getResources().getDisplayMetrics().density); + holder.colorView.setPadding(padding, padding, padding, padding); + holder.colorView.setImageResource(R.drawable.ic_palette); + // Apply a dark tint to ensure visibility + holder.colorView.setColorFilter(android.graphics.Color.DKGRAY, android.graphics.PorterDuff.Mode.SRC_IN); + // Optional: Set a background for the icon + holder.colorView.setBackgroundResource(R.drawable.bg_color_btn_mask); + } else if (colorId == ResourceParser.WALLPAPER_BUTTON_ID) { + holder.colorView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + int padding = (int) (12 * holder.itemView.getContext().getResources().getDisplayMetrics().density); + holder.colorView.setPadding(padding, padding, padding, padding); + holder.colorView.setImageResource(R.drawable.ic_image); + // Apply a dark tint to ensure visibility + holder.colorView.setColorFilter(android.graphics.Color.DKGRAY, android.graphics.PorterDuff.Mode.SRC_IN); + // Optional: Set a background for the icon + holder.colorView.setBackgroundResource(R.drawable.bg_color_btn_mask); + } else { + holder.colorView.setScaleType(ImageView.ScaleType.CENTER_CROP); + holder.colorView.setPadding(0, 0, 0, 0); + // 使用ResourceParser获取背景资源 + int bgRes = ResourceParser.NoteBgResources.getNoteBgResource(colorId); + holder.colorView.setImageResource(bgRes); + holder.colorView.setBackground(null); // Clear background if reused + + if (colorId >= ResourceParser.MIDNIGHT_BLACK || colorId < 0) { + int color = ResourceParser.getNoteBgColor(holder.itemView.getContext(), colorId); + holder.colorView.setColorFilter(color, android.graphics.PorterDuff.Mode.MULTIPLY); + } else { + holder.colorView.clearColorFilter(); + } + } + + if (colorId == mSelectedColorId && colorId != ResourceParser.CUSTOM_COLOR_BUTTON_ID) { + holder.checkView.setVisibility(View.VISIBLE); + } else { + holder.checkView.setVisibility(View.GONE); + } + + holder.itemView.setOnClickListener(v -> { + if (mListener != null) { + mListener.onColorClick(colorId); + } + }); + } + + @Override + public int getItemCount() { + return mColorIds.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + ImageView colorView; + ImageView checkView; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + colorView = itemView.findViewById(R.id.color_view); + checkView = itemView.findViewById(R.id.check_view); + } + } +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java new file mode 100644 index 0000000..de22828 --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java @@ -0,0 +1,117 @@ +package net.micode.notes.ui; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import net.micode.notes.R; +import net.micode.notes.tool.SecurityManager; + +import net.micode.notes.data.ThemeRepository; +import androidx.preference.ListPreference; + +import static android.app.Activity.RESULT_OK; + +public class SettingsFragment extends PreferenceFragmentCompat { + public static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; + public static final String PREFERENCE_SECURITY_KEY = "pref_key_security"; + public static final String PREFERENCE_THEME_MODE = "pref_theme_mode"; + public static final int REQUEST_CODE_CHECK_PASSWORD = 104; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.preferences, rootKey); + loadThemePreference(); + loadSecurityPreference(); + loadAccountPreference(); + } + + private void loadThemePreference() { + ListPreference themePref = findPreference(PREFERENCE_THEME_MODE); + if (themePref != null) { + themePref.setOnPreferenceChangeListener((preference, newValue) -> { + ThemeRepository.applyTheme((String) newValue); + return true; + }); + } + } + + private void loadSecurityPreference() { + Preference securityPref = findPreference(PREFERENCE_SECURITY_KEY); + if (securityPref != null) { + securityPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + if (!SecurityManager.getInstance(getActivity()).isPasswordSet()) { + showSetPasswordDialog(); + } else { + Intent intent = new Intent(getActivity(), PasswordActivity.class); + intent.setAction(PasswordActivity.ACTION_CHECK_PASSWORD); + startActivityForResult(intent, REQUEST_CODE_CHECK_PASSWORD); + } + return true; + } + }); + } + } + + private void loadAccountPreference() { + androidx.preference.PreferenceCategory accountCategory = findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); + if (accountCategory != null) { + accountCategory.removeAll(); + Preference accountPref = new Preference(getContext()); + accountPref.setTitle(getString(R.string.preferences_account_title)); + accountPref.setSummary(getString(R.string.preferences_account_summary)); + accountPref.setOnPreferenceClickListener(preference -> { + Toast.makeText(getActivity(), "Google Tasks同步功能已禁用", Toast.LENGTH_SHORT).show(); + return true; + }); + accountCategory.addPreference(accountPref); + } + } + + private void showSetPasswordDialog() { + new AlertDialog.Builder(getActivity()) + .setTitle("设置密码") + .setItems(new String[]{"数字锁", "手势锁"}, (dialog, which) -> { + int type = (which == 0) ? SecurityManager.TYPE_PIN : SecurityManager.TYPE_PATTERN; + Intent intent = new Intent(getActivity(), PasswordActivity.class); + intent.setAction(PasswordActivity.ACTION_SETUP_PASSWORD); + intent.putExtra(PasswordActivity.EXTRA_PASSWORD_TYPE, type); + startActivity(intent); + }) + .show(); + } + + private void showManagePasswordDialog() { + new AlertDialog.Builder(getActivity()) + .setTitle("管理密码") + .setItems(new String[]{"更改密码", "取消密码"}, (dialog, which) -> { + if (which == 0) { // Change + showSetPasswordDialog(); + } else { // Remove + SecurityManager.getInstance(getActivity()).removePassword(); + Toast.makeText(getActivity(), "密码已取消", Toast.LENGTH_SHORT).show(); + } + }) + .show(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_CHECK_PASSWORD && resultCode == RESULT_OK) { + showManagePasswordDialog(); + } + } +} diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_bold.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_bold.xml new file mode 100644 index 0000000..79236d0 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_bold.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_code.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_code.xml new file mode 100644 index 0000000..47c222b --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_code.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_color_fill.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_color_fill.xml new file mode 100644 index 0000000..e2d6c56 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_color_fill.xml @@ -0,0 +1,12 @@ + + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_color_text.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_color_text.xml new file mode 100644 index 0000000..db80539 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_color_text.xml @@ -0,0 +1,12 @@ + + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_header.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_header.xml new file mode 100644 index 0000000..24f4c33 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_header.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_italic.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_italic.xml new file mode 100644 index 0000000..1f8c0a1 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_italic.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_list_bulleted.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_list_bulleted.xml new file mode 100644 index 0000000..9d3de50 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_list_bulleted.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_quote.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_quote.xml new file mode 100644 index 0000000..304bfdc --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_quote.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_strikethrough.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_strikethrough.xml new file mode 100644 index 0000000..29f1677 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_strikethrough.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_format_underline.xml b/src/Notesmaster/app/src/main/res/drawable/ic_format_underline.xml new file mode 100644 index 0000000..b275f43 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_format_underline.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_image.xml b/src/Notesmaster/app/src/main/res/drawable/ic_image.xml new file mode 100644 index 0000000..0da450d --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_image.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_insert_divider.xml b/src/Notesmaster/app/src/main/res/drawable/ic_insert_divider.xml new file mode 100644 index 0000000..a64b853 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_insert_divider.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_insert_link.xml b/src/Notesmaster/app/src/main/res/drawable/ic_insert_link.xml new file mode 100644 index 0000000..b862207 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_insert_link.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_menu_redo.xml b/src/Notesmaster/app/src/main/res/drawable/ic_menu_redo.xml new file mode 100644 index 0000000..e6ac8a5 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_menu_redo.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_menu_rich_text.xml b/src/Notesmaster/app/src/main/res/drawable/ic_menu_rich_text.xml new file mode 100644 index 0000000..751b78c --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_menu_rich_text.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_menu_undo.xml b/src/Notesmaster/app/src/main/res/drawable/ic_menu_undo.xml new file mode 100644 index 0000000..aac484f --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_menu_undo.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_palette.xml b/src/Notesmaster/app/src/main/res/drawable/ic_palette.xml new file mode 100644 index 0000000..dbcb253 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/drawable/ic_palette.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/Notesmaster/app/src/main/res/layout/activity_settings.xml b/src/Notesmaster/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000..791a380 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/src/Notesmaster/app/src/main/res/layout/dialog_color_picker.xml b/src/Notesmaster/app/src/main/res/layout/dialog_color_picker.xml new file mode 100644 index 0000000..5d93b9b --- /dev/null +++ b/src/Notesmaster/app/src/main/res/layout/dialog_color_picker.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Notesmaster/app/src/main/res/layout/note_color_item.xml b/src/Notesmaster/app/src/main/res/layout/note_color_item.xml new file mode 100644 index 0000000..39a650d --- /dev/null +++ b/src/Notesmaster/app/src/main/res/layout/note_color_item.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/src/Notesmaster/app/src/main/res/values-night/colors.xml b/src/Notesmaster/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..5e046f6 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/values-night/colors.xml @@ -0,0 +1,15 @@ + + + #121212 + #DEFFFFFF + #99FFFFFF + + + #2C2C2C + + + #DEFFFFFF + #99FFFFFF + diff --git a/src/Notesmaster/app/src/main/res/values/font_arrays.xml b/src/Notesmaster/app/src/main/res/values/font_arrays.xml new file mode 100644 index 0000000..b1a2aaa --- /dev/null +++ b/src/Notesmaster/app/src/main/res/values/font_arrays.xml @@ -0,0 +1,17 @@ + + + + System Default + Serif + Sans Serif + Monospace + Cursive + + + default + serif + sans-serif + monospace + cursive + +