From 19721c364fc0a41d0b3b14dffb6a7f53784f8c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E5=A4=A9=E7=BF=94?= Date: Mon, 26 Jan 2026 23:30:03 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E4=BE=BF=E7=AD=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Notesmaster/.gitignore | 5 +++++ .../app/src/main/AndroidManifest.xml | 9 ++++++++ .../micode/notes/data/NotesRepository.java | 6 +++-- .../micode/notes/ui/NotesListActivity.java | 4 ++-- .../src/main/res/color/primary_text_dark.xml | 22 ------------------- .../main/res/color/secondary_text_dark.xml | 20 ----------------- .../app/src/main/res/layout/note_list.xml | 3 ++- .../app/src/main/res/values/colors.xml | 7 ++++-- .../app/src/main/res/values/strings.xml | 5 +++++ 9 files changed, 32 insertions(+), 49 deletions(-) delete mode 100644 src/Notesmaster/app/src/main/res/color/primary_text_dark.xml delete mode 100644 src/Notesmaster/app/src/main/res/color/secondary_text_dark.xml diff --git a/src/Notesmaster/.gitignore b/src/Notesmaster/.gitignore index aa724b7..7f909be 100644 --- a/src/Notesmaster/.gitignore +++ b/src/Notesmaster/.gitignore @@ -13,3 +13,8 @@ .externalNativeBuild .cxx local.properties +build.gradle.kts +gradle.properties +gradlew +gradlew.bat +settings.gradle.kts \ No newline at end of file diff --git a/src/Notesmaster/app/src/main/AndroidManifest.xml b/src/Notesmaster/app/src/main/AndroidManifest.xml index 34b32a4..68fb7ab 100644 --- a/src/Notesmaster/app/src/main/AndroidManifest.xml +++ b/src/Notesmaster/app/src/main/AndroidManifest.xml @@ -91,6 +91,15 @@ android:resource="@xml/searchable" /> + + + ?) AND (" + + NoteColumns.TITLE + " LIKE ? OR " + NoteColumns.SNIPPET + " LIKE ? OR " + NoteColumns.ID + " IN (SELECT " + DataColumns.NOTE_ID + " FROM data WHERE " + DataColumns.CONTENT + " LIKE ?))"; String[] selectionArgs = new String[]{ - String.valueOf(Notes.TYPE_NOTE), + String.valueOf(Notes.TYPE_SYSTEM), + "%" + keyword + "%", "%" + keyword + "%", "%" + keyword + "%" }; diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java index 982f0ad..da74a11 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -746,8 +746,8 @@ public class NotesListActivity extends AppCompatActivity switch (itemId) { case R.id.menu_search: - // TODO: 打开搜索对话框 - Toast.makeText(this, "搜索功能开发中", Toast.LENGTH_SHORT).show(); + Intent searchIntent = new Intent(this, NoteSearchActivity.class); + startActivity(searchIntent); return true; case R.id.menu_new_folder: // 创建新文件夹 diff --git a/src/Notesmaster/app/src/main/res/color/primary_text_dark.xml b/src/Notesmaster/app/src/main/res/color/primary_text_dark.xml deleted file mode 100644 index 7c85459..0000000 --- a/src/Notesmaster/app/src/main/res/color/primary_text_dark.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/Notesmaster/app/src/main/res/color/secondary_text_dark.xml b/src/Notesmaster/app/src/main/res/color/secondary_text_dark.xml deleted file mode 100644 index c1c2384..0000000 --- a/src/Notesmaster/app/src/main/res/color/secondary_text_dark.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Notesmaster/app/src/main/res/layout/note_list.xml b/src/Notesmaster/app/src/main/res/layout/note_list.xml index 21c7ba8..3d88391 100644 --- a/src/Notesmaster/app/src/main/res/layout/note_list.xml +++ b/src/Notesmaster/app/src/main/res/layout/note_list.xml @@ -22,7 +22,7 @@ android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@drawable/list_background"> + android:background="@color/background_color"> diff --git a/src/Notesmaster/app/src/main/res/values/colors.xml b/src/Notesmaster/app/src/main/res/values/colors.xml index 82d81bf..3c01874 100644 --- a/src/Notesmaster/app/src/main/res/values/colors.xml +++ b/src/Notesmaster/app/src/main/res/values/colors.xml @@ -17,7 +17,10 @@ #335b5b5b - #1976D2 + #263238 #FFFFFF - #FAFAFA + #E8E8E8 + #000000 + #808080 + #FFC107 diff --git a/src/Notesmaster/app/src/main/res/values/strings.xml b/src/Notesmaster/app/src/main/res/values/strings.xml index c6f38f4..5b212aa 100644 --- a/src/Notesmaster/app/src/main/res/values/strings.xml +++ b/src/Notesmaster/app/src/main/res/values/strings.xml @@ -161,4 +161,9 @@ Are you sure you want to delete selected notes? Unpin Unlock + + + No results found + Search History + Clear -- 2.34.1 From 269d287c71015260512659c91d20f10c93f7813d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E5=A4=A9=E7=BF=94?= Date: Mon, 26 Jan 2026 23:44:54 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/tool/SearchHistoryManager.java | 67 +++++++ .../micode/notes/ui/NoteSearchActivity.java | 188 ++++++++++++++++++ .../micode/notes/ui/NoteSearchAdapter.java | 180 +++++++++++++++++ .../main/res/layout/activity_note_search.xml | 68 +++++++ .../main/res/layout/search_history_item.xml | 35 ++++ 5 files changed, 538 insertions(+) create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/tool/SearchHistoryManager.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java create mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchAdapter.java create mode 100644 src/Notesmaster/app/src/main/res/layout/activity_note_search.xml create mode 100644 src/Notesmaster/app/src/main/res/layout/search_history_item.xml diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/SearchHistoryManager.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/SearchHistoryManager.java new file mode 100644 index 0000000..1f3f9b0 --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/SearchHistoryManager.java @@ -0,0 +1,67 @@ +package net.micode.notes.tool; + +import android.content.Context; +import android.content.SharedPreferences; +import android.text.TextUtils; +import org.json.JSONArray; +import org.json.JSONException; +import java.util.ArrayList; +import java.util.List; + +public class SearchHistoryManager { + private static final String PREF_NAME = "search_history"; + private static final String KEY_HISTORY = "history_list"; + private static final int MAX_HISTORY_SIZE = 10; + + private final SharedPreferences mPrefs; + + public SearchHistoryManager(Context context) { + mPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + } + + public List getHistory() { + String json = mPrefs.getString(KEY_HISTORY, ""); + List list = new ArrayList<>(); + if (TextUtils.isEmpty(json)) { + return list; + } + try { + JSONArray array = new JSONArray(json); + for (int i = 0; i < array.length(); i++) { + list.add(array.getString(i)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + return list; + } + + public void addHistory(String keyword) { + if (TextUtils.isEmpty(keyword)) return; + List history = getHistory(); + // Remove existing to move to top + history.remove(keyword); + history.add(0, keyword); + // Limit size + if (history.size() > MAX_HISTORY_SIZE) { + history = history.subList(0, MAX_HISTORY_SIZE); + } + saveHistory(history); + } + + public void removeHistory(String keyword) { + List history = getHistory(); + if (history.remove(keyword)) { + saveHistory(history); + } + } + + public void clearHistory() { + mPrefs.edit().remove(KEY_HISTORY).apply(); + } + + private void saveHistory(List history) { + JSONArray array = new JSONArray(history); + mPrefs.edit().putString(KEY_HISTORY, array.toString()).apply(); + } +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java new file mode 100644 index 0000000..523120b --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java @@ -0,0 +1,188 @@ +package net.micode.notes.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.SearchView; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.NotesRepository; +import net.micode.notes.tool.SearchHistoryManager; + +import java.util.ArrayList; +import java.util.List; + +public class NoteSearchActivity extends AppCompatActivity implements SearchView.OnQueryTextListener, NoteSearchAdapter.OnItemClickListener { + + private SearchView mSearchView; + private RecyclerView mRecyclerView; + private TextView mTvNoResult; + private NoteSearchAdapter mAdapter; + private NotesRepository mRepository; + private SearchHistoryManager mHistoryManager; + + private TextView mBtnShowHistory; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_note_search); + + mRepository = new NotesRepository(getContentResolver()); + mHistoryManager = new SearchHistoryManager(this); + + initViews(); + // Initial state: search is empty, show history button if there is history, or just show list + // Requirement: "history option below search bar" + showHistoryOption(); + } + + private void initViews() { + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + toolbar.setNavigationOnClickListener(v -> finish()); + + mSearchView = findViewById(R.id.search_view); + mSearchView.setOnQueryTextListener(this); + mSearchView.setFocusable(true); + mSearchView.setIconified(false); + mSearchView.requestFocusFromTouch(); + + mBtnShowHistory = findViewById(R.id.btn_show_history); + mBtnShowHistory.setOnClickListener(v -> showHistoryList()); + + mRecyclerView = findViewById(R.id.recycler_view); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mAdapter = new NoteSearchAdapter(this, this); + mRecyclerView.setAdapter(mAdapter); + + mTvNoResult = findViewById(R.id.tv_no_result); + } + + private void showHistoryOption() { + // Show the "History" button, hide the list + mBtnShowHistory.setVisibility(View.VISIBLE); + mRecyclerView.setVisibility(View.GONE); + mTvNoResult.setVisibility(View.GONE); + } + + private void showHistoryList() { + List history = mHistoryManager.getHistory(); + if (history.isEmpty()) { + // If no history, maybe show a toast or empty state? + // But for now, let's just show the empty list which is fine + } + List data = new ArrayList<>(history); + mAdapter.setData(data, null); + + mBtnShowHistory.setVisibility(View.GONE); // Hide button when showing list + mTvNoResult.setVisibility(View.GONE); + mRecyclerView.setVisibility(View.VISIBLE); + } + + private void performSearch(String query) { + if (TextUtils.isEmpty(query)) { + showHistoryOption(); + return; + } + + // Hide history button when searching + mBtnShowHistory.setVisibility(View.GONE); + + mRepository.searchNotes(query, new NotesRepository.Callback>() { + @Override + public void onSuccess(List result) { + runOnUiThread(() -> { + List data = new ArrayList<>(result); + mAdapter.setData(data, query); + if (data.isEmpty()) { + mTvNoResult.setVisibility(View.VISIBLE); + mRecyclerView.setVisibility(View.GONE); + } else { + mTvNoResult.setVisibility(View.GONE); + mRecyclerView.setVisibility(View.VISIBLE); + } + }); + } + + @Override + public void onError(Exception error) { + runOnUiThread(() -> { + Toast.makeText(NoteSearchActivity.this, "Search failed: " + error.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + }); + } + + @Override + public boolean onQueryTextSubmit(String query) { + if (!TextUtils.isEmpty(query)) { + mHistoryManager.addHistory(query); + performSearch(query); + mSearchView.clearFocus(); // Hide keyboard + } + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + if (TextUtils.isEmpty(newText)) { + showHistoryOption(); + } else { + performSearch(newText); + } + return true; + } + + @Override + public void onNoteClick(NotesRepository.NoteInfo note) { + // Save history when user clicks a result + String query = mSearchView.getQuery().toString(); + if (!TextUtils.isEmpty(query)) { + 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); + } + + @Override + public void onHistoryClick(String keyword) { + mSearchView.setQuery(keyword, true); + } + + @Override + public void onHistoryDelete(String keyword) { + mHistoryManager.removeHistory(keyword); + // Refresh history view if we are currently showing history (search box is empty) + if (TextUtils.isEmpty(mSearchView.getQuery())) { + showHistoryList(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mRepository != null) { + mRepository.shutdown(); + } + } +} diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchAdapter.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchAdapter.java new file mode 100644 index 0000000..15fbb80 --- /dev/null +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchAdapter.java @@ -0,0 +1,180 @@ +package net.micode.notes.ui; + +import android.content.Context; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.style.BackgroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.NotesRepository; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NoteSearchAdapter extends RecyclerView.Adapter { + + private static final int TYPE_HISTORY = 1; + private static final int TYPE_NOTE = 2; + + private Context mContext; + private List mDataList; + private String mSearchKeyword; + private OnItemClickListener mListener; + + public interface OnItemClickListener { + void onNoteClick(NotesRepository.NoteInfo note); + void onHistoryClick(String keyword); + void onHistoryDelete(String keyword); + } + + public NoteSearchAdapter(Context context, OnItemClickListener listener) { + mContext = context; + mListener = listener; + mDataList = new ArrayList<>(); + } + + public void setData(List data, String keyword) { + mDataList = data; + mSearchKeyword = keyword; + notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + Object item = mDataList.get(position); + if (item instanceof String) { + return TYPE_HISTORY; + } else if (item instanceof NotesRepository.NoteInfo) { + return TYPE_NOTE; + } + return super.getItemViewType(position); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == TYPE_HISTORY) { + View view = LayoutInflater.from(mContext).inflate(R.layout.search_history_item, parent, false); + return new HistoryViewHolder(view); + } else { + View view = LayoutInflater.from(mContext).inflate(R.layout.note_item, parent, false); + return new NoteViewHolder(view); + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (holder instanceof HistoryViewHolder) { + String keyword = (String) mDataList.get(position); + ((HistoryViewHolder) holder).bind(keyword); + } else if (holder instanceof NoteViewHolder) { + NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) mDataList.get(position); + ((NoteViewHolder) holder).bind(note); + } + } + + @Override + public int getItemCount() { + return mDataList.size(); + } + + class HistoryViewHolder extends RecyclerView.ViewHolder { + TextView tvKeyword; + ImageView ivDelete; + + public HistoryViewHolder(View itemView) { + super(itemView); + tvKeyword = itemView.findViewById(R.id.tv_history_keyword); + ivDelete = itemView.findViewById(R.id.iv_delete_history); + } + + public void bind(final String keyword) { + tvKeyword.setText(keyword); + itemView.setOnClickListener(v -> { + if (mListener != null) mListener.onHistoryClick(keyword); + }); + ivDelete.setOnClickListener(v -> { + if (mListener != null) mListener.onHistoryDelete(keyword); + }); + } + } + + class NoteViewHolder extends RecyclerView.ViewHolder { + ImageView ivTypeIcon; + TextView tvTitle; + TextView tvTime; + TextView tvName; + ImageView ivAlertIcon; + CheckBox checkbox; + + public NoteViewHolder(View itemView) { + super(itemView); + ivTypeIcon = itemView.findViewById(R.id.iv_type_icon); + tvTitle = itemView.findViewById(R.id.tv_title); + tvTime = itemView.findViewById(R.id.tv_time); + tvName = itemView.findViewById(R.id.tv_name); + ivAlertIcon = itemView.findViewById(R.id.iv_alert_icon); + checkbox = itemView.findViewById(android.R.id.checkbox); + } + + public void bind(final NotesRepository.NoteInfo note) { + // 设置标题和高亮 + // NoteInfo.title defaults to snippet if title is empty, so it's safe to use title + if (!TextUtils.isEmpty(mSearchKeyword)) { + tvTitle.setText(getHighlightText(note.title, mSearchKeyword)); + } else { + tvTitle.setText(note.title); + } + + // 设置时间 + tvTime.setText(android.text.format.DateUtils.getRelativeTimeSpanString(note.modifiedDate)); + + // 设置背景(如果 NoteInfo 中有背景ID) + // 注意:NoteInfo 中 bgColorId 是整型ID,需要转换为资源ID + // 这里为了简单,暂不设置复杂的背景,或者使用默认背景 + + // 点击事件 + itemView.setOnClickListener(v -> { + if (mListener != null) mListener.onNoteClick(note); + }); + + // 隐藏不需要的视图 + ivTypeIcon.setVisibility(View.GONE); + tvName.setVisibility(View.GONE); + checkbox.setVisibility(View.GONE); + ivAlertIcon.setVisibility(View.GONE); + } + } + + private Spannable getHighlightText(String text, String keyword) { + if (text == null) text = ""; + SpannableString spannable = new SpannableString(text); + if (!TextUtils.isEmpty(keyword)) { + Pattern pattern = Pattern.compile(Pattern.quote(keyword), Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(text); + while (matcher.find()) { + spannable.setSpan( + new BackgroundColorSpan(0x40FFFF00), // 半透明黄色 + matcher.start(), + matcher.end(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } + } + return spannable; + } +} diff --git a/src/Notesmaster/app/src/main/res/layout/activity_note_search.xml b/src/Notesmaster/app/src/main/res/layout/activity_note_search.xml new file mode 100644 index 0000000..6fc7a8a --- /dev/null +++ b/src/Notesmaster/app/src/main/res/layout/activity_note_search.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Notesmaster/app/src/main/res/layout/search_history_item.xml b/src/Notesmaster/app/src/main/res/layout/search_history_item.xml new file mode 100644 index 0000000..b506853 --- /dev/null +++ b/src/Notesmaster/app/src/main/res/layout/search_history_item.xml @@ -0,0 +1,35 @@ + + + + + + + + + + -- 2.34.1 From d1dd9362cd77f8cbc14870e3286f0c4610391eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E5=A4=A9=E7=BF=94?= Date: Tue, 27 Jan 2026 13:15:21 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=9A=201.=E6=90=9C=E7=B4=A2=202.=E6=92=A4=E5=9B=9E=203.the?= =?UTF-8?q?me=E8=87=AA=E5=AE=9A=E4=B9=89=204.=E5=AF=8C=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/src/main/AndroidManifest.xml | 7 +- .../net/micode/notes/model/WorkingNote.java | 21 + .../net/micode/notes/tool/ResourceParser.java | 40 ++ .../net/micode/notes/ui/NoteEditActivity.java | 494 +++++++++++--- .../net/micode/notes/ui/NotesListItem.java | 31 +- .../notes/ui/NotesPreferenceActivity.java | 639 +----------------- .../app/src/main/res/layout/note_edit.xml | 262 ++++--- .../app/src/main/res/menu/note_edit.xml | 25 +- .../src/main/res/values-zh-rCN/strings.xml | 7 + .../app/src/main/res/values/arrays.xml | 12 + .../app/src/main/res/values/colors.xml | 17 + .../app/src/main/res/values/strings.xml | 10 + .../app/src/main/res/values/styles.xml | 10 +- .../app/src/main/res/values/themes.xml | 7 +- .../app/src/main/res/xml/preferences.xml | 18 + 15 files changed, 779 insertions(+), 821 deletions(-) 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用于管理应用的各种设置,主要包括: - *

    - *
  • Google Tasks同步账户的设置和管理
  • - *
  • 同步状态显示和手动同步控制
  • - *
  • 背景颜色随机显示设置
  • - *
- *

- *

- * 该类继承自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 - *

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

    - *
  • 启用ActionBar的返回导航
  • - *
  • 加载preferences.xml配置文件
  • - *
  • 初始化账户分类和广播接收器
  • - *
  • 添加设置界面头部视图
  • - *
- *

- * @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,并清理相关数据: - *

    - *
  • 清除最后同步时间
  • - *
  • 清除所有笔记的GTASK_ID和SYNC_ID
  • - *
- *

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