笔记编辑页面优化

pull/32/head
包尔俊 2 months ago
parent 48344838f1
commit 574573cad8

@ -62,6 +62,11 @@ public class Notes {
*/
public static final int TYPE_TASK = 3;
/**
*
*/
public static final int TYPE_TEMPLATE = 4;
/**
* ID
* {@link Notes#ID_ROOT_FOLDER }

@ -982,21 +982,21 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 工作模板
long workFolderId = insertFolder(db, Notes.ID_TEMPLATE_FOLDER, "工作");
if (workFolderId > 0) {
insertNote(db, workFolderId, "会议记录", "会议主题:\n时间\n地点\n参会人\n\n会议内容\n\n行动项\n");
insertNote(db, workFolderId, "周报", "本周工作总结:\n1. \n2. \n\n下周工作计划\n1. \n2. \n\n需要协调的问题\n");
insertNote(db, workFolderId, "会议记录", "会议主题:\n时间\n地点\n参会人\n\n会议内容\n\n行动项\n", Notes.TYPE_TEMPLATE);
insertNote(db, workFolderId, "周报", "本周工作总结:\n1. \n2. \n\n下周工作计划\n1. \n2. \n\n需要协调的问题\n", Notes.TYPE_TEMPLATE);
}
// 生活模板
long lifeFolderId = insertFolder(db, Notes.ID_TEMPLATE_FOLDER, "生活");
if (lifeFolderId > 0) {
insertNote(db, lifeFolderId, "日记", "日期:\n天气\n心情\n\n正文\n");
insertNote(db, lifeFolderId, "购物清单", "1. \n2. \n3. \n");
insertNote(db, lifeFolderId, "日记", "日期:\n天气\n心情\n\n正文\n", Notes.TYPE_TEMPLATE);
insertNote(db, lifeFolderId, "购物清单", "1. \n2. \n3. \n", Notes.TYPE_TEMPLATE);
}
// 学习模板
long studyFolderId = insertFolder(db, Notes.ID_TEMPLATE_FOLDER, "学习");
if (studyFolderId > 0) {
insertNote(db, studyFolderId, "读书笔记", "书名:\n作者\n\n核心观点\n\n精彩摘录\n\n读后感\n");
insertNote(db, studyFolderId, "读书笔记", "书名:\n作者\n\n核心观点\n\n精彩摘录\n\n读后感\n", Notes.TYPE_TEMPLATE);
}
}
@ -1012,10 +1012,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
return db.insert(TABLE.NOTE, null, values);
}
private void insertNote(SQLiteDatabase db, long parentId, String title, String content) {
private void insertNote(SQLiteDatabase db, long parentId, String title, String content, int type) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, parentId);
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.TYPE, type);
values.put(NoteColumns.CREATED_DATE, System.currentTimeMillis());
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
values.put(NoteColumns.SNIPPET, content); // SNIPPET acts as content preview or full content for simple notes

@ -168,7 +168,7 @@ public class NotesProvider extends ContentProvider {
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
+ " AND (" + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " OR " + NoteColumns.TYPE + "=" + Notes.TYPE_TEMPLATE + ")";
/**
* Content Provider

@ -88,6 +88,10 @@ public class NotesRepository {
return parentId;
}
public void setParentId(long parentId) {
this.parentId = parentId;
}
public String getNoteDataValue() {
return snippet;
}
@ -308,11 +312,17 @@ public class NotesRepository {
selection = NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " +
NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER;
selectionArgs = null;
} else if (folderId == Notes.ID_TEMPLATE_FOLDER) {
// Special case for template folder: show all templates regardless of category
selection = NoteColumns.TYPE + "=" + Notes.TYPE_TEMPLATE;
selectionArgs = null;
} else if (folderId == Notes.ID_ROOT_FOLDER) {
selection = "(" + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=?)";
selectionArgs = new String[]{String.valueOf(Notes.ID_ROOT_FOLDER)};
} else {
selection = NoteColumns.PARENT_ID + "=? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
// In a sub-folder, show both normal notes and templates if they exist there
selection = NoteColumns.PARENT_ID + "=? AND (" + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE +
" OR " + NoteColumns.TYPE + "=" + Notes.TYPE_TEMPLATE + ")";
selectionArgs = new String[]{String.valueOf(folderId)};
}
@ -605,8 +615,19 @@ public class NotesRepository {
ContentValues values = new ContentValues();
long currentTime = System.currentTimeMillis();
int type = Notes.TYPE_NOTE;
if (folderId == Notes.ID_TEMPLATE_FOLDER) {
type = Notes.TYPE_TEMPLATE;
} else if (folderId > 0) {
// Check if folder is under templates
NoteInfo folder = getFolderInfo(folderId);
if (folder != null && folder.parentId == Notes.ID_TEMPLATE_FOLDER) {
type = Notes.TYPE_TEMPLATE;
}
}
values.put(NoteColumns.PARENT_ID, folderId);
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.TYPE, type);
values.put(NoteColumns.CREATED_DATE, currentTime);
values.put(NoteColumns.MODIFIED_DATE, currentTime);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
@ -1482,7 +1503,7 @@ public class NotesRepository {
long currentTime = System.currentTimeMillis();
values.put(NoteColumns.PARENT_ID, categoryId);
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.TYPE, Notes.TYPE_TEMPLATE);
values.put(NoteColumns.CREATED_DATE, currentTime);
values.put(NoteColumns.MODIFIED_DATE, currentTime);
values.put(NoteColumns.LOCAL_MODIFIED, 1);

@ -115,6 +115,7 @@ public class WorkingNote {
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
DataColumns.DATA5,
};
/** 数据查询投影 - 笔记元数据 */
@ -190,7 +191,36 @@ public class WorkingNote {
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
mLocalModified = 1; // 新建笔记需要同步
mType = Notes.TYPE_NOTE; // 默认为普通笔记类型
// Determine type based on folder
if (folderId == Notes.ID_TEMPLATE_FOLDER) {
mType = Notes.TYPE_TEMPLATE;
} else if (folderId > 0) {
// Check if parent is template folder
int parentType = net.micode.notes.tool.DataUtils.getNoteTypeById(context.getContentResolver(), folderId);
if (parentType == Notes.TYPE_FOLDER) {
// We need to check the folder's parent
long parentId = 0;
android.database.Cursor c = context.getContentResolver().query(
android.content.ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, folderId),
new String[] { NoteColumns.PARENT_ID }, null, null, null);
if (c != null) {
if (c.moveToFirst()) {
parentId = c.getLong(0);
}
c.close();
}
if (parentId == Notes.ID_TEMPLATE_FOLDER) {
mType = Notes.TYPE_TEMPLATE;
} else {
mType = Notes.TYPE_NOTE;
}
} else {
mType = Notes.TYPE_NOTE;
}
} else {
mType = Notes.TYPE_NOTE;
}
}
/**
@ -287,6 +317,12 @@ public class WorkingNote {
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
// 加载壁纸路径
int wallpaperIndex = cursor.getColumnIndex(DataColumns.DATA5);
if (wallpaperIndex != -1) {
mWallpaperPath = cursor.getString(wallpaperIndex);
}
} else if (DataConstants.CALL_NOTE.equals(type)) {
// 加载通话记录数据
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
@ -366,9 +402,9 @@ public class WorkingNote {
public synchronized boolean saveNote() {
if (isWorthSaving()) {
if (!existInDatabase()) {
// 创建新笔记
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
// 创建新笔记
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId, mType)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
@ -501,16 +537,12 @@ public class WorkingNote {
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
if (!TextUtils.equals(mWallpaperPath, path)) {
mWallpaperPath = path;
mNote.setTextData(DataColumns.DATA5, mWallpaperPath);
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged(); // Reuse this to trigger refresh
}
}
}

@ -182,10 +182,22 @@ public class DataUtils {
* @return true false
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
String selection;
String[] selectionArgs;
if (type == Notes.TYPE_NOTE) {
// If checking for a regular note, also allow templates as they are essentially notes
selection = "(" + NoteColumns.TYPE + "=? OR " + NoteColumns.TYPE + "=?) AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER;
selectionArgs = new String[] {String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.TYPE_TEMPLATE)};
} else {
selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER;
selectionArgs = new String [] {String.valueOf(type)};
}
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
selection,
selectionArgs,
null);
boolean exist = false;

@ -51,6 +51,12 @@ public class ResourceParser {
public static final int EYE_CARE_GREEN = 6;
public static final int WARM = 7;
public static final int COOL = 8;
// Gradient Presets
public static final int SUNSET = 9;
public static final int OCEAN = 10;
public static final int FOREST = 11;
public static final int LAVENDER = 12;
/** 自定义颜色按钮 ID (用于 UI 显示) */
public static final int CUSTOM_COLOR_BUTTON_ID = -100;
@ -108,7 +114,15 @@ public class ResourceParser {
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
R.drawable.edit_red,
R.color.bg_midnight_black,
R.color.bg_eye_care_green,
R.color.bg_warm,
R.color.bg_cool,
R.drawable.preset_sunset,
R.drawable.preset_ocean,
R.drawable.preset_forest,
R.drawable.preset_lavender
};
/** 标题栏背景资源数组 */

@ -77,7 +77,10 @@ import java.util.regex.Pattern;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import net.micode.notes.databinding.DialogBackgroundSelectorBinding;
import net.micode.notes.databinding.DialogColorPickerBinding;
import net.micode.notes.databinding.NoteEditBinding;
import net.micode.notes.tool.RichTextHelper;
@ -162,7 +165,6 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
private UndoRedoManager mUndoRedoManager;
private boolean mInUndoRedo = false;
private androidx.recyclerview.widget.RecyclerView mColorSelectorRv;
private NoteColorAdapter mColorAdapter;
/**
@ -331,7 +333,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
* </p>
*/
private void initResources() {
mHeadViewPanel = binding.noteTitle;
mHeadViewPanel = binding.cvEditorSurface;
mNoteHeaderHolder = new HeadViewHolder();
mNoteHeaderHolder.tvModified = binding.tvModifiedDate;
mNoteHeaderHolder.ivAlertIcon = binding.ivAlertIcon;
@ -342,9 +344,8 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
mNoteHeaderHolder.etTitle = binding.etTitle;
mNoteEditor = binding.noteEditView;
mNoteEditorPanel = binding.svNoteEdit;
mNoteEditorPanel = binding.cvEditorSurface;
mNoteBgColorSelector = binding.noteBgColorSelector;
mColorSelectorRv = binding.rvBgColorSelector;
mNoteEditor.addTextChangedListener(new TextWatcher() {
private CharSequence mBeforeText;
@ -406,6 +407,10 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
ResourceParser.EYE_CARE_GREEN,
ResourceParser.WARM,
ResourceParser.COOL,
ResourceParser.SUNSET,
ResourceParser.OCEAN,
ResourceParser.FOREST,
ResourceParser.LAVENDER,
ResourceParser.CUSTOM_COLOR_BUTTON_ID,
ResourceParser.WALLPAPER_BUTTON_ID
);
@ -418,11 +423,11 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
pickWallpaper();
} else {
mWorkingNote.setBgColorId(colorId);
mWorkingNote.setWallpaper(null);
mNoteBgColorSelector.setVisibility(View.GONE);
}
}
});
mColorSelectorRv.setAdapter(mColorAdapter);
mFontSizeSelector = binding.fontSizeSelector;
for (int id : sFontSizeBtnsMap.keySet()) {
@ -680,8 +685,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
// Note: Adapter selection is already set in onBackgroundColorChanged or init
showBackgroundSelector();
} else if (sFontSizeBtnsMap.containsKey(id)) {
View fontView = getFontSelectorView(sFontSelectorSelectionMap.get(mFontSizeId));
if (fontView != null) {
@ -764,27 +768,14 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
String wallpaperPath = mWorkingNote.getWallpaperPath();
if (wallpaperPath != null) {
// Load wallpaper
binding.ivNoteWallpaper.setVisibility(View.VISIBLE);
binding.viewBgMask.setVisibility(View.VISIBLE);
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());
binding.ivNoteWallpaper.setImageBitmap(bitmap);
// Dynamic Coloring with Palette
androidx.palette.graphics.Palette.from(bitmap).generate(palette -> {
@ -795,12 +786,14 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
} catch (Exception e) {
Log.e(TAG, "Failed to load wallpaper", e);
// Fallback to color
binding.ivNoteWallpaper.setVisibility(View.GONE);
binding.viewBgMask.setVisibility(View.GONE);
applyColorBackground(colorId);
}
} else {
binding.ivNoteWallpaper.setVisibility(View.GONE);
binding.viewBgMask.setVisibility(View.GONE);
applyColorBackground(colorId);
// Reset toolbar colors to default/theme
resetToolbarColors();
}
updateTextColor(colorId);
@ -809,30 +802,47 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
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);
int mutedColor = palette.getMutedColor(android.graphics.Color.WHITE);
// Ensure contrast for onPrimaryColor
if (androidx.core.graphics.ColorUtils.calculateContrast(onPrimaryColor, primaryColor) < 3.0) {
onPrimaryColor = android.graphics.Color.WHITE;
onPrimaryColor = isColorDark(primaryColor) ? android.graphics.Color.WHITE : android.graphics.Color.BLACK;
}
binding.toolbar.setBackgroundColor(primaryColor);
binding.toolbar.setTitleTextColor(onPrimaryColor);
if (binding.toolbar.getNavigationIcon() != null) {
binding.toolbar.getNavigationIcon().setTint(onPrimaryColor);
}
getWindow().setStatusBarColor(primaryColor);
// Update Card Surface - semi-transparent glass effect
int surfaceColor = androidx.core.graphics.ColorUtils.setAlphaComponent(mutedColor, 230); // 90% opacity
binding.cvEditorSurface.setCardBackgroundColor(surfaceColor);
// Update input text color based on surface color
int textColor = isColorDark(surfaceColor) ? android.graphics.Color.WHITE : android.graphics.Color.BLACK;
mNoteEditor.setTextColor(textColor);
if (mNoteHeaderHolder != null && mNoteHeaderHolder.etTitle != null) {
mNoteHeaderHolder.etTitle.setTextColor(textColor);
mNoteHeaderHolder.etTitle.setHintTextColor(androidx.core.graphics.ColorUtils.setAlphaComponent(textColor, 128));
}
binding.tvCharCount.setTextColor(textColor);
binding.tvModifiedDate.setTextColor(textColor);
}
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.setBackgroundColor(android.graphics.Color.TRANSPARENT);
binding.toolbar.setTitleTextColor(onPrimaryColor);
if (binding.toolbar.getNavigationIcon() != null) {
binding.toolbar.getNavigationIcon().setTint(onPrimaryColor);
}
getWindow().setStatusBarColor(primaryColor);
// Reset Card Surface
binding.cvEditorSurface.setCardBackgroundColor(android.graphics.Color.parseColor("#CCFFFFFF"));
}
private void updateTextColor(int colorId) {
@ -843,20 +853,27 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
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);
// If wallpaper is set, applyPaletteColors already handled text color.
if (mWorkingNote.getWallpaperPath() == null) {
mNoteEditor.setTextColor(textColor);
if (mNoteHeaderHolder != null && mNoteHeaderHolder.etTitle != null) {
mNoteHeaderHolder.etTitle.setTextColor(textColor);
mNoteHeaderHolder.etTitle.setHintTextColor(androidx.core.graphics.ColorUtils.setAlphaComponent(textColor, 128));
}
binding.tvCharCount.setTextColor(textColor);
binding.tvModifiedDate.setTextColor(textColor);
// Adjust card surface opacity for pure colors
if (colorId == ResourceParser.WHITE) {
binding.cvEditorSurface.setCardBackgroundColor(android.graphics.Color.WHITE);
} else {
binding.cvEditorSurface.setCardBackgroundColor(android.graphics.Color.parseColor("#CCFFFFFF"));
}
}
}
@ -868,27 +885,10 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}
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);
}
if (colorId < 0) {
binding.noteEditRoot.setBackgroundColor(colorId);
} else {
// Clear tint for legacy resources
if (mNoteEditorPanel.getBackground() != null) {
mNoteEditorPanel.getBackground().clearColorFilter();
}
if (mHeadViewPanel.getBackground() != null) {
mHeadViewPanel.getBackground().clearColorFilter();
}
binding.noteEditRoot.setBackgroundResource(ResourceParser.NoteBgResources.getNoteBgResource(colorId));
}
}
@ -1535,13 +1535,50 @@ 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);
private void showBackgroundSelector() {
BottomSheetDialog dialog = new BottomSheetDialog(this);
DialogBackgroundSelectorBinding dialogBinding = DialogBackgroundSelectorBinding.inflate(getLayoutInflater());
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.SUNSET,
ResourceParser.OCEAN,
ResourceParser.FOREST,
ResourceParser.LAVENDER
);
NoteColorAdapter adapter = new NoteColorAdapter(colors, mWorkingNote.getBgColorId(), colorId -> {
mWorkingNote.setBgColorId(colorId);
mWorkingNote.setWallpaper(null); // Clear wallpaper when color selected
dialog.dismiss();
});
dialogBinding.rvBackgroundOptions.setAdapter(adapter);
dialogBinding.btnPickWallpaper.setOnClickListener(v -> {
pickWallpaper();
dialog.dismiss();
});
dialogBinding.btnCustomColor.setOnClickListener(v -> {
showColorPickerDialog();
dialog.dismiss();
});
dialog.setContentView(dialogBinding.getRoot());
dialog.show();
}
private void showColorPickerDialog() {
DialogColorPickerBinding dialogBinding = DialogColorPickerBinding.inflate(getLayoutInflater());
int currentColor = android.graphics.Color.WHITE;
if (mWorkingNote.getBgColorId() < 0) {
currentColor = mWorkingNote.getBgColorId();
@ -1553,10 +1590,10 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
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]);
dialogBinding.viewColorPreview.setBackgroundColor(android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]));
dialogBinding.sbRed.setProgress(rgb[0]);
dialogBinding.sbGreen.setProgress(rgb[1]);
dialogBinding.sbBlue.setProgress(rgb[2]);
android.widget.SeekBar.OnSeekBarChangeListener listener = new android.widget.SeekBar.OnSeekBarChangeListener() {
@Override
@ -1564,7 +1601,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
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]));
dialogBinding.viewColorPreview.setBackgroundColor(android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]));
}
@Override
@ -1574,21 +1611,18 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
public void onStopTrackingTouch(android.widget.SeekBar seekBar) {}
};
sbRed.setOnSeekBarChangeListener(listener);
sbGreen.setOnSeekBarChangeListener(listener);
sbBlue.setOnSeekBarChangeListener(listener);
dialogBinding.sbRed.setOnSeekBarChangeListener(listener);
dialogBinding.sbGreen.setOnSeekBarChangeListener(listener);
dialogBinding.sbBlue.setOnSeekBarChangeListener(listener);
new AlertDialog.Builder(this)
.setTitle("Custom Color")
.setView(dialogView)
.setView(dialogBinding.getRoot())
.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);
mWorkingNote.setWallpaper(null);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
@ -1695,27 +1729,26 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}
private void initRichTextToolbar() {
mRichTextSelector = findViewById(R.id.rich_text_selector);
findViewById(R.id.btn_bold).setOnClickListener(new OnClickListener() {
mRichTextSelector = binding.richTextSelector;
binding.btnBold.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyBold(mNoteEditor); }
});
findViewById(R.id.btn_italic).setOnClickListener(new OnClickListener() {
binding.btnItalic.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyItalic(mNoteEditor); }
});
findViewById(R.id.btn_underline).setOnClickListener(new OnClickListener() {
binding.btnUnderline.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyUnderline(mNoteEditor); }
});
findViewById(R.id.btn_strikethrough).setOnClickListener(new OnClickListener() {
binding.btnStrikethrough.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyStrikethrough(mNoteEditor); }
});
findViewById(R.id.btn_header).setOnClickListener(new OnClickListener() {
binding.btnHeader.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);
}
@ -1723,22 +1756,22 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
builder.show();
}
});
findViewById(R.id.btn_list).setOnClickListener(new OnClickListener() {
binding.btnList.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyBullet(mNoteEditor); }
});
findViewById(R.id.btn_quote).setOnClickListener(new OnClickListener() {
binding.btnQuote.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyQuote(mNoteEditor); }
});
findViewById(R.id.btn_code).setOnClickListener(new OnClickListener() {
binding.btnCode.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyCode(mNoteEditor); }
});
findViewById(R.id.btn_link).setOnClickListener(new OnClickListener() {
binding.btnLink.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.insertLink(NoteEditActivity.this, mNoteEditor); }
});
findViewById(R.id.btn_divider).setOnClickListener(new OnClickListener() {
binding.btnDivider.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.insertDivider(mNoteEditor); }
});
findViewById(R.id.btn_color_text).setOnClickListener(new OnClickListener() {
binding.btnColorText.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};
@ -1752,7 +1785,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
builder.show();
}
});
findViewById(R.id.btn_color_fill).setOnClickListener(new OnClickListener() {
binding.btnColorFill.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};

@ -196,8 +196,8 @@ public class NoteItemData {
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
// 如果是普通笔记且不是第一项,检查前一项是否为文件夹
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
// 如果是普通笔记或模板且不是第一项,检查前一项是否为文件夹
if ((mType == Notes.TYPE_NOTE || mType == Notes.TYPE_TEMPLATE) && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
// 前一项是文件夹或系统文件夹

@ -154,14 +154,36 @@ public class NoteSearchActivity extends AppCompatActivity implements SearchView.
mHistoryManager.addHistory(query);
}
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, note.getId());
// Pass search keyword for highlighting in editor
// NoteEditActivity uses SearchManager.EXTRA_DATA_KEY for ID and USER_QUERY for keyword
intent.putExtra(android.app.SearchManager.EXTRA_DATA_KEY, String.valueOf(note.getId()));
intent.putExtra(android.app.SearchManager.USER_QUERY, mSearchView.getQuery().toString());
startActivity(intent);
if (note.type == Notes.TYPE_TEMPLATE) {
// Apply template: create a new note based on this template
mRepository.applyTemplate(note.getId(), Notes.ID_ROOT_FOLDER, new NotesRepository.Callback<Long>() {
@Override
public void onSuccess(Long newNoteId) {
runOnUiThread(() -> {
Intent intent = new Intent(NoteSearchActivity.this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, newNoteId);
startActivity(intent);
Toast.makeText(NoteSearchActivity.this, "已根据模板创建新笔记", Toast.LENGTH_SHORT).show();
});
}
@Override
public void onError(Exception e) {
runOnUiThread(() -> {
Toast.makeText(NoteSearchActivity.this, "应用模板失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
}
});
} else {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, note.getId());
// Pass search keyword for highlighting in editor
intent.putExtra(android.app.SearchManager.EXTRA_DATA_KEY, String.valueOf(note.getId()));
intent.putExtra(android.app.SearchManager.USER_QUERY, query);
startActivity(intent);
}
}
@Override

@ -148,6 +148,30 @@ public class NotesListFragment extends Fragment implements
NotesRepository.NoteInfo note = viewModel.getNotesLiveData().getValue().get(position);
if (note.type == Notes.TYPE_FOLDER) {
viewModel.enterFolder(note.getId());
} else if (note.type == Notes.TYPE_TEMPLATE) {
// Apply template: create a new note based on this template
viewModel.applyTemplate(note.getId(), new net.micode.notes.data.NotesRepository.Callback<Long>() {
@Override
public void onSuccess(Long newNoteId) {
// Create a temporary NoteInfo to open the editor
net.micode.notes.data.NotesRepository.NoteInfo newNote = new net.micode.notes.data.NotesRepository.NoteInfo();
newNote.setId(newNoteId);
newNote.setParentId(Notes.ID_ROOT_FOLDER);
newNote.type = Notes.TYPE_NOTE;
requireActivity().runOnUiThread(() -> {
openNoteEditor(newNote);
Toast.makeText(requireContext(), "已根据模板创建新笔记", Toast.LENGTH_SHORT).show();
});
}
@Override
public void onError(Exception e) {
requireActivity().runOnUiThread(() -> {
Toast.makeText(requireContext(), "应用模板失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
}
});
} else {
if (note.isLocked) {
pendingNote = note;

@ -69,7 +69,7 @@ public class NotesListItem extends LinearLayout {
* @param checked
*/
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
if (choiceMode && (data.getType() == Notes.TYPE_NOTE || data.getType() == Notes.TYPE_TEMPLATE)) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
@ -136,7 +136,7 @@ public class NotesListItem extends LinearLayout {
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
int resId;
if (data.getType() == Notes.TYPE_NOTE) {
if (data.getType() == Notes.TYPE_NOTE || data.getType() == Notes.TYPE_TEMPLATE) {
if (data.isSingle() || data.isOneFollowingFolder()) {
resId = NoteItemBgResources.getNoteBgSingleRes(id);
} else if (data.isLast()) {
@ -153,7 +153,7 @@ public class NotesListItem extends LinearLayout {
setBackgroundResource(resId);
// Apply tint for new colors
if (data.getType() == Notes.TYPE_NOTE && (id >= net.micode.notes.tool.ResourceParser.MIDNIGHT_BLACK || id < 0)) {
if ((data.getType() == Notes.TYPE_NOTE || data.getType() == Notes.TYPE_TEMPLATE) && (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);

@ -105,7 +105,7 @@ public class NotesRecyclerAdapter extends RecyclerView.Adapter<NotesRecyclerAdap
}
public void bind(NoteItemData data, boolean choiceMode, boolean checked) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
if (choiceMode && (data.getType() == Notes.TYPE_NOTE || data.getType() == Notes.TYPE_TEMPLATE)) {
checkBox.setVisibility(View.VISIBLE);
checkBox.setChecked(checked);
} else {

@ -160,44 +160,60 @@ public class NotesListViewModel extends ViewModel {
@Override
public void onSuccess(List<NotesRepository.NoteInfo> path) {
folderPathLiveData.postValue(path);
}
@Override
public void onError(Exception error) {
Log.e(TAG, "Failed to load folder path", error);
}
});
// 加载子文件夹 (Category Tabs) - Always load root folders to keep tabs visible
repository.getSubFolders(Notes.ID_ROOT_FOLDER, new NotesRepository.Callback<List<NotesRepository.NoteInfo>>() {
@Override
public void onSuccess(List<NotesRepository.NoteInfo> folders) {
// Construct the display list with "All" and "Uncategorized"
List<NotesRepository.NoteInfo> displayFolders = new ArrayList<>();
// 1. "All" Folder (Virtual)
NotesRepository.NoteInfo allFolder = new NotesRepository.NoteInfo();
allFolder.setId(Notes.ID_ALL_NOTES_FOLDER);
allFolder.snippet = "所有"; // Name
displayFolders.add(allFolder);
// 2. Real Folders (from DB)
if (folders != null) {
displayFolders.addAll(folders);
// Determine if we are in template mode
boolean isTemplate = (folderId == Notes.ID_TEMPLATE_FOLDER);
if (!isTemplate && path != null) {
for (NotesRepository.NoteInfo info : path) {
if (info.getId() == Notes.ID_TEMPLATE_FOLDER) {
isTemplate = true;
break;
}
}
}
// 3. "Uncategorized" Folder (Actually Root Folder)
NotesRepository.NoteInfo uncategorizedFolder = new NotesRepository.NoteInfo();
uncategorizedFolder.setId(Notes.ID_ROOT_FOLDER);
uncategorizedFolder.snippet = "未分类"; // Custom Name for Root
displayFolders.add(uncategorizedFolder);
foldersLiveData.postValue(displayFolders);
final boolean templateMode = isTemplate;
long tabParentId = templateMode ? Notes.ID_TEMPLATE_FOLDER : Notes.ID_ROOT_FOLDER;
// 加载子文件夹 (Category Tabs)
repository.getSubFolders(tabParentId, new NotesRepository.Callback<List<NotesRepository.NoteInfo>>() {
@Override
public void onSuccess(List<NotesRepository.NoteInfo> folders) {
// Construct the display list with "All" and "Uncategorized"
List<NotesRepository.NoteInfo> displayFolders = new ArrayList<>();
// 1. "All" / "All Templates" Folder (Virtual)
NotesRepository.NoteInfo allFolder = new NotesRepository.NoteInfo();
allFolder.setId(templateMode ? Notes.ID_TEMPLATE_FOLDER : Notes.ID_ALL_NOTES_FOLDER);
allFolder.snippet = templateMode ? "所有模板" : "所有"; // Name
displayFolders.add(allFolder);
// 2. Real Folders (from DB)
if (folders != null) {
displayFolders.addAll(folders);
}
// 3. "Uncategorized" Folder (only for normal notes)
if (!templateMode) {
NotesRepository.NoteInfo uncategorizedFolder = new NotesRepository.NoteInfo();
uncategorizedFolder.setId(Notes.ID_ROOT_FOLDER);
uncategorizedFolder.snippet = "未分类"; // Custom Name for Root
displayFolders.add(uncategorizedFolder);
}
foldersLiveData.postValue(displayFolders);
}
@Override
public void onError(Exception error) {
Log.e(TAG, "Failed to load sub-folders", error);
}
});
}
@Override
public void onError(Exception error) {
Log.e(TAG, "Failed to load sub-folders", error);
Log.e(TAG, "Failed to load folder path", error);
}
});

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners
android:topLeftRadius="24dp"
android:topRightRadius="24dp" />
</shape>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:startColor="@color/preset_forest_start"
android:endColor="@color/preset_forest_end"
android:angle="45" />

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:startColor="@color/preset_lavender_start"
android:endColor="@color/preset_lavender_end"
android:angle="45" />

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:startColor="@color/preset_ocean_start"
android:endColor="@color/preset_ocean_end"
android:angle="45" />

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:startColor="@color/preset_sunset_start"
android:endColor="@color/preset_sunset_end"
android:angle="45" />

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:background="@drawable/bg_bottom_sheet">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择背景"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/text_color_primary"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="纯色与预设"
android:textSize="14sp"
android:textColor="@color/text_color_secondary"
android:layout_marginBottom="12dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_background_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更多选项"
android:textSize="14sp"
android:textColor="@color/text_color_secondary"
android:layout_marginBottom="12dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_pick_wallpaper"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="选择图片"
app:icon="@android:drawable/ic_menu_gallery"
android:layout_marginRight="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_custom_color"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="自定义颜色"
app:icon="@android:drawable/ic_menu_edit"
android:layout_marginLeft="8dp" />
</LinearLayout>
</LinearLayout>

@ -15,482 +15,258 @@
limitations under the License.
-->
<!-- 统一使用Material风格与列表页面一致 -->
<LinearLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/note_edit_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:background="@color/background_color">
<!-- 统一使用MaterialToolbar与列表页面一致 -->
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
<!-- 全屏背景层 -->
<ImageView
android:id="@+id/iv_note_wallpaper"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:title="@string/menu_edit_note"
app:navigationIcon="@android:drawable/ic_menu_close_clear_cancel" />
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:visibility="gone" />
<!-- 主内容区域 -->
<LinearLayout
<View
android:id="@+id/view_bg_mask"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:id="@+id/note_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="match_parent"
android:background="#1A000000"
android:visibility="gone" />
<TextView
android:id="@+id/tv_modified_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_marginRight="8dp"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<TextView
android:id="@+id/tv_char_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:text="0 字"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@drawable/title_alert" />
<TextView
android:id="@+id/tv_alert_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="2dp"
android:layout_marginRight="8dp"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
</LinearLayout>
<!-- 顶部 Toolbar -->
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:elevation="0dp">
<!-- Title Input Field -->
<EditText
android:id="@+id/et_title"
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:hint="Title"
android:paddingLeft="15dip"
android:paddingRight="15dip"
android:paddingTop="10dip"
android:paddingBottom="5dip"
android:singleLine="true"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:textColorHint="@android:color/darker_gray" />
android:layout_height="?attr/actionBarSize"
android:background="@android:color/transparent"
app:navigationIcon="@android:drawable/ic_menu_close_clear_cancel"
app:title="@string/menu_edit_note"
app:titleTextAppearance="@style/TextAppearance.Material3.TitleMedium" />
</com.google.android.material.appbar.AppBarLayout>
<!-- 编辑区域容器 -->
<androidx.core.widget.NestedScrollView
android:id="@+id/sv_note_edit_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/sv_note_edit"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="7dp"
android:background="@drawable/bg_color_btn_mask" />
<ScrollView
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="20dp"
android:paddingTop="8dp"
android:paddingBottom="100dp">
<!-- 玻璃拟态卡片容器 -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/cv_editor_surface"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_gravity="left|top"
android:fadingEdgeLength="0dp">
android:layout_height="wrap_content"
app:cardCornerRadius="24dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#CCFFFFFF"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<!-- 状态与操作行 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/tv_modified_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:alpha="0.6"
android:textColor="@color/text_color_primary"
android:fontFamily="sans-serif-medium" />
<TextView
android:id="@+id/tv_char_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textSize="12sp"
android:alpha="0.4"
android:textColor="@color/text_color_primary"
android:text="0 characters" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/title_alert"
android:tint="@color/fab_color"
android:visibility="gone" />
<TextView
android:id="@+id/tv_alert_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:textSize="12sp"
android:textColor="@color/fab_color"
android:visibility="gone" />
<ImageButton
android:id="@+id/btn_set_bg_color"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/bg_btn_set_color"
android:padding="8dp"
android:scaleType="centerInside" />
</LinearLayout>
</RelativeLayout>
<!-- 标题输入 -->
<EditText
android:id="@+id/et_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:hint="Note Title"
android:paddingVertical="8dp"
android:singleLine="true"
android:textSize="28sp"
android:textStyle="bold"
android:fontFamily="sans-serif-black"
android:textColor="@color/text_color_primary"
android:textColorHint="#4D000000" />
<View
android:layout_width="40dp"
android:layout_height="4dp"
android:background="#21000000"
android:layout_marginTop="12dp"
android:layout_marginBottom="24dp" />
<!-- 正文编辑 -->
<net.micode.notes.ui.NoteEditText
android:id="@+id/note_edit_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|top"
android:gravity="top"
android:background="@null"
android:autoLink="all"
android:linksClickable="false"
android:minLines="12"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:lineSpacingMultiplier="1.2" />
android:textSize="18sp"
android:textColor="@color/text_color_primary"
android:lineSpacingMultiplier="1.5"
android:fontFamily="sans-serif" />
<LinearLayout
android:id="@+id/note_edit_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="-10dp"
android:visibility="gone" />
</LinearLayout>
</ScrollView>
<ImageView
android:layout_width="match_parent"
android:layout_height="7dp"
android:background="@drawable/bg_color_btn_mask" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_height="43dp"
</androidx.core.widget.NestedScrollView>
<!-- 底部悬浮富文本工具栏 -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/rich_text_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="24dp"
android:visibility="gone"
app:cardCornerRadius="30dp"
app:cardElevation="8dp"
app:cardBackgroundColor="#F0FFFFFF"
app:strokeWidth="0.5dp"
app:strokeColor="#1A000000">
<HorizontalScrollView
android:layout_width="wrap_content"
android:background="@drawable/bg_color_btn_mask"
android:layout_gravity="top|right" />
android:layout_height="56dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/note_bg_color_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/note_edit_color_selector_panel"
android:layout_marginTop="30dp"
android:layout_marginRight="8dp"
android:layout_gravity="top|right"
android:visibility="gone"
android:orientation="horizontal">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_bg_color_selector"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>
<LinearLayout
android:id="@+id/font_size_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">
<FrameLayout
android:id="@+id/ll_font_small"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_small"
android:layout_marginBottom="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_small"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_small_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="6dp"
android:layout_marginBottom="-7dp"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_normal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_normal"
android:layout_marginBottom="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_normal"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_medium_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="6dp"
android:layout_marginBottom="-7dp"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_large"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_large"
android:layout_marginBottom="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_large"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_large_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="6dp"
android:layout_marginBottom="-7dp"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_super"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_super"
android:layout_marginBottom="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_super"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_super_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="6dp"
android:layout_marginBottom="-7dp"
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>
android:gravity="center_vertical"
android:paddingHorizontal="12dp">
<ImageButton android:id="@+id/btn_bold" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_bold" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_italic" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_italic" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_underline" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_underline" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_strikethrough" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_strikethrough" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<View android:layout_width="1dp" android:layout_height="24dp" android:background="#1A000000" android:layout_marginHorizontal="8dp"/>
<ImageButton android:id="@+id/btn_header" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_header" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_list" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_list_bulleted" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_quote" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_quote" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_code" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_code" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<View android:layout_width="1dp" android:layout_height="24dp" android:background="#1A000000" android:layout_marginHorizontal="8dp"/>
<ImageButton android:id="@+id/btn_link" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_insert_link" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_divider" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_insert_divider" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_color_text" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_color_text" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_color_fill" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_color_fill" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
</LinearLayout>
</HorizontalScrollView>
</com.google.android.material.card.MaterialCardView>
<!-- 兼容性保留 ID 的隐藏视图 -->
<View android:id="@+id/note_bg_color_selector" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/font_size_selector" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/ll_font_small" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/ll_font_normal" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/ll_font_large" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/ll_font_super" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/iv_small_select" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/iv_medium_select" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/iv_large_select" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/iv_super_select" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/sv_note_edit" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -43,6 +43,16 @@
<color name="bg_warm">#FFE0B2</color>
<color name="bg_cool">#E1BEE7</color>
<!-- Modern Presets -->
<color name="preset_sunset_start">#FF512F</color>
<color name="preset_sunset_end">#DD2476</color>
<color name="preset_ocean_start">#2193B0</color>
<color name="preset_ocean_end">#6DD5ED</color>
<color name="preset_forest_start">#11998E</color>
<color name="preset_forest_end">#38EF7D</color>
<color name="preset_lavender_start">#834D9B</color>
<color name="preset_lavender_end">#D04ED6</color>
<!-- Drawer Header Colors - Material Design 3 Blue Theme -->
<color name="drawer_header_gradient_start">#1976D2</color>
<color name="drawer_header_gradient_center">#2196F3</color>

Loading…
Cancel
Save