合并新增的富文本编辑功能

蒋天翔 4 weeks ago
commit 66b87244f0

@ -25,6 +25,7 @@
<!-- ==================== 应用配置 ==================== -->
<application
android:name=".NotesApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@ -171,7 +172,7 @@
android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
android:theme="@style/Theme.Notesmaster" >
</activity>
<!-- ==================== 设置活动 ==================== -->
@ -180,14 +181,14 @@
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
android:theme="@style/Theme.Notesmaster" >
</activity>
<!-- ==================== 密码设置/验证活动 ==================== -->
<activity
android:name=".ui.PasswordActivity"
android:label="Password"
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
android:theme="@style/Theme.Notesmaster"
android:windowSoftInputMode="stateVisible|adjustResize">
</activity>

@ -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());
}
}

@ -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;
}
}
}

@ -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;
}
}
}

@ -0,0 +1,6 @@
package net.micode.notes.model;
public interface Command {
void execute();
void undo();
}

@ -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());
}
}
}

@ -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<Command> mUndoStack = new Stack<>();
private final Stack<Command> 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();
}
}

@ -427,6 +427,27 @@ public class WorkingNote {
}
}
// Wallpaper Support
private String mWallpaperPath;
public void setWallpaper(String path) {
mWallpaperPath = path;
// Ideally we should save this to DB, but for now we might use shared prefs or a separate table
// Or reuse bg_color_id with a special flag if we want to stick to existing schema strictly?
// Better: store in a new column or reuse a data column if possible.
// Given existing schema, let's use DataColumns.DATA5 if available? No DATA5.
// Let's use a SharedPreference for mapping noteId -> wallpaperPath for now to avoid schema migration complexity in this step.
// Or just use a special negative color ID range for wallpapers?
// Actually, let's use a separate storage for wallpapers map: note_id -> uri string
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged(); // Reuse this to trigger refresh
}
}
public String getWallpaperPath() {
return mWallpaperPath;
}
/**
*
* <p>

@ -45,10 +45,40 @@ public class ResourceParser {
/** 红色背景 */
public static final int RED = 4;
// New Presets
public static final int MIDNIGHT_BLACK = 5;
public static final int EYE_CARE_GREEN = 6;
public static final int WARM = 7;
public static final int COOL = 8;
/** 自定义颜色按钮 ID (用于 UI 显示) */
public static final int CUSTOM_COLOR_BUTTON_ID = -100;
/** 壁纸按钮 ID (用于 UI 显示) */
public static final int WALLPAPER_BUTTON_ID = -101;
/** 默认背景颜色 */
public static final int BG_DEFAULT_COLOR = YELLOW;
public static int getNoteBgColor(Context context, int id) {
if (id < 0) {
return id; // Custom color (ARGB)
}
switch (id) {
case YELLOW: return context.getColor(R.color.bg_yellow);
case BLUE: return context.getColor(R.color.bg_blue);
case WHITE: return context.getColor(R.color.bg_white);
case GREEN: return context.getColor(R.color.bg_green);
case RED: return context.getColor(R.color.bg_red);
case MIDNIGHT_BLACK: return context.getColor(R.color.bg_midnight_black);
case EYE_CARE_GREEN: return context.getColor(R.color.bg_eye_care_green);
case WARM: return context.getColor(R.color.bg_warm);
case COOL: return context.getColor(R.color.bg_cool);
default: return context.getColor(R.color.bg_white);
}
}
/** 小号字体 */
public static final int TEXT_SMALL = 0;
@ -97,6 +127,9 @@ public class ResourceParser {
* @return ID
*/
public static int getNoteBgResource(int id) {
if (id >= BG_EDIT_RESOURCES.length || id < 0) {
return R.drawable.edit_white;
}
return BG_EDIT_RESOURCES[id];
}
@ -107,6 +140,9 @@ public class ResourceParser {
* @return ID
*/
public static int getNoteTitleBgResource(int id) {
if (id >= BG_EDIT_TITLE_RESOURCES.length || id < 0) {
return R.drawable.edit_title_white;
}
return BG_EDIT_TITLE_RESOURCES[id];
}
}
@ -182,6 +218,7 @@ public class ResourceParser {
* @return ID
*/
public static int getNoteBgFirstRes(int id) {
if (id >= BG_FIRST_RESOURCES.length || id < 0) return R.drawable.list_white_up;
return BG_FIRST_RESOURCES[id];
}
@ -192,6 +229,7 @@ public class ResourceParser {
* @return ID
*/
public static int getNoteBgLastRes(int id) {
if (id >= BG_LAST_RESOURCES.length || id < 0) return R.drawable.list_white_down;
return BG_LAST_RESOURCES[id];
}
@ -202,6 +240,7 @@ public class ResourceParser {
* @return ID
*/
public static int getNoteBgSingleRes(int id) {
if (id >= BG_SINGLE_RESOURCES.length || id < 0) return R.drawable.list_white_single;
return BG_SINGLE_RESOURCES[id];
}
@ -212,6 +251,7 @@ public class ResourceParser {
* @return ID
*/
public static int getNoteBgNormalRes(int id) {
if (id >= BG_NORMAL_RESOURCES.length || id < 0) return R.drawable.list_white_middle;
return BG_NORMAL_RESOURCES[id];
}

@ -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);
}
}

@ -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<NoteColorAdapter.ViewHolder> {
public interface OnColorClickListener {
void onColorClick(int colorId);
}
private List<Integer> mColorIds;
private int mSelectedColorId;
private OnColorClickListener mListener;
public NoteColorAdapter(List<Integer> 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);
}
}
}

@ -57,6 +57,8 @@ import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.NoteCommand;
import net.micode.notes.model.UndoRedoManager;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
@ -77,8 +79,11 @@ import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.appbar.MaterialToolbar;
import net.micode.notes.databinding.NoteEditBinding;
import net.micode.notes.tool.RichTextHelper;
import net.micode.notes.data.FontManager;
public class NoteEditActivity extends AppCompatActivity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
/**
@ -101,24 +106,6 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
public EditText etTitle;
}
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
@ -145,6 +132,8 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
private View mFontSizeSelector;
private View mRichTextSelector;
private EditText mNoteEditor;
private View mNoteEditorPanel;
@ -170,25 +159,11 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
private NoteEditBinding binding;
/**
* ID
*/
private View getBgSelectorView(int viewId) {
switch (viewId) {
case R.id.iv_bg_yellow_select:
return binding.ivBgYellowSelect;
case R.id.iv_bg_red_select:
return binding.ivBgRedSelect;
case R.id.iv_bg_blue_select:
return binding.ivBgBlueSelect;
case R.id.iv_bg_green_select:
return binding.ivBgGreenSelect;
case R.id.iv_bg_white_select:
return binding.ivBgWhiteSelect;
default:
throw new IllegalArgumentException("Unknown view ID: " + viewId);
}
}
private UndoRedoManager mUndoRedoManager;
private boolean mInUndoRedo = false;
private androidx.recyclerview.widget.RecyclerView mColorSelectorRv;
private NoteColorAdapter mColorAdapter;
/**
* ID
@ -215,6 +190,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
// 使用ViewBinding设置布局
binding = NoteEditBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
mUndoRedoManager = new UndoRedoManager();
// 初始化Toolbar使用MaterialToolbar与列表页面一致
setSupportActionBar(binding.toolbar);
@ -368,10 +344,16 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
mNoteEditor = binding.noteEditView;
mNoteEditorPanel = binding.svNoteEdit;
mNoteBgColorSelector = binding.noteBgColorSelector;
mColorSelectorRv = binding.rvBgColorSelector;
mNoteEditor.addTextChangedListener(new TextWatcher() {
private CharSequence mBeforeText;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (!mInUndoRedo) {
mBeforeText = s.subSequence(start, start + count).toString();
}
}
@Override
@ -379,6 +361,13 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
if (mNoteHeaderHolder != null && mNoteHeaderHolder.tvCharCount != null) {
mNoteHeaderHolder.tvCharCount.setText(String.valueOf(s.length()) + " 字");
}
if (!mInUndoRedo) {
CharSequence afterText = s.subSequence(start, start + count).toString();
if (!TextUtils.equals(mBeforeText, afterText)) {
mUndoRedoManager.addCommand(new NoteCommand(mNoteEditor, start, mBeforeText, afterText));
invalidateOptionsMenu();
}
}
}
@Override
@ -406,30 +395,34 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}
});
// 设置背景颜色选择器的点击事件
for (int id : sBgSelectorBtnsMap.keySet()) {
ImageView iv;
switch (id) {
case R.id.iv_bg_yellow:
iv = binding.ivBgYellow;
break;
case R.id.iv_bg_red:
iv = binding.ivBgRed;
break;
case R.id.iv_bg_blue:
iv = binding.ivBgBlue;
break;
case R.id.iv_bg_green:
iv = binding.ivBgGreen;
break;
case R.id.iv_bg_white:
iv = binding.ivBgWhite;
break;
default:
throw new IllegalArgumentException("Unknown view ID: " + id);
// Initialize Color Adapter
java.util.List<Integer> colors = java.util.Arrays.asList(
ResourceParser.YELLOW,
ResourceParser.BLUE,
ResourceParser.WHITE,
ResourceParser.GREEN,
ResourceParser.RED,
ResourceParser.MIDNIGHT_BLACK,
ResourceParser.EYE_CARE_GREEN,
ResourceParser.WARM,
ResourceParser.COOL,
ResourceParser.CUSTOM_COLOR_BUTTON_ID,
ResourceParser.WALLPAPER_BUTTON_ID
);
mColorAdapter = new NoteColorAdapter(colors, ResourceParser.YELLOW, new NoteColorAdapter.OnColorClickListener() {
@Override
public void onColorClick(int colorId) {
if (colorId == ResourceParser.CUSTOM_COLOR_BUTTON_ID) {
showColorPickerDialog();
} else if (colorId == ResourceParser.WALLPAPER_BUTTON_ID) {
pickWallpaper();
} else {
mWorkingNote.setBgColorId(colorId);
mNoteBgColorSelector.setVisibility(View.GONE);
}
}
iv.setOnClickListener(this);
}
});
mColorSelectorRv.setAdapter(mColorAdapter);
mFontSizeSelector = binding.fontSizeSelector;
for (int id : sFontSizeBtnsMap.keySet()) {
@ -464,6 +457,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = binding.noteEditList;
initRichTextToolbar();
}
@Override
@ -487,21 +481,28 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
private void initNoteScreen() {
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));
// Apply custom font
FontManager.getInstance(this).applyFont(mNoteEditor);
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
String content = mWorkingNote.getContent();
if (content.contains("<") && content.contains(">")) {
mNoteEditor.setText(RichTextHelper.fromHtml(content));
} else {
mNoteEditor.setText(getHighlightQueryResult(content, mUserQuery));
}
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
mNoteHeaderHolder.etTitle.setText(mWorkingNote.getTitle());
for (Integer id : sBgSelectorSelectionMap.keySet()) {
View view = getBgSelectorView(sBgSelectorSelectionMap.get(id));
if (view != null) {
view.setVisibility(View.GONE);
}
// Update Color Adapter selection
if (mColorAdapter != null) {
mColorAdapter.setSelectedColor(mWorkingNote.getBgColorId());
}
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
updateNoteBackgrounds();
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
@ -678,17 +679,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
View bgView = getBgSelectorView(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId()));
if (bgView != null) {
bgView.setVisibility(View.VISIBLE);
}
} else if (sBgSelectorBtnsMap.containsKey(id)) {
View bgView = getBgSelectorView(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId()));
if (bgView != null) {
bgView.setVisibility(View.GONE);
}
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
// Note: Adapter selection is already set in onBackgroundColorChanged or init
} else if (sFontSizeBtnsMap.containsKey(id)) {
View fontView = getFontSelectorView(sFontSelectorSelectionMap.get(mFontSizeId));
if (fontView != null) {
@ -706,6 +697,8 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
} else {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
// Apply custom font again as setTextAppearance might reset it
FontManager.getInstance(this).applyFont(mNoteEditor);
}
mFontSizeSelector.setVisibility(View.GONE);
}
@ -758,12 +751,143 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
* </p>
*/
public void onBackgroundColorChanged() {
View bgView = getBgSelectorView(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId()));
if (bgView != null) {
bgView.setVisibility(View.VISIBLE);
if (mColorAdapter != null) {
mColorAdapter.setSelectedColor(mWorkingNote.getBgColorId());
}
updateNoteBackgrounds();
}
private void updateNoteBackgrounds() {
int colorId = mWorkingNote.getBgColorId();
String wallpaperPath = mWorkingNote.getWallpaperPath();
if (wallpaperPath != null) {
// Load wallpaper
android.net.Uri uri = android.net.Uri.parse(wallpaperPath);
try {
java.io.InputStream inputStream = getContentResolver().openInputStream(uri);
android.graphics.Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(inputStream);
android.graphics.drawable.BitmapDrawable drawable = new android.graphics.drawable.BitmapDrawable(getResources(), bitmap);
// Tiling mode (can be configurable later)
drawable.setTileModeXY(android.graphics.Shader.TileMode.REPEAT, android.graphics.Shader.TileMode.REPEAT);
// Add Blur Effect for Android 12+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
mNoteEditorPanel.setBackground(drawable);
mNoteEditorPanel.setRenderEffect(android.graphics.RenderEffect.createBlurEffect(
20f, 20f, android.graphics.Shader.TileMode.CLAMP));
} else {
mNoteEditorPanel.setBackground(drawable);
}
// Header always uses original wallpaper (or maybe slightly darker?)
mHeadViewPanel.setBackground(drawable.getConstantState().newDrawable());
// Dynamic Coloring with Palette
androidx.palette.graphics.Palette.from(bitmap).generate(palette -> {
if (palette != null) {
applyPaletteColors(palette);
}
});
} catch (Exception e) {
Log.e(TAG, "Failed to load wallpaper", e);
// Fallback to color
applyColorBackground(colorId);
}
} else {
applyColorBackground(colorId);
// Reset toolbar colors to default/theme
resetToolbarColors();
}
updateTextColor(colorId);
}
private void applyPaletteColors(androidx.palette.graphics.Palette palette) {
int primaryColor = palette.getDominantColor(getResources().getColor(R.color.primary_color));
int onPrimaryColor = getResources().getColor(R.color.on_primary_color);
// Ensure contrast for onPrimaryColor
if (androidx.core.graphics.ColorUtils.calculateContrast(onPrimaryColor, primaryColor) < 3.0) {
onPrimaryColor = android.graphics.Color.WHITE;
}
binding.toolbar.setBackgroundColor(primaryColor);
binding.toolbar.setTitleTextColor(onPrimaryColor);
if (binding.toolbar.getNavigationIcon() != null) {
binding.toolbar.getNavigationIcon().setTint(onPrimaryColor);
}
getWindow().setStatusBarColor(primaryColor);
}
private void resetToolbarColors() {
int primaryColor = getResources().getColor(R.color.primary_color);
int onPrimaryColor = getResources().getColor(R.color.on_primary_color);
binding.toolbar.setBackgroundColor(primaryColor);
binding.toolbar.setTitleTextColor(onPrimaryColor);
if (binding.toolbar.getNavigationIcon() != null) {
binding.toolbar.getNavigationIcon().setTint(onPrimaryColor);
}
getWindow().setStatusBarColor(primaryColor);
}
private void updateTextColor(int colorId) {
// Default to black for light backgrounds
int textColor = android.graphics.Color.BLACK;
if (colorId == ResourceParser.MIDNIGHT_BLACK) {
textColor = android.graphics.Color.WHITE;
} else if (colorId < 0) {
// Custom color: Calculate luminance
// colorId is the ARGB value for custom colors
if (isColorDark(colorId)) {
textColor = android.graphics.Color.WHITE;
}
}
// For wallpaper, we might want to check palette, but for now default to black or keep current
// If wallpaper is set, this method is called with the underlying colorId.
// We should probably rely on the underlying color or default to white/black.
mNoteEditor.setTextColor(textColor);
// Also update title color if needed
if (mNoteHeaderHolder != null && mNoteHeaderHolder.etTitle != null) {
mNoteHeaderHolder.etTitle.setTextColor(textColor);
}
}
private boolean isColorDark(int color) {
double darkness = 1 - (0.299 * android.graphics.Color.red(color) +
0.587 * android.graphics.Color.green(color) +
0.114 * android.graphics.Color.blue(color)) / 255;
return darkness >= 0.5;
}
private void applyColorBackground(int colorId) {
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
if (colorId >= ResourceParser.MIDNIGHT_BLACK || colorId < 0) {
int color = ResourceParser.getNoteBgColor(this, colorId);
if (mNoteEditorPanel.getBackground() != null) {
mNoteEditorPanel.getBackground().setTint(color);
mNoteEditorPanel.getBackground().setTintMode(android.graphics.PorterDuff.Mode.MULTIPLY);
}
if (mHeadViewPanel.getBackground() != null) {
mHeadViewPanel.getBackground().setTint(color);
mHeadViewPanel.getBackground().setTintMode(android.graphics.PorterDuff.Mode.MULTIPLY);
}
} else {
// Clear tint for legacy resources
if (mNoteEditorPanel.getBackground() != null) {
mNoteEditorPanel.getBackground().clearColorFilter();
}
if (mHeadViewPanel.getBackground() != null) {
mHeadViewPanel.getBackground().clearColorFilter();
}
}
}
/**
@ -790,6 +914,18 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
getMenuInflater().inflate(R.menu.call_note_edit, menu);
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
MenuItem undoItem = menu.findItem(R.id.menu_undo);
MenuItem redoItem = menu.findItem(R.id.menu_redo);
MenuItem clearItem = menu.findItem(R.id.menu_clear_history);
if (undoItem != null) {
undoItem.setEnabled(mUndoRedoManager.canUndo());
}
if (redoItem != null) {
redoItem.setEnabled(mUndoRedoManager.canRedo());
}
if (clearItem != null) {
clearItem.setEnabled(mUndoRedoManager.canUndo() || mUndoRedoManager.canRedo());
}
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
@ -825,6 +961,33 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_rich_text:
if (mRichTextSelector.getVisibility() == View.VISIBLE) {
mRichTextSelector.setVisibility(View.GONE);
} else {
mRichTextSelector.setVisibility(View.VISIBLE);
mFontSizeSelector.setVisibility(View.GONE);
}
break;
case R.id.menu_undo:
mInUndoRedo = true;
mUndoRedoManager.undo();
mInUndoRedo = false;
invalidateOptionsMenu();
showToast(R.string.undo_success);
break;
case R.id.menu_redo:
mInUndoRedo = true;
mUndoRedoManager.redo();
mInUndoRedo = false;
invalidateOptionsMenu();
showToast(R.string.redo_success);
break;
case R.id.menu_clear_history:
mUndoRedoManager.clear();
invalidateOptionsMenu();
showToast(R.string.menu_clear_history);
break;
case R.id.menu_new_note:
createNewNote();
break;
@ -1143,6 +1306,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
// Apply custom font
FontManager.getInstance(this).applyFont(edit);
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@ -1202,6 +1368,8 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
* @param newMode
*/
public void onCheckListModeChanged(int oldMode, int newMode) {
mUndoRedoManager.clear();
invalidateOptionsMenu();
if (newMode == TextNote.MODE_CHECK_LIST) {
switchToListMode(mNoteEditor.getText().toString());
} else {
@ -1245,7 +1413,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
mWorkingNote.setWorkingText(RichTextHelper.toHtml(mNoteEditor.getText()));
}
return hasChecked;
}
@ -1336,6 +1504,92 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
private void showColorPickerDialog() {
final View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_color_picker, null);
final View colorPreview = dialogView.findViewById(R.id.view_color_preview);
android.widget.SeekBar sbRed = dialogView.findViewById(R.id.sb_red);
android.widget.SeekBar sbGreen = dialogView.findViewById(R.id.sb_green);
android.widget.SeekBar sbBlue = dialogView.findViewById(R.id.sb_blue);
int currentColor = android.graphics.Color.WHITE;
if (mWorkingNote.getBgColorId() < 0) {
currentColor = mWorkingNote.getBgColorId();
}
final int[] rgb = new int[]{
android.graphics.Color.red(currentColor),
android.graphics.Color.green(currentColor),
android.graphics.Color.blue(currentColor)
};
colorPreview.setBackgroundColor(android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]));
sbRed.setProgress(rgb[0]);
sbGreen.setProgress(rgb[1]);
sbBlue.setProgress(rgb[2]);
android.widget.SeekBar.OnSeekBarChangeListener listener = new android.widget.SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(android.widget.SeekBar seekBar, int progress, boolean fromUser) {
if (seekBar.getId() == R.id.sb_red) rgb[0] = progress;
else if (seekBar.getId() == R.id.sb_green) rgb[1] = progress;
else if (seekBar.getId() == R.id.sb_blue) rgb[2] = progress;
colorPreview.setBackgroundColor(android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]));
}
@Override
public void onStartTrackingTouch(android.widget.SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(android.widget.SeekBar seekBar) {}
};
sbRed.setOnSeekBarChangeListener(listener);
sbGreen.setOnSeekBarChangeListener(listener);
sbBlue.setOnSeekBarChangeListener(listener);
new AlertDialog.Builder(this)
.setTitle("Custom Color")
.setView(dialogView)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
int newColor = android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]);
// Use negative integer for custom color. Ensure it's negative.
// ARGB color with alpha 255 is negative in Java int.
// If alpha is 0, it might be positive. We assume full opacity.
newColor |= 0xFF000000;
mWorkingNote.setBgColorId(newColor);
mNoteBgColorSelector.setVisibility(View.GONE);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private static final int REQUEST_CODE_PICK_WALLPAPER = 105;
private void pickWallpaper() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_CODE_PICK_WALLPAPER);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_PICK_WALLPAPER && resultCode == RESULT_OK && data != null) {
android.net.Uri uri = data.getData();
if (uri != null) {
// Take persistent permissions
try {
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (SecurityException e) {
Log.e(TAG, "Failed to take persistable uri permission", e);
}
mWorkingNote.setWallpaper(uri.toString());
mNoteBgColorSelector.setVisibility(View.GONE);
}
}
}
/**
* Toast
* <p>
@ -1358,4 +1612,78 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
private void initRichTextToolbar() {
mRichTextSelector = findViewById(R.id.rich_text_selector);
findViewById(R.id.btn_bold).setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyBold(mNoteEditor); }
});
findViewById(R.id.btn_italic).setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyItalic(mNoteEditor); }
});
findViewById(R.id.btn_underline).setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyUnderline(mNoteEditor); }
});
findViewById(R.id.btn_strikethrough).setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyStrikethrough(mNoteEditor); }
});
findViewById(R.id.btn_header).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final CharSequence[] items = {"H1 (Largest)", "H2", "H3", "H4", "H5", "H6 (Smallest)", "Normal"};
AlertDialog.Builder builder = new AlertDialog.Builder(NoteEditActivity.this);
builder.setTitle("Header Level");
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
// item index maps to level: 0->1, 1->2, ..., 5->6, 6->0 (Normal)
int level = (item == 6) ? 0 : (item + 1);
RichTextHelper.applyHeading(mNoteEditor, level);
}
});
builder.show();
}
});
findViewById(R.id.btn_list).setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyBullet(mNoteEditor); }
});
findViewById(R.id.btn_quote).setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyQuote(mNoteEditor); }
});
findViewById(R.id.btn_code).setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyCode(mNoteEditor); }
});
findViewById(R.id.btn_link).setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.insertLink(NoteEditActivity.this, mNoteEditor); }
});
findViewById(R.id.btn_divider).setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.insertDivider(mNoteEditor); }
});
findViewById(R.id.btn_color_text).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final CharSequence[] items = {"Black", "Red", "Blue"};
final int[] colors = {android.graphics.Color.BLACK, android.graphics.Color.RED, android.graphics.Color.BLUE};
AlertDialog.Builder builder = new AlertDialog.Builder(NoteEditActivity.this);
builder.setTitle("Text Color");
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
RichTextHelper.applyColor(mNoteEditor, colors[item], false);
}
});
builder.show();
}
});
findViewById(R.id.btn_color_fill).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final CharSequence[] items = {"None", "Yellow", "Green", "Cyan"};
final int[] colors = {android.graphics.Color.TRANSPARENT, android.graphics.Color.YELLOW, android.graphics.Color.GREEN, android.graphics.Color.CYAN};
AlertDialog.Builder builder = new AlertDialog.Builder(NoteEditActivity.this);
builder.setTitle("Background Color");
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
RichTextHelper.applyColor(mNoteEditor, colors[item], true);
}
});
builder.show();
}
});
}
}

@ -28,6 +28,7 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
import net.micode.notes.data.FontManager;
/**
@ -80,6 +81,7 @@ public class NotesListItem extends LinearLayout {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
FontManager.getInstance(context).applyFont(mTitle);
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
@ -87,6 +89,7 @@ public class NotesListItem extends LinearLayout {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
FontManager.getInstance(context).applyFont(mTitle);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
@ -97,6 +100,7 @@ public class NotesListItem extends LinearLayout {
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
FontManager.getInstance(context).applyFont(mTitle);
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
@ -131,18 +135,35 @@ public class NotesListItem extends LinearLayout {
*/
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
int resId;
if (data.getType() == Notes.TYPE_NOTE) {
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
resId = NoteItemBgResources.getNoteBgSingleRes(id);
} else if (data.isLast()) {
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
resId = NoteItemBgResources.getNoteBgLastRes(id);
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
resId = NoteItemBgResources.getNoteBgFirstRes(id);
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
resId = NoteItemBgResources.getNoteBgNormalRes(id);
}
} else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
resId = NoteItemBgResources.getFolderBgRes();
}
setBackgroundResource(resId);
// Apply tint for new colors
if (data.getType() == Notes.TYPE_NOTE && (id >= net.micode.notes.tool.ResourceParser.MIDNIGHT_BLACK || id < 0)) {
int color = net.micode.notes.tool.ResourceParser.getNoteBgColor(getContext(), id);
if (getBackground() != null) {
getBackground().setTint(color);
getBackground().setTintMode(android.graphics.PorterDuff.Mode.MULTIPLY);
}
} else {
// Ensure no tint for legacy colors (if view is recycled)
if (getBackground() != null) {
getBackground().clearColorFilter();
}
}
}

@ -1,659 +1,60 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.databinding.SettingsHeaderBinding;
// Google Tasks同步功能已禁用
// import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.tool.SecurityManager;
import net.micode.notes.ui.PasswordActivity;
public class NotesPreferenceActivity extends AppCompatActivity {
/**
* Activity
* <p>
* Activity
* <ul>
* <li>Google Tasks</li>
* <li></li>
* <li></li>
* </ul>
* </p>
* <p>
* PreferenceActivity使SharedPreferences
* GTaskReceiver广
* </p>
*/
public class NotesPreferenceActivity extends PreferenceActivity {
/**
* SharedPreferences
*/
public static final String PREFERENCE_NAME = "notes_preferences";
/**
* SharedPreferences
*/
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
/**
* SharedPreferences
*/
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
/**
* SharedPreferences
*/
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
public static final String PREFERENCE_SECURITY_KEY = "pref_key_security";
public static final int REQUEST_CODE_CHECK_PASSWORD = 104;
/**
* Preference
*/
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
/**
* Intent
*/
private static final String AUTHORITIES_FILTER_KEY = "authorities";
/**
* PreferenceCategory
*/
private PreferenceCategory mAccountCategory;
/**
* 广
*/
private GTaskReceiver mReceiver;
/**
*
*/
private SettingsHeaderBinding mHeaderBinding;
/**
*
*/
private Account[] mOriAccounts;
/**
*
*/
private boolean mHasAddedAccount;
/**
* Activity
* <p>
*
* <ul>
* <li>ActionBar</li>
* <li>preferences.xml</li>
* <li>广</li>
* <li></li>
* </ul>
* </p>
* @param icicle
*/
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
/* using the app icon for navigation */
getActionBar().setDisplayHomeAsUpEnabled(true);
addPreferencesFromResource(R.xml.preferences);
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// Google Tasks同步功能已禁用
// mReceiver = new GTaskReceiver();
// IntentFilter filter = new IntentFilter();
// filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
//registerReceiver(mReceiver, filter);
// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
// // Android 13 (API 33) 及以上版本需要指定导出标志
// registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
// } else {
// // Android 12 及以下版本使用旧方法
// registerReceiver(mReceiver, filter);
// }
mOriAccounts = null;
mHeaderBinding = SettingsHeaderBinding.inflate(getLayoutInflater());
getListView().addHeaderView(mHeaderBinding.getRoot(), null, true);
loadSecurityPreference();
}
/**
* Activity
* <p>
* Google
* UI
* </p>
*/
@Override
protected void onResume() {
super.onResume();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
// need to set sync account automatically if user has added a new
// account
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
for (Account accountNew : accounts) {
boolean found = false;
for (Account accountOld : mOriAccounts) {
if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true;
break;
}
}
if (!found) {
setSyncAccount(accountNew.name);
break;
}
}
}
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.preferences_title);
}
refreshUI();
}
/**
* Activity
* <p>
* 广
* </p>
*/
@Override
protected void onDestroy() {
// Google Tasks同步功能已禁用
// if (mReceiver != null) {
// unregisterReceiver(mReceiver);
// }
mHeaderBinding = null;
super.onDestroy();
}
private void loadSecurityPreference() {
Preference securityPref = findPreference(PREFERENCE_SECURITY_KEY);
if (securityPref != null) {
securityPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
if (!SecurityManager.getInstance(NotesPreferenceActivity.this).isPasswordSet()) {
showSetPasswordDialog();
} else {
Intent intent = new Intent(NotesPreferenceActivity.this, PasswordActivity.class);
intent.setAction(PasswordActivity.ACTION_CHECK_PASSWORD);
startActivityForResult(intent, REQUEST_CODE_CHECK_PASSWORD);
}
return true;
}
});
}
}
private void showSetPasswordDialog() {
new AlertDialog.Builder(this)
.setTitle("设置密码")
.setItems(new String[]{"数字锁", "手势锁"}, (dialog, which) -> {
int type = (which == 0) ? SecurityManager.TYPE_PIN : SecurityManager.TYPE_PATTERN;
Intent intent = new Intent(this, PasswordActivity.class);
intent.setAction(PasswordActivity.ACTION_SETUP_PASSWORD);
intent.putExtra(PasswordActivity.EXTRA_PASSWORD_TYPE, type);
startActivity(intent);
})
.show();
}
private void showManagePasswordDialog() {
new AlertDialog.Builder(this)
.setTitle("管理密码")
.setItems(new String[]{"更改密码", "取消密码"}, (dialog, which) -> {
if (which == 0) { // Change
showSetPasswordDialog();
} else { // Remove
SecurityManager.getInstance(this).removePassword();
Toast.makeText(this, "密码已取消", Toast.LENGTH_SHORT).show();
}
})
.show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_CHECK_PASSWORD && resultCode == RESULT_OK) {
showManagePasswordDialog();
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings_container, new SettingsFragment())
.commit();
}
}
/**
*
* <p>
* Preference
* Preference
* <ul>
* <li></li>
* <li></li>
* <li></li>
* </ul>
* </p>
*/
private void loadAccountPreference() {
mAccountCategory.removeAll();
Preference accountPref = new Preference(this);
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
// Google Tasks同步功能已禁用
// if (!GTaskSyncService.isSyncing()) {
// if (TextUtils.isEmpty(defaultAccount)) {
// // first time to set account
// showSelectAccountAlertDialog();
// } else {
// // if account has already been set, we need to promp
// // user about risk
// showChangeAccountConfirmAlertDialog();
// }
// } else {
// Toast.makeText(NotesPreferenceActivity.this,
// R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
// .show();
// }
Toast.makeText(NotesPreferenceActivity.this,
"Google Tasks同步功能已禁用", Toast.LENGTH_SHORT)
.show();
return true;
}
});
mAccountCategory.addPreference(accountPref);
loadSyncButton();
}
/**
*
* <p>
*
* <ul>
* <li>"取消同步"</li>
* <li>"立即同步"</li>
* </ul>
*
* </p>
*/
private void loadSyncButton() {
Button syncButton = mHeaderBinding.preferenceSyncButton;
TextView lastSyncTimeView = mHeaderBinding.prefenereceSyncStatusTextview;
Button syncButton = findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = findViewById(R.id.prefenerece_sync_status_textview);
// Google Tasks同步功能已禁用
// set button state
// if (GTaskSyncService.isSyncing()) {
// syncButton.setText(getString(R.string.preferences_button_sync_cancel));
// syncButton.setOnClickListener(new View.OnClickListener() {
// public void onClick(View v) {
// GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
// }
// });
// } else {
// syncButton.setText(getString(R.string.preferences_button_sync_immediately));
// syncButton.setOnClickListener(new View.OnClickListener() {
// public void onClick(View v) {
// GTaskSyncService.startSync(NotesPreferenceActivity.this);
// }
// });
// }
// syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// 禁用同步按钮
syncButton.setEnabled(false);
syncButton.setText("同步功能已禁用");
// set last sync time
// if (GTaskSyncService.isSyncing()) {
// lastSyncTimeView.setText(GTaskSyncService.getProgressString());
// lastSyncTimeView.setVisibility(View.VISIBLE);
// } else {
// long lastSyncTime = getLastSyncTime(this);
// if (lastSyncTime != 0) {
// lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
// DateFormat.format(getString(R.string.preferences_last_sync_time_format),
// lastSyncTime)));
// lastSyncTimeView.setVisibility(View.VISIBLE);
// } else {
// lastSyncTimeView.setVisibility(View.GONE);
// }
// }
lastSyncTimeView.setText("Google Tasks同步功能已禁用");
lastSyncTimeView.setVisibility(View.VISIBLE);
}
/**
* UI
* <p>
*
* </p>
*/
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
/**
*
* <p>
* Google
* "添加账户"
* </p>
*/
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
mOriAccounts = accounts;
mHasAddedAccount = false;
if (accounts.length > 0) {
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
}
items[index++] = account.name;
}
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
refreshUI();
}
});
}
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
final AlertDialog dialog = dialogBuilder.show();
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
});
startActivityForResult(intent, -1);
dialog.dismiss();
}
});
}
/**
*
* <p>
*
* <ul>
* <li></li>
* <li></li>
* <li></li>
* </ul>
* </p>
*/
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this)));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
dialogBuilder.setCustomTitle(titleView);
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
};
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
showSelectAccountAlertDialog();
} else if (which == 1) {
removeSyncAccount();
refreshUI();
}
}
});
dialogBuilder.show();
}
/**
* Google
* <p>
* AccountManager"com.google"
* </p>
* @return Google
*/
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
/**
*
* <p>
* SharedPreferences
* <ul>
* <li></li>
* <li>GTASK_IDSYNC_ID</li>
* </ul>
* </p>
* @param account
*/
private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
editor.commit();
// clean up last sync time
setLastSyncTime(this, 0);
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
/**
*
* <p>
* SharedPreferences
* GTASK_IDSYNC_ID
* </p>
*/
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
editor.commit();
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
}
/**
*
* <p>
* SharedPreferences
* </p>
* @param context
* @return
*/
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
public static String getSyncAccountName(android.content.Context context) {
android.content.SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, android.content.Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
/**
*
* <p>
* SharedPreferences
* </p>
* @param context
* @param time
*/
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
editor.commit();
}
/**
*
* <p>
* SharedPreferences
* </p>
* @param context
* @return 0
*/
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
/**
* 广
* <p>
* GTaskSyncService广UI
* </p>
*/
private class GTaskReceiver extends BroadcastReceiver {
/**
* 广
* <p>
* 广UI
* </p>
* @param context
* @param intent 广Intent
*/
@Override
public void onReceive(Context context, Intent intent) {
refreshUI();
// Google Tasks同步功能已禁用
// if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
// TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// syncStatus.setText(intent
// .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
// }
}
}
/**
*
* <p>
* ActionBar
*
* </p>
* @param item
* @return truefalse
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:

@ -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();
}
}
}

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M15.6,11.79c0.97,-0.67 1.65,-1.77 1.65,-2.79 0,-2.26 -1.75,-4 -4,-4H7v14h7c2.09,0 3.85,-1.75 3.85,-3.75 0,-1.58 -0.95,-2.9 -2.25,-3.46zM10,7.5h3c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5h-3v-3zM13.5,16.5h-3.5v-3h3.5c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/>
</vector>

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M16.56,8.94L7.62,0 6.21,1.41l2.38,2.38 -5.15,5.15c-0.59,0.59 -0.59,1.54 0,2.12l5.5,5.5c0.29,0.29 0.68,0.44 1.06,0.44s0.77,-0.15 1.06,-0.44l5.5,-5.5c0.59,-0.58 0.59,-1.53 0,-2.12zM5.21,10L10,5.21 14.79,10H5.21zM19,11.5s-2,2.17 -2,3.5c0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-1.33 -2,-3.5 -2,-3.5z"/>
<path
android:fillColor="#FF000000"
android:pathData="M0,20h24v4H0z"/>
</vector>

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M0,20h24v4H0z"/>
<path
android:fillColor="#FF000000"
android:pathData="M11,3L5.5,17h2.25l1.12,-3h6.25l1.12,3h2.25L13,3h-2zM10,11l2,-5.5 2,5.5h-4z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M5,4v3h5.5v12h3V7H19V4z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M10,4v3h2.21l-3.42,8H6v3h8v-3h-2.21l3.42,-8H18V4z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M4,10.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,4.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,16.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM8,19h12v-2H8v2zM8,13h12v-2H8v2zM8,5v2h12V5H8z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M6,17h3l2,-4V7H5v6h3l-2,4zM14,17h3l2,-4V7h-6v6h3l-2,4z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M10,19h4v-3h-4v3zM5,4v3h5v3h4V7h5V4H5zM3,14h18v-2H3v2z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,17c3.31,0 6,-2.69 6,-6V3h-2.5v8c0,1.93 -1.57,3.5 -3.5,3.5S8.5,12.93 8.5,11V3H6v8c0,3.31 2.69,6 6,6zM5,19v2h14v-2H5z"/>
</vector>

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#000000">
<path
android:fillColor="@android:color/white"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19,13H5v-2h14v2z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4V7H7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9H7c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2H8v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4V17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M2.5,4v3h5v12h3V7h5V4H2.5zM21.5,9h-9v3h3v7h3v-7h3V9z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z"/>
</vector>

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#000000">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5H16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,6c-0.83,0 -1.5,-0.67 -1.5,-1.5S8.67,3 9.5,3s1.5,0.67 1.5,1.5S10.33,6 9.5,6zM14.5,6c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,3 14.5,3s1.5,0.67 1.5,1.5S15.33,6 14.5,6zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5S18.33,12 17.5,12z"/>
</vector>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/settings_header" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settings_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="12dp"
app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline"
xmlns:app="http://schemas.android.com/apk/res-auto">
<View
android:id="@+id/view_color_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Red"
android:layout_marginBottom="4dp"/>
<SeekBar
android:id="@+id/sb_red"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:progressTint="@android:color/holo_red_light"
android:thumbTint="@android:color/holo_red_dark"
android:max="255" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Green"
android:layout_marginBottom="4dp"/>
<SeekBar
android:id="@+id/sb_green"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:progressTint="@android:color/holo_green_light"
android:thumbTint="@android:color/holo_green_dark"
android:max="255" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Blue"
android:layout_marginBottom="4dp"/>
<SeekBar
android:id="@+id/sb_blue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:progressTint="@android:color/holo_blue_light"
android:thumbTint="@android:color/holo_blue_dark"
android:max="255" />
</LinearLayout>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="4dp"
android:padding="2dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/color_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.Full"
app:strokeWidth="1dp"
app:strokeColor="@color/secondary_text_dark" />
<ImageView
android:id="@+id/check_view"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/selected"
android:visibility="gone" />
</FrameLayout>

@ -170,112 +170,16 @@
android:layout_marginTop="30dp"
android:layout_marginRight="8dp"
android:layout_gravity="top|right"
android:visibility="gone">
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_yellow"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_yellow_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="5dp"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_blue"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_blue_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="3dp"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_white"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_white_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="2dp"
android:src="@drawable/selected" />
</FrameLayout>
android:visibility="gone"
android:orientation="horizontal">
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_green"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_green_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="5dp"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_red"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_bg_color_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<ImageView
android:id="@+id/iv_bg_red_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="3dp"
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
<LinearLayout
@ -438,5 +342,155 @@
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/rich_text_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/font_size_selector_bg"
android:layout_gravity="bottom"
android:visibility="gone"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<ImageButton
android:id="@+id/btn_bold"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_bold"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Bold"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_italic"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_italic"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Italic"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_underline"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_underline"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Underline"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_strikethrough"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_strikethrough"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Strikethrough"
android:scaleType="centerInside" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:background="#CCCCCC"/>
<ImageButton
android:id="@+id/btn_header"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_header"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Header"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_list"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_list_bulleted"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="List"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_quote"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_quote"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Quote"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_code"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_code"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Code"
android:scaleType="centerInside" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:background="#CCCCCC"/>
<ImageButton
android:id="@+id/btn_link"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_insert_link"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Link"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_divider"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_insert_divider"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Divider"
android:scaleType="centerInside" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:background="#CCCCCC"/>
<ImageButton
android:id="@+id/btn_color_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_color_text"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Text Color"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_color_fill"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_color_fill"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Background Color"
android:scaleType="centerInside" />
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</LinearLayout>
</LinearLayout>

@ -16,12 +16,35 @@
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"/>
<item
android:id="@+id/menu_rich_text"
android:title="@string/menu_rich_text"
android:icon="@drawable/ic_menu_rich_text"
app:showAsAction="always"/>
<item
android:id="@+id/menu_undo"
android:title="@string/menu_undo"
android:icon="@drawable/ic_menu_undo"
app:showAsAction="always"/>
<item
android:id="@+id/menu_redo"
android:title="@string/menu_redo"
android:icon="@drawable/ic_menu_redo"
app:showAsAction="always"/>
<item
android:id="@+id/menu_clear_history"
android:title="@string/menu_clear_history"/>
<item
android:id="@+id/menu_delete"
android:title="@string/menu_delete"/>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="background_color">#121212</color>
<color name="text_color_primary">#DEFFFFFF</color>
<color name="text_color_secondary">#99FFFFFF</color>
<!-- In Night mode, keeping note backgrounds desaturated/dark is better,
but for now we keep them linked to original resources unless we want to override them.
We override bg_white to be dark grey for general UI usage (if any). -->
<color name="bg_white">#2C2C2C</color>
<!-- Overriding primary_text_dark to ensure legacy styles also switch if they are not updated -->
<color name="primary_text_dark">#DEFFFFFF</color>
<color name="secondary_text_dark">#99FFFFFF</color>
</resources>

@ -133,4 +133,11 @@
<string name="menu_trash">回收站</string>
<string name="create_folder_success">创建文件夹成功</string>
<string name="menu_undo">撤回</string>
<string name="menu_redo">重做</string>
<string name="menu_clear_history">清空撤回历史</string>
<string name="undo_success">撤回成功</string>
<string name="redo_success">重做成功</string>
<string name="undo_fail">无可撤回</string>
<string name="redo_fail">无可重做</string>
</resources>

@ -28,4 +28,16 @@
<item>Messaging</item>
<item>Email</item>
</string-array>
<string-array name="theme_entries">
<item>Follow System</item>
<item>Light</item>
<item>Dark</item>
</string-array>
<string-array name="theme_values">
<item>system</item>
<item>light</item>
<item>dark</item>
</string-array>
</resources>

@ -23,4 +23,21 @@
<color name="primary_text_dark">#000000</color>
<color name="secondary_text_dark">#808080</color>
<color name="fab_color">#FFC107</color>
<!-- Semantic Text Colors -->
<color name="text_color_primary">#000000</color>
<color name="text_color_secondary">#808080</color>
<!-- Note Background Colors -->
<color name="bg_yellow">#FFF9C4</color>
<color name="bg_blue">#B3E5FC</color>
<color name="bg_white">#FFFFFF</color>
<color name="bg_green">#C8E6C9</color>
<color name="bg_red">#FFCDD2</color>
<!-- New Presets -->
<color name="bg_midnight_black">#212121</color>
<color name="bg_eye_care_green">#C7EDCC</color>
<color name="bg_warm">#FFE0B2</color>
<color name="bg_cool">#E1BEE7</color>
</resources>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="font_family_entries">
<item>System Default</item>
<item>Serif</item>
<item>Sans Serif</item>
<item>Monospace</item>
<item>Cursive</item>
</string-array>
<string-array name="font_family_values">
<item>default</item>
<item>serif</item>
<item>sans-serif</item>
<item>monospace</item>
<item>cursive</item>
</string-array>
</resources>

@ -166,4 +166,14 @@
<string name="search_no_results">No results found</string>
<string name="search_history_title">Search History</string>
<string name="search_history_clear">Clear</string>
<!-- Undo/Redo -->
<string name="menu_undo">Undo</string>
<string name="menu_redo">Redo</string>
<string name="menu_clear_history">Clear Undo History</string>
<string name="undo_success">Undo successful</string>
<string name="redo_success">Redo successful</string>
<string name="undo_fail">Nothing to undo</string>
<string name="redo_fail">Nothing to redo</string>
<string name="menu_rich_text">Rich Text</string>
</resources>

@ -35,27 +35,27 @@
<style name="TextAppearancePrimaryItem">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@color/primary_text_dark</item>
<item name="android:textColor">@color/text_color_primary</item>
</style>
<style name="TextAppearanceSecondaryItem">
<item name="android:textSize">@dimen/text_font_size_small</item>
<item name="android:textColor">@color/secondary_text_dark</item>
<item name="android:textColor">@color/text_color_secondary</item>
</style>
<style name="TextAppearanceUnderMenuIcon">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@android:color/black</item>
<item name="android:textColor">@color/text_color_primary</item>
</style>
<style name="HighlightTextAppearancePrimary">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@color/primary_text_dark</item>
<item name="android:textColor">@color/text_color_primary</item>
</style>
<style name="HighlightTextAppearanceSecondary">
<item name="android:textSize">@dimen/text_font_size_small</item>
<item name="android:textColor">@color/secondary_text_dark</item>
<item name="android:textColor">@color/text_color_secondary</item>
</style>
<style name="NoteTheme" parent="@android:style/Theme.Holo.Light">

@ -11,14 +11,19 @@
<item name="android:navigationBarColor">@android:color/transparent</item>
<!-- 根据内容自动调整状态栏图标颜色(深色背景=浅色图标) -->
<item name="android:windowLightStatusBar">false</item>
<!-- Preference Theme for SettingsFragment -->
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style>
<style name="Theme.Notesmaster" parent="Base.Theme.Notesmaster" />
<!-- NoteEditActivity使用的主题统一使用Material3风格 -->
<style name="Theme.Notesmaster.Edit" parent="Theme.Material3.Light.NoActionBar">
<style name="Theme.Notesmaster.Edit" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/primary_color</item>
<item name="colorOnPrimary">@color/on_primary_color</item>
<item name="android:statusBarColor">@color/primary_color</item>
<!-- 确保背景色适配深色模式,虽然会被便签背景覆盖 -->
<item name="android:colorBackground">@color/background_color</item>
<item name="android:windowBackground">@color/background_color</item>
</style>
</resources>

@ -21,6 +21,24 @@
android:key="pref_sync_account_key">
</PreferenceCategory>
<PreferenceCategory android:title="Appearance">
<ListPreference
android:key="pref_theme_mode"
android:title="Theme"
android:summary="%s"
android:entries="@array/theme_entries"
android:entryValues="@array/theme_values"
android:defaultValue="system" />
<ListPreference
android:key="pref_font_family"
android:title="Font Family"
android:summary="%s"
android:entries="@array/font_family_entries"
android:entryValues="@array/font_family_values"
android:defaultValue="default" />
</PreferenceCategory>
<PreferenceCategory>
<CheckBoxPreference
android:key="pref_key_bg_random_appear"

Loading…
Cancel
Save