ui界面现代化优化以及功能的迁移

jiangtianxiang_branch
蒋天翔 4 weeks ago
parent 7922ccee55
commit 02847cb9f8

@ -197,6 +197,13 @@
android:theme="@style/Theme.Notesmaster" >
</activity>
<activity
android:name=".ui.SettingsActivity"
android:label="@string/menu_settings"
android:launchMode="singleTop"
android:theme="@style/Theme.Notesmaster"
android:exported="false" />
<!-- ==================== 密码设置/验证活动 ==================== -->
<activity
android:name=".ui.PasswordActivity"
@ -216,6 +223,39 @@
</service>
-->
<!-- ==================== 全局速记胶囊动作活动 ==================== -->
<!-- 用于系统文本选择菜单集成 -->
<activity
android:name=".ui.CapsuleActionActivity"
android:label="添加到胶囊"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.PROCESS_TEXT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<!-- ==================== 全局速记胶囊服务 ==================== -->
<service
android:name=".capsule.CapsuleService"
android:enabled="true"
android:exported="false" />
<service
android:name=".capsule.ClipboardMonitorService"
android:exported="true"
android:label="@string/preferences_capsule_title"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
<!-- ==================== 搜索元数据 ==================== -->
<!-- 指定默认的搜索活动为NoteEditActivity -->
<meta-data

@ -86,6 +86,16 @@ public class Notes {
*/
public static final int ID_TEMPLATE_FOLDER = -4;
/**
* Capsule folder ID for global quick notes
*/
public static final int ID_CAPSULE_FOLDER = -5;
/**
* ID for "All Notes" virtual folder
*/
public static final int ID_ALL_NOTES_FOLDER = -10;
/**
* Intent Extra
*/

@ -70,6 +70,7 @@ public class NotesRepository {
public int bgColorId;
public boolean isPinned; // 新增置顶字段
public boolean isLocked; // 新增锁定字段
public int notesCount;
public NoteInfo() {}
@ -77,6 +78,10 @@ public class NotesRepository {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getParentId() {
return parentId;
}
@ -84,6 +89,14 @@ public class NotesRepository {
public String getNoteDataValue() {
return snippet;
}
public String getSnippet() {
return snippet;
}
public int getNotesCount() {
return notesCount;
}
}
private static final String TAG = "NotesRepository";
@ -172,6 +185,11 @@ public class NotesRepository {
} else {
noteInfo.isLocked = false;
}
int countIndex = cursor.getColumnIndex(NoteColumns.NOTES_COUNT);
if (countIndex != -1) {
noteInfo.notesCount = cursor.getInt(countIndex);
}
return noteInfo;
}
@ -203,7 +221,8 @@ public class NotesRepository {
public void getNotes(long folderId, Callback<List<NoteInfo>> callback) {
executor.execute(() -> {
try {
List<NoteInfo> notes = queryNotes(folderId);
// Modified to only return notes (no folders) as per new UI requirement
List<NoteInfo> notes = queryNotesOnly(folderId);
callback.onSuccess(notes);
Log.d(TAG, "Successfully loaded notes for folder: " + folderId);
} catch (Exception e) {
@ -214,10 +233,108 @@ public class NotesRepository {
}
/**
*
* <p>
* 便便
* </p>
*
*
* @param folderId ID
* @param callback
*/
public void getSubFolders(long folderId, Callback<List<NoteInfo>> callback) {
executor.execute(() -> {
try {
List<NoteInfo> folders = querySubFolders(folderId);
callback.onSuccess(folders);
Log.d(TAG, "Successfully loaded sub-folders for folder: " + folderId);
} catch (Exception e) {
Log.e(TAG, "Failed to load sub-folders for folder: " + folderId, e);
callback.onError(e);
}
});
}
private List<NoteInfo> querySubFolders(long folderId) {
List<NoteInfo> folders = new ArrayList<>();
String selection;
String[] selectionArgs;
if (folderId == Notes.ID_ROOT_FOLDER) {
selection = "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + NoteColumns.PARENT_ID + "=?) OR (" +
NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)";
selectionArgs = new String[]{String.valueOf(Notes.ID_ROOT_FOLDER)};
} else {
selection = NoteColumns.PARENT_ID + "=? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER;
selectionArgs = new String[]{String.valueOf(folderId)};
}
Cursor cursor = contentResolver.query(
Notes.CONTENT_NOTE_URI,
null,
selection,
selectionArgs,
NoteColumns.MODIFIED_DATE + " DESC"
);
if (cursor != null) {
try {
while (cursor.moveToNext()) {
folders.add(noteFromCursor(cursor));
}
} finally {
cursor.close();
}
}
return folders;
}
private List<NoteInfo> queryNotesOnly(long folderId) {
List<NoteInfo> normalNotes = new ArrayList<>();
String selection;
String[] selectionArgs;
if (folderId == Notes.ID_ALL_NOTES_FOLDER) {
// Query ALL notes (except trash and system folders)
// We want all notes where TYPE=NOTE and PARENT_ID != TRASH
selection = NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " +
NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER;
selectionArgs = null;
} else if (folderId == Notes.ID_ROOT_FOLDER) {
selection = "(" + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=?)";
selectionArgs = new String[]{String.valueOf(Notes.ID_ROOT_FOLDER)};
} else {
selection = NoteColumns.PARENT_ID + "=? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
selectionArgs = new String[]{String.valueOf(folderId)};
}
Cursor cursor = contentResolver.query(
Notes.CONTENT_NOTE_URI,
null,
selection,
selectionArgs,
NoteColumns.MODIFIED_DATE + " DESC"
);
if (cursor != null) {
try {
while (cursor.moveToNext()) {
normalNotes.add(noteFromCursor(cursor));
}
} finally {
cursor.close();
}
}
// Sort: Pinned first, then Modified Date DESC
normalNotes.sort((a, b) -> {
if (a.isPinned != b.isPinned) {
return a.isPinned ? -1 : 1;
}
return Long.compare(b.modifiedDate, a.modifiedDate);
});
return normalNotes;
}
/**
* - Deprecated logic kept for reference but unused by main flow now
*
* @param folderId ID
* @return 便
@ -589,7 +706,7 @@ public class NotesRepository {
/**
*
* <p>
*
*
* </p>
*
* @param noteId ID
@ -598,8 +715,12 @@ public class NotesRepository {
public void deleteNote(long noteId, Callback<Integer> callback) {
executor.execute(() -> {
try {
// 1. 获取当前父文件夹ID
long currentParentId = getParentFolderId(noteId);
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.ORIGIN_PARENT_ID, currentParentId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
@ -607,7 +728,7 @@ public class NotesRepository {
if (rows > 0) {
callback.onSuccess(rows);
Log.d(TAG, "Successfully moved note to trash: " + noteId);
Log.d(TAG, "Successfully moved note to trash: " + noteId + ", origin: " + currentParentId);
} else {
callback.onError(new RuntimeException("No note found with ID: " + noteId));
}
@ -634,8 +755,12 @@ public class NotesRepository {
int totalRows = 0;
for (Long noteId : noteIds) {
// 1. 获取当前父文件夹ID
long currentParentId = getParentFolderId(noteId);
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.ORIGIN_PARENT_ID, currentParentId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
@ -657,7 +782,7 @@ public class NotesRepository {
}
/**
*
*
*
* @param noteIds ID
* @param callback
@ -671,12 +796,40 @@ public class NotesRepository {
}
int totalRows = 0;
// 恢复到根目录
long targetFolderId = Notes.ID_ROOT_FOLDER;
for (Long noteId : noteIds) {
// 1. 查询原始父文件夹ID
long originParentId = Notes.ID_ROOT_FOLDER;
Cursor cursor = contentResolver.query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
new String[]{NoteColumns.ORIGIN_PARENT_ID},
null,
null,
null
);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
int idx = cursor.getColumnIndex(NoteColumns.ORIGIN_PARENT_ID);
if (idx != -1) {
originParentId = cursor.getLong(idx);
}
}
} finally {
cursor.close();
}
}
// 如果原始位置无效例如0或仍是回收站则恢复到根目录
if (originParentId == Notes.ID_TRASH_FOLER || originParentId == 0) {
originParentId = Notes.ID_ROOT_FOLDER;
}
// 2. 恢复
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, targetFolderId);
values.put(NoteColumns.PARENT_ID, originParentId);
values.put(NoteColumns.ORIGIN_PARENT_ID, 0); // Reset origin
values.put(NoteColumns.LOCAL_MODIFIED, 1);
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
@ -1004,6 +1157,36 @@ public class NotesRepository {
});
}
/**
*
* <p>
*
* </p>
*
* @param callback
*/
public void unlockAllNotes(Callback<Integer> callback) {
executor.execute(() -> {
try {
ContentValues values = new ContentValues();
values.put(NoteColumns.LOCKED, 0);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// Update all notes where locked is 1
String selection = NoteColumns.LOCKED + " = ?";
String[] selectionArgs = new String[]{"1"};
int rows = contentResolver.update(Notes.CONTENT_NOTE_URI, values, selection, selectionArgs);
callback.onSuccess(rows);
Log.d(TAG, "Successfully unlocked " + rows + " notes");
} catch (Exception e) {
Log.e(TAG, "Failed to unlock all notes", e);
callback.onError(e);
}
});
}
/**
*
*
@ -1014,10 +1197,22 @@ public class NotesRepository {
if (content == null || content.isEmpty()) {
return "";
}
// Convert HTML to plain text to remove tags and resolve entities
String plainText;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
plainText = android.text.Html.fromHtml(content, android.text.Html.FROM_HTML_MODE_LEGACY).toString();
} else {
plainText = android.text.Html.fromHtml(content).toString();
}
// Remove extra whitespace
plainText = plainText.trim();
int maxLength = 100;
return content.length() > maxLength
? content.substring(0, maxLength)
: content;
return plainText.length() > maxLength
? plainText.substring(0, maxLength)
: plainText;
}
/**

@ -1,37 +1,22 @@
/*
* Copyright (c) 2025, Modern Notes Project
*
* 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.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
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.ResourceParser;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@ -40,143 +25,68 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
/**
* 便
* <p>
* List<NoteInfo> ListView
* 便
* </p>
* <p>
* 使 ViewHolder
* </p>
*/
public class NoteInfoAdapter extends BaseAdapter {
private LayoutInflater inflater;
public class NoteInfoAdapter extends RecyclerView.Adapter<NoteInfoAdapter.NoteViewHolder> {
private Context context;
private List<NotesRepository.NoteInfo> notes;
private HashSet<Long> selectedIds;
private boolean isSelectionMode;
private OnNoteButtonClickListener buttonClickListener;
private OnNoteItemClickListener itemClickListener;
private OnNoteItemLongClickListener itemLongClickListener;
/**
* 便
*/
public interface OnNoteButtonClickListener {
/**
*
*
* @param position
* @param noteId 便 ID
*/
void onEditButtonClick(int position, long noteId);
}
/**
* 便
*/
public interface OnNoteItemClickListener {
void onNoteItemClick(int position, long noteId);
}
/**
* 便
*/
public interface OnNoteItemLongClickListener {
void onNoteItemLongClick(int position, long noteId);
}
/**
*
*
* @param context
*/
public NoteInfoAdapter(Context context) {
this.inflater = LayoutInflater.from(context);
this.context = context;
this.notes = new ArrayList<>();
this.selectedIds = new HashSet<>();
this.isSelectionMode = false;
}
/**
* 便
*
* @param notes 便
*/
public void setSelectionMode(boolean isSelectionMode) {
this.isSelectionMode = isSelectionMode;
notifyDataSetChanged();
}
public void setNotes(List<NotesRepository.NoteInfo> notes) {
this.notes = notes != null ? notes : new ArrayList<>();
notifyDataSetChanged();
}
/**
* 便 ID
* <p>
* ViewModel selectedNoteIds
* Adapter selectedIds
* </p>
*
* @param selectedIds 便 ID
*/
public void setSelectedIds(HashSet<Long> selectedIds) {
if (selectedIds != null && selectedIds != this.selectedIds) {
this.selectedIds.clear();
this.selectedIds.addAll(selectedIds);
notifyDataSetChanged();
} else if (selectedIds == null) {
this.selectedIds.clear();
notifyDataSetChanged();
}
this.selectedIds = selectedIds != null ? new HashSet<>(selectedIds) : new HashSet<>();
notifyDataSetChanged();
}
/**
* 便 ID
* <p>
* List<Long> HashSet
* </p>
*
* @param selectedIds 便 ID
*/
public void setSelectedIds(List<Long> selectedIds) {
if (selectedIds != null && !selectedIds.isEmpty()) {
this.selectedIds.clear();
this.selectedIds.addAll(selectedIds);
notifyDataSetChanged();
} else {
this.selectedIds.clear();
notifyDataSetChanged();
}
@NonNull
@Override
public NoteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.note_item, parent, false);
return new NoteViewHolder(view);
}
/**
* 便 ID
*
* @return 便 ID
*/
public HashSet<Long> getSelectedIds() {
return selectedIds;
@Override
public void onBindViewHolder(@NonNull NoteViewHolder holder, int position) {
NotesRepository.NoteInfo note = notes.get(position);
holder.bind(note, position);
}
/**
*
*
* @param noteId 便 ID
*/
public void toggleSelection(long noteId) {
if (selectedIds.contains(noteId)) {
selectedIds.remove(noteId);
} else {
selectedIds.add(noteId);
}
notifyDataSetChanged();
@Override
public int getItemCount() {
return notes.size();
}
/**
*
*
* @param listener
*/
public void setOnNoteButtonClickListener(OnNoteButtonClickListener listener) {
this.buttonClickListener = listener;
}
public void setOnNoteItemClickListener(OnNoteItemClickListener listener) {
this.itemClickListener = listener;
}
@ -184,168 +94,113 @@ public class NoteInfoAdapter extends BaseAdapter {
public void setOnNoteItemLongClickListener(OnNoteItemLongClickListener listener) {
this.itemLongClickListener = listener;
}
@Override
public int getCount() {
return notes.size();
}
@Override
public Object getItem(int position) {
return position >= 0 && position < notes.size() ? notes.get(position) : null;
}
@Override
public long getItemId(int position) {
NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) getItem(position);
return note != null ? note.getId() : -1;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.d("NoteInfoAdapter", "getView called, position: " + position + ", convertView: " + (convertView != null ? "REUSED" : "NEW"));
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.note_item, parent, false);
holder = new ViewHolder();
holder.title = convertView.findViewById(R.id.tv_title);
holder.time = convertView.findViewById(R.id.tv_time);
holder.typeIcon = convertView.findViewById(R.id.iv_type_icon);
holder.checkBox = convertView.findViewById(android.R.id.checkbox);
holder.pinnedIcon = convertView.findViewById(R.id.iv_pinned_icon);
holder.lockIcon = convertView.findViewById(R.id.iv_lock_icon);
convertView.setTag(holder);
convertView.setOnClickListener(v -> {
Log.d("NoteInfoAdapter", "===== onClick TRIGGERED =====");
ViewHolder currentHolder = (ViewHolder) v.getTag();
if (currentHolder != null && itemClickListener != null) {
Log.d("NoteInfoAdapter", "Calling itemClickListener");
NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) getItem(currentHolder.position);
if (note != null) {
itemClickListener.onNoteItemClick(currentHolder.position, note.getId());
class NoteViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
TextView title, time, name;
ImageView typeIcon, lockIcon, alertIcon, pinnedIcon;
CheckBox checkBox;
public NoteViewHolder(View itemView) {
super(itemView);
cardView = (CardView) itemView;
title = itemView.findViewById(R.id.tv_title);
time = itemView.findViewById(R.id.tv_time);
name = itemView.findViewById(R.id.tv_name);
checkBox = itemView.findViewById(android.R.id.checkbox);
typeIcon = itemView.findViewById(R.id.iv_type_icon);
lockIcon = itemView.findViewById(R.id.iv_lock_icon);
alertIcon = itemView.findViewById(R.id.iv_alert_icon);
pinnedIcon = itemView.findViewById(R.id.iv_pinned_icon);
itemView.setOnClickListener(v -> {
if (itemClickListener != null) {
int pos = getAdapterPosition();
if (pos != RecyclerView.NO_POSITION) {
// Pass click event to Fragment, which will handle selection toggle if in selection mode
itemClickListener.onNoteItemClick(pos, notes.get(pos).getId());
}
}
Log.d("NoteInfoAdapter", "===== onClick END =====");
});
convertView.setOnLongClickListener(v -> {
Log.d("NoteInfoAdapter", "===== setOnLongClickListener TRIGGERED =====");
Log.d("NoteInfoAdapter", "Event triggered on view: " + v.getClass().getSimpleName());
ViewHolder currentHolder = (ViewHolder) v.getTag();
if (currentHolder != null && itemLongClickListener != null) {
Log.d("NoteInfoAdapter", "Calling itemLongClickListener");
itemLongClickListener.onNoteItemLongClick(currentHolder.position, currentHolder.position < notes.size() ? notes.get(currentHolder.position).getId() : -1);
} else {
Log.e("NoteInfoAdapter", "itemLongClickListener is NULL!");
itemView.setOnLongClickListener(v -> {
if (itemLongClickListener != null) {
int pos = getAdapterPosition();
if (pos != RecyclerView.NO_POSITION) {
itemLongClickListener.onNoteItemLongClick(pos, notes.get(pos).getId());
return true;
}
}
Log.d("NoteInfoAdapter", "===== setOnLongClickListener END =====");
return true;
return false;
});
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.position = position;
NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) getItem(position);
if (note != null) {
String title = note.title;
if (title == null || title.trim().isEmpty()) {
title = "无标题";
}
holder.title.setText(title);
public void bind(NotesRepository.NoteInfo note, int position) {
String titleStr = note.title;
String snippet = note.snippet;
// 设置类型图标和时间显示
if (note.type == Notes.TYPE_FOLDER) {
// 文件夹
holder.typeIcon.setVisibility(View.VISIBLE);
holder.typeIcon.setImageResource(R.drawable.ic_folder);
// 文件夹不显示时间
holder.time.setVisibility(View.GONE);
} else {
// 便签
holder.typeIcon.setVisibility(View.GONE);
holder.time.setVisibility(View.VISIBLE);
holder.time.setText(formatDate(note.modifiedDate));
if (titleStr == null || titleStr.trim().isEmpty()) {
titleStr = snippet;
if (titleStr != null && titleStr.contains("\n")) {
titleStr = titleStr.substring(0, titleStr.indexOf("\n"));
}
if (titleStr == null || titleStr.trim().isEmpty()) {
titleStr = "无标题";
}
}
int bgResId;
int totalCount = getCount();
int bgColorId = note.bgColorId;
if (totalCount == 1) {
bgResId = NoteItemBgResources.getNoteBgSingleRes(bgColorId);
} else if (position == 0) {
bgResId = NoteItemBgResources.getNoteBgFirstRes(bgColorId);
} else if (position == totalCount - 1) {
bgResId = NoteItemBgResources.getNoteBgLastRes(bgColorId);
} else {
bgResId = NoteItemBgResources.getNoteBgNormalRes(bgColorId);
// Bind Summary
TextView summaryView = itemView.findViewById(R.id.tv_summary);
if (summaryView != null) {
// Clean up snippet for display (in case it contains HTML or entities)
String cleanSnippet = snippet;
if (cleanSnippet != null && !cleanSnippet.isEmpty()) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
cleanSnippet = android.text.Html.fromHtml(cleanSnippet, android.text.Html.FROM_HTML_MODE_LEGACY).toString();
} else {
cleanSnippet = android.text.Html.fromHtml(cleanSnippet).toString();
}
cleanSnippet = cleanSnippet.trim();
}
summaryView.setText(cleanSnippet);
}
convertView.setBackgroundResource(bgResId);
if (selectedIds.contains(note.getId())) {
convertView.setActivated(true);
if (note.type == Notes.TYPE_FOLDER) {
title.setText(note.getSnippet() + " (" + note.getNotesCount() + ")");
time.setVisibility(View.GONE);
typeIcon.setVisibility(View.VISIBLE);
typeIcon.setImageResource(R.drawable.ic_folder);
cardView.setCardBackgroundColor(context.getColor(R.color.bg_white));
} else {
convertView.setActivated(false);
typeIcon.setVisibility(View.GONE);
time.setVisibility(View.VISIBLE);
title.setText(titleStr);
time.setText(formatDate(note.modifiedDate));
// Color Logic
int bgColorId = note.bgColorId;
int color = ResourceParser.getNoteBgColor(context, bgColorId);
cardView.setCardBackgroundColor(color);
}
Log.d("NoteInfoAdapter", "===== Setting checkbox visibility =====");
Log.d("NoteInfoAdapter", "selectedIds.isEmpty(): " + selectedIds.isEmpty());
Log.d("NoteInfoAdapter", "selectedIds.size(): " + selectedIds.size());
Log.d("NoteInfoAdapter", "selectedIds contains note " + note.getId() + ": " + selectedIds.contains(note.getId()));
if (!selectedIds.isEmpty()) {
Log.d("NoteInfoAdapter", "Setting checkbox VISIBLE");
holder.checkBox.setVisibility(View.VISIBLE);
holder.checkBox.setChecked(selectedIds.contains(note.getId()));
holder.checkBox.setClickable(false);
// Selection Logic
if (isSelectionMode) {
checkBox.setVisibility(View.VISIBLE);
checkBox.setChecked(selectedIds.contains(note.getId()));
} else {
Log.d("NoteInfoAdapter", "Setting checkbox GONE");
holder.checkBox.setVisibility(View.GONE);
checkBox.setVisibility(View.GONE);
}
Log.d("NoteInfoAdapter", "===== Checkbox visibility set =====");
if (note.isPinned) {
holder.pinnedIcon.setVisibility(View.VISIBLE);
} else {
holder.pinnedIcon.setVisibility(View.GONE);
}
if (note.isPinned) pinnedIcon.setVisibility(View.VISIBLE);
else pinnedIcon.setVisibility(View.GONE);
if (note.isLocked) {
holder.lockIcon.setVisibility(View.VISIBLE);
} else {
holder.lockIcon.setVisibility(View.GONE);
}
if (note.isLocked) lockIcon.setVisibility(View.VISIBLE);
else lockIcon.setVisibility(View.GONE);
}
return convertView;
}
/**
*
*
* @param timestamp
* @return
*/
private String formatDate(long timestamp) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
return sdf.format(new Date(timestamp));
}
/**
* ViewHolder ListView
*/
private static class ViewHolder {
TextView title;
TextView time;
ImageView typeIcon;
ImageView lockIcon;
CheckBox checkBox;
ImageView pinnedIcon;
int position;
}
}
}

@ -18,7 +18,13 @@ import net.micode.notes.R;
import net.micode.notes.tool.SecurityManager;
import net.micode.notes.data.ThemeRepository;
import net.micode.notes.data.NotesRepository;
import android.provider.Settings;
import android.net.Uri;
import androidx.preference.SwitchPreferenceCompat;
import androidx.preference.ListPreference;
import net.micode.notes.capsule.CapsuleService;
import net.micode.notes.capsule.ClipboardMonitorService;
import static android.app.Activity.RESULT_OK;
@ -26,7 +32,9 @@ 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 String PREFERENCE_CAPSULE_ENABLE = "pref_key_capsule_enable";
public static final int REQUEST_CODE_CHECK_PASSWORD = 104;
public static final int REQUEST_CODE_OVERLAY_PERMISSION = 105;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@ -34,6 +42,116 @@ public class SettingsFragment extends PreferenceFragmentCompat {
loadThemePreference();
loadSecurityPreference();
loadAccountPreference();
loadCapsulePreference();
}
private void loadCapsulePreference() {
SwitchPreferenceCompat capsulePref = findPreference(PREFERENCE_CAPSULE_ENABLE);
if (capsulePref != null) {
capsulePref.setOnPreferenceChangeListener((preference, newValue) -> {
boolean enabled = (boolean) newValue;
if (enabled) {
checkCapsulePermissions();
} else {
stopCapsuleService();
}
return true;
});
}
}
private void checkCapsulePermissions() {
Context context = getContext();
if (context == null) return;
// Check Overlay Permission
if (!Settings.canDrawOverlays(context)) {
new AlertDialog.Builder(context)
.setTitle(R.string.capsule_permission_alert_window_title)
.setMessage(R.string.capsule_permission_alert_window_message)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + context.getPackageName()));
startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMISSION);
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
// Reset switch if cancelled
SwitchPreferenceCompat pref = findPreference(PREFERENCE_CAPSULE_ENABLE);
if (pref != null) pref.setChecked(false);
})
.show();
return;
}
// Check Accessibility Permission
if (!isAccessibilitySettingsOn(context)) {
new AlertDialog.Builder(context)
.setTitle(R.string.capsule_permission_accessibility_title)
.setMessage(R.string.capsule_permission_accessibility_message)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
// Reset switch
SwitchPreferenceCompat pref = findPreference(PREFERENCE_CAPSULE_ENABLE);
if (pref != null) pref.setChecked(false);
})
.show();
return;
}
startCapsuleService();
}
private boolean isAccessibilitySettingsOn(Context mContext) {
int accessibilityEnabled = 0;
final String service = mContext.getPackageName() + "/" + ClipboardMonitorService.class.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(
mContext.getApplicationContext().getContentResolver(),
android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1) {
String settingValue = Settings.Secure.getString(
mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase(service)) {
return true;
}
}
}
}
return false;
}
private void startCapsuleService() {
Context context = getContext();
if (context != null) {
Intent intent = new Intent(context, CapsuleService.class);
// In Android 8.0+, we might need startForegroundService, but for now stick to startService
// The service itself should call startForeground
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
}
private void stopCapsuleService() {
Context context = getContext();
if (context != null) {
Intent intent = new Intent(context, CapsuleService.class);
context.stopService(intent);
}
}
private void loadThemePreference() {

@ -131,10 +131,13 @@ public class SidebarFragment extends Fragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof OnSidebarItemSelectedListener) {
if (getParentFragment() instanceof OnSidebarItemSelectedListener) {
listener = (OnSidebarItemSelectedListener) getParentFragment();
} else if (context instanceof OnSidebarItemSelectedListener) {
listener = (OnSidebarItemSelectedListener) context;
} else {
throw new RuntimeException(context.toString() + " must implement OnSidebarItemSelectedListener");
// throw new RuntimeException(context.toString() + " must implement OnSidebarItemSelectedListener");
// Allow null listener for now to avoid crashes during refactoring
}
}

@ -36,21 +36,13 @@ public class TaskListActivity extends AppCompatActivity implements TaskListAdapt
// Initialize Toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Remove default title
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
// Setup custom "Notes" navigation
View notesTitle = findViewById(R.id.tv_toolbar_title_notes);
if (notesTitle != null) {
notesTitle.setOnClickListener(v -> {
finish();
overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right);
});
if (toolbar != null) {
setSupportActionBar(toolbar);
// Remove default title
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
}
// Setup Navigation Icon (Back Button) - REMOVED as per new requirement

@ -36,7 +36,7 @@ import java.util.List;
* </p>
*
* @see NotesRepository
* @see Note
* @see net.micode.notes.model.Note
*/
public class NotesListViewModel extends ViewModel {
private static final String TAG = "NotesListViewModel";
@ -46,6 +46,9 @@ public class NotesListViewModel extends ViewModel {
// 笔记列表LiveData
private final MutableLiveData<List<NotesRepository.NoteInfo>> notesLiveData = new MutableLiveData<>();
// 文件夹列表LiveData (用于顶部Tab)
private final MutableLiveData<List<NotesRepository.NoteInfo>> foldersLiveData = new MutableLiveData<>();
// 加载状态LiveData
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
@ -54,9 +57,16 @@ public class NotesListViewModel extends ViewModel {
// 选中的笔记ID集合
private final HashSet<Long> selectedNoteIds = new HashSet<>();
private final MutableLiveData<HashSet<Long>> selectedIdsLiveData = new MutableLiveData<>(new HashSet<>());
// 是否处于多选模式
private final MutableLiveData<Boolean> isSelectionMode = new MutableLiveData<>(false);
// 当前文件夹ID
private long currentFolderId = Notes.ID_ROOT_FOLDER;
private long currentFolderId = Notes.ID_ALL_NOTES_FOLDER; // Default to "All"
// 当前文件夹ID LiveData (用于UI监听)
private final MutableLiveData<Long> currentFolderIdLiveData = new MutableLiveData<>((long) Notes.ID_ALL_NOTES_FOLDER);
// 文件夹路径LiveData用于面包屑导航
private final MutableLiveData<List<NotesRepository.NoteInfo>> folderPathLiveData = new MutableLiveData<>();
@ -104,6 +114,30 @@ public class NotesListViewModel extends ViewModel {
return errorMessage;
}
/**
* LiveData (Tab)
*/
public MutableLiveData<List<NotesRepository.NoteInfo>> getFoldersLiveData() {
return foldersLiveData;
}
/**
*
*/
public MutableLiveData<Boolean> getIsSelectionMode() {
return isSelectionMode;
}
/**
*
*/
public void setIsSelectionMode(boolean isSelection) {
isSelectionMode.postValue(isSelection);
if (!isSelection) {
clearSelection();
}
}
/**
*
* <p>
@ -114,8 +148,12 @@ public class NotesListViewModel extends ViewModel {
*/
public void loadNotes(long folderId) {
this.currentFolderId = folderId;
this.currentFolderIdLiveData.postValue(folderId);
isLoading.postValue(true);
errorMessage.postValue(null);
// 退出选择模式
setIsSelectionMode(false);
// 加载文件夹路径
repository.getFolderPath(folderId, new NotesRepository.Callback<List<NotesRepository.NoteInfo>>() {
@ -129,8 +167,41 @@ public class NotesListViewModel extends ViewModel {
Log.e(TAG, "Failed to load folder path", error);
}
});
// 加载子文件夹 (Category Tabs) - Always load root folders to keep tabs visible
repository.getSubFolders(Notes.ID_ROOT_FOLDER, new NotesRepository.Callback<List<NotesRepository.NoteInfo>>() {
@Override
public void onSuccess(List<NotesRepository.NoteInfo> folders) {
// Construct the display list with "All" and "Uncategorized"
List<NotesRepository.NoteInfo> displayFolders = new ArrayList<>();
// 1. "All" Folder (Virtual)
NotesRepository.NoteInfo allFolder = new NotesRepository.NoteInfo();
allFolder.setId(Notes.ID_ALL_NOTES_FOLDER);
allFolder.snippet = "所有"; // Name
displayFolders.add(allFolder);
// 2. Real Folders (from DB)
if (folders != null) {
displayFolders.addAll(folders);
}
// 3. "Uncategorized" Folder (Actually Root Folder)
NotesRepository.NoteInfo uncategorizedFolder = new NotesRepository.NoteInfo();
uncategorizedFolder.setId(Notes.ID_ROOT_FOLDER);
uncategorizedFolder.snippet = "未分类"; // Custom Name for Root
displayFolders.add(uncategorizedFolder);
foldersLiveData.postValue(displayFolders);
}
@Override
public void onError(Exception error) {
Log.e(TAG, "Failed to load sub-folders", error);
}
});
// 加载笔记
// 加载笔记 (No folders)
repository.getNotes(folderId, new NotesRepository.Callback<List<NotesRepository.NoteInfo>>() {
@Override
public void onSuccess(List<NotesRepository.NoteInfo> notes) {
@ -283,6 +354,14 @@ public class NotesListViewModel extends ViewModel {
});
}
public MutableLiveData<HashSet<Long>> getSelectedIdsLiveData() {
return selectedIdsLiveData;
}
public void notifySelectionChanged() {
selectedIdsLiveData.postValue(new HashSet<>(selectedNoteIds));
}
/**
*
*
@ -295,6 +374,7 @@ public class NotesListViewModel extends ViewModel {
} else {
selectedNoteIds.remove(noteId);
}
notifySelectionChanged();
}
/**
@ -310,6 +390,7 @@ public class NotesListViewModel extends ViewModel {
selectedNoteIds.add(note.getId());
}
}
notifySelectionChanged();
}
/**
@ -320,6 +401,7 @@ public class NotesListViewModel extends ViewModel {
*/
public void deselectAllNotes() {
selectedNoteIds.clear();
notifySelectionChanged();
}
/**
@ -363,6 +445,13 @@ public class NotesListViewModel extends ViewModel {
return currentFolderId;
}
/**
* ID LiveData
*/
public MutableLiveData<Long> getCurrentFolderIdLiveData() {
return currentFolderIdLiveData;
}
/**
*
*
@ -432,6 +521,7 @@ public class NotesListViewModel extends ViewModel {
*/
public void clearSelection() {
selectedNoteIds.clear();
notifySelectionChanged();
}
/**
@ -745,4 +835,33 @@ public class NotesListViewModel extends ViewModel {
selectedNoteIds.clear();
Log.d(TAG, "ViewModel cleared");
}
}
/**
*
*
* @param noteId ID
* @param isLocked
*/
public void toggleNoteLock(long noteId, boolean isLocked) {
List<Long> ids = new ArrayList<>();
ids.add(noteId);
isLoading.postValue(true);
repository.batchLock(ids, isLocked, new NotesRepository.Callback<Integer>() {
@Override
public void onSuccess(Integer rowsAffected) {
isLoading.postValue(false);
refreshNotes();
Log.d(TAG, "Successfully toggled lock state for note: " + noteId);
}
@Override
public void onError(Exception error) {
isLoading.postValue(false);
String message = "锁定操作失败: " + error.getMessage();
errorMessage.postValue(message);
Log.e(TAG, message, error);
}
});
}
}

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

@ -5,47 +5,12 @@
android:layout_height="match_parent"
android:background="@color/background_color">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.Material3.ActionBar"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp">
<TextView
android:id="@+id/tv_toolbar_title_notes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Notes"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary"
android:layout_gravity="start|center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/task_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="80dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
android:paddingBottom="80dp"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btn_new_task"
@ -55,7 +20,7 @@
android:layout_margin="16dp"
android:src="@drawable/ic_add"
app:backgroundTint="@color/fab_color"
app:tint="@color/text_color_primary"
app:tint="@color/white"
android:contentDescription="New Task" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -1,122 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/note_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:background="@null">
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/bg_white">
<!-- 标题行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
android:orientation="vertical"
android:padding="16dp">
<!-- 类型图标(文件夹/便签) -->
<ImageView
android:id="@+id/iv_type_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:visibility="gone" />
<!-- Header: Title -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/iv_type_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:visibility="gone" />
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="18sp"
android:textColor="@color/text_color_primary"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"/>
<ImageView
android:id="@+id/iv_lock_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="4dp"
android:src="@android:drawable/ic_lock_lock"
app:tint="@color/text_color_secondary"
android:visibility="gone" />
</LinearLayout>
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:id="@+id/tv_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textColor="@android:color/black"
android:textStyle="bold"
android:maxLines="1"
android:layout_marginTop="4dp"
android:textSize="14sp"
android:textColor="@color/text_color_secondary"
android:maxLines="3"
android:ellipsize="end"
android:singleLine="true" />
<ImageView
android:id="@+id/iv_lock_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="4dp"
android:src="@android:drawable/ic_lock_lock"
android:tint="@android:color/darker_gray"
android:visibility="gone" />
android:visibility="visible" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textSize="12sp"
android:textColor="@android:color/darker_gray" />
</LinearLayout>
<!-- 通话名称行(用于通话记录) -->
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:maxLines="1"
android:ellipsize="end"
android:visibility="gone" />
<!-- 底部控制行:复选框和提醒图标 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<CheckBox
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:clickable="false"
android:layout_marginTop="4dp"
android:textSize="14sp"
android:textColor="@color/text_color_secondary"
android:maxLines="1"
android:ellipsize="end"
android:visibility="gone" />
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
<!-- Footer: Time, Checkbox, Alerts -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:visibility="gone" />
<!-- 填充空间 -->
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:id="@+id/iv_pinned_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@android:drawable/ic_menu_upload"
android:tint="@android:color/darker_gray"
android:visibility="gone" />
android:layout_marginTop="12dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="10sp"
android:textColor="#9E9E9E"
android:fontFamily="sans-serif-medium"/>
<CheckBox
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:clickable="false"
android:visibility="gone" />
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:visibility="gone" />
<ImageView
android:id="@+id/iv_pinned_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:src="@android:drawable/ic_menu_upload"
app:tint="@color/text_color_secondary"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

@ -1,106 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<!-- DrawerLayout支持侧栏滑动 -->
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_color">
<!-- 主内容区域 -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- AppBarLayout替代传统 ActionBar -->
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.Material3.ActionBar"
android:fitsSystemWindows="true">
<!-- Toolbar现代替代 ActionBar 的标准组件,带汉堡菜单按钮 -->
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/app_name"
app:navigationIcon="@android:drawable/ic_menu_sort_by_size"
app:layout_scrollFlags="scroll|enterAlways|snap" />
</com.google.android.material.appbar.AppBarLayout>
<!-- 便签列表:使用 NestedScrollView 包裹 ListView 以支持滚动 -->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 面包屑导航:显示当前文件夹路径(在列表上方) -->
<include
android:id="@+id/breadcrumb_include"
layout="@layout/breadcrumb_layout" />
<!-- 便签列表 -->
<ListView
android:id="@+id/notes_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="200dp"
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="@null" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- 悬浮按钮:替代原来的底部按钮 -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btn_new_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:contentDescription="@string/notelist_menu_new"
app:backgroundTint="@color/fab_color"
app:srcCompat="@android:drawable/ic_input_add" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!-- 侧栏 -->
<fragment
android:id="@+id/sidebar_fragment"
android:name="net.micode.notes.ui.SidebarFragment"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:tag="sidebar" />
</androidx.drawerlayout.widget.DrawerLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/notes_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="4dp"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="2" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btn_new_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_add"
app:backgroundTint="@color/fab_color"
app:tint="@color/white"
android:contentDescription="New Note" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -10,7 +10,7 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:orientation="vertical"
android:background="@android:color/white"
android:background="@color/bg_white"
android:elevation="8dp">
<!-- 操作栏 -->
@ -19,7 +19,7 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:background="@color/bg_white"
android:elevation="4dp">
<!-- 关闭按钮 -->
@ -30,7 +30,8 @@
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="关闭侧栏"
android:padding="8dp"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:tint="@color/text_color_primary" />
<View
android:layout_width="0dp"
@ -45,7 +46,8 @@
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="创建文件夹"
android:padding="8dp"
android:src="@android:drawable/ic_input_add" />
android:src="@android:drawable/ic_input_add"
android:tint="@color/text_color_primary" />
</LinearLayout>
<!-- 分隔线 -->
@ -64,7 +66,7 @@
android:drawablePadding="12dp"
android:text="@string/root_folder_name"
android:textSize="16sp"
android:textColor="@android:color/black"
android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@ -105,7 +107,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_sync"
android:textSize="16sp"
android:textColor="@android:color/black"
android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@ -119,7 +121,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_login"
android:textSize="16sp"
android:textColor="@android:color/black"
android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@ -133,7 +135,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_export"
android:textSize="16sp"
android:textColor="@android:color/black"
android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@ -147,7 +149,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_templates"
android:textSize="16sp"
android:textColor="@android:color/black"
android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@ -161,7 +163,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_settings"
android:textSize="16sp"
android:textColor="@android:color/black"
android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
</LinearLayout>
@ -182,7 +184,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_trash"
android:textSize="16sp"
android:textColor="@android:color/black"
android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
</LinearLayout>
</LinearLayout>

@ -1,70 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp"
android:gravity="center_vertical"
android:background="@color/bg_white"
android:layout_marginBottom="1dp">
<CheckBox
android:id="@+id/task_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/bg_white">
<LinearLayout
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp">
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/task_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/text_color_primary"
android:ellipsize="end"
android:maxLines="2" />
<CheckBox
android:id="@+id/task_checkbox"
android:layout_width="24dp"
android:layout_height="24dp"
android:button="@drawable/selector_checkbox_round"
android:background="@null"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="4dp"
android:gravity="center_vertical">
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingEnd="8dp">
<TextView
android:id="@+id/task_priority"
android:layout_width="wrap_content"
android:id="@+id/task_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:layout_marginEnd="8dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:background="#FFCDD2"
android:textColor="#B71C1C"
android:text="HIGH"
android:visibility="gone"/>
android:textSize="16sp"
android:textColor="@color/text_color_primary"
android:ellipsize="end"
android:maxLines="2" />
<TextView
android:id="@+id/task_date"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@color/text_color_secondary" />
android:orientation="horizontal"
android:layout_marginTop="4dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/task_priority"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:layout_marginEnd="8dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:background="#FFCDD2"
android:textColor="#B71C1C"
android:text="HIGH"
android:visibility="gone"/>
<TextView
android:id="@+id/task_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@color/text_color_secondary" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/task_alarm_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@android:drawable/ic_lock_idle_alarm"
android:tint="@color/text_color_secondary"
android:visibility="gone" />
<ImageView
android:id="@+id/task_alarm_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@android:drawable/ic_lock_idle_alarm"
android:tint="@color/text_color_secondary"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

@ -19,23 +19,23 @@
<color name="user_query_highlight">#335b5b5b</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="primary_color">#263238</color>
<color name="on_primary_color">#FFFFFF</color>
<color name="background_color">#E8E8E8</color>
<color name="primary_text_dark">#000000</color>
<color name="secondary_text_dark">#808080</color>
<color name="primary_color">#F7F7F7</color> <!-- Changed to match background for seamless look, or #263238 if we want dark header. User wants "Unified Frame". -->
<color name="on_primary_color">#212121</color>
<color name="background_color">#F7F7F7</color>
<color name="primary_text_dark">#212121</color>
<color name="secondary_text_dark">#757575</color>
<color name="fab_color">#FFC107</color>
<!-- Semantic Text Colors -->
<color name="text_color_primary">#000000</color>
<color name="text_color_secondary">#808080</color>
<color name="text_color_primary">#212121</color>
<color name="text_color_secondary">#757575</color>
<!-- Note Background Colors -->
<color name="bg_yellow">#FFF9C4</color>
<color name="bg_blue">#B3E5FC</color>
<color name="bg_yellow">#FFF8D6</color>
<color name="bg_blue">#E3F2FD</color>
<color name="bg_white">#FFFFFF</color>
<color name="bg_green">#C8E6C9</color>
<color name="bg_red">#FFCDD2</color>
<color name="bg_green">#E0F2F1</color>
<color name="bg_red">#FCE4EC</color>
<!-- New Presets -->
<color name="bg_midnight_black">#212121</color>

@ -179,4 +179,13 @@
<string name="menu_save_as_template">Save as template</string>
<string name="menu_picture">Picture</string>
<string name="menu_rich_text">Rich Text</string>
<!-- Capsule Feature -->
<string name="accessibility_service_description">小米便签-全局速记服务,用于监听剪贴板和应用来源。</string>
<string name="capsule_permission_alert_window_title">需要悬浮窗权限</string>
<string name="capsule_permission_alert_window_message">全局速记胶囊需要悬浮窗权限才能显示在其他应用上层。</string>
<string name="capsule_permission_accessibility_title">需要无障碍服务权限</string>
<string name="capsule_permission_accessibility_message">全局速记胶囊需要无障碍服务权限来监听剪贴板和获取来源应用。</string>
<string name="preferences_capsule_title">全局速记胶囊</string>
<string name="preferences_capsule_summary">开启侧边悬浮胶囊,支持跨应用拖拽和剪贴板速记</string>
</resources>

@ -36,6 +36,15 @@
<style name="TextAppearancePrimaryItem">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@color/text_color_primary</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">bold</item>
</style>
<style name="NotesBigTitle">
<item name="android:textSize">34sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/text_color_primary</item>
<item name="android:fontFamily">sans-serif-medium</item>
</style>
<style name="TextAppearanceSecondaryItem">

@ -1,56 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="pref_sync_account_key">
</PreferenceCategory>
<PreferenceCategory android:title="Appearance">
<ListPreference
android:key="pref_theme_mode"
android:title="Theme"
android:summary="%s"
android:entries="@array/theme_entries"
android:entryValues="@array/theme_values"
android:defaultValue="system" />
<ListPreference
android:key="pref_font_family"
android:title="Font Family"
android:summary="%s"
android:entries="@array/font_family_entries"
android:entryValues="@array/font_family_values"
android:defaultValue="default" />
</PreferenceCategory>
<PreferenceCategory>
<CheckBoxPreference
android:key="pref_key_bg_random_appear"
android:title="@string/preferences_bg_random_appear_title"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory android:title="Safe">
<Preference
android:key="pref_key_security"
android:title="Security Settings"
android:summary="Manage password lock"
android:icon="@android:drawable/ic_lock_lock" />
</PreferenceCategory>
</PreferenceScreen>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Sync">
<SwitchPreferenceCompat
android:key="sync_enable"
android:title="Auto Sync"
android:summary="Sync notes automatically"
android:defaultValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="UI">
<SwitchPreferenceCompat
android:key="show_preview"
android:title="Show Preview"
android:defaultValue="true" />
</PreferenceCategory>
</PreferenceScreen>
Loading…
Cancel
Save