diff --git a/src/Notesmaster/app/src/main/AndroidManifest.xml b/src/Notesmaster/app/src/main/AndroidManifest.xml index 68fb7ab..db4380f 100644 --- a/src/Notesmaster/app/src/main/AndroidManifest.xml +++ b/src/Notesmaster/app/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ + android:theme="@style/Theme.Notesmaster" > @@ -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" > diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java b/src/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java index f8aadb4..c062c24 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java @@ -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; + } + /** * 设置清单模式 *

diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java index 4677289..595d7a5 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java @@ -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]; } diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java index ed3df61..d7993be 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -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 sBgSelectorBtnsMap = new HashMap(); - 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 sBgSelectorSelectionMap = new HashMap(); - 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 sFontSizeBtnsMap = new HashMap(); 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 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 *

*/ 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提示 *

@@ -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(); + } + }); + } } diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java index ddfe1ce..9a6d238 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java @@ -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(); + } } } diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java index 368f0d0..de53616 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java @@ -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 - *

- * 该Activity用于管理应用的各种设置,主要包括: - *

- *

- *

- * 该类继承自PreferenceActivity,使用SharedPreferences来持久化设置数据。 - * 通过GTaskReceiver接收同步服务的广播,实时更新同步状态。 - *

- */ -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 - *

- * 初始化设置界面,包括: - *

- *

- * @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恢复时调用 - *

- * 检查是否有新添加的Google账户,如果有则自动设置为同步账户。 - * 然后刷新UI显示。 - *

- */ @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销毁时调用 - *

- * 注销同步服务广播接收器,防止内存泄漏。 - *

- */ - @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(); } - } - - /** - * 加载账户设置选项 - *

- * 创建并添加账户Preference到账户分类中。 - * 点击该Preference时: - *

- *

- */ - 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(); } - /** - * 加载同步按钮和同步状态显示 - *

- * 根据当前同步状态设置按钮文本和点击事件: - *

- * 同时显示最后同步时间或当前同步进度。 - *

- */ 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显示 - *

- * 重新加载账户设置选项和同步按钮状态。 - *

- */ - private void refreshUI() { - loadAccountPreference(); - loadSyncButton(); - } - - /** - * 显示选择账户对话框 - *

- * 显示一个对话框,列出所有可用的Google账户供用户选择。 - * 同时提供"添加账户"选项,点击后跳转到系统账户添加界面。 - *

- */ - 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(); - } - }); - } - - /** - * 显示更改账户确认对话框 - *

- * 显示一个对话框,提供三个选项: - *

- *

- */ - 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账户 - *

- * 从系统AccountManager中获取所有类型为"com.google"的账户。 - *

- * @return Google账户数组 - */ - private Account[] getGoogleAccounts() { - AccountManager accountManager = AccountManager.get(this); - return accountManager.getAccountsByType("com.google"); - } - - /** - * 设置同步账户 - *

- * 保存指定的账户名称到SharedPreferences,并清理相关数据: - *

- *

- * @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(); - } - } - - /** - * 移除同步账户 - *

- * 从SharedPreferences中删除同步账户和最后同步时间, - * 并清理所有笔记的GTASK_ID和SYNC_ID。 - *

- */ - 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(); - } - - /** - * 获取同步账户名称 - *

- * 从SharedPreferences中读取已设置的同步账户名称。 - *

- * @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, ""); } - /** - * 设置最后同步时间 - *

- * 将指定的同步时间保存到SharedPreferences。 - *

- * @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(); - } - - /** - * 获取最后同步时间 - *

- * 从SharedPreferences中读取最后同步时间。 - *

- * @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); - } - - /** - * 同步服务广播接收器 - *

- * 接收GTaskSyncService发送的广播,实时更新UI显示同步状态和进度。 - *

- */ - private class GTaskReceiver extends BroadcastReceiver { - - /** - * 接收广播 - *

- * 当收到同步服务广播时,刷新UI并更新同步状态显示。 - *

- * @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)); - // } - - } - } - - /** - * 处理菜单项选择 - *

- * 处理ActionBar上的菜单项点击事件。 - * 当点击返回按钮时,返回到笔记列表界面。 - *

- * @param item 被点击的菜单项 - * @return true表示已处理,false表示未处理 - */ + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: diff --git a/src/Notesmaster/app/src/main/res/layout/note_edit.xml b/src/Notesmaster/app/src/main/res/layout/note_edit.xml index b0b9e28..aba7781 100644 --- a/src/Notesmaster/app/src/main/res/layout/note_edit.xml +++ b/src/Notesmaster/app/src/main/res/layout/note_edit.xml @@ -170,112 +170,16 @@ android:layout_marginTop="30dp" android:layout_marginRight="8dp" android:layout_gravity="top|right" - android:visibility="gone"> - - - - - - - - - - - - - - - - - - - - - + android:visibility="gone" + android:orientation="horizontal"> - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Notesmaster/app/src/main/res/menu/note_edit.xml b/src/Notesmaster/app/src/main/res/menu/note_edit.xml index 35cacd1..bff7ff4 100644 --- a/src/Notesmaster/app/src/main/res/menu/note_edit.xml +++ b/src/Notesmaster/app/src/main/res/menu/note_edit.xml @@ -16,12 +16,35 @@ --> + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + + + + + diff --git a/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml b/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml index 61b9801..66848e0 100644 --- a/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml @@ -133,4 +133,11 @@ 回收站 创建文件夹成功 + 撤回 + 重做 + 清空撤回历史 + 撤回成功 + 重做成功 + 无可撤回 + 无可重做 diff --git a/src/Notesmaster/app/src/main/res/values/arrays.xml b/src/Notesmaster/app/src/main/res/values/arrays.xml index e00210b..ce7ac3a 100644 --- a/src/Notesmaster/app/src/main/res/values/arrays.xml +++ b/src/Notesmaster/app/src/main/res/values/arrays.xml @@ -28,4 +28,16 @@ Messaging Email + + + Follow System + Light + Dark + + + + system + light + dark + \ No newline at end of file diff --git a/src/Notesmaster/app/src/main/res/values/colors.xml b/src/Notesmaster/app/src/main/res/values/colors.xml index 3c01874..2495561 100644 --- a/src/Notesmaster/app/src/main/res/values/colors.xml +++ b/src/Notesmaster/app/src/main/res/values/colors.xml @@ -23,4 +23,21 @@ #000000 #808080 #FFC107 + + + #000000 + #808080 + + + #FFF9C4 + #B3E5FC + #FFFFFF + #C8E6C9 + #FFCDD2 + + + #212121 + #C7EDCC + #FFE0B2 + #E1BEE7 diff --git a/src/Notesmaster/app/src/main/res/values/strings.xml b/src/Notesmaster/app/src/main/res/values/strings.xml index 5b212aa..9e40ea6 100644 --- a/src/Notesmaster/app/src/main/res/values/strings.xml +++ b/src/Notesmaster/app/src/main/res/values/strings.xml @@ -166,4 +166,14 @@ No results found Search History Clear + + + Undo + Redo + Clear Undo History + Undo successful + Redo successful + Nothing to undo + Nothing to redo + Rich Text diff --git a/src/Notesmaster/app/src/main/res/values/styles.xml b/src/Notesmaster/app/src/main/res/values/styles.xml index c1eddb2..67bc75f 100644 --- a/src/Notesmaster/app/src/main/res/values/styles.xml +++ b/src/Notesmaster/app/src/main/res/values/styles.xml @@ -35,27 +35,27 @@ \ No newline at end of file diff --git a/src/Notesmaster/app/src/main/res/xml/preferences.xml b/src/Notesmaster/app/src/main/res/xml/preferences.xml index 1ae2a83..b9efcb3 100644 --- a/src/Notesmaster/app/src/main/res/xml/preferences.xml +++ b/src/Notesmaster/app/src/main/res/xml/preferences.xml @@ -21,6 +21,24 @@ android:key="pref_sync_account_key"> + + + + + +