Compare commits

..

1 Commits
main ... main

Author SHA1 Message Date
mxvwfs5gq 644b8501ec ADD file via upload
2 months ago

@ -1,431 +0,0 @@
/*
* 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.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
/**
* -
* /
*/
public class DataUtils {
private static final String TAG = "DataUtils";
/**
*
*
* @param resolver
* @param ids ID
* @return truefalse
*/
public static boolean batchDeleteNotes(ContentResolver resolver, Set<Long> ids) {
// 参数有效性检查
if (resolver == null) {
Log.w(TAG, "ContentResolver is null");
return false;
}
if (ids == null || ids.isEmpty()) {
Log.d(TAG, "No notes to delete");
return true;
}
ArrayList<ContentProviderOperation> operations = new ArrayList<>(ids.size());
for (long id : ids) {
// 防止删除系统根文件夹
if (id == Notes.ID_ROOT_FOLDER) {
Log.w(TAG, "Skipping system root folder deletion");
continue;
}
// 添加删除操作
operations.add(ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id))
.build());
}
if (operations.isEmpty()) {
Log.d(TAG, "No valid operations to execute");
return true;
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operations);
return results != null && results.length > 0;
} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "Batch delete failed", e);
}
return false;
}
/**
*
*
* @param resolver
* @param id ID
* @param srcFolderId ID
* @param desFolderId ID
*/
public static void moveNoteToFolder(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
if (resolver == null) {
Log.w(TAG, "ContentResolver is null");
return;
}
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地已修改
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
/**
*
*
* @param resolver
* @param ids ID
* @param folderId ID
* @return truefalse
*/
public static boolean batchMoveToFolder(ContentResolver resolver, Set<Long> ids, long folderId) {
if (resolver == null) {
Log.w(TAG, "ContentResolver is null");
return false;
}
if (ids == null || ids.isEmpty()) {
Log.d(TAG, "No notes to move");
return true;
}
ArrayList<ContentProviderOperation> operations = new ArrayList<>(ids.size());
for (long id : ids) {
operations.add(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id))
.withValue(NoteColumns.PARENT_ID, folderId)
.withValue(NoteColumns.LOCAL_MODIFIED, 1)
.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operations);
return results != null && results.length > 0;
} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "Batch move failed", e);
}
return false;
}
/**
*
*
* @param resolver
* @return
*/
public static int getUserFolderCount(ContentResolver resolver) {
if (resolver == null) {
Log.w(TAG, "ContentResolver is null");
return 0;
}
String[] projection = {NoteColumns.ID};
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?";
String[] selectionArgs = {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLDER) // 修正拼写错误FOLER -> FOLDER
};
try (Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
projection, selection, selectionArgs, null)) {
return cursor != null ? cursor.getCount() : 0;
}
}
/**
*
*
* @param resolver
* @param noteId ID
* @param type
* @return true
*/
public static boolean isNoteVisible(ContentResolver resolver, long noteId, int type) {
if (resolver == null) return false;
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?";
String[] selectionArgs = {
String.valueOf(type),
String.valueOf(Notes.ID_TRASH_FOLDER)
};
try (Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, selection, selectionArgs, null)) {
return cursor != null && cursor.getCount() > 0;
}
}
/**
*
*
* @param resolver
* @param noteId ID
* @return true
*/
public static boolean doesNoteExist(ContentResolver resolver, long noteId) {
if (resolver == null) return false;
try (Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null)) {
return cursor != null && cursor.getCount() > 0;
}
}
/**
*
*
* @param resolver
* @param dataId ID
* @return true
*/
public static boolean doesDataExist(ContentResolver resolver, long dataId) {
if (resolver == null) return false;
try (Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null)) {
return cursor != null && cursor.getCount() > 0;
}
}
/**
*
*
* @param resolver
* @param name
* @return true
*/
public static boolean isFolderNameExist(ContentResolver resolver, String name) {
if (resolver == null || TextUtils.isEmpty(name)) return false;
String selection = NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER +
" AND " + NoteColumns.SNIPPET + "=?";
String[] selectionArgs = { name };
try (Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
null, selection, selectionArgs, null)) {
return cursor != null && cursor.getCount() > 0;
}
}
/**
*
*
* @param resolver
* @param folderId ID
* @return
*/
public static Set<AppWidgetAttribute> getFolderWidgets(ContentResolver resolver, long folderId) {
Set<AppWidgetAttribute> widgets = new HashSet<>();
if (resolver == null) return widgets;
String[] projection = { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE };
String selection = NoteColumns.PARENT_ID + "=?";
String[] selectionArgs = { String.valueOf(folderId) };
try (Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
projection, selection, selectionArgs, null)) {
if (cursor != null && cursor.moveToFirst()) {
do {
try {
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = cursor.getInt(0);
widget.widgetType = cursor.getInt(1);
widgets.add(widget);
} catch (Exception e) {
Log.e(TAG, "Error reading widget attributes", e);
}
} while (cursor.moveToNext());
}
}
return widgets;
}
/**
* ID
*
* @param resolver
* @param noteId ID
* @return
*/
public static String getCallNumber(ContentResolver resolver, long noteId) {
if (resolver == null) return "";
String[] projection = { CallNote.PHONE_NUMBER };
String selection = CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?";
String[] selectionArgs = {
String.valueOf(noteId),
CallNote.CONTENT_ITEM_TYPE
};
try (Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
projection, selection, selectionArgs, null)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0);
}
} catch (Exception e) {
Log.e(TAG, "Error getting call number", e);
}
return "";
}
/**
* ID
*
* @param resolver
* @param phoneNumber
* @param callDate
* @return ID0
*/
public static long findNoteByCallDetails(ContentResolver resolver, String phoneNumber, long callDate) {
if (resolver == null || TextUtils.isEmpty(phoneNumber)) return 0;
String[] projection = { CallNote.NOTE_ID };
String selection = CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND " +
"PHONE_NUMBERS_EQUAL(" + CallNote.PHONE_NUMBER + ", ?)";
String[] selectionArgs = {
String.valueOf(callDate),
CallNote.CONTENT_ITEM_TYPE,
phoneNumber
};
try (Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
projection, selection, selectionArgs, null)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(0);
}
} catch (Exception e) {
Log.e(TAG, "Error finding note by call details", e);
}
return 0;
}
/**
*
*
* @param resolver
* @param noteId ID
* @return
* @throws IllegalArgumentException
*/
public static String getNoteSnippet(ContentResolver resolver, long noteId) {
if (resolver == null) throw new IllegalArgumentException("Resolver is null");
String[] projection = { NoteColumns.SNIPPET };
String selection = NoteColumns.ID + "=?";
String[] selectionArgs = { String.valueOf(noteId) };
try (Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
projection, selection, selectionArgs, null)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0);
}
throw new IllegalArgumentException("Note not found with id: " + noteId);
}
}
/**
*
*
* @param snippet
* @return
*/
public static String formatSnippet(String snippet) {
if (snippet == null) return null;
String formatted = snippet.trim();
int newlineIndex = formatted.indexOf('\n');
return (newlineIndex != -1) ? formatted.substring(0, newlineIndex) : formatted;
}
/* ----------------- 新增功能 ----------------- */
/**
*
*
* @param resolver
* @param noteId ID
* @return true
*/
public static boolean safeDeleteNote(ContentResolver resolver, long noteId) {
Set<Long> ids = new HashSet<>(1);
ids.add(noteId);
return batchDeleteNotes(resolver, ids);
}
/**
*
*
* @param resolver
* @param ids ID
* @param modified (1-, 0-)
* @return true
*/
public static boolean batchMarkModified(ContentResolver resolver, Set<Long> ids, int modified) {
if (resolver == null) return false;
ArrayList<ContentProviderOperation> operations = new ArrayList<>(ids.size());
for (long id : ids) {
operations.add(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id))
.withValue(NoteColumns.LOCAL_MODIFIED, modified)
.build());
}
try {
resolver.applyBatch(Notes.AUTHORITY, operations);
return true;
} catch (Exception e) {
Log.e(TAG, "Batch mark modified failed", e);
}
return false;
}
}

@ -0,0 +1,485 @@
/*
* 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.tool;
import android.content.Context;
import android.content.res.Resources;
import android.preference.PreferenceManager;
import android.util.SparseIntArray;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* - UI
*
* 1.
* 2.
* 3.
* 4.
*
*
* - ID
* -
* -
*/
public class ResourceParser {
// 防止实例化
private ResourceParser() {
throw new AssertionError("This class cannot be instantiated");
}
/* ================== 颜色常量枚举 ================== */
/**
*
*/
public enum NoteColor {
YELLOW(0),
BLUE(1),
WHITE(2),
GREEN(3),
RED(4),
// 扩展更多颜色选项
PURPLE(5),
ORANGE(6);
public final int id;
NoteColor(int id) {
this.id = id;
}
private static final int DEFAULT_ID = YELLOW.id;
/**
* ID
*/
public static int getDefault() {
return DEFAULT_ID;
}
/**
*
*/
public static NoteColor fromId(int id) {
for (NoteColor color : values()) {
if (color.id == id) {
return color;
}
}
return YELLOW;
}
}
/* ================== 字体大小常量枚举 ================== */
/**
*
*/
public enum FontSize {
SMALL(0),
MEDIUM(1),
LARGE(2),
SUPER(3);
public final int id;
FontSize(int id) {
this.id = id;
}
private static final int DEFAULT_ID = MEDIUM.id;
/**
* ID
*/
public static int getDefault() {
return DEFAULT_ID;
}
}
/* ================== 主题模式常量 ================== */
/**
*
*/
public enum ThemeMode {
LIGHT(0),
DARK(1),
AUTO(2);
public final int id;
ThemeMode(int id) {
this.id = id;
}
public static ThemeMode fromId(int id) {
switch (id) {
case 1: return DARK;
case 2: return AUTO;
default: return LIGHT;
}
}
}
/* ================== 笔记背景资源管理 ================== */
/**
*
*/
public static class NoteBgResources {
// 编辑界面背景资源
private static final SparseIntArray BG_EDIT_RESOURCES = new SparseIntArray();
// 编辑界面标题背景资源
private static final SparseIntArray BG_EDIT_TITLE_RESOURCES = new SparseIntArray();
// 静态初始化资源映射
static {
// 浅色主题资源
BG_EDIT_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.edit_yellow);
BG_EDIT_RESOURCES.put(NoteColor.BLUE.id, R.drawable.edit_blue);
BG_EDIT_RESOURCES.put(NoteColor.WHITE.id, R.drawable.edit_white);
BG_EDIT_RESOURCES.put(NoteColor.GREEN.id, R.drawable.edit_green);
BG_EDIT_RESOURCES.put(NoteColor.RED.id, R.drawable.edit_red);
BG_EDIT_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.edit_purple);
BG_EDIT_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.edit_orange);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.edit_title_yellow);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.BLUE.id, R.drawable.edit_title_blue);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.WHITE.id, R.drawable.edit_title_white);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.GREEN.id, R.drawable.edit_title_green);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.RED.id, R.drawable.edit_title_red);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.edit_title_purple);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.edit_title_orange);
}
/**
* ID
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgResource(int colorId) {
return BG_EDIT_RESOURCES.get(colorId, R.drawable.edit_yellow);
}
/**
* ID
*
* @param colorId ID
* @return ID
*/
public static int getNoteTitleBgResource(int colorId) {
return BG_EDIT_TITLE_RESOURCES.get(colorId, R.drawable.edit_title_yellow);
}
/**
* ID
* @return ID
*/
public static int[] getAvailableColorIds() {
int[] ids = new int[BG_EDIT_RESOURCES.size()];
for (int i = 0; i < BG_EDIT_RESOURCES.size(); i++) {
ids[i] = BG_EDIT_RESOURCES.keyAt(i);
}
return ids;
}
}
/**
* ID
*
* @param context
* @return ID
*/
public static int getDefaultBgId(Context context) {
// 检查是否启用随机背景颜色
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
// 获取所有可用颜色ID
int[] colorIds = NoteBgResources.getAvailableColorIds();
if (colorIds.length > 0) {
return colorIds[(int) (Math.random() * colorIds.length)];
}
}
// 返回默认颜色
return NoteColor.getDefault();
}
/* ================== 笔记布局资源管理 ================== */
/**
*
*/
public static class NoteLayoutResources {
// 列表顶部背景资源
private static final SparseIntArray BG_FIRST_RESOURCES = new SparseIntArray();
// 列表中部背景资源
private static final SparseIntArray BG_NORMAL_RESOURCES = new SparseIntArray();
// 列表底部背景资源
private static final SparseIntArray BG_LAST_RESOURCES = new SparseIntArray();
// 单条笔记背景资源
private static final SparseIntArray BG_SINGLE_RESOURCES = new SparseIntArray();
// 文件夹背景资源
private static final SparseIntArray BG_FOLDER_RESOURCES = new SparseIntArray();
// 静态初始化资源映射
static {
// 浅色主题资源
BG_FIRST_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.list_yellow_up);
BG_FIRST_RESOURCES.put(NoteColor.BLUE.id, R.drawable.list_blue_up);
BG_FIRST_RESOURCES.put(NoteColor.WHITE.id, R.drawable.list_white_up);
BG_FIRST_RESOURCES.put(NoteColor.GREEN.id, R.drawable.list_green_up);
BG_FIRST_RESOURCES.put(NoteColor.RED.id, R.drawable.list_red_up);
BG_FIRST_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.list_purple_up);
BG_FIRST_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.list_orange_up);
BG_NORMAL_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.list_yellow_middle);
BG_NORMAL_RESOURCES.put(NoteColor.BLUE.id, R.drawable.list_blue_middle);
BG_NORMAL_RESOURCES.put(NoteColor.WHITE.id, R.drawable.list_white_middle);
BG_NORMAL_RESOURCES.put(NoteColor.GREEN.id, R.drawable.list_green_middle);
BG_NORMAL_RESOURCES.put(NoteColor.RED.id, R.drawable.list_red_middle);
BG_NORMAL_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.list_purple_middle);
BG_NORMAL_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.list_orange_middle);
BG_LAST_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.list_yellow_down);
BG_LAST_RESOURCES.put(NoteColor.BLUE.id, R.drawable.list_blue_down);
BG_LAST_RESOURCES.put(NoteColor.WHITE.id, R.drawable.list_white_down);
BG_LAST_RESOURCES.put(NoteColor.GREEN.id, R.drawable.list_green_down);
BG_LAST_RESOURCES.put(NoteColor.RED.id, R.drawable.list_red_down);
BG_LAST_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.list_purple_down);
BG_LAST_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.list_orange_down);
BG_SINGLE_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.list_yellow_single);
BG_SINGLE_RESOURCES.put(NoteColor.BLUE.id, R.drawable.list_blue_single);
BG_SINGLE_RESOURCES.put(NoteColor.WHITE.id, R.drawable.list_white_single);
BG_SINGLE_RESOURCES.put(NoteColor.GREEN.id, R.drawable.list_green_single);
BG_SINGLE_RESOURCES.put(NoteColor.RED.id, R.drawable.list_red_single);
BG_SINGLE_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.list_purple_single);
BG_SINGLE_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.list_orange_single);
BG_FOLDER_RESOURCES.put(ThemeMode.LIGHT.id, R.drawable.list_folder_light);
BG_FOLDER_RESOURCES.put(ThemeMode.DARK.id, R.drawable.list_folder_dark);
}
/**
*
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgFirstRes(int colorId) {
return BG_FIRST_RESOURCES.get(colorId, R.drawable.list_yellow_up);
}
/**
*
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgLastRes(int colorId) {
return BG_LAST_RESOURCES.get(colorId, R.drawable.list_yellow_down);
}
/**
*
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgSingleRes(int colorId) {
return BG_SINGLE_RESOURCES.get(colorId, R.drawable.list_yellow_single);
}
/**
*
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgNormalRes(int colorId) {
return BG_NORMAL_RESOURCES.get(colorId, R.drawable.list_yellow_middle);
}
/**
*
*
* @param themeMode
* @return ID
*/
public static int getFolderBgRes(ThemeMode themeMode) {
return BG_FOLDER_RESOURCES.get(themeMode.id, R.drawable.list_folder_light);
}
}
/* ================== 小部件资源管理 ================== */
/**
*
*/
public static class WidgetBgResources {
// 2x小部件背景资源
private static final SparseIntArray BG_2X_RESOURCES = new SparseIntArray();
// 4x小部件背景资源
private static final SparseIntArray BG_4X_RESOURCES = new SparseIntArray();
// 静态初始化资源映射
static {
// 浅色主题资源
BG_2X_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.widget_2x_yellow);
BG_2X_RESOURCES.put(NoteColor.BLUE.id, R.drawable.widget_2x_blue);
BG_2X_RESOURCES.put(NoteColor.WHITE.id, R.drawable.widget_2x_white);
BG_2X_RESOURCES.put(NoteColor.GREEN.id, R.drawable.widget_2x_green);
BG_2X_RESOURCES.put(NoteColor.RED.id, R.drawable.widget_2x_red);
BG_2X_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.widget_2x_purple);
BG_2X_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.widget_2x_orange);
BG_4X_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.widget_4x_yellow);
BG_4X_RESOURCES.put(NoteColor.BLUE.id, R.drawable.widget_4x_blue);
BG_4X_RESOURCES.put(NoteColor.WHITE.id, R.drawable.widget_4x_white);
BG_4X_RESOURCES.put(NoteColor.GREEN.id, R.drawable.widget_4x_green);
BG_4X_RESOURCES.put(NoteColor.RED.id, R.drawable.widget_4x_red);
BG_4X_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.widget_4x_purple);
BG_4X_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.widget_4x_orange);
}
/**
* 2x
*
* @param colorId ID
* @return ID
*/
public static int getWidget2xBgResource(int colorId) {
return BG_2X_RESOURCES.get(colorId, R.drawable.widget_2x_yellow);
}
/**
* 4x
*
* @param colorId ID
* @return ID
*/
public static int getWidget4xBgResource(int colorId) {
return BG_4X_RESOURCES.get(colorId, R.drawable.widget_4x_yellow);
}
}
/* ================== 文本外观资源管理 ================== */
/**
*
*/
public static class TextAppearanceResources {
// 文本外观资源映射
private static final SparseIntArray TEXTAPPEARANCE_RESOURCES = new SparseIntArray();
// 静态初始化资源映射
static {
TEXTAPPEARANCE_RESOURCES.put(FontSize.SMALL.id, R.style.TextAppearanceSmall);
TEXTAPPEARANCE_RESOURCES.put(FontSize.MEDIUM.id, R.style.TextAppearanceMedium);
TEXTAPPEARANCE_RESOURCES.put(FontSize.LARGE.id, R.style.TextAppearanceLarge);
TEXTAPPEARANCE_RESOURCES.put(FontSize.SUPER.id, R.style.TextAppearanceSuper);
}
/**
*
*
* @param fontSizeId ID
* @return ID
*/
public static int getTextAppearanceResource(int fontSizeId) {
// 边界检查防止越界
int resourceId = TEXTAPPEARANCE_RESOURCES.get(fontSizeId, -1);
return resourceId != -1 ? resourceId : FontSize.getDefault();
}
/**
*
*
* @return
*/
public static int getAvailableSizeCount() {
return TEXTAPPEARANCE_RESOURCES.size();
}
/**
* sp
*
* @param context
* @param fontSizeId ID
* @return sp
*/
public static float getFontSizeSp(Context context, int fontSizeId) {
Resources res = context.getResources();
// 映射字体大小ID到实际尺寸值
switch (fontSizeId) {
case 0: return res.getDimension(R.dimen.text_size_small) / res.getDisplayMetrics().scaledDensity;
case 1: return res.getDimension(R.dimen.text_size_medium) / res.getDisplayMetrics().scaledDensity;
case 2: return res.getDimension(R.dimen.text_size_large) / res.getDisplayMetrics().scaledDensity;
case 3: return res.getDimension(R.dimen.text_size_super) / res.getDisplayMetrics().scaledDensity;
default: return 16; // 默认16sp
}
}
}
/* ================== 新功能:主题资源解析 ================== */
/**
*
*
* @param context
* @return
*/
public static ThemeMode getCurrentThemeMode(Context context) {
// 从偏好设置获取主题ID
int themeId = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(NotesPreferenceActivity.PREFERENCE_THEME_MODE, 0);
return ThemeMode.fromId(themeId);
}
/**
* ID
*
* @param colorId ID
* @param themeMode
* @return ID
*/
public static int getThemeAdjustedColor(int colorId, ThemeMode themeMode) {
// 深色模式下调整颜色饱和度
if (themeMode == ThemeMode.DARK) {
switch (NoteColor.fromId(colorId)) {
case YELLOW: return NoteColor.ORANGE.id;
case WHITE: return NoteColor.BLUE.id;
case GREEN: return NoteColor.PURPLE.id;
default: return colorId;
}
}
return colorId;
}
}

@ -13,16 +13,10 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// 启用全屏显示,内容可以延伸到屏幕边缘
EdgeToEdge.enable(this); EdgeToEdge.enable(this);
// 设置主布局
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
// 设置窗口插入监听器,处理系统状态栏和导航栏的空间
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
// 获取系统状态栏和导航栏的尺寸
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
// 设置视图的内边距确保内容不会被系统UI遮挡
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets; return insets;
}); });

@ -1,3 +1,19 @@
/*
* 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.data; package net.micode.notes.data;
import android.content.ContentValues; import android.content.ContentValues;
@ -10,80 +26,80 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
/**
*
* 使
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper { public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db"; // 数据库名称 private static final String DB_NAME = "note.db";
private static final int DB_VERSION = 4; // 当前数据库版本
private static final String TAG = "NotesDatabaseHelper"; // 日志标签 private static final int DB_VERSION = 4;
// 表名常量接口
public interface TABLE { public interface TABLE {
public static final String NOTE = "note"; // 笔记表 public static final String NOTE = "note";
public static final String DATA = "data"; // 数据表
public static final String DATA = "data";
} }
// 单例实例 private static final String TAG = "NotesDatabaseHelper";
private static NotesDatabaseHelper sInstance;
private static NotesDatabaseHelper mInstance;
// 创建笔记表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL = private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + " (\n" + "CREATE TABLE " + TABLE.NOTE + "(" +
" " + NoteColumns.ID + " INTEGER PRIMARY KEY,\n" + NoteColumns.ID + " INTEGER PRIMARY KEY," +
" " + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000),\n" + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
" " + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000),\n" + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
" " + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT '',\n" + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +
" " + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1,\n" + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +
" " + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
" " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT '',\n" + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
" " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0\n" + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")"; ")";
// 创建数据表的SQL语句 private static final String CREATE_DATA_TABLE_SQL =
private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + "(" +
"CREATE TABLE " + TABLE.DATA + " (\n" + DataColumns.ID + " INTEGER PRIMARY KEY," +
" " + DataColumns.ID + " INTEGER PRIMARY KEY,\n" + DataColumns.MIME_TYPE + " TEXT NOT NULL," +
" " + DataColumns.MIME_TYPE + " TEXT NOT NULL,\n" + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +
" " + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0,\n" + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
" " + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000),\n" + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
" " + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000),\n" + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +
" " + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT '',\n" + DataColumns.DATA1 + " INTEGER," +
" " + DataColumns.DATA1 + " INTEGER,\n" + DataColumns.DATA2 + " INTEGER," +
" " + DataColumns.DATA2 + " INTEGER,\n" + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
" " + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT '',\n" + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
" " + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT '',\n" + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
" " + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''\n" +
")"; ")";
// 创建数据表索引的SQL语句
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " + TABLE.DATA + "CREATE INDEX IF NOT EXISTS note_id_index ON " +
"(" + DataColumns.NOTE_ID + ");"; TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
// 文件夹计数更新触发器 /**
* Increase folder's note count when move note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update " + "CREATE TRIGGER increase_folder_count_on_update "+
"AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " + " BEGIN " +
" UPDATE " + TABLE.NOTE + " UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END"; " END";
/**
* Decrease folder's note count when move note from folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " + "CREATE TRIGGER decrease_folder_count_on_update " +
"AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " + " BEGIN " +
" UPDATE " + TABLE.NOTE + " UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
@ -91,18 +107,24 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END"; " END";
/**
* Increase folder's note count when insert new note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " + "CREATE TRIGGER increase_folder_count_on_insert " +
"AFTER INSERT ON " + TABLE.NOTE + " AFTER INSERT ON " + TABLE.NOTE +
" BEGIN " + " BEGIN " +
" UPDATE " + TABLE.NOTE + " UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END"; " END";
/**
* Decrease folder's note count when delete note from the folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " + "CREATE TRIGGER decrease_folder_count_on_delete " +
"AFTER DELETE ON " + TABLE.NOTE + " AFTER DELETE ON " + TABLE.NOTE +
" BEGIN " + " BEGIN " +
" UPDATE " + TABLE.NOTE + " UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
@ -110,10 +132,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" AND " + NoteColumns.NOTES_COUNT + ">0;" + " AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END"; " END";
// 笔记内容同步触发器 /**
* Update note's content when insert data with type {@link DataConstants#NOTE}
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " + "CREATE TRIGGER update_note_content_on_insert " +
"AFTER INSERT ON " + TABLE.DATA + " AFTER INSERT ON " + TABLE.DATA +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" + " BEGIN" +
" UPDATE " + TABLE.NOTE + " UPDATE " + TABLE.NOTE +
@ -121,9 +145,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END"; " END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " + "CREATE TRIGGER update_note_content_on_update " +
"AFTER UPDATE ON " + TABLE.DATA + " AFTER UPDATE ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" + " BEGIN" +
" UPDATE " + TABLE.NOTE + " UPDATE " + TABLE.NOTE +
@ -131,9 +158,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END"; " END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " + "CREATE TRIGGER update_note_content_on_delete " +
"AFTER delete ON " + TABLE.DATA + " AFTER delete ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" + " BEGIN" +
" UPDATE " + TABLE.NOTE + " UPDATE " + TABLE.NOTE +
@ -141,88 +171,61 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END"; " END";
// 级联删除触发器 /**
* Delete datas belong to note which has been deleted
*/
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " + "CREATE TRIGGER delete_data_on_delete " +
"AFTER DELETE ON " + TABLE.NOTE + " AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" + " BEGIN" +
" DELETE FROM " + TABLE.DATA + " DELETE FROM " + TABLE.DATA +
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END"; " END";
/**
* Delete notes belong to folder which has been deleted
*/
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " + "CREATE TRIGGER folder_delete_notes_on_delete " +
"AFTER DELETE ON " + TABLE.NOTE + " AFTER DELETE ON " + TABLE.NOTE +
" WHEN old." + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" BEGIN" + " BEGIN" +
" DELETE FROM " + TABLE.NOTE + " DELETE FROM " + TABLE.NOTE +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END"; " END";
/**
* Move notes belong to folder which has been moved to trash folder
*/
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " + "CREATE TRIGGER folder_move_notes_on_trash " +
"AFTER UPDATE ON " + TABLE.NOTE + " AFTER UPDATE ON " + TABLE.NOTE +
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" AND old." + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" BEGIN" + " BEGIN" +
" UPDATE " + TABLE.NOTE + " UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END"; " END";
/** public NotesDatabaseHelper(Context context) {
*
* 使线
*/
public static synchronized NotesDatabaseHelper getInstance(Context context) {
if (sInstance == null) {
sInstance = new NotesDatabaseHelper(context.getApplicationContext());
}
return sInstance;
}
/**
*
*/
private NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION); super(context, DB_NAME, null, DB_VERSION);
} }
/** public void createNoteTable(SQLiteDatabase db) {
* db.execSQL(CREATE_NOTE_TABLE_SQL);
*/ reCreateNoteTableTriggers(db);
private void createNoteTable(SQLiteDatabase db) { createSystemFolder(db);
try { Log.d(TAG, "note table has been created");
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "Note table created successfully");
} catch (Exception e) {
Log.e(TAG, "Failed to create note table: " + e.getMessage(), e);
throw e;
}
} }
/**
*
*/
private void reCreateNoteTableTriggers(SQLiteDatabase db) { private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除旧触发器 db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
String[] noteTriggers = { db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
"increase_folder_count_on_update", db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
"decrease_folder_count_on_update", db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
"decrease_folder_count_on_delete", db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
"delete_data_on_delete", db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
"increase_folder_count_on_insert", db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
"folder_delete_notes_on_delete",
"folder_move_notes_on_trash"
};
for (String trigger : noteTriggers) {
db.execSQL("DROP TRIGGER IF EXISTS " + trigger);
}
// 创建新触发器
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
@ -232,216 +235,128 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
} }
/**
*
*/
private void createSystemFolder(SQLiteDatabase db) { private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// 创建通话记录文件夹 /**
* call record foler for call notes
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
// 创建根文件夹 /**
* root folder which is default folder
*/
values.clear(); values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
// 创建临时文件夹 /**
* temporary folder which is used for moving note
*/
values.clear(); values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
// 创建回收站文件夹 /**
* create trash folder
*/
values.clear(); values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
Log.d(TAG, "System folders created successfully");
} }
/** public void createDataTable(SQLiteDatabase db) {
* db.execSQL(CREATE_DATA_TABLE_SQL);
*/ reCreateDataTableTriggers(db);
private void createDataTable(SQLiteDatabase db) { db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
try { Log.d(TAG, "data table has been created");
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "Data table created successfully");
} catch (Exception e) {
Log.e(TAG, "Failed to create data table: " + e.getMessage(), e);
throw e;
}
} }
/**
*
*/
private void reCreateDataTableTriggers(SQLiteDatabase db) { private void reCreateDataTableTriggers(SQLiteDatabase db) {
// 删除旧触发器 db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
String[] dataTriggers = { db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
"update_note_content_on_insert", db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
"update_note_content_on_update",
"update_note_content_on_delete"
};
for (String trigger : dataTriggers) {
db.execSQL("DROP TRIGGER IF EXISTS " + trigger);
}
// 创建新触发器
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
} }
/** static synchronized NotesDatabaseHelper getInstance(Context context) {
* if (mInstance == null) {
*/ mInstance = new NotesDatabaseHelper(context);
}
return mInstance;
}
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
Log.d(TAG, "Creating database version " + DB_VERSION); createNoteTable(db);
createDataTable(db);
// 使用事务提高性能
db.beginTransaction();
try {
createNoteTable(db);
createDataTable(db);
db.setTransactionSuccessful();
Log.d(TAG, "Database created successfully");
} catch (Exception e) {
Log.e(TAG, "Failed to create database: " + e.getMessage(), e);
throw e;
} finally {
db.endTransaction();
}
} }
/**
*
*/
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion); boolean reCreateTriggers = false;
boolean skipV2 = false;
// 使用事务确保升级过程的原子性
db.beginTransaction(); if (oldVersion == 1) {
try { upgradeToV2(db);
for (int version = oldVersion; version < newVersion; version++) { skipV2 = true; // this upgrade including the upgrade from v2 to v3
upgradeToVersion(db, version); oldVersion++;
}
db.setTransactionSuccessful();
Log.d(TAG, "Database upgraded successfully");
} catch (Exception e) {
Log.e(TAG, "Failed to upgrade database: " + e.getMessage(), e);
throw e;
} finally {
db.endTransaction();
} }
}
/** if (oldVersion == 2 && !skipV2) {
* upgradeToV3(db);
*/ reCreateTriggers = true;
private void upgradeToVersion(SQLiteDatabase db, int currentVersion) { oldVersion++;
switch (currentVersion) { }
case 1:
upgradeFromV1ToV2(db); if (oldVersion == 3) {
break; upgradeToV4(db);
case 2: oldVersion++;
upgradeFromV2ToV3(db); }
break;
case 3: if (reCreateTriggers) {
upgradeFromV3ToV4(db); reCreateNoteTableTriggers(db);
break; reCreateDataTableTriggers(db);
default: }
throw new IllegalStateException("Unknown database version: " + currentVersion);
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
} }
} }
/** private void upgradeToV2(SQLiteDatabase db) {
* 12
*/
private void upgradeFromV1ToV2(SQLiteDatabase db) {
Log.d(TAG, "Upgrading from version 1 to 2");
// 版本1到版本2的升级包含了版本2到版本3的升级
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
createNoteTable(db); createNoteTable(db);
createDataTable(db); createDataTable(db);
Log.d(TAG, "Upgraded to version 2 successfully");
} }
/** private void upgradeToV3(SQLiteDatabase db) {
* 23 // drop unused triggers
*/ db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
private void upgradeFromV2ToV3(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
Log.d(TAG, "Upgrading from version 2 to 3"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id
// 删除旧触发器
String[] oldTriggers = {
"update_note_modified_date_on_insert",
"update_note_modified_date_on_delete",
"update_note_modified_date_on_update"
};
for (String trigger : oldTriggers) {
db.execSQL("DROP TRIGGER IF EXISTS " + trigger);
}
// 添加新列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''"); + " TEXT NOT NULL DEFAULT ''");
// add a trash system folder
// 添加回收站系统文件夹
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
Log.d(TAG, "Upgraded to version 3 successfully");
} }
/** private void upgradeToV4(SQLiteDatabase db) {
* 34
*/
private void upgradeFromV3ToV4(SQLiteDatabase db) {
Log.d(TAG, "Upgrading from version 3 to 4");
// 添加版本列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0"); + " INTEGER NOT NULL DEFAULT 0");
Log.d(TAG, "Upgraded to version 4 successfully");
}
/**
*
*/
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Downgrading database from version " + oldVersion + " to " + newVersion);
// 降级策略:删除所有表并重新创建
db.beginTransaction();
try {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
onCreate(db);
db.setTransactionSuccessful();
Log.d(TAG, "Database downgraded successfully");
} catch (Exception e) {
Log.e(TAG, "Failed to downgrade database: " + e.getMessage(), e);
throw e;
} finally {
db.endTransaction();
}
} }
} }

@ -16,6 +16,7 @@
package net.micode.notes.data; package net.micode.notes.data;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentUris; import android.content.ContentUris;
@ -33,32 +34,37 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE; import net.micode.notes.data.NotesDatabaseHelper.TABLE;
public class NotesProvider extends ContentProvider { public class NotesProvider extends ContentProvider {
private static final UriMatcher mMatcher; // URI匹配器用于识别不同的URI private static final UriMatcher mMatcher;
private NotesDatabaseHelper mHelper; // 数据库帮助类实例
private static final String TAG = "NotesProvider"; // 日志标签 private NotesDatabaseHelper mHelper;
private static final String TAG = "NotesProvider";
// URI匹配常量 private static final int URI_NOTE = 1;
private static final int URI_NOTE = 1; // 笔记集合URI private static final int URI_NOTE_ITEM = 2;
private static final int URI_NOTE_ITEM = 2; // 单个笔记URI private static final int URI_DATA = 3;
private static final int URI_DATA = 3; // 数据集合URI private static final int URI_DATA_ITEM = 4;
private static final int URI_DATA_ITEM = 4; // 单个数据URI
private static final int URI_SEARCH = 5; // 搜索URI private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6; // 搜索建议URI private static final int URI_SEARCH_SUGGEST = 6;
// 静态初始化块设置URI匹配规则
static { static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 匹配所有笔记 mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 匹配单个笔记 mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 匹配所有数据 mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 匹配单个数据 mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); // 匹配搜索 mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); // 匹配搜索建议 mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); // 匹配带参数的搜索建议 mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
} }
// 搜索投影和查询SQL /**
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
@ -73,44 +79,42 @@ public class NotesProvider extends ContentProvider {
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
// 初始化提供者
@Override @Override
public boolean onCreate() { public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext()); mHelper = NotesDatabaseHelper.getInstance(getContext());
return true; return true;
} }
// 查询数据
@Override @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) { String sortOrder) {
Cursor c = null; Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase(); SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null; String id = null;
// 根据URI匹配类型执行不同的查询
switch (mMatcher.match(uri)) { switch (mMatcher.match(uri)) {
case URI_NOTE: case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break; break;
case URI_NOTE_ITEM: case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + parseSelection(selection), c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
selectionArgs, null, null, sortOrder); + parseSelection(selection), selectionArgs, null, null, sortOrder);
break; break;
case URI_DATA: case URI_DATA:
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break; break;
case URI_DATA_ITEM: case URI_DATA_ITEM:
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection), c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
selectionArgs, null, null, sortOrder); + parseSelection(selection), selectionArgs, null, null, sortOrder);
break; break;
case URI_SEARCH: case URI_SEARCH:
case URI_SEARCH_SUGGEST: case URI_SEARCH_SUGGEST:
// 处理搜索请求
if (sortOrder != null || projection != null) { if (sortOrder != null || projection != null) {
throw new IllegalArgumentException("不支持指定排序或投影"); throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
} }
String searchString = null; String searchString = null;
@ -128,29 +132,25 @@ public class NotesProvider extends ContentProvider {
try { try {
searchString = String.format("%%%s%%", searchString); searchString = String.format("%%%s%%", searchString);
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString });
} catch (IllegalStateException ex) { } catch (IllegalStateException ex) {
Log.e(TAG, "查询异常: " + ex.toString()); Log.e(TAG, "got exception: " + ex.toString());
} }
break; break;
default: default:
throw new IllegalArgumentException("未知URI: " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
// 设置内容观察者通知
if (c != null) { if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri); c.setNotificationUri(getContext().getContentResolver(), uri);
} }
return c; return c;
} }
// 插入数据
@Override @Override
public Uri insert(Uri uri, ContentValues values) { public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase(); SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0; long dataId = 0, noteId = 0, insertedId = 0;
// 根据URI匹配类型执行不同的插入操作
switch (mMatcher.match(uri)) { switch (mMatcher.match(uri)) {
case URI_NOTE: case URI_NOTE:
insertedId = noteId = db.insert(TABLE.NOTE, null, values); insertedId = noteId = db.insert(TABLE.NOTE, null, values);
@ -159,20 +159,20 @@ public class NotesProvider extends ContentProvider {
if (values.containsKey(DataColumns.NOTE_ID)) { if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID); noteId = values.getAsLong(DataColumns.NOTE_ID);
} else { } else {
Log.d(TAG, "数据格式错误缺少笔记ID: " + values.toString()); Log.d(TAG, "Wrong data format without note id:" + values.toString());
} }
insertedId = dataId = db.insert(TABLE.DATA, null, values); insertedId = dataId = db.insert(TABLE.DATA, null, values);
break; break;
default: default:
throw new IllegalArgumentException("未知URI: " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
// Notify the note uri
// 通知数据变化
if (noteId > 0) { if (noteId > 0) {
getContext().getContentResolver().notifyChange( getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
} }
// Notify the data uri
if (dataId > 0) { if (dataId > 0) {
getContext().getContentResolver().notifyChange( getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
@ -181,15 +181,12 @@ public class NotesProvider extends ContentProvider {
return ContentUris.withAppendedId(uri, insertedId); return ContentUris.withAppendedId(uri, insertedId);
} }
// 删除数据
@Override @Override
public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0; int count = 0;
String id = null; String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase(); SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false; boolean deleteData = false;
// 根据URI匹配类型执行不同的删除操作
switch (mMatcher.match(uri)) { switch (mMatcher.match(uri)) {
case URI_NOTE: case URI_NOTE:
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
@ -197,7 +194,10 @@ public class NotesProvider extends ContentProvider {
break; break;
case URI_NOTE_ITEM: case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
// 系统文件夹不允许删除 /**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
long noteId = Long.valueOf(id); long noteId = Long.valueOf(id);
if (noteId <= 0) { if (noteId <= 0) {
break; break;
@ -216,10 +216,8 @@ public class NotesProvider extends ContentProvider {
deleteData = true; deleteData = true;
break; break;
default: default:
throw new IllegalArgumentException("未知URI: " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
// 通知数据变化
if (count > 0) { if (count > 0) {
if (deleteData) { if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
@ -229,15 +227,12 @@ public class NotesProvider extends ContentProvider {
return count; return count;
} }
// 更新数据
@Override @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0; int count = 0;
String id = null; String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase(); SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false; boolean updateData = false;
// 根据URI匹配类型执行不同的更新操作
switch (mMatcher.match(uri)) { switch (mMatcher.match(uri)) {
case URI_NOTE: case URI_NOTE:
increaseNoteVersion(-1, selection, selectionArgs); increaseNoteVersion(-1, selection, selectionArgs);
@ -246,8 +241,8 @@ public class NotesProvider extends ContentProvider {
case URI_NOTE_ITEM: case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + parseSelection(selection), count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
selectionArgs); + parseSelection(selection), selectionArgs);
break; break;
case URI_DATA: case URI_DATA:
count = db.update(TABLE.DATA, values, selection, selectionArgs); count = db.update(TABLE.DATA, values, selection, selectionArgs);
@ -255,15 +250,14 @@ public class NotesProvider extends ContentProvider {
break; break;
case URI_DATA_ITEM: case URI_DATA_ITEM:
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + parseSelection(selection), count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
selectionArgs); + parseSelection(selection), selectionArgs);
updateData = true; updateData = true;
break; break;
default: default:
throw new IllegalArgumentException("未知URI: " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
// 通知数据变化
if (count > 0) { if (count > 0) {
if (updateData) { if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
@ -273,12 +267,10 @@ public class NotesProvider extends ContentProvider {
return count; return count;
} }
// 解析查询条件
private String parseSelection(String selection) { private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
} }
// 增加笔记版本号
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120); StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE "); sql.append("UPDATE ");
@ -304,37 +296,10 @@ public class NotesProvider extends ContentProvider {
mHelper.getWritableDatabase().execSQL(sql.toString()); mHelper.getWritableDatabase().execSQL(sql.toString());
} }
// 获取内容类型
@Override @Override
public String getType(Uri uri) { public String getType(Uri uri) {
switch (mMatcher.match(uri)) { // TODO Auto-generated method stub
case URI_NOTE: return null;
// 返回笔记集合的MIME类型
return Notes.TextNote.CONTENT_TYPE;
case URI_NOTE_ITEM:
// 返回单个笔记的MIME类型
return Notes.TextNote.CONTENT_ITEM_TYPE;
case URI_DATA:
// 返回数据集合的MIME类型
return Notes.Data.CONTENT_TYPE;
case URI_DATA_ITEM:
// 返回单个数据的MIME类型
return Notes.Data.CONTENT_ITEM_TYPE;
case URI_SEARCH:
// 返回搜索结果的MIME类型
return Notes.TextNote.CONTENT_TYPE;
case URI_SEARCH_SUGGEST:
// 返回搜索建议的MIME类型
return SearchManager.SUGGEST_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
} }
}
}

@ -28,34 +28,30 @@ import org.json.JSONObject;
public class MetaData extends Task { public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName(); private final static String TAG = MetaData.class.getSimpleName();
private String mRelatedGid = null;// 关联的Google Tasks ID private String mRelatedGid = null;
// 设置元数据信息
public void setMeta(String gid, JSONObject metaInfo) { public void setMeta(String gid, JSONObject metaInfo) {
try { try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "failed to put related gid"); Log.e(TAG, "failed to put related gid");
} }
setNotes(metaInfo.toString());// 将元数据转为JSON字符串并存入笔记 setNotes(metaInfo.toString());
setName(GTaskStringUtils.META_NOTE_NAME);// 设置笔记名称为元数据专用名称 setName(GTaskStringUtils.META_NOTE_NAME);
} }
// 获取关联的Google Tasks ID
public String getRelatedGid() { public String getRelatedGid() {
return mRelatedGid; return mRelatedGid;
} }
// 判断是否值得保存
@Override @Override
public boolean isWorthSaving() { public boolean isWorthSaving() {
return getNotes() != null;// 只要有备注内容就值得保存 return getNotes() != null;
} }
// 从远程JSON设置内容
@Override @Override
public void setContentByRemoteJSON(JSONObject js) { public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);// 调用父类方法设置基本内容 super.setContentByRemoteJSON(js);
if (getNotes() != null) { if (getNotes() != null) {
try { try {
JSONObject metaInfo = new JSONObject(getNotes().trim()); JSONObject metaInfo = new JSONObject(getNotes().trim());
@ -67,18 +63,17 @@ public class MetaData extends Task {
} }
} }
// 从本地JSON设置内容
@Override @Override
public void setContentByLocalJSON(JSONObject js) { public void setContentByLocalJSON(JSONObject js) {
// this function should not be called // this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
} }
// 获取本地JSON内容
@Override @Override
public JSONObject getLocalJSONFromContent() { public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
} }
// 获取同步操作
@Override @Override
public int getSyncAction(Cursor c) { public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called"); throw new IllegalAccessError("MetaData:getSyncAction should not be called");

@ -18,15 +18,15 @@ package net.micode.notes.gtask.exception;
public class ActionFailureException extends RuntimeException { public class ActionFailureException extends RuntimeException {
private static final long serialVersionUID = 4425249765923293627L; private static final long serialVersionUID = 4425249765923293627L;
// 无参构造函数
public ActionFailureException() { public ActionFailureException() {
super(); super();
} }
// 带消息的构造函数
public ActionFailureException(String paramString) { public ActionFailureException(String paramString) {
super(paramString); super(paramString);
} }
// 带消息和原因的构造函数
public ActionFailureException(String paramString, Throwable paramThrowable) { public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable); super(paramString, paramThrowable);
} }

@ -19,15 +19,14 @@ package net.micode.notes.gtask.exception;
public class NetworkFailureException extends Exception { public class NetworkFailureException extends Exception {
private static final long serialVersionUID = 2107610287180234136L; private static final long serialVersionUID = 2107610287180234136L;
// 无参构造函数
public NetworkFailureException() { public NetworkFailureException() {
super(); super();
} }
// 带消息的构造函数
public NetworkFailureException(String paramString) { public NetworkFailureException(String paramString) {
super(paramString); super(paramString);
} }
// 带消息和原因的构造函数
public NetworkFailureException(String paramString, Throwable paramThrowable) { public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable); super(paramString, paramThrowable);
} }

@ -31,22 +31,20 @@ import net.micode.notes.ui.NotesPreferenceActivity;
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> { public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;// 同步通知的唯一ID private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 完成监听器接口
public interface OnCompleteListener { public interface OnCompleteListener {
void onComplete(); void onComplete();
} }
private Context mContext; // 应用上下文 private Context mContext;
private NotificationManager mNotifiManager; // 通知管理器 private NotificationManager mNotifiManager;
private GTaskManager mTaskManager; // Google Tasks管理器 private GTaskManager mTaskManager;
private OnCompleteListener mOnCompleteListener; // 完成监听器 private OnCompleteListener mOnCompleteListener;
// 构造函数
public GTaskASyncTask(Context context, OnCompleteListener listener) { public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context; mContext = context;
mOnCompleteListener = listener; mOnCompleteListener = listener;
@ -55,11 +53,10 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
mTaskManager = GTaskManager.getInstance(); mTaskManager = GTaskManager.getInstance();
} }
// 取消同步操作
public void cancelSync() { public void cancelSync() {
mTaskManager.cancelSync(); mTaskManager.cancelSync();
} }
// 发布进度信息
public void publishProgess(String message) { public void publishProgess(String message) {
publishProgress(new String[] { publishProgress(new String[] {
message message
@ -84,8 +81,6 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// pendingIntent); // pendingIntent);
//mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); //mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
//} //}
// 显示通知
private void showNotification(int tickerId, String content) { private void showNotification(int tickerId, String content) {
PendingIntent pendingIntent; PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) { if (tickerId != R.string.ticker_success) {
@ -105,8 +100,7 @@ private void showNotification(int tickerId, String content) {
Notification notification=builder.getNotification(); Notification notification=builder.getNotification();
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
} }
// 在后台线程执行同步操作
@Override @Override
protected Integer doInBackground(Void... unused) { protected Integer doInBackground(Void... unused) {
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
@ -114,7 +108,6 @@ private void showNotification(int tickerId, String content) {
return mTaskManager.sync(mContext, this); return mTaskManager.sync(mContext, this);
} }
// 更新UI线程中的进度
@Override @Override
protected void onProgressUpdate(String... progress) { protected void onProgressUpdate(String... progress) {
showNotification(R.string.ticker_syncing, progress[0]); showNotification(R.string.ticker_syncing, progress[0]);
@ -122,8 +115,7 @@ private void showNotification(int tickerId, String content) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]); ((GTaskSyncService) mContext).sendBroadcast(progress[0]);
} }
} }
// 同步完成后在UI线程执行
@Override @Override
protected void onPostExecute(Integer result) { protected void onPostExecute(Integer result) {
if (result == GTaskManager.STATE_SUCCESS) { if (result == GTaskManager.STATE_SUCCESS) {
@ -138,7 +130,6 @@ private void showNotification(int tickerId, String content) {
showNotification(R.string.ticker_cancel, mContext showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled)); .getString(R.string.error_sync_cancelled));
} }
// 调用完成监听器
if (mOnCompleteListener != null) { if (mOnCompleteListener != null) {
new Thread(new Runnable() { new Thread(new Runnable() {

@ -65,22 +65,31 @@ public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName(); private static final String TAG = GTaskClient.class.getSimpleName();
private static final String GTASK_URL = "https://mail.google.com/tasks/"; private static final String GTASK_URL = "https://mail.google.com/tasks/";
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
private static GTaskClient mInstance = null; // 单例实例 private static GTaskClient mInstance = null;
private DefaultHttpClient mHttpClient;
private String mGetUrl;
private String mPostUrl;
private long mClientVersion;
private boolean mLoggedin;
private long mLastLoginTime;
private int mActionId;
private Account mAccount;
private DefaultHttpClient mHttpClient; // HTTP客户端 private JSONArray mUpdateArray;
private String mGetUrl; // GET请求URL
private String mPostUrl; // POST请求URL
private long mClientVersion; // 客户端版本号
private boolean mLoggedin; // 登录状态
private long mLastLoginTime; // 最后登录时间
private int mActionId; // 操作ID
private Account mAccount; // 当前账户
private JSONArray mUpdateArray; // 更新操作数组
// 私有构造函数,实现单例模式
private GTaskClient() { private GTaskClient() {
mHttpClient = null; mHttpClient = null;
mGetUrl = GTASK_GET_URL; mGetUrl = GTASK_GET_URL;
@ -93,7 +102,6 @@ public class GTaskClient {
mUpdateArray = null; mUpdateArray = null;
} }
// 获取单例实例
public static synchronized GTaskClient getInstance() { public static synchronized GTaskClient getInstance() {
if (mInstance == null) { if (mInstance == null) {
mInstance = new GTaskClient(); mInstance = new GTaskClient();
@ -101,18 +109,15 @@ public class GTaskClient {
return mInstance; return mInstance;
} }
// 登录Google账户
public boolean login(Activity activity) { public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes // we suppose that the cookie would expire after 5 minutes
// then we need to re-login // then we need to re-login
// 检查是否需要重新登录5分钟过期或账户切换
final long interval = 1000 * 60 * 5; final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) { if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false; mLoggedin = false;
} }
// need to re-login after account switch // need to re-login after account switch
// 账户切换时需要重新登录
if (mLoggedin if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) { .getSyncAccountName(activity))) {
@ -132,7 +137,6 @@ public class GTaskClient {
} }
// login with custom domain if necessary // login with custom domain if necessary
// 处理自定义域名账户
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) { .endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
@ -148,7 +152,6 @@ public class GTaskClient {
} }
// try to login with google official url // try to login with google official url
// 尝试使用Google官方URL登录
if (!mLoggedin) { if (!mLoggedin) {
mGetUrl = GTASK_GET_URL; mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL; mPostUrl = GTASK_POST_URL;
@ -161,7 +164,6 @@ public class GTaskClient {
return true; return true;
} }
// 登录Google账户并获取认证令牌
private String loginGoogleAccount(Activity activity, boolean invalidateToken) { private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken; String authToken;
AccountManager accountManager = AccountManager.get(activity); AccountManager accountManager = AccountManager.get(activity);
@ -172,7 +174,6 @@ public class GTaskClient {
return null; return null;
} }
// 获取同步账户
String accountName = NotesPreferenceActivity.getSyncAccountName(activity); String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null; Account account = null;
for (Account a : accounts) { for (Account a : accounts) {
@ -189,7 +190,6 @@ public class GTaskClient {
} }
// get the token now // get the token now
// 获取认证令牌
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account, AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null); "goanna_mobile", null, activity, null, null);
try { try {
@ -207,12 +207,10 @@ public class GTaskClient {
return authToken; return authToken;
} }
// 尝试登录Google Tasks
private boolean tryToLoginGtask(Activity activity, String authToken) { private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) { if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the // maybe the auth token is out of date, now let's invalidate the
// token and try again // token and try again
// 认证令牌可能过期,使令牌失效并重新尝试
authToken = loginGoogleAccount(activity, true); authToken = loginGoogleAccount(activity, true);
if (authToken == null) { if (authToken == null) {
Log.e(TAG, "login google account failed"); Log.e(TAG, "login google account failed");
@ -227,9 +225,7 @@ public class GTaskClient {
return true; return true;
} }
// 登录Google Tasks服务
private boolean loginGtask(String authToken) { private boolean loginGtask(String authToken) {
// 设置HTTP客户端参数
int timeoutConnection = 10000; int timeoutConnection = 10000;
int timeoutSocket = 15000; int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams(); HttpParams httpParameters = new BasicHttpParams();
@ -241,7 +237,6 @@ public class GTaskClient {
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask // login gtask
// 登录Google Tasks
try { try {
String loginUrl = mGetUrl + "?auth=" + authToken; String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl); HttpGet httpGet = new HttpGet(loginUrl);
@ -249,7 +244,6 @@ public class GTaskClient {
response = mHttpClient.execute(httpGet); response = mHttpClient.execute(httpGet);
// get the cookie now // get the cookie now
// 检查认证Cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies(); List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false; boolean hasAuthCookie = false;
for (Cookie cookie : cookies) { for (Cookie cookie : cookies) {
@ -262,7 +256,6 @@ public class GTaskClient {
} }
// get the client version // get the client version
// 获取客户端版本
String resString = getResponseContent(response.getEntity()); String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup("; String jsBegin = "_setup(";
String jsEnd = ")}</script>"; String jsEnd = ")}</script>";
@ -287,12 +280,10 @@ public class GTaskClient {
return true; return true;
} }
// 获取操作ID
private int getActionId() { private int getActionId() {
return mActionId++; return mActionId++;
} }
// 创建HTTP POST请求
private HttpPost createHttpPost() { private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl); HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
@ -300,7 +291,6 @@ public class GTaskClient {
return httpPost; return httpPost;
} }
// 处理HTTP响应内容
private String getResponseContent(HttpEntity entity) throws IOException { private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null; String contentEncoding = null;
if (entity.getContentEncoding() != null) { if (entity.getContentEncoding() != null) {
@ -308,7 +298,6 @@ public class GTaskClient {
Log.d(TAG, "encoding: " + contentEncoding); Log.d(TAG, "encoding: " + contentEncoding);
} }
// 处理压缩响应
InputStream input = entity.getContent(); InputStream input = entity.getContent();
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent()); input = new GZIPInputStream(entity.getContent());
@ -334,7 +323,6 @@ public class GTaskClient {
} }
} }
// 发送POST请求
private JSONObject postRequest(JSONObject js) throws NetworkFailureException { private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) { if (!mLoggedin) {
Log.e(TAG, "please login first"); Log.e(TAG, "please login first");
@ -349,7 +337,6 @@ public class GTaskClient {
httpPost.setEntity(entity); httpPost.setEntity(entity);
// execute the post // execute the post
// 执行POST请求
HttpResponse response = mHttpClient.execute(httpPost); HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity()); String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString); return new JSONObject(jsString);
@ -373,7 +360,6 @@ public class GTaskClient {
} }
} }
// 创建任务
public void createTask(Task task) throws NetworkFailureException { public void createTask(Task task) throws NetworkFailureException {
commitUpdate(); commitUpdate();
try { try {
@ -381,7 +367,6 @@ public class GTaskClient {
JSONArray actionList = new JSONArray(); JSONArray actionList = new JSONArray();
// action_list // action_list
// 添加创建任务的操作
actionList.put(task.getCreateAction(getActionId())); actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
@ -389,7 +374,6 @@ public class GTaskClient {
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post // post
// 发送请求并获取新任务ID
JSONObject jsResponse = postRequest(jsPost); JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0); GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
@ -402,7 +386,6 @@ public class GTaskClient {
} }
} }
// 创建任务列表
public void createTaskList(TaskList tasklist) throws NetworkFailureException { public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate(); commitUpdate();
try { try {
@ -410,7 +393,6 @@ public class GTaskClient {
JSONArray actionList = new JSONArray(); JSONArray actionList = new JSONArray();
// action_list // action_list
// 添加创建任务列表的操作
actionList.put(tasklist.getCreateAction(getActionId())); actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
@ -418,7 +400,6 @@ public class GTaskClient {
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post // post
// 发送请求并获取新任务列表ID
JSONObject jsResponse = postRequest(jsPost); JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0); GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
@ -431,7 +412,6 @@ public class GTaskClient {
} }
} }
// 提交更新操作
public void commitUpdate() throws NetworkFailureException { public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) { if (mUpdateArray != null) {
try { try {
@ -453,12 +433,10 @@ public class GTaskClient {
} }
} }
// 添加更新节点
public void addUpdateNode(Node node) throws NetworkFailureException { public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) { if (node != null) {
// too many update items may result in an error // too many update items may result in an error
// set max to 10 items // set max to 10 items
// 批量操作限制超过10个项目时提交更新
if (mUpdateArray != null && mUpdateArray.length() > 10) { if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate(); commitUpdate();
} }
@ -469,8 +447,6 @@ public class GTaskClient {
} }
} }
// 移动任务
public void moveTask(Task task, TaskList preParent, TaskList curParent) public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException { throws NetworkFailureException {
commitUpdate(); commitUpdate();
@ -480,12 +456,10 @@ public class GTaskClient {
JSONObject action = new JSONObject(); JSONObject action = new JSONObject();
// action_list // action_list
// 创建移动任务的操作
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
// 设置前置兄弟任务ID如果在同一任务列表中移动且不是第一个任务
if (preParent == curParent && task.getPriorSibling() != null) { if (preParent == curParent && task.getPriorSibling() != null) {
// put prioring_sibing_id only if moving within the tasklist and // put prioring_sibing_id only if moving within the tasklist and
// it is not the first one // it is not the first one
@ -493,8 +467,6 @@ public class GTaskClient {
} }
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
// 设置目标任务列表ID如果在不同任务列表之间移动
if (preParent != curParent) { if (preParent != curParent) {
// put the dest_list only if moving between tasklists // put the dest_list only if moving between tasklists
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
@ -514,7 +486,6 @@ public class GTaskClient {
} }
} }
// 删除节点
public void deleteNode(Node node) throws NetworkFailureException { public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate(); commitUpdate();
try { try {
@ -522,7 +493,6 @@ public class GTaskClient {
JSONArray actionList = new JSONArray(); JSONArray actionList = new JSONArray();
// action_list // action_list
// 设置节点为已删除并添加更新操作
node.setDeleted(true); node.setDeleted(true);
actionList.put(node.getUpdateAction(getActionId())); actionList.put(node.getUpdateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
@ -539,7 +509,6 @@ public class GTaskClient {
} }
} }
// 获取任务列表
public JSONArray getTaskLists() throws NetworkFailureException { public JSONArray getTaskLists() throws NetworkFailureException {
if (!mLoggedin) { if (!mLoggedin) {
Log.e(TAG, "please login first"); Log.e(TAG, "please login first");
@ -552,7 +521,6 @@ public class GTaskClient {
response = mHttpClient.execute(httpGet); response = mHttpClient.execute(httpGet);
// get the task list // get the task list
// 解析响应内容获取任务列表
String resString = getResponseContent(response.getEntity()); String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup("; String jsBegin = "_setup(";
String jsEnd = ")}</script>"; String jsEnd = ")}</script>";
@ -579,7 +547,6 @@ public class GTaskClient {
} }
} }
// 获取特定任务列表中的任务
public JSONArray getTaskList(String listGid) throws NetworkFailureException { public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate(); commitUpdate();
try { try {
@ -588,7 +555,6 @@ public class GTaskClient {
JSONObject action = new JSONObject(); JSONObject action = new JSONObject();
// action_list // action_list
// 创建获取任务列表的操作
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
@ -609,12 +575,10 @@ public class GTaskClient {
} }
} }
// 获取同步账户
public Account getSyncAccount() { public Account getSyncAccount() {
return mAccount; return mAccount;
} }
// 重置更新数组
public void resetUpdateArray() { public void resetUpdateArray() {
mUpdateArray = null; mUpdateArray = null;
} }

@ -35,16 +35,14 @@ import java.util.ArrayList;
public class Note { public class Note {
private ContentValues mNoteDiffValues;//笔记差异值 private ContentValues mNoteDiffValues;
private NoteData mNoteData;//笔记数据 private NoteData mNoteData;
private static final String TAG = "Note"; private static final String TAG = "Note";
/** /**
* Create a new note id for adding a new note to databases * Create a new note id for adding a new note to databases
*/ */
//获取新笔记id
public static synchronized long getNewNoteId(Context context, long folderId) { public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database // Create a new note in the database
//创建新笔记并插入数据库
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
long createdTime = System.currentTimeMillis(); long createdTime = System.currentTimeMillis();
values.put(NoteColumns.CREATED_DATE, createdTime); values.put(NoteColumns.CREATED_DATE, createdTime);
@ -67,50 +65,46 @@ public class Note {
return noteId; return noteId;
} }
// 构造函数
public Note() { public Note() {
mNoteDiffValues = new ContentValues(); mNoteDiffValues = new ContentValues();
mNoteData = new NoteData(); mNoteData = new NoteData();
} }
// 设置笔记值
public void setNoteValue(String key, String value) { public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value); mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
} }
// 设置文本数据
public void setTextData(String key, String value) { public void setTextData(String key, String value) {
mNoteData.setTextData(key, value); mNoteData.setTextData(key, value);
} }
// 设置文本数据ID
public void setTextDataId(long id) { public void setTextDataId(long id) {
mNoteData.setTextDataId(id); mNoteData.setTextDataId(id);
} }
// 获取文本数据ID
public long getTextDataId() { public long getTextDataId() {
return mNoteData.mTextDataId; return mNoteData.mTextDataId;
} }
// 设置通话数据ID
public void setCallDataId(long id) { public void setCallDataId(long id) {
mNoteData.setCallDataId(id); mNoteData.setCallDataId(id);
} }
// 设置通话数据
public void setCallData(String key, String value) { public void setCallData(String key, String value) {
mNoteData.setCallData(key, value); mNoteData.setCallData(key, value);
} }
// 检查是否有本地修改
public boolean isLocalModified() { public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
} }
// 同步笔记数据到ContentProvider
public boolean syncNote(Context context, long noteId) { public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) { if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId); throw new IllegalArgumentException("Wrong note id:" + noteId);
} }
// 更新笔记基本信息
if (!isLocalModified()) { if (!isLocalModified()) {
return true; return true;
} }
@ -124,12 +118,10 @@ public class Note {
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) { null) == 0) {
Log.e(TAG, "Update note error, should not happen"); Log.e(TAG, "Update note error, should not happen");
// 即使更新失败也继续处理数据
// Do not return, fall through // Do not return, fall through
} }
mNoteDiffValues.clear(); mNoteDiffValues.clear();
// 更新笔记关联数据
if (mNoteData.isLocalModified() if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) { && (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false; return false;
@ -137,54 +129,55 @@ public class Note {
return true; return true;
} }
// 内部类:管理笔记关联数据
private class NoteData { private class NoteData {
private long mTextDataId; // 文本数据ID private long mTextDataId;
private ContentValues mTextDataValues; // 文本数据值
private long mCallDataId; // 通话数据ID private ContentValues mTextDataValues;
private ContentValues mCallDataValues; // 通话数据值
private long mCallDataId;
private ContentValues mCallDataValues;
private static final String TAG = "NoteData"; private static final String TAG = "NoteData";
// 构造函数
public NoteData() { public NoteData() {
mTextDataValues = new ContentValues(); mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues(); mCallDataValues = new ContentValues();
mTextDataId = 0; mTextDataId = 0;
mCallDataId = 0; mCallDataId = 0;
} }
// 检查是否有本地修改
boolean isLocalModified() { boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
} }
// 设置文本数据ID
void setTextDataId(long id) { void setTextDataId(long id) {
if(id <= 0) { if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0"); throw new IllegalArgumentException("Text data id should larger than 0");
} }
mTextDataId = id; mTextDataId = id;
} }
// 设置通话数据ID
void setCallDataId(long id) { void setCallDataId(long id) {
if (id <= 0) { if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0"); throw new IllegalArgumentException("Call data id should larger than 0");
} }
mCallDataId = id; mCallDataId = id;
} }
// 设置通话数据
void setCallData(String key, String value) { void setCallData(String key, String value) {
mCallDataValues.put(key, value); mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
} }
// 设置文本数据
void setTextData(String key, String value) { void setTextData(String key, String value) {
mTextDataValues.put(key, value); mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
} }
// 将数据推送到ContentProvider
Uri pushIntoContentResolver(Context context, long noteId) { Uri pushIntoContentResolver(Context context, long noteId) {
/** /**
* Check for safety * Check for safety
@ -196,11 +189,9 @@ public class Note {
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null; ContentProviderOperation.Builder builder = null;
// 处理文本数据
if(mTextDataValues.size() > 0) { if(mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId); mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) { if (mTextDataId == 0) {
// 插入新文本数据
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues); mTextDataValues);
@ -212,7 +203,6 @@ public class Note {
return null; return null;
} }
} else { } else {
// 更新现有文本数据
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId)); Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues); builder.withValues(mTextDataValues);
@ -221,11 +211,9 @@ public class Note {
mTextDataValues.clear(); mTextDataValues.clear();
} }
// 处理通话数据
if(mCallDataValues.size() > 0) { if(mCallDataValues.size() > 0) {
mCallDataValues.put(DataColumns.NOTE_ID, noteId); mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) { if (mCallDataId == 0) {
// 插入新通话数据
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues); mCallDataValues);
@ -237,7 +225,6 @@ public class Note {
return null; return null;
} }
} else { } else {
// 更新现有通话数据
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId)); Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues); builder.withValues(mCallDataValues);
@ -246,7 +233,6 @@ public class Note {
mCallDataValues.clear(); mCallDataValues.clear();
} }
// 批量执行操作
if (operationList.size() > 0) { if (operationList.size() > 0) {
try { try {
ContentProviderResult[] results = context.getContentResolver().applyBatch( ContentProviderResult[] results = context.getContentResolver().applyBatch(

@ -59,9 +59,9 @@ public class WorkingNote {
private static final String TAG = "WorkingNote"; private static final String TAG = "WorkingNote";
private boolean mIsDeleted; private boolean mIsDeleted;
// 笔记设置变化监听器
private NoteSettingChangedListener mNoteSettingStatusListener; private NoteSettingChangedListener mNoteSettingStatusListener;
// 查询数据的投影
public static final String[] DATA_PROJECTION = new String[] { public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID, DataColumns.ID,
DataColumns.CONTENT, DataColumns.CONTENT,
@ -72,7 +72,6 @@ public class WorkingNote {
DataColumns.DATA4, DataColumns.DATA4,
}; };
//查询笔记的投影
public static final String[] NOTE_PROJECTION = new String[] { public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID, NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE, NoteColumns.ALERTED_DATE,
@ -82,21 +81,27 @@ public class WorkingNote {
NoteColumns.MODIFIED_DATE NoteColumns.MODIFIED_DATE
}; };
// 数据列索引常量
private static final int DATA_ID_COLUMN = 0; private static final int DATA_ID_COLUMN = 0;
private static final int DATA_CONTENT_COLUMN = 1; private static final int DATA_CONTENT_COLUMN = 1;
private static final int DATA_MIME_TYPE_COLUMN = 2; private static final int DATA_MIME_TYPE_COLUMN = 2;
private static final int DATA_MODE_COLUMN = 3; private static final int DATA_MODE_COLUMN = 3;
// 笔记列索引常量
private static final int NOTE_PARENT_ID_COLUMN = 0; private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1; private static final int NOTE_ALERTED_DATE_COLUMN = 1;
private static final int NOTE_BG_COLOR_ID_COLUMN = 2; private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
private static final int NOTE_WIDGET_ID_COLUMN = 3; private static final int NOTE_WIDGET_ID_COLUMN = 3;
private static final int NOTE_WIDGET_TYPE_COLUMN = 4; private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5; private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct // New note construct
// 新建笔记的构造函数
private WorkingNote(Context context, long folderId) { private WorkingNote(Context context, long folderId) {
mContext = context; mContext = context;
mAlertDate = 0; mAlertDate = 0;
@ -110,7 +115,6 @@ public class WorkingNote {
} }
// Existing note construct // Existing note construct
// 加载现有笔记的构造函数
private WorkingNote(Context context, long noteId, long folderId) { private WorkingNote(Context context, long noteId, long folderId) {
mContext = context; mContext = context;
mNoteId = noteId; mNoteId = noteId;
@ -120,7 +124,6 @@ public class WorkingNote {
loadNote(); loadNote();
} }
// 加载笔记基本信息
private void loadNote() { private void loadNote() {
Cursor cursor = mContext.getContentResolver().query( Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
@ -142,7 +145,7 @@ public class WorkingNote {
} }
loadNoteData(); loadNoteData();
} }
// 加载笔记数据
private void loadNoteData() { private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] { DataColumns.NOTE_ID + "=?", new String[] {
@ -171,7 +174,6 @@ public class WorkingNote {
} }
} }
// 创建空笔记的静态方法
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) { int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId); WorkingNote note = new WorkingNote(context, folderId);
@ -181,12 +183,10 @@ public class WorkingNote {
return note; return note;
} }
// 加载现有笔记的静态方法
public static WorkingNote load(Context context, long id) { public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0); return new WorkingNote(context, id, 0);
} }
// 保存笔记
public synchronized boolean saveNote() { public synchronized boolean saveNote() {
if (isWorthSaving()) { if (isWorthSaving()) {
if (!existInDatabase()) { if (!existInDatabase()) {
@ -201,7 +201,6 @@ public class WorkingNote {
/** /**
* Update widget content if there exist any widget of this note * Update widget content if there exist any widget of this note
*/ */
// 如果笔记关联了小部件,更新小部件内容
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) { && mNoteSettingStatusListener != null) {
@ -212,13 +211,11 @@ public class WorkingNote {
return false; return false;
} }
} }
// 检查笔记是否存在于数据库中
public boolean existInDatabase() { public boolean existInDatabase() {
return mNoteId > 0; return mNoteId > 0;
} }
// 判断笔记是否值得保存
private boolean isWorthSaving() { private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) { || (existInDatabase() && !mNote.isLocalModified())) {
@ -228,11 +225,10 @@ public class WorkingNote {
} }
} }
// 设置笔记设置变化监听器
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l; mNoteSettingStatusListener = l;
} }
// 设置提醒日期
public void setAlertDate(long date, boolean set) { public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) { if (date != mAlertDate) {
mAlertDate = date; mAlertDate = date;
@ -243,7 +239,6 @@ public class WorkingNote {
} }
} }
// 标记笔记为已删除
public void markDeleted(boolean mark) { public void markDeleted(boolean mark) {
mIsDeleted = mark; mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
@ -252,7 +247,6 @@ public class WorkingNote {
} }
} }
// 设置背景颜色
public void setBgColorId(int id) { public void setBgColorId(int id) {
if (id != mBgColorId) { if (id != mBgColorId) {
mBgColorId = id; mBgColorId = id;
@ -263,7 +257,6 @@ public class WorkingNote {
} }
} }
// 设置清单模式
public void setCheckListMode(int mode) { public void setCheckListMode(int mode) {
if (mMode != mode) { if (mMode != mode) {
if (mNoteSettingStatusListener != null) { if (mNoteSettingStatusListener != null) {
@ -274,7 +267,6 @@ public class WorkingNote {
} }
} }
// 设置小部件类型
public void setWidgetType(int type) { public void setWidgetType(int type) {
if (type != mWidgetType) { if (type != mWidgetType) {
mWidgetType = type; mWidgetType = type;
@ -282,7 +274,6 @@ public class WorkingNote {
} }
} }
// 设置小部件ID
public void setWidgetId(int id) { public void setWidgetId(int id) {
if (id != mWidgetId) { if (id != mWidgetId) {
mWidgetId = id; mWidgetId = id;
@ -290,101 +281,87 @@ public class WorkingNote {
} }
} }
// 设置笔记文本内容
public void setWorkingText(String text) { public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) { if (!TextUtils.equals(mContent, text)) {
mContent = text; mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent); mNote.setTextData(DataColumns.CONTENT, mContent);
} }
} }
// 转换为通话记录笔记
public void convertToCallNote(String phoneNumber, long callDate) { public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
} }
// 检查是否有提醒
public boolean hasClockAlert() { public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false); return (mAlertDate > 0 ? true : false);
} }
// 获取内容
public String getContent() { public String getContent() {
return mContent; return mContent;
} }
// 获取提醒日期
public long getAlertDate() { public long getAlertDate() {
return mAlertDate; return mAlertDate;
} }
// 获取修改日期
public long getModifiedDate() { public long getModifiedDate() {
return mModifiedDate; return mModifiedDate;
} }
// 获取背景颜色资源ID
public int getBgColorResId() { public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId); return NoteBgResources.getNoteBgResource(mBgColorId);
} }
// 获取背景颜色ID
public int getBgColorId() { public int getBgColorId() {
return mBgColorId; return mBgColorId;
} }
// 获取标题背景资源ID
public int getTitleBgResId() { public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId); return NoteBgResources.getNoteTitleBgResource(mBgColorId);
} }
// 获取清单模式
public int getCheckListMode() { public int getCheckListMode() {
return mMode; return mMode;
} }
// 获取笔记ID
public long getNoteId() { public long getNoteId() {
return mNoteId; return mNoteId;
} }
// 获取文件夹ID
public long getFolderId() { public long getFolderId() {
return mFolderId; return mFolderId;
} }
// 获取小部件ID
public int getWidgetId() { public int getWidgetId() {
return mWidgetId; return mWidgetId;
} }
// 获取小部件类型
public int getWidgetType() { public int getWidgetType() {
return mWidgetType; return mWidgetType;
} }
// 笔记设置变化监听器接口
public interface NoteSettingChangedListener { public interface NoteSettingChangedListener {
/** /**
* * Called when the background color of current note has just changed
*/ */
void onBackgroundColorChanged(); void onBackgroundColorChanged();
/** /**
* * Called when user set clock
*/ */
void onClockAlertChanged(long date, boolean set); void onClockAlertChanged(long date, boolean set);
/** /**
* * Call when user create note from widget
*/ */
void onWidgetChanged(); void onWidgetChanged();
/** /**
* * Call when switch between check list mode and normal mode
* @param oldMode * @param oldMode is previous mode before change
* @param newMode * @param newMode is new mode
*/ */
void onCheckListModeChanged(int oldMode, int newMode); void onCheckListModeChanged(int oldMode, int newMode);
} }

@ -18,7 +18,6 @@ package net.micode.notes.tool;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateFormat; import android.text.format.DateFormat;
@ -30,14 +29,12 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class BackupUtils { public class BackupUtils {
private static final String TAG = "BackupUtils"; private static final String TAG = "BackupUtils";
@ -65,8 +62,6 @@ public class BackupUtils {
public static final int STATE_SYSTEM_ERROR = 3; public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success // Backup or restore success
public static final int STATE_SUCCESS = 4; public static final int STATE_SUCCESS = 4;
// Permission denied
public static final int STATE_PERMISSION_DENIED = 5;
private TextExport mTextExport; private TextExport mTextExport;
@ -95,15 +90,14 @@ public class BackupUtils {
NoteColumns.ID, NoteColumns.ID,
NoteColumns.MODIFIED_DATE, NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET, NoteColumns.SNIPPET,
NoteColumns.TYPE, NoteColumns.TYPE
NoteColumns.PARENT_ID
}; };
private static final int NOTE_COLUMN_ID = 0; private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1; private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2; private static final int NOTE_COLUMN_SNIPPET = 2;
private static final int NOTE_COLUMN_TYPE = 3;
private static final int NOTE_COLUMN_PARENT_ID = 4;
private static final String[] DATA_PROJECTION = { private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT, DataColumns.CONTENT,
@ -115,8 +109,11 @@ public class BackupUtils {
}; };
private static final int DATA_COLUMN_CONTENT = 0; private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1; private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2; private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4; private static final int DATA_COLUMN_PHONE_NUMBER = 4;
private final String [] TEXT_FORMAT; private final String [] TEXT_FORMAT;
@ -127,17 +124,12 @@ public class BackupUtils {
private Context mContext; private Context mContext;
private String mFileName; private String mFileName;
private String mFileDirectory; private String mFileDirectory;
private BufferedWriter mWriter;
private int mNotesCount;
private int mFoldersCount;
public TextExport(Context context) { public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context; mContext = context;
mFileName = ""; mFileName = "";
mFileDirectory = ""; mFileDirectory = "";
mNotesCount = 0;
mFoldersCount = 0;
} }
private String getFormat(int id) { private String getFormat(int id) {
@ -145,268 +137,208 @@ public class BackupUtils {
} }
/** /**
* Export notes to text file * Export the folder identified by folder id to text
*/ */
public int exportToText() { private void exportFolderToText(String folderId, PrintStream ps) {
if (!externalStorageAvailable()) { // Query notes belong to this folder
Log.d(TAG, "Media was not mounted"); Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
return STATE_SD_CARD_UNMOUONTED; NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
} folderId
}, null);
File file = generateFileMountedOnSDcard(mContext, R.string.file_path, if (notesCursor != null) {
R.string.file_name_txt_format); if (notesCursor.moveToFirst()) {
if (file == null) {
Log.e(TAG, "create file to exported failed");
return STATE_SYSTEM_ERROR;
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
// 使用try-with-resources确保资源释放
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
mWriter = writer;
// 导出文件夹和笔记
exportFoldersAndNotes();
// 写入导出统计信息
writeExportSummary();
Log.d(TAG, "Export successful. Exported " + mFoldersCount + " folders and " + mNotesCount + " notes.");
return STATE_SUCCESS;
} catch (IOException e) {
Log.e(TAG, "Error during export: " + e.getMessage(), e);
return STATE_SYSTEM_ERROR;
}
}
/**
*
*/
private void writeExportSummary() throws IOException {
mWriter.write("\n");
mWriter.write("==============================\n");
mWriter.write(mContext.getString(R.string.export_summary) + "\n");
mWriter.write(mContext.getString(R.string.export_date) + ": "
+ DateFormat.format(mContext.getString(R.string.format_datetime_ymdhm), System.currentTimeMillis()) + "\n");
mWriter.write(mContext.getString(R.string.folder_count) + ": " + mFoldersCount + "\n");
mWriter.write(mContext.getString(R.string.note_count) + ": " + mNotesCount + "\n");
mWriter.write("==============================\n");
}
/**
*
*/
private void exportFoldersAndNotes() throws IOException {
// 首先导出文件夹和其中的笔记
exportFolders();
// 导出根文件夹中的笔记
exportRootNotes();
}
/**
*
*/
private void exportFolders() throws IOException {
// 查询所有文件夹
String selection = "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER;
try (Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
selection, null, null)) {
if (folderCursor != null && folderCursor.moveToFirst()) {
do { do {
mFoldersCount++; // Print note's last modified date
exportFolder(folderCursor); ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
} while (folderCursor.moveToNext()); mContext.getString(R.string.format_datetime_mdhm),
} notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
} // Query data belong to this note
} String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
/**
*
*/
private void exportFolder(Cursor folderCursor) throws IOException {
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
String folderName;
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
mWriter.write(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
mWriter.newLine();
}
// 导出文件夹中的笔记
exportNotesInFolder(folderId);
// 添加文件夹分隔线
mWriter.write("\n");
}
/**
*
*/
private void exportNotesInFolder(String folderId) throws IOException {
try (Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { folderId }, null)) {
if (notesCursor != null && notesCursor.moveToFirst()) {
do {
mNotesCount++;
exportNote(notesCursor);
} while (notesCursor.moveToNext()); } while (notesCursor.moveToNext());
} }
notesCursor.close();
} }
} }
/**
*
*/
private void exportRootNotes() throws IOException {
try (Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=0",
null, null)) {
if (noteCursor != null && noteCursor.moveToFirst()) {
do {
mNotesCount++;
exportNote(noteCursor);
} while (noteCursor.moveToNext());
}
}
}
/**
*
*/
private void exportNote(Cursor noteCursor) throws IOException {
// 打印笔记的最后修改日期
mWriter.write(String.format(getFormat(FORMAT_NOTE_DATE),
DateFormat.format(mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
mWriter.newLine();
// 查询并导出笔记的数据
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteData(noteId);
// 添加笔记分隔线
mWriter.write("\n");
}
/** /**
* * Export note identified by id to a print stream
*/ */
private void exportNoteData(String noteId) throws IOException { private void exportNoteToText(String noteId, PrintStream ps) {
try (Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { noteId }, null)) { DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
if (dataCursor != null && dataCursor.moveToFirst()) { }, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do { do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) { if (DataConstants.CALL_NOTE.equals(mimeType)) {
exportCallNote(dataCursor); // Print phone number
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) { } else if (DataConstants.NOTE.equals(mimeType)) {
exportTextNote(dataCursor); String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
} }
} while (dataCursor.moveToNext()); } while (dataCursor.moveToNext());
} }
dataCursor.close();
}
// print a line separator between note
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
} }
} }
/** /**
* * Note will be exported as text which is user readable
*/ */
private void exportCallNote(Cursor dataCursor) throws IOException { public int exportToText() {
// 打印电话号码 if (!externalStorageAvailable()) {
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); Log.d(TAG, "Media was not mounted");
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); return STATE_SD_CARD_UNMOUONTED;
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
mWriter.write(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber));
mWriter.newLine();
} }
// 打印通话日期 PrintStream ps = getExportToTextPrintStream();
mWriter.write(String.format(getFormat(FORMAT_NOTE_CONTENT), if (ps == null) {
DateFormat.format(mContext.getString(R.string.format_datetime_mdhm), callDate))); Log.e(TAG, "get print stream error");
mWriter.newLine(); return STATE_SYSTEM_ERROR;
// 打印通话附件位置
if (!TextUtils.isEmpty(location)) {
mWriter.write(String.format(getFormat(FORMAT_NOTE_CONTENT), location));
mWriter.newLine();
} }
} // First export folder and its notes
Cursor folderCursor = mContext.getContentResolver().query(
/** Notes.CONTENT_NOTE_URI,
* NOTE_PROJECTION,
*/ "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
private void exportTextNote(Cursor dataCursor) throws IOException { + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
String content = dataCursor.getString(DATA_COLUMN_CONTENT); + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (!TextUtils.isEmpty(content)) {
mWriter.write(String.format(getFormat(FORMAT_NOTE_CONTENT), content)); if (folderCursor != null) {
mWriter.newLine(); if (folderCursor.moveToFirst()) {
do {
// Print folder's name
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
} }
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
} }
/** /**
* Generate the text file to store imported data * Get a print stream pointed to the file {@generateExportedTextFile}
*/ */
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { private PrintStream getExportToTextPrintStream() {
StringBuilder sb = new StringBuilder(); File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
// 适配Android 10+的存储访问 if (file == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Log.e(TAG, "create file to exported failed");
sb.append(context.getExternalFilesDir(null));
} else {
sb.append(Environment.getExternalStorageDirectory());
}
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
// 确保目录存在
if (!filedir.exists() && !filedir.mkdirs()) {
Log.e(TAG, "Failed to create directory: " + filedir.getAbsolutePath());
return null; return null;
} }
mFileName = file.getName();
// 构建文件名 mFileDirectory = mContext.getString(R.string.file_path);
sb.append(context.getString( PrintStream ps = null;
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
try { try {
if (!file.exists() && !file.createNewFile()) { FileOutputStream fos = new FileOutputStream(file);
Log.e(TAG, "Failed to create file: " + file.getAbsolutePath()); ps = new PrintStream(fos);
return null; } catch (FileNotFoundException e) {
} e.printStackTrace();
return file; return null;
} catch (SecurityException | IOException e) { } catch (NullPointerException e) {
Log.e(TAG, "Error creating file: " + e.getMessage(), e); e.printStackTrace();
return null; return null;
} }
return ps;
} }
} }
}
/**
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
try {
if (!filedir.exists()) {
filedir.mkdir();
}
if (!file.exists()) {
file.createNewFile();
}
return file;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

@ -34,12 +34,9 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
public class DataUtils { public class DataUtils {
public static final String TAG = "DataUtils"; public static final String TAG = "DataUtils";
/**
*
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) { public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) { if (ids == null) {
Log.d(TAG, "the ids is null"); Log.d(TAG, "the ids is null");
@ -75,9 +72,6 @@ public class DataUtils {
return false; return false;
} }
/**
*
*/
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId); values.put(NoteColumns.PARENT_ID, desFolderId);
@ -86,9 +80,6 @@ public class DataUtils {
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
} }
/**
*
*/
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids, public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) { long folderId) {
if (ids == null) { if (ids == null) {
@ -121,10 +112,10 @@ public class DataUtils {
} }
/** /**
* * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/ */
public static int getUserFolderCount(ContentResolver resolver) { public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" }, new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
@ -145,9 +136,6 @@ public class DataUtils {
return count; return count;
} }
/**
*
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null,
@ -165,9 +153,6 @@ public class DataUtils {
return exist; return exist;
} }
/**
*
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null); null, null, null, null);
@ -182,9 +167,6 @@ public class DataUtils {
return exist; return exist;
} }
/**
*
*/
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null); null, null, null, null);
@ -199,9 +181,6 @@ public class DataUtils {
return exist; return exist;
} }
/**
*
*/
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
@ -218,9 +197,6 @@ public class DataUtils {
return exist; return exist;
} }
/**
*
*/
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) { public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
@ -248,9 +224,6 @@ public class DataUtils {
return set; return set;
} }
/**
* ID
*/
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER }, new String [] { CallNote.PHONE_NUMBER },
@ -270,9 +243,6 @@ public class DataUtils {
return ""; return "";
} }
/**
* ID
*/
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID }, new String [] { CallNote.NOTE_ID },
@ -294,9 +264,6 @@ public class DataUtils {
return 0; return 0;
} }
/**
* ID
*/
public static String getSnippetById(ContentResolver resolver, long noteId) { public static String getSnippetById(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET }, new String [] { NoteColumns.SNIPPET },
@ -315,9 +282,6 @@ public class DataUtils {
throw new IllegalArgumentException("Note is not found with id: " + noteId); throw new IllegalArgumentException("Note is not found with id: " + noteId);
} }
/**
*
*/
public static String getFormattedSnippet(String snippet) { public static String getFormattedSnippet(String snippet) {
if (snippet != null) { if (snippet != null) {
snippet = snippet.trim(); snippet = snippet.trim();
@ -329,4 +293,3 @@ public class DataUtils {
return snippet; return snippet;
} }
} }

@ -16,268 +16,98 @@
package net.micode.notes.tool; package net.micode.notes.tool;
/** public class GTaskStringUtils {
* Google Tasks API - Google Tasks API
*
* 1. JSON
* 2. API
* 3.
* 4.
*
*
* -
* -
* - 使
*/
public final class GTaskStringUtils {
// 防止实例化
private GTaskStringUtils() {
throw new AssertionError("This class cannot be instantiated");
}
/* ================== JSON 键名常量 ================== */
/** JSON字段操作ID */
public static final String GTASK_JSON_ACTION_ID = "action_id";
/** JSON字段操作列表 */
public static final String GTASK_JSON_ACTION_LIST = "action_list";
/** JSON字段操作类型 */
public static final String GTASK_JSON_ACTION_TYPE = "action_type";
/** JSON字段创建者ID */
public static final String GTASK_JSON_CREATOR_ID = "creator_id";
/** JSON字段子实体 */
public static final String GTASK_JSON_CHILD_ENTITY = "child_entity";
/** JSON字段客户端版本 */
public static final String GTASK_JSON_CLIENT_VERSION = "client_version";
/** JSON字段完成状态 */
public static final String GTASK_JSON_COMPLETED = "completed";
/** JSON字段当前列表ID */
public static final String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
/** JSON字段默认列表ID */ public final static String GTASK_JSON_ACTION_ID = "action_id";
public static final String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
/** JSON字段删除标记 */ public final static String GTASK_JSON_ACTION_LIST = "action_list";
public static final String GTASK_JSON_DELETED = "deleted";
/** JSON字段目标列表 */ public final static String GTASK_JSON_ACTION_TYPE = "action_type";
public static final String GTASK_JSON_DEST_LIST = "dest_list";
/** JSON字段目标父级 */ public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
public static final String GTASK_JSON_DEST_PARENT = "dest_parent";
/** JSON字段目标父级类型 */ public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
public static final String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
/** JSON字段实体增量 */ public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
public static final String GTASK_JSON_ENTITY_DELTA = "entity_delta";
/** JSON字段实体类型 */ public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
public static final String GTASK_JSON_ENTITY_TYPE = "entity_type";
/** JSON字段获取删除项 */ public final static String GTASK_JSON_CREATOR_ID = "creator_id";
public static final String GTASK_JSON_GET_DELETED = "get_deleted";
/** JSON字段唯一标识 */ public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
public static final String GTASK_JSON_ID = "id";
/** JSON字段索引位置 */ public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
public static final String GTASK_JSON_INDEX = "index";
/** JSON字段最后修改时间 */ public final static String GTASK_JSON_COMPLETED = "completed";
public static final String GTASK_JSON_LAST_MODIFIED = "last_modified";
/** JSON字段最新同步点 */ public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
public static final String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
/** JSON字段列表ID */ public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
public static final String GTASK_JSON_LIST_ID = "list_id";
/** JSON字段列表集合 */ public final static String GTASK_JSON_DELETED = "deleted";
public static final String GTASK_JSON_LISTS = "lists";
/** JSON字段名称 */ public final static String GTASK_JSON_DEST_LIST = "dest_list";
public static final String GTASK_JSON_NAME = "name";
/** JSON字段新ID */ public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
public static final String GTASK_JSON_NEW_ID = "new_id";
/** JSON字段笔记集合 */ public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
public static final String GTASK_JSON_NOTES = "notes";
/** JSON字段父级ID */ public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
public static final String GTASK_JSON_PARENT_ID = "parent_id";
/** JSON字段前置兄弟ID */ public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
public static final String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
/** JSON字段结果集 */ public final static String GTASK_JSON_GET_DELETED = "get_deleted";
public static final String GTASK_JSON_RESULTS = "results";
/** JSON字段源列表 */ public final static String GTASK_JSON_ID = "id";
public static final String GTASK_JSON_SOURCE_LIST = "source_list";
/** JSON字段任务集合 */ public final static String GTASK_JSON_INDEX = "index";
public static final String GTASK_JSON_TASKS = "tasks";
/** JSON字段类型标识 */ public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
public static final String GTASK_JSON_TYPE = "type";
/* ================== 操作类型常量 ================== */ public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
/** 操作类型:创建 */ public final static String GTASK_JSON_LIST_ID = "list_id";
public static final String GTASK_JSON_ACTION_TYPE_CREATE = "create";
/** 操作类型:获取所有 */ public final static String GTASK_JSON_LISTS = "lists";
public static final String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
/** 操作类型:移动 */ public final static String GTASK_JSON_NAME = "name";
public static final String GTASK_JSON_ACTION_TYPE_MOVE = "move";
/** 操作类型:更新 */ public final static String GTASK_JSON_NEW_ID = "new_id";
public static final String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
/* ================== 实体类型常量 ================== */ public final static String GTASK_JSON_NOTES = "notes";
/** 实体类型:分组 */ public final static String GTASK_JSON_PARENT_ID = "parent_id";
public static final String GTASK_JSON_TYPE_GROUP = "GROUP";
/** 实体类型:任务 */ public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
public static final String GTASK_JSON_TYPE_TASK = "TASK";
/** JSON字段用户 */ public final static String GTASK_JSON_RESULTS = "results";
public static final String GTASK_JSON_USER = "user";
/* ================== 文件夹常量 ================== */ public final static String GTASK_JSON_SOURCE_LIST = "source_list";
/** MIUI笔记文件夹前缀 */ public final static String GTASK_JSON_TASKS = "tasks";
public static final String MIUI_FOLDER_PREFIX = "[MIUI_Notes]"; // 修正PREFFIX → PREFIX
/** 默认文件夹名称 */ public final static String GTASK_JSON_TYPE = "type";
public static final String FOLDER_DEFAULT = "Default";
/** 通话记录文件夹名称 */ public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
public static final String FOLDER_CALL_NOTE = "Call_Note";
/** 元数据文件夹名称 */ public final static String GTASK_JSON_TYPE_TASK = "TASK";
public static final String FOLDER_META = "METADATA";
/* ================== 元数据常量 ================== */ public final static String GTASK_JSON_USER = "user";
/** 元数据头Google Tasks ID */ public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
public static final String META_HEAD_GTASK_ID = "meta_gid";
/** 元数据头:笔记信息 */ public final static String FOLDER_DEFAULT = "Default";
public static final String META_HEAD_NOTE = "meta_note";
/** 元数据头:数据信息 */ public final static String FOLDER_CALL_NOTE = "Call_Note";
public static final String META_HEAD_DATA = "meta_data";
/** 元数据笔记名称(警告用户不要修改) */ public final static String FOLDER_META = "METADATA";
public static final String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
/* ================== 增强功能:操作类型枚举 ================== */ public final static String META_HEAD_GTASK_ID = "meta_gid";
/** public final static String META_HEAD_NOTE = "meta_note";
* Google Tasks
*/
public enum GTaskActionType {
CREATE(GTASK_JSON_ACTION_TYPE_CREATE),
GET_ALL(GTASK_JSON_ACTION_TYPE_GETALL),
MOVE(GTASK_JSON_ACTION_TYPE_MOVE),
UPDATE(GTASK_JSON_ACTION_TYPE_UPDATE);
private final String actionValue; public final static String META_HEAD_DATA = "meta_data";
GTaskActionType(String value) { public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
this.actionValue = value;
}
public String getValue() { }
return actionValue;
}
/**
*
* @param value
* @return null
*/
public static GTaskActionType fromValue(String value) {
for (GTaskActionType type : values()) {
if (type.actionValue.equals(value)) {
return type;
}
}
return null;
}
}
/* ================== 增强功能:实体类型枚举 ================== */
/**
* Google Tasks
*/
public enum GTaskEntityType {
GROUP(GTASK_JSON_TYPE_GROUP),
TASK(GTASK_JSON_TYPE_TASK);
private final String typeValue;
GTaskEntityType(String value) {
this.typeValue = value;
}
public String getValue() {
return typeValue;
}
public static GTaskEntityType fromValue(String value) {
for (GTaskEntityType type : values()) {
if (type.typeValue.equals(value)) {
return type;
}
}
return null;
}
}
/* ================== 新功能:文件夹工具方法 ================== */
/**
* MIUI
* @param folderName
* @return
*/
public static String generateSyncFolderName(String folderName) {
return MIUI_FOLDER_PREFIX + folderName;
}
/**
* MIUI
* @param folderName
* @return MIUItrue
*/
public static boolean isMiuiSyncFolder(String folderName) {
return folderName != null && folderName.startsWith(MIUI_FOLDER_PREFIX);
}
/**
* MIUI
* @param miuiFolderName MIUI
* @return MIUInull
*/
public static String extractOriginalFolderName(String miuiFolderName) {
if (isMiuiSyncFolder(miuiFolderName)) {
return miuiFolderName.substring(MIUI_FOLDER_PREFIX.length());
}
return null;
}
}

@ -17,469 +17,165 @@
package net.micode.notes.tool; package net.micode.notes.tool;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.SparseIntArray;
import net.micode.notes.R; import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity; import net.micode.notes.ui.NotesPreferenceActivity;
/**
* - UI
*
* 1.
* 2.
* 3.
* 4.
*
*
* - ID
* -
* -
*/
public class ResourceParser { public class ResourceParser {
// 防止实例化 public static final int YELLOW = 0;
private ResourceParser() { public static final int BLUE = 1;
throw new AssertionError("This class cannot be instantiated"); public static final int WHITE = 2;
} public static final int GREEN = 3;
public static final int RED = 4;
/* ================== 颜色常量枚举 ================== */
/**
*
*/
public enum NoteColor {
YELLOW(0),
BLUE(1),
WHITE(2),
GREEN(3),
RED(4),
// 扩展更多颜色选项
PURPLE(5),
ORANGE(6);
public final int id; public static final int BG_DEFAULT_COLOR = YELLOW;
NoteColor(int id) { public static final int TEXT_SMALL = 0;
this.id = id; public static final int TEXT_MEDIUM = 1;
} public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
private static final int DEFAULT_ID = YELLOW.id;
/**
* ID
*/
public static int getDefault() {
return DEFAULT_ID;
}
/** public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
*
*/
public static NoteColor fromId(int id) {
for (NoteColor color : values()) {
if (color.id == id) {
return color;
}
}
return YELLOW;
}
}
/* ================== 字体大小常量枚举 ================== */
/**
*
*/
public enum FontSize {
SMALL(0),
MEDIUM(1),
LARGE(2),
SUPER(3);
public final int id;
FontSize(int id) {
this.id = id;
}
private static final int DEFAULT_ID = MEDIUM.id;
/**
* ID
*/
public static int getDefault() {
return DEFAULT_ID;
}
}
/* ================== 主题模式常量 ================== */
/**
*
*/
public enum ThemeMode {
LIGHT(0),
DARK(1),
AUTO(2);
public final int id;
ThemeMode(int id) {
this.id = id;
}
public static ThemeMode fromId(int id) {
switch (id) {
case 1: return DARK;
case 2: return AUTO;
default: return LIGHT;
}
}
}
/* ================== 笔记背景资源管理 ================== */
/**
*
*/
public static class NoteBgResources { public static class NoteBgResources {
// 编辑界面背景资源 private final static int [] BG_EDIT_RESOURCES = new int [] {
private static final SparseIntArray BG_EDIT_RESOURCES = new SparseIntArray(); R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
};
// 编辑界面标题背景资源 private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
private static final SparseIntArray BG_EDIT_TITLE_RESOURCES = new SparseIntArray(); R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
// 静态初始化资源映射 public static int getNoteBgResource(int id) {
static { return BG_EDIT_RESOURCES[id];
// 浅色主题资源
BG_EDIT_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.edit_yellow);
BG_EDIT_RESOURCES.put(NoteColor.BLUE.id, R.drawable.edit_blue);
BG_EDIT_RESOURCES.put(NoteColor.WHITE.id, R.drawable.edit_white);
BG_EDIT_RESOURCES.put(NoteColor.GREEN.id, R.drawable.edit_green);
BG_EDIT_RESOURCES.put(NoteColor.RED.id, R.drawable.edit_red);
BG_EDIT_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.edit_purple);
BG_EDIT_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.edit_orange);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.edit_title_yellow);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.BLUE.id, R.drawable.edit_title_blue);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.WHITE.id, R.drawable.edit_title_white);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.GREEN.id, R.drawable.edit_title_green);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.RED.id, R.drawable.edit_title_red);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.edit_title_purple);
BG_EDIT_TITLE_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.edit_title_orange);
} }
/** public static int getNoteTitleBgResource(int id) {
* ID return BG_EDIT_TITLE_RESOURCES[id];
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgResource(int colorId) {
return BG_EDIT_RESOURCES.get(colorId, R.drawable.edit_yellow);
}
/**
* ID
*
* @param colorId ID
* @return ID
*/
public static int getNoteTitleBgResource(int colorId) {
return BG_EDIT_TITLE_RESOURCES.get(colorId, R.drawable.edit_title_yellow);
}
/**
* ID
* @return ID
*/
public static int[] getAvailableColorIds() {
int[] ids = new int[BG_EDIT_RESOURCES.size()];
for (int i = 0; i < BG_EDIT_RESOURCES.size(); i++) {
ids[i] = BG_EDIT_RESOURCES.keyAt(i);
}
return ids;
} }
} }
/**
* ID
*
* @param context
* @return ID
*/
public static int getDefaultBgId(Context context) { public static int getDefaultBgId(Context context) {
// 检查是否启用随机背景颜色
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
// 获取所有可用颜色ID return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
int[] colorIds = NoteBgResources.getAvailableColorIds(); } else {
if (colorIds.length > 0) { return BG_DEFAULT_COLOR;
return colorIds[(int) (Math.random() * colorIds.length)];
}
} }
// 返回默认颜色
return NoteColor.getDefault();
} }
/* ================== 笔记布局资源管理 ================== */ public static class NoteItemBgResources {
private final static int [] BG_FIRST_RESOURCES = new int [] {
/** R.drawable.list_yellow_up,
* R.drawable.list_blue_up,
*/ R.drawable.list_white_up,
public static class NoteLayoutResources { R.drawable.list_green_up,
// 列表顶部背景资源 R.drawable.list_red_up
private static final SparseIntArray BG_FIRST_RESOURCES = new SparseIntArray(); };
// 列表中部背景资源
private static final SparseIntArray BG_NORMAL_RESOURCES = new SparseIntArray();
// 列表底部背景资源
private static final SparseIntArray BG_LAST_RESOURCES = new SparseIntArray();
// 单条笔记背景资源 private final static int [] BG_NORMAL_RESOURCES = new int [] {
private static final SparseIntArray BG_SINGLE_RESOURCES = new SparseIntArray(); R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
};
// 文件夹背景资源 private final static int [] BG_LAST_RESOURCES = new int [] {
private static final SparseIntArray BG_FOLDER_RESOURCES = new SparseIntArray(); R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
};
// 静态初始化资源映射 private final static int [] BG_SINGLE_RESOURCES = new int [] {
static { R.drawable.list_yellow_single,
// 浅色主题资源 R.drawable.list_blue_single,
BG_FIRST_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.list_yellow_up); R.drawable.list_white_single,
BG_FIRST_RESOURCES.put(NoteColor.BLUE.id, R.drawable.list_blue_up); R.drawable.list_green_single,
BG_FIRST_RESOURCES.put(NoteColor.WHITE.id, R.drawable.list_white_up); R.drawable.list_red_single
BG_FIRST_RESOURCES.put(NoteColor.GREEN.id, R.drawable.list_green_up); };
BG_FIRST_RESOURCES.put(NoteColor.RED.id, R.drawable.list_red_up);
BG_FIRST_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.list_purple_up);
BG_FIRST_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.list_orange_up);
BG_NORMAL_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.list_yellow_middle); public static int getNoteBgFirstRes(int id) {
BG_NORMAL_RESOURCES.put(NoteColor.BLUE.id, R.drawable.list_blue_middle); return BG_FIRST_RESOURCES[id];
BG_NORMAL_RESOURCES.put(NoteColor.WHITE.id, R.drawable.list_white_middle);
BG_NORMAL_RESOURCES.put(NoteColor.GREEN.id, R.drawable.list_green_middle);
BG_NORMAL_RESOURCES.put(NoteColor.RED.id, R.drawable.list_red_middle);
BG_NORMAL_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.list_purple_middle);
BG_NORMAL_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.list_orange_middle);
BG_LAST_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.list_yellow_down);
BG_LAST_RESOURCES.put(NoteColor.BLUE.id, R.drawable.list_blue_down);
BG_LAST_RESOURCES.put(NoteColor.WHITE.id, R.drawable.list_white_down);
BG_LAST_RESOURCES.put(NoteColor.GREEN.id, R.drawable.list_green_down);
BG_LAST_RESOURCES.put(NoteColor.RED.id, R.drawable.list_red_down);
BG_LAST_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.list_purple_down);
BG_LAST_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.list_orange_down);
BG_SINGLE_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.list_yellow_single);
BG_SINGLE_RESOURCES.put(NoteColor.BLUE.id, R.drawable.list_blue_single);
BG_SINGLE_RESOURCES.put(NoteColor.WHITE.id, R.drawable.list_white_single);
BG_SINGLE_RESOURCES.put(NoteColor.GREEN.id, R.drawable.list_green_single);
BG_SINGLE_RESOURCES.put(NoteColor.RED.id, R.drawable.list_red_single);
BG_SINGLE_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.list_purple_single);
BG_SINGLE_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.list_orange_single);
BG_FOLDER_RESOURCES.put(ThemeMode.LIGHT.id, R.drawable.list_folder_light);
BG_FOLDER_RESOURCES.put(ThemeMode.DARK.id, R.drawable.list_folder_dark);
}
/**
*
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgFirstRes(int colorId) {
return BG_FIRST_RESOURCES.get(colorId, R.drawable.list_yellow_up);
} }
/** public static int getNoteBgLastRes(int id) {
* return BG_LAST_RESOURCES[id];
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgLastRes(int colorId) {
return BG_LAST_RESOURCES.get(colorId, R.drawable.list_yellow_down);
} }
/** public static int getNoteBgSingleRes(int id) {
* return BG_SINGLE_RESOURCES[id];
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgSingleRes(int colorId) {
return BG_SINGLE_RESOURCES.get(colorId, R.drawable.list_yellow_single);
} }
/** public static int getNoteBgNormalRes(int id) {
* return BG_NORMAL_RESOURCES[id];
*
* @param colorId ID
* @return ID
*/
public static int getNoteBgNormalRes(int colorId) {
return BG_NORMAL_RESOURCES.get(colorId, R.drawable.list_yellow_middle);
} }
/** public static int getFolderBgRes() {
* return R.drawable.list_folder;
*
* @param themeMode
* @return ID
*/
public static int getFolderBgRes(ThemeMode themeMode) {
return BG_FOLDER_RESOURCES.get(themeMode.id, R.drawable.list_folder_light);
} }
} }
/* ================== 小部件资源管理 ================== */
/**
*
*/
public static class WidgetBgResources { public static class WidgetBgResources {
// 2x小部件背景资源 private final static int [] BG_2X_RESOURCES = new int [] {
private static final SparseIntArray BG_2X_RESOURCES = new SparseIntArray(); R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
// 4x小部件背景资源 R.drawable.widget_2x_white,
private static final SparseIntArray BG_4X_RESOURCES = new SparseIntArray(); R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
// 静态初始化资源映射 };
static {
// 浅色主题资源
BG_2X_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.widget_2x_yellow);
BG_2X_RESOURCES.put(NoteColor.BLUE.id, R.drawable.widget_2x_blue);
BG_2X_RESOURCES.put(NoteColor.WHITE.id, R.drawable.widget_2x_white);
BG_2X_RESOURCES.put(NoteColor.GREEN.id, R.drawable.widget_2x_green);
BG_2X_RESOURCES.put(NoteColor.RED.id, R.drawable.widget_2x_red);
BG_2X_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.widget_2x_purple);
BG_2X_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.widget_2x_orange);
BG_4X_RESOURCES.put(NoteColor.YELLOW.id, R.drawable.widget_4x_yellow); public static int getWidget2xBgResource(int id) {
BG_4X_RESOURCES.put(NoteColor.BLUE.id, R.drawable.widget_4x_blue); return BG_2X_RESOURCES[id];
BG_4X_RESOURCES.put(NoteColor.WHITE.id, R.drawable.widget_4x_white);
BG_4X_RESOURCES.put(NoteColor.GREEN.id, R.drawable.widget_4x_green);
BG_4X_RESOURCES.put(NoteColor.RED.id, R.drawable.widget_4x_red);
BG_4X_RESOURCES.put(NoteColor.PURPLE.id, R.drawable.widget_4x_purple);
BG_4X_RESOURCES.put(NoteColor.ORANGE.id, R.drawable.widget_4x_orange);
} }
/** private final static int [] BG_4X_RESOURCES = new int [] {
* 2x R.drawable.widget_4x_yellow,
* R.drawable.widget_4x_blue,
* @param colorId ID R.drawable.widget_4x_white,
* @return ID R.drawable.widget_4x_green,
*/ R.drawable.widget_4x_red
public static int getWidget2xBgResource(int colorId) { };
return BG_2X_RESOURCES.get(colorId, R.drawable.widget_2x_yellow);
}
/** public static int getWidget4xBgResource(int id) {
* 4x return BG_4X_RESOURCES[id];
*
* @param colorId ID
* @return ID
*/
public static int getWidget4xBgResource(int colorId) {
return BG_4X_RESOURCES.get(colorId, R.drawable.widget_4x_yellow);
} }
} }
/* ================== 文本外观资源管理 ================== */
/**
*
*/
public static class TextAppearanceResources { public static class TextAppearanceResources {
// 文本外观资源映射 private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
private static final SparseIntArray TEXTAPPEARANCE_RESOURCES = new SparseIntArray(); R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
// 静态初始化资源映射 R.style.TextAppearanceLarge,
static { R.style.TextAppearanceSuper
TEXTAPPEARANCE_RESOURCES.put(FontSize.SMALL.id, R.style.TextAppearanceSmall); };
TEXTAPPEARANCE_RESOURCES.put(FontSize.MEDIUM.id, R.style.TextAppearanceMedium);
TEXTAPPEARANCE_RESOURCES.put(FontSize.LARGE.id, R.style.TextAppearanceLarge); public static int getTexAppearanceResource(int id) {
TEXTAPPEARANCE_RESOURCES.put(FontSize.SUPER.id, R.style.TextAppearanceSuper); /**
} * HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
/** * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
* */
* if (id >= TEXTAPPEARANCE_RESOURCES.length) {
* @param fontSizeId ID return BG_DEFAULT_FONT_SIZE;
* @return ID
*/
public static int getTextAppearanceResource(int fontSizeId) {
// 边界检查防止越界
int resourceId = TEXTAPPEARANCE_RESOURCES.get(fontSizeId, -1);
return resourceId != -1 ? resourceId : FontSize.getDefault();
}
/**
*
*
* @return
*/
public static int getAvailableSizeCount() {
return TEXTAPPEARANCE_RESOURCES.size();
}
/**
* sp
*
* @param context
* @param fontSizeId ID
* @return sp
*/
public static float getFontSizeSp(Context context, int fontSizeId) {
Resources res = context.getResources();
// 映射字体大小ID到实际尺寸值
switch (fontSizeId) {
case 0: return res.getDimension(R.dimen.text_size_small) / res.getDisplayMetrics().scaledDensity;
case 1: return res.getDimension(R.dimen.text_size_medium) / res.getDisplayMetrics().scaledDensity;
case 2: return res.getDimension(R.dimen.text_size_large) / res.getDisplayMetrics().scaledDensity;
case 3: return res.getDimension(R.dimen.text_size_super) / res.getDisplayMetrics().scaledDensity;
default: return 16; // 默认16sp
} }
return TEXTAPPEARANCE_RESOURCES[id];
} }
}
/* ================== 新功能:主题资源解析 ================== */
/** public static int getResourcesSize() {
* return TEXTAPPEARANCE_RESOURCES.length;
*
* @param context
* @return
*/
public static ThemeMode getCurrentThemeMode(Context context) {
// 从偏好设置获取主题ID
int themeId = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(NotesPreferenceActivity.PREFERENCE_THEME_MODE, 0);
return ThemeMode.fromId(themeId);
}
/**
* ID
*
* @param colorId ID
* @param themeMode
* @return ID
*/
public static int getThemeAdjustedColor(int colorId, ThemeMode themeMode) {
// 深色模式下调整颜色饱和度
if (themeMode == ThemeMode.DARK) {
switch (NoteColor.fromId(colorId)) {
case YELLOW: return NoteColor.ORANGE.id;
case WHITE: return NoteColor.BLUE.id;
case GREEN: return NoteColor.PURPLE.id;
default: return colorId;
}
} }
return colorId;
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

@ -19,187 +19,12 @@ package net.micode.notes.ui;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
/**
* 广 - 广
*
*
* 1.
* 2. 广
* 3.
* 4. Android 8.0+
* 5.
*/
public class AlarmReceiver extends BroadcastReceiver { public class AlarmReceiver extends BroadcastReceiver {
private static final String TAG = "AlarmReceiver";
private static final String WAKE_LOCK_TAG = "Notes:AlarmWakeLock";
private static final long WAKE_LOCK_TIMEOUT = 30 * 1000; // 30秒超时
/**
* 广 - 广
*
* @param context
* @param intent 广
*/
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
// 日志记录接收到的广播 intent.setClass(context, AlarmAlertActivity.class);
logBroadcastReceived(intent); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
// 验证广播是否有效
if (!isValidAlarmIntent(intent)) {
Log.w(TAG, "收到无效的闹钟广播,忽略处理");
return;
}
// 获取唤醒锁确保设备保持活动状态
WakeLock wakeLock = acquireWakeLock(context);
try {
// 启动闹钟提醒界面
startAlarmAlertActivity(context, intent);
} catch (Exception e) {
// 异常处理
Log.e(TAG, "启动闹钟提醒活动失败", e);
} finally {
// 确保释放唤醒锁
releaseWakeLock(wakeLock);
}
}
/**
* 广
*
* @param intent
*/
private void logBroadcastReceived(Intent intent) {
if (intent == null) {
Log.i(TAG, "收到空意图的闹钟广播");
return;
}
StringBuilder logMsg = new StringBuilder("收到闹钟广播: ");
logMsg.append("Action=").append(intent.getAction());
if (intent.getData() != null) {
logMsg.append(", URI=").append(intent.getData().toString());
}
if (intent.getExtras() != null) {
logMsg.append(", Extras=").append(intent.getExtras().toString());
}
Log.d(TAG, logMsg.toString());
}
/**
* 广
*
* @param intent
* @return true
*/
private boolean isValidAlarmIntent(Intent intent) {
return intent != null &&
intent.getData() != null &&
intent.getData().getScheme() != null &&
intent.getData().getScheme().equals("content");
}
/**
*
*
* @param context
* @return
*/
private WakeLock acquireWakeLock(Context context) {
try {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm == null) {
Log.w(TAG, "无法获取电源服务");
return null;
}
WakeLock wakeLock = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP,
WAKE_LOCK_TAG
);
wakeLock.setReferenceCounted(false);
wakeLock.acquire(WAKE_LOCK_TIMEOUT);
Log.d(TAG, "成功获取唤醒锁");
return wakeLock;
} catch (Exception e) {
Log.e(TAG, "获取唤醒锁时出错", e);
return null;
}
}
/**
*
*
* @param wakeLock
*/
private void releaseWakeLock(WakeLock wakeLock) {
if (wakeLock == null) {
return;
}
try {
if (wakeLock.isHeld()) {
wakeLock.release();
Log.d(TAG, "唤醒锁已释放");
}
} catch (Exception e) {
Log.e(TAG, "释放唤醒锁时出错", e);
}
}
/**
*
*
* @param context
* @param originalIntent 广
*/
private void startAlarmAlertActivity(Context context, Intent originalIntent) {
// 创建启动AlarmAlertActivity的意图
Intent alertIntent = new Intent(context, AlarmAlertActivity.class);
// 传递原始意图的数据
alertIntent.setData(originalIntent.getData());
// 添加必要的标志
alertIntent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
);
// 复制额外的参数
if (originalIntent.getExtras() != null) {
alertIntent.putExtras(originalIntent.getExtras());
}
// 启动活动
try {
Log.d(TAG, "正在启动闹钟提醒活动");
context.startActivity(alertIntent);
} catch (SecurityException e) {
Log.e(TAG, "启动活动权限不足", e);
} catch (Exception e) {
Log.e(TAG, "启动活动失败", e);
}
}
/**
* -
*/
@Override
public void finalize() {
// 添加资源清理逻辑
Log.v(TAG, "闹钟接收器实例被回收");
} }
} }

@ -16,46 +16,22 @@
package net.micode.notes.ui; package net.micode.notes.ui;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.NumberPicker; import android.widget.NumberPicker;
import android.widget.Toast;
import net.micode.notes.R;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import java.util.Locale;
/**
* -
*
* :
* 1. (/)
* 2.
* 3.
* 4.
* 5.
* 6.
* 7.
*
* :
* 1. 12/24
* 2.
* 3. AM/PM
* 4.
*/
public class DateTimePicker extends FrameLayout { public class DateTimePicker extends FrameLayout {
private static final String TAG = "DateTimePicker";
// 配置常量
private static final boolean DEFAULT_ENABLE_STATE = true; private static final boolean DEFAULT_ENABLE_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12; private static final int HOURS_IN_HALF_DAY = 12;
private static final int HOURS_IN_ALL_DAY = 24; private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7; private static final int DAYS_IN_ALL_WEEK = 7;
@ -65,114 +41,130 @@ public class DateTimePicker extends FrameLayout {
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUTE_SPINNER_MIN_VAL = 0; private static final int MINUT_SPINNER_MIN_VAL = 0;
private static final int MINUTE_SPINNER_MAX_VAL = 59; private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0; private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1; private static final int AMPM_SPINNER_MAX_VAL = 1;
// 状态常量
private static final int MODE_INITIALIZING = 0;
private static final int MODE_NORMAL = 1;
// 范围限制 (默认无限制)
private long mMinDate = Long.MIN_VALUE;
private long mMaxDate = Long.MAX_VALUE;
// UI组件
private final NumberPicker mDateSpinner; private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner; private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner; private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner; private final NumberPicker mAmPmSpinner;
// 数据模型
private Calendar mDate; private Calendar mDate;
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm; private boolean mIsAm;
private boolean mIs24HourView; private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE; private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private int mState = MODE_INITIALIZING;
// 监听器 private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener; private OnDateTimeChangedListener mOnDateTimeChangedListener;
/** private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
* @Override
*/ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
private final NumberPicker.OnValueChangeListener mOnDateChangedListener = mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
(picker, oldVal, newVal) -> {
// 计算日期变化差值
int dayDiff = newVal - oldVal;
mDate.add(Calendar.DAY_OF_YEAR, dayDiff);
// 验证日期范围
if (!isDateInRange()) {
revertDateChange();
showDateRangeWarning();
return;
}
updateDateControl(); updateDateControl();
onDateTimeChanged(); onDateTimeChanged();
}; }
};
/**
* private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
*/ @Override
private final NumberPicker.OnValueChangeListener mOnHourChangedListener = public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
(picker, oldVal, newVal) -> { boolean isDateChanged = false;
int newHour = computeNewHour(oldVal, newVal); Calendar cal = Calendar.getInstance();
if (!mIs24HourView) {
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour); mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged(); onDateTimeChanged();
}; if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
/** setCurrentMonth(cal.get(Calendar.MONTH));
* setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
*/ }
private final NumberPicker.OnValueChangeListener mOnMinuteChangedListener = }
(picker, oldVal, newVal) -> { };
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal); mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged(); onDateTimeChanged();
}; }
};
/**
* AM/PM private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
*/ @Override
private final NumberPicker.OnValueChangeListener mOnAmPmChangedListener = public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
(picker, oldVal, newVal) -> { mIsAm = !mIsAm;
// 切换AM/PM状态 if (mIsAm) {
mIsAm = (newVal == Calendar.AM); mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
// 调整时间AM->PM加12小时PM->AM减12小时 mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
int hour = mDate.get(Calendar.HOUR_OF_DAY); }
hour = mIsAm ? hour % 12 : hour % 12 + 12; updateAmPmControl();
mDate.set(Calendar.HOUR_OF_DAY, hour);
updateHourControl();
onDateTimeChanged(); onDateTimeChanged();
}; }
};
/**
*
*/
public interface OnDateTimeChangedListener { public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month, void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute); int dayOfMonth, int hourOfDay, int minute);
} }
/*************************** 构造函数 ***************************/
public DateTimePicker(Context context) { public DateTimePicker(Context context) {
this(context, null); this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DateTimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 使用当前时间初始化
init(context, System.currentTimeMillis(), DateFormat.is24HourFormat(context));
} }
public DateTimePicker(Context context, long date) { public DateTimePicker(Context context, long date) {
@ -181,85 +173,57 @@ public class DateTimePicker extends FrameLayout {
public DateTimePicker(Context context, long date, boolean is24HourView) { public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context); super(context);
init(context, date, is24HourView);
}
/**
*
*/
private void init(Context context, long date, boolean is24HourView) {
// 初始化日期对象
mDate = Calendar.getInstance(); mDate = Calendar.getInstance();
mState = MODE_INITIALIZING; mInitialising = true;
mIs24HourView = is24HourView; mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
// 确定初始AM/PM状态
int currentHour = (int) (date / (60 * 60 * 1000) % 24);
mIsAm = currentHour < 12;
// 加载布局
inflate(context, R.layout.datetime_picker, this); inflate(context, R.layout.datetime_picker, this);
// 初始化日期选择器 mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner = findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
mDateSpinner.setWrapSelectorWheel(false);
// 初始化小时选择器 mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner = findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
// 初始化分钟选择器 mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner = findViewById(R.id.minute); mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setMinValue(MINUTE_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUTE_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100); mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
// 初始化AM/PM选择器 String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = findViewById(R.id.amPm); mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
// 本地化AM/PM符号
updateAmPmSymbols();
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// 更新UI控件 // update controls to initial state
updateDateControl(); updateDateControl();
updateHourControl(); updateHourControl();
updateAmPmControl(); updateAmPmControl();
// 设置初始时间 set24HourView(is24HourView);
// set to current time
setCurrentDate(date); setCurrentDate(date);
// 应用启用状态
setEnabled(isEnabled()); setEnabled(isEnabled());
// 应用深色模式 // set the content descriptions
applyDarkMode(); mInitialising = false;
// 初始化完成
mState = MODE_NORMAL;
} }
/*************************** 公共方法 ***************************/
@Override @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) { if (mIsEnabled == enabled) {
return; return;
} }
super.setEnabled(enabled); super.setEnabled(enabled);
// 设置子控件状态
mDateSpinner.setEnabled(enabled); mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled); mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled); mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled); mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled; mIsEnabled = enabled;
} }
@ -269,37 +233,37 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* () * Get the current date in millis
*
* @return the current date in millis
*/ */
public long getCurrentDateInTimeMillis() { public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis(); return mDate.getTimeInMillis();
} }
/** /**
* * Set the current date
*
* @param date The current date in millis
*/ */
public void setCurrentDate(long date) { public void setCurrentDate(long date) {
// 验证日期范围
if (date < mMinDate || date > mMaxDate) {
Log.w(TAG, "设置日期超出范围: " + date);
return;
}
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date); cal.setTimeInMillis(date);
setCurrentDate( setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.YEAR), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE)
);
} }
/** /**
* * Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/ */
public void setCurrentDate(int year, int month, int dayOfMonth, int hourOfDay, int minute) { public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year); setCurrentYear(year);
setCurrentMonth(month); setCurrentMonth(month);
setCurrentDay(dayOfMonth); setCurrentDay(dayOfMonth);
@ -308,18 +272,21 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* * Get current year
*
* @return The current year
*/ */
public int getCurrentYear() { public int getCurrentYear() {
return mDate.get(Calendar.YEAR); return mDate.get(Calendar.YEAR);
} }
/** /**
* * Set current year
*
* @param year The current year
*/ */
public void setCurrentYear(int year) { public void setCurrentYear(int year) {
// 检查状态避免不必要的更新 if (!mInitialising && year == getCurrentYear()) {
if (mState != MODE_INITIALIZING && year == getCurrentYear()) {
return; return;
} }
mDate.set(Calendar.YEAR, year); mDate.set(Calendar.YEAR, year);
@ -328,17 +295,21 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* * Get current month in the year
*
* @return The current month in the year
*/ */
public int getCurrentMonth() { public int getCurrentMonth() {
return mDate.get(Calendar.MONTH); return mDate.get(Calendar.MONTH);
} }
/** /**
* * Set current month in the year
*
* @param month The month in the year
*/ */
public void setCurrentMonth(int month) { public void setCurrentMonth(int month) {
if (mState != MODE_INITIALIZING && month == getCurrentMonth()) { if (!mInitialising && month == getCurrentMonth()) {
return; return;
} }
mDate.set(Calendar.MONTH, month); mDate.set(Calendar.MONTH, month);
@ -347,17 +318,21 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* * Get current day of the month
*
* @return The day of the month
*/ */
public int getCurrentDay() { public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH); return mDate.get(Calendar.DAY_OF_MONTH);
} }
/** /**
* * Set current day of the month
*
* @param dayOfMonth The day of the month
*/ */
public void setCurrentDay(int dayOfMonth) { public void setCurrentDay(int dayOfMonth) {
if (mState != MODE_INITIALIZING && dayOfMonth == getCurrentDay()) { if (!mInitialising && dayOfMonth == getCurrentDay()) {
return; return;
} }
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
@ -366,66 +341,68 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* (24) * Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/ */
public int getCurrentHourOfDay() { public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY); return mDate.get(Calendar.HOUR_OF_DAY);
} }
/** private int getCurrentHour() {
* if (mIs24HourView){
*/ return getCurrentHourOfDay();
private int getCurrentDisplayHour() {
int hour = getCurrentHourOfDay();
if (mIs24HourView) {
return hour;
} else { } else {
if (hour == 0 || hour == 12) { int hour = getCurrentHourOfDay();
return 12; if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
} }
return hour % 12;
} }
} }
/** /**
* (24) * Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/ */
public void setCurrentHour(int hourOfDay) { public void setCurrentHour(int hourOfDay) {
// 验证小时值是否有效 if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
if (hourOfDay < 0 || hourOfDay > 23) {
Log.e(TAG, "无效的小时值: " + hourOfDay);
return;
}
if (mState != MODE_INITIALIZING && hourOfDay == getCurrentHourOfDay()) {
return; return;
} }
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
// 更新AM/PM状态
if (!mIs24HourView) { if (!mIs24HourView) {
mIsAm = (hourOfDay < 12); if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl(); updateAmPmControl();
} }
mHourSpinner.setValue(hourOfDay);
// 更新显示值
mHourSpinner.setValue(getCurrentDisplayHour());
onDateTimeChanged(); onDateTimeChanged();
} }
/** /**
* * Get currentMinute
*
* @return The Current Minute
*/ */
public int getCurrentMinute() { public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE); return mDate.get(Calendar.MINUTE);
} }
/** /**
* * Set current minute
*/ */
public void setCurrentMinute(int minute) { public void setCurrentMinute(int minute) {
if (mState != MODE_INITIALIZING && minute == getCurrentMinute()) { if (!mInitialising && minute == getCurrentMinute()) {
return; return;
} }
mMinuteSpinner.setValue(minute); mMinuteSpinner.setValue(minute);
@ -434,148 +411,53 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* 使24 * @return true if this is in 24 hour view else false.
*/ */
public boolean is24HourView () { public boolean is24HourView () {
return mIs24HourView; return mIs24HourView;
} }
/** /**
* 24 * Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/ */
public void set24HourView(boolean is24HourView) { public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) { if (mIs24HourView == is24HourView) {
return; return;
} }
mIs24HourView = is24HourView; mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
// 更新AM/PM显示 int hour = getCurrentHourOfDay();
int amPmVisibility = is24HourView ? View.GONE : View.VISIBLE;
mAmPmSpinner.setVisibility(amPmVisibility);
// 更新小时控制
updateHourControl(); updateHourControl();
setCurrentHour(getCurrentHourOfDay()); setCurrentHour(hour);
} updateAmPmControl();
/**
*
*/
public void setMinDate(long minDate) {
mMinDate = minDate;
validateCurrentDate();
}
/**
*
*/
public void setMaxDate(long maxDate) {
mMaxDate = maxDate;
validateCurrentDate();
}
/**
*
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener listener) {
mOnDateTimeChangedListener = listener;
}
/*************************** 私有方法 ***************************/
/**
* ()
*/
private int computeNewHour(int oldVal, int newVal) {
int currentHour = getCurrentHourOfDay();
if (mIs24HourView) {
// 24小时制处理
if (oldVal == HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW && newVal == 0) {
return 0; // 23 -> 0
} else if (oldVal == 0 && newVal == HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW) {
return 23; // 0 -> 23
}
} else {
// 12小时制处理
if (oldVal == 12 && newVal == 11) {
return mIsAm ? 11 : 23; // PM状态下12->11实际上是23点
} else if (oldVal == 11 && newVal == 12) {
return mIsAm ? 12 : 0; // AM状态下11->12是中午12点
}
}
// 基本转换
int hour = newVal;
if (!mIs24HourView && !mIsAm && hour != 12) {
hour += 12;
}
// 特殊处理中午12点
if (hour == 24) hour = 0;
return hour;
} }
/**
*
*/
private void updateDateControl() { private void updateDateControl() {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis()); cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
// 以当前日期为中心生成一周的日期 mDateSpinner.setDisplayedValues(null);
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2); for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
for (int i = 0; i < DAYS_IN_ALL_WEEK; i++) {
// 生成日期显示字符串 (带星期)
mDateDisplayValues[i] = formatDateWithWeekday(cal);
cal.add(Calendar.DAY_OF_YEAR, 1); cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
} }
// 设置日期选择器显示内容
mDateSpinner.setDisplayedValues(mDateDisplayValues); mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 当前日期在中间位置 mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
} mDateSpinner.invalidate();
/**
* ()
*/
private String formatDateWithWeekday(Calendar calendar) {
java.text.DateFormat dateFormat = java.text.DateFormat.getDateInstance(
java.text.DateFormat.MEDIUM, Locale.getDefault());
// 添加星期显示
String weekday = getWeekdayName(calendar.get(Calendar.DAY_OF_WEEK));
return String.format("%s (%s)",
dateFormat.format(calendar.getTime()),
weekday
);
}
/**
*
*/
private String getWeekdayName(int dayOfWeek) {
String[] weekdays = new DateFormatSymbols().getWeekdays();
if (dayOfWeek >= Calendar.SUNDAY && dayOfWeek <= Calendar.SATURDAY) {
return weekdays[dayOfWeek];
}
return "";
} }
/**
* AM/PM
*/
private void updateAmPmControl() { private void updateAmPmControl() {
if (mIs24HourView) { if (mIs24HourView) {
return; mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
} }
mAmPmSpinner.setValue(mIsAm ? 0 : 1);
} }
/**
*
*/
private void updateHourControl() { private void updateHourControl() {
if (mIs24HourView) { if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
@ -584,101 +466,20 @@ public class DateTimePicker extends FrameLayout {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
} }
mHourSpinner.setValue(getCurrentDisplayHour());
}
/**
* AM/PM
*/
private void updateAmPmSymbols() {
String[] ampmSymbols = new DateFormatSymbols().getAmPmStrings();
if (ampmSymbols.length < 2) {
Log.e(TAG, "AM/PM符号获取失败");
ampmSymbols = new String[]{"AM", "PM"}; // 默认值
}
mAmPmSpinner.setDisplayedValues(ampmSymbols);
}
/**
*
*/
private void applyDarkMode() {
// 在Android 10+系统上添加深色模式支持
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setForceDarkAllowed(true);
}
}
/**
*
*/
private void validateCurrentDate() {
long currentTime = getCurrentDateInTimeMillis();
if (currentTime < mMinDate || currentTime > mMaxDate) {
// 自动调整到有效范围
long newTime = Math.max(mMinDate, Math.min(currentTime, mMaxDate));
setCurrentDate(newTime);
// 触发更新
updateDateControl();
updateHourControl();
updateAmPmControl();
}
} }
/** /**
* * Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/ */
private boolean isDateInRange() { public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
long currentTime = mDate.getTimeInMillis(); mOnDateTimeChangedListener = callback;
return currentTime >= mMinDate && currentTime <= mMaxDate;
} }
/**
*
*/
private void revertDateChange() {
// 回滚到上一次有效日期
if (mDateSpinner.getValue() > DAYS_IN_ALL_WEEK / 2) {
mDateSpinner.setValue(mDateSpinner.getValue() - 1);
} else if (mDateSpinner.getValue() < DAYS_IN_ALL_WEEK / 2) {
mDateSpinner.setValue(mDateSpinner.getValue() + 1);
}
}
/**
*
*/
private void showDateRangeWarning() {
Context context = getContext();
Calendar minCal = Calendar.getInstance();
minCal.setTimeInMillis(mMinDate);
Calendar maxCal = Calendar.getInstance();
maxCal.setTimeInMillis(mMaxDate);
java.text.DateFormat df = java.text.DateFormat.getDateInstance();
String msg = String.format(
context.getString(R.string.date_range_warning),
df.format(minCal.getTime()),
df.format(maxCal.getTime())
);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
/**
*
*/
private void onDateTimeChanged() { private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) { if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged( mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
this, getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
getCurrentYear(),
getCurrentMonth(),
getCurrentDay(),
getCurrentHourOfDay(),
getCurrentMinute()
);
} }
} }
} }

@ -14,10 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
/**
*
*
*/
package net.micode.notes.ui; package net.micode.notes.ui;
import java.util.Calendar; import java.util.Calendar;
@ -33,130 +29,62 @@ import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
/**
*
*
*
*/
public class DateTimePickerDialog extends AlertDialog implements OnClickListener { public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
// 用于存储用户选择的日期和时间
private Calendar mDate = Calendar.getInstance(); private Calendar mDate = Calendar.getInstance();
// 是否使用24小时制显示时间
private boolean mIs24HourView; private boolean mIs24HourView;
// 日期时间设置回调接口
private OnDateTimeSetListener mOnDateTimeSetListener; private OnDateTimeSetListener mOnDateTimeSetListener;
// 自定义的日期时间选择器视图
private DateTimePicker mDateTimePicker; private DateTimePicker mDateTimePicker;
/**
*
*
*/
public interface OnDateTimeSetListener { public interface OnDateTimeSetListener {
/**
*
*
* @param dialog
* @param date
*/
void OnDateTimeSet(AlertDialog dialog, long date); void OnDateTimeSet(AlertDialog dialog, long date);
} }
/**
*
*
* @param context
* @param date
*/
public DateTimePickerDialog(Context context, long date) { public DateTimePickerDialog(Context context, long date) {
super(context); super(context);
// 初始化日期时间选择器视图
mDateTimePicker = new DateTimePicker(context); mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker); setView(mDateTimePicker);
// 设置日期时间变化监听器,当用户调整日期或时间时更新内部日期对象和标题
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month, public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) { int dayOfMonth, int hourOfDay, int minute) {
// 更新内部日期对象的年、月、日、时、分
mDate.set(Calendar.YEAR, year); mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month); mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute); mDate.set(Calendar.MINUTE, minute);
// 更新对话框标题,显示当前选择的日期时间
updateTitle(mDate.getTimeInMillis()); updateTitle(mDate.getTimeInMillis());
} }
}); });
// 设置初始日期时间并将秒设置为0
mDate.setTimeInMillis(date); mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0); mDate.set(Calendar.SECOND, 0);
// 设置日期时间选择器的当前日期
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框按钮
setButton(context.getString(R.string.datetime_dialog_ok), this); setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 根据系统设置确定是否使用24小时制
set24HourView(DateFormat.is24HourFormat(this.getContext())); set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 更新对话框标题
updateTitle(mDate.getTimeInMillis()); updateTitle(mDate.getTimeInMillis());
} }
/**
* 使24
*
* @param is24HourView true使24false使12
*/
public void set24HourView(boolean is24HourView) { public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView; mIs24HourView = is24HourView;
} }
/**
*
*
* @param callBack OnDateTimeSetListener
*/
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack; mOnDateTimeSetListener = callBack;
} }
/**
*
*
* @param date
*/
private void updateTitle(long date) { private void updateTitle(long date) {
// 设置日期时间格式化标志
int flag = int flag =
DateUtils.FORMAT_SHOW_YEAR | // 显示年份 DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE | // 显示日期(月、日) DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME; // 显示时间(时、分) DateUtils.FORMAT_SHOW_TIME;
// 根据24小时制设置添加相应的格式化标志
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
// 使用DateFormat格式化日期时间并设置为对话框标题
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
} }
/**
*
*
* @param arg0
* @param arg1
*/
public void onClick(DialogInterface arg0, int arg1) { public void onClick(DialogInterface arg0, int arg1) {
// 当用户点击确定按钮时,调用回调函数通知日期时间已设置
if (mOnDateTimeSetListener != null) { if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
} }
} }
} }

File diff suppressed because it is too large Load Diff

@ -17,292 +17,124 @@
package net.micode.notes.ui; package net.micode.notes.ui;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri;
import android.text.Layout; import android.text.Layout;
import android.text.Selection; import android.text.Selection;
import android.text.Spannable;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan; import android.text.style.URLSpan;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Toast;
import androidx.core.text.util.LinkifyCompat;
import net.micode.notes.R; import net.micode.notes.R;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern;
/**
* -
*
* :
* 1. (////)
* 2.
* 3. (/)
* 4.
* 5.
* 6.
* 7. /
*
* :
* 1.
* 2.
* 3.
* 4.
*/
public class NoteEditText extends EditText { public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText"; private static final String TAG = "NoteEditText";
private int mIndex;
// 段落管理常量
private static final int NO_INDEX = -1;
private int mParagraphIndex = NO_INDEX;
private int mSelectionStartBeforeDelete; private int mSelectionStartBeforeDelete;
private boolean mIsLastParagraph = false;
// 链接类型定义 private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_TEL = "tel:"; private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_MAILTO = "mailto:"; private static final String SCHEME_EMAIL = "mailto:" ;
private static final String SCHEME_HTTP = "http:";
private static final String SCHEME_HTTPS = "https:"; private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
private static final String SCHEME_GEO = "geo:";
private static final String SCHEME_NOTE = "note://";
// 链接模式定义
private static final Pattern USER_MENTION_PATTERN = Pattern.compile("@\\w+");
private static final Pattern NOTE_REF_PATTERN = Pattern.compile("#\\w+");
// 链接操作映射表
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<>();
static { static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_MAILTO, R.string.note_link_email);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_HTTPS, R.string.note_link_web); sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
sSchemaActionResMap.put(SCHEME_GEO, R.string.note_link_map);
sSchemaActionResMap.put(SCHEME_NOTE, R.string.note_link_note);
} }
// 智能编辑标记
private static final int UNDO_HISTORY_SIZE = 50;
private int mIndentLevel = 0;
private boolean mInBulletList = false;
/** /**
* * Call by the {@link NoteEditActivity} to delete or add edit text
*/ */
public interface OnTextViewChangeListener { public interface OnTextViewChangeListener {
/** /**
* () * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* * and the text is null
* @param paragraphIndex
*/ */
void onParagraphDelete(int paragraphIndex); void onEditTextDelete(int index, String text);
/**
* ()
*
* @param fromParagraphIndex
* @param newParagraphText
* @param offset
*/
void onParagraphSplit(int fromParagraphIndex, String newParagraphText, int offset);
/** /**
* * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* * happen
* @param paragraphIndex
* @param hasText
*/ */
void onTextChange(int paragraphIndex, boolean hasText); void onEditTextEnter(int index, String text);
/** /**
* * Hide or show item option when text change
*
* @param url URL
* @return
*/ */
boolean onLinkClick(String url); void onTextChange(int index, boolean hasText);
} }
private OnTextViewChangeListener mOnTextViewChangeListener; private OnTextViewChangeListener mOnTextViewChangeListener;
// 历史管理类
private static class EditHistory {
private static final int MAX_UNDO = UNDO_HISTORY_SIZE;
private final HistoryItem[] items = new HistoryItem[MAX_UNDO];
private int position = -1;
private int size = 0;
static class HistoryItem {
String before;
String after;
int selectionStart;
int selectionEnd;
HistoryItem(CharSequence before, CharSequence after, int selStart, int selEnd) {
this.before = before.toString();
this.after = after.toString();
this.selectionStart = selStart;
this.selectionEnd = selEnd;
}
}
void add(HistoryItem item) {
if (size < MAX_UNDO) {
size++;
}
position = (position + 1) % MAX_UNDO;
items[position] = item;
}
HistoryItem getLast() {
if (size == 0) return null;
return items[position];
}
boolean canUndo() {
return size > 0;
}
}
private final EditHistory mEditHistory = new EditHistory();
private boolean mIsUndoRedoInProgress = false;
public NoteEditText(Context context) { public NoteEditText(Context context) {
this(context, null); super(context, null);
mIndex = 0;
} }
public NoteEditText(Context context, AttributeSet attrs) { public void setIndex(int index) {
this(context, attrs, android.R.attr.editTextStyle); mIndex = index;
} }
public NoteEditText(Context context, AttributeSet attrs, int defStyleAttr) { public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
super(context, attrs, defStyleAttr); mOnTextViewChangeListener = listener;
init();
} }
/** public NoteEditText(Context context, AttributeSet attrs) {
* super(context, attrs, android.R.attr.editTextStyle);
*/
private void init() {
// 配置富文本支持
setMovementMethod(new CustomLinkMovementMethod());
// 设置自动链接类型
int linkifyMask = LinkifyCompat.WEB_URLS
| LinkifyCompat.EMAIL_ADDRESSES
| LinkifyCompat.PHONE_NUMBERS
| LinkifyCompat.MAP_ADDRESSES;
LinkifyCompat.addLinks(this, linkifyMask);
// 添加自定义链接
addCustomLinkPatterns();
// 启用撤销支持
enableUndoRedo();
}
/**
*
*
* @param paragraphIndex
* @param isLastParagraph
*/
public void setParagraphInfo(int paragraphIndex, boolean isLastParagraph) {
this.mParagraphIndex = paragraphIndex;
this.mIsLastParagraph = isLastParagraph;
} }
/** public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
* super(context, attrs, defStyle);
*/ // TODO Auto-generated constructor stub
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
this.mOnTextViewChangeListener = listener;
}
/**
*
*
* @param isDarkMode
*/
public void setDarkMode(boolean isDarkMode) {
if (isDarkMode) {
setBackgroundColor(getResources().getColor(R.color.note_bg_dark));
setTextColor(getResources().getColor(R.color.text_color_dark));
} else {
setBackgroundColor(getResources().getColor(R.color.note_bg_light));
setTextColor(getResources().getColor(R.color.text_color_light));
}
} }
@Override @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(MotionEvent event) {
try { switch (event.getAction()) {
return super.onTouchEvent(event); case MotionEvent.ACTION_DOWN:
} catch (Exception e) {
Log.e(TAG, "触摸事件处理异常", e); int x = (int) event.getX();
return false; int y = (int) event.getY();
} x -= getTotalPaddingLeft();
} y -= getTotalPaddingTop();
x += getScrollX();
@Override y += getScrollY();
public boolean onTextContextMenuItem(int id) {
// 处理自定义上下文菜单项 Layout layout = getLayout();
if (id == R.id.note_menu_undo) { int line = layout.getLineForVertical(y);
undo(); int off = layout.getOffsetForHorizontal(line, x);
return true; Selection.setSelection(getText(), off);
} else if (id == R.id.note_menu_redo) { break;
redo();
return true;
} }
return super.onTextContextMenuItem(id);
return super.onTouchEvent(event);
} }
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
// 检测删除键,记录删除前的光标位置 switch (keyCode) {
if (keyCode == KeyEvent.KEYCODE_DEL) { case KeyEvent.KEYCODE_ENTER:
mSelectionStartBeforeDelete = getSelectionStart(); if (mOnTextViewChangeListener != null) {
} return false;
}
// 处理自定义快捷键 break;
if (event.isCtrlPressed()) { case KeyEvent.KEYCODE_DEL:
switch (keyCode) { mSelectionStartBeforeDelete = getSelectionStart();
case KeyEvent.KEYCODE_Z: break;
if (event.isShiftPressed()) { default:
redo(); break;
} else {
undo();
}
return true;
case KeyEvent.KEYCODE_B:
toggleBulletList();
return true;
case KeyEvent.KEYCODE_L:
indent();
return true;
case KeyEvent.KEYCODE_M:
dedent();
return true;
}
} }
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} }
@ -310,17 +142,25 @@ public class NoteEditText extends EditText {
public boolean onKeyUp(int keyCode, KeyEvent event) { public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) { switch(keyCode) {
case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_DEL:
handleDeleteAction(); if (mOnTextViewChangeListener != null) {
return true; if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_ENTER:
handleEnterAction(); if (mOnTextViewChangeListener != null) {
return true; int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
case KeyEvent.KEYCODE_TAB: setText(getText().subSequence(0, selectionStart));
handleTabAction(); mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
return true; } else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default: default:
break; break;
} }
@ -329,531 +169,49 @@ public class NoteEditText extends EditText {
@Override @Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
if (mOnTextViewChangeListener != null) { if (mOnTextViewChangeListener != null) {
boolean hasText = !TextUtils.isEmpty(getText()); if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mParagraphIndex, hasText); mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
} }
super.onFocusChanged(focused, direction, previouslyFocusedRect);
} }
@Override @Override
protected void onCreateContextMenu(ContextMenu menu) { protected void onCreateContextMenu(ContextMenu menu) {
// 添加自定义菜单项 if (getText() instanceof Spanned) {
menu.add(Menu.NONE, R.id.note_menu_undo, 0, R.string.note_undo) int selStart = getSelectionStart();
.setIcon(R.drawable.ic_undo) int selEnd = getSelectionEnd();
.setEnabled(canUndo());
int min = Math.min(selStart, selEnd);
menu.add(Menu.NONE, R.id.note_menu_redo, 1, R.string.note_redo) int max = Math.max(selStart, selEnd);
.setIcon(R.drawable.ic_redo)
.setEnabled(canRedo()); final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
// 添加分割线 int defaultResId = 0;
menu.add(Menu.NONE, -1, Menu.NONE, ""); for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
// 处理链接菜单 defaultResId = sSchemaActionResMap.get(schema);
handleLinkContextMenu(menu); break;
// 添加系统默认菜单
super.onCreateContextMenu(menu);
}
/**
* /
*/
private void enableUndoRedo() {
addTextChangedListener(new TextWatcher() {
private EditHistory.HistoryItem mItem;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (mIsUndoRedoInProgress) return;
mItem = new EditHistory.HistoryItem(
s,
"",
getSelectionStart(),
getSelectionEnd()
);
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 不做处理
}
@Override
public void afterTextChanged(android.text.Editable s) {
if (mIsUndoRedoInProgress) return;
if (mItem != null) {
mItem.after = s.toString();
mItem.selectionStart = getSelectionStart();
mItem.selectionEnd = getSelectionEnd();
mEditHistory.add(mItem);
mItem = null;
}
}
});
}
/**
*
*/
public void undo() {
if (!canUndo()) return;
EditHistory.HistoryItem item = mEditHistory.getLast();
if (item != null) {
mIsUndoRedoInProgress = true;
setText(item.before);
setSelection(item.selectionStart, item.selectionEnd);
mIsUndoRedoInProgress = false;
}
}
/**
* ()
*/
public void redo() {
// 实际实现需要维护重做栈
Toast.makeText(getContext(), "重做功能正在开发中", Toast.LENGTH_SHORT).show();
}
public boolean canUndo() {
return mEditHistory.canUndo();
}
public boolean canRedo() {
return false; // 未实现
}
/*************************** 链接处理 ***************************/
/**
*
*/
private void addCustomLinkPatterns() {
addLinkPattern(NOTE_REF_PATTERN, SCHEME_NOTE,
new LinkifyCompat.MatchFilter() {
@Override
public boolean acceptMatch(CharSequence s, int start, int end) {
return start == 0 || s.charAt(start - 1) != '!';
}
});
addLinkPattern(USER_MENTION_PATTERN, "user://",
new LinkifyCompat.MatchFilter() {
@Override
public boolean acceptMatch(CharSequence s, int start, int end) {
return true;
}
});
}
/**
*
*/
private void addLinkPattern(Pattern pattern, String scheme, LinkifyCompat.MatchFilter filter) {
LinkifyCompat.addLinks(this, pattern, scheme, null, filter, null);
}
/**
*
*/
private void handleLinkContextMenu(ContextMenu menu) {
Spannable text = getText();
int min = Math.max(0, getSelectionStart());
int max = Math.min(text.length(), getSelectionEnd());
final URLSpan[] urls = text.getSpans(min, max, URLSpan.class);
if (urls.length > 0) {
URLSpan primaryUrl = getPrimaryUrl(urls);
if (primaryUrl != null) {
addLinkActionsToMenu(menu, primaryUrl);
}
}
}
/**
* ()
*/
private URLSpan getPrimaryUrl(URLSpan[] urls) {
if (urls.length == 1) {
return urls[0];
}
// 寻找覆盖选区最长的链接
URLSpan bestMatch = null;
int maxOverlap = 0;
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
for (URLSpan url : urls) {
int spanStart = getText().getSpanStart(url);
int spanEnd = getText().getSpanEnd(url);
int overlap = Math.min(selEnd, spanEnd) - Math.max(selStart, spanStart);
if (overlap > maxOverlap) {
maxOverlap = overlap;
bestMatch = url;
}
}
return bestMatch;
}
/**
*
*/
private void addLinkActionsToMenu(ContextMenu menu, final URLSpan urlSpan) {
String url = urlSpan.getURL();
String displayText = getDisplayTextForUrl(urlSpan);
// 添加标题项
menu.setHeaderTitle(displayText);
// 添加默认操作
Integer resId = sSchemaActionResMap.get(getScheme(url));
if (resId == null) {
resId = R.string.note_link_open;
}
menu.add(0, 0, 0, resId).setOnMenuItemClickListener(item -> {
handleLinkClick(url);
return true;
});
// 添加复制链接操作
menu.add(0, 1, 1, R.string.note_link_copy).setOnMenuItemClickListener(item -> {
copyToClipboard(displayText, url);
return true;
});
}
/**
*
*/
private String getDisplayTextForUrl(URLSpan urlSpan) {
String url = urlSpan.getURL();
if (url.startsWith(SCHEME_NOTE)) {
return url.substring(SCHEME_NOTE.length());
}
return url;
}
/**
* scheme
*/
private String getScheme(String url) {
int colonPos = url.indexOf(':');
if (colonPos != -1) {
return url.substring(0, colonPos + 1);
}
return url;
}
/**
*
*/
private boolean handleLinkClick(String url) {
if (mOnTextViewChangeListener != null) {
if (mOnTextViewChangeListener.onLinkClick(url)) {
return true;
}
}
// 默认链接处理
if (url.startsWith(SCHEME_TEL)) {
callPhoneNumber(url.substring(SCHEME_TEL.length()));
} else if (url.startsWith(SCHEME_MAILTO)) {
sendEmail(url.substring(SCHEME_MAILTO.length()));
} else if (url.startsWith(SCHEME_HTTP) || url.startsWith(SCHEME_HTTPS)) {
openWebPage(url);
} else if (url.startsWith(SCHEME_GEO)) {
openMap(url.substring(SCHEME_GEO.length()));
} else if (url.startsWith(SCHEME_NOTE)) {
openNoteReference(url.substring(SCHEME_NOTE.length()));
} else {
openWebPage(url); // 尝试打开
}
return true;
}
/**
* ()
*/
private class CustomLinkMovementMethod extends LinkMovementMethod {
@Override
public boolean onTouchEvent(android.widget.TextView widget, Spannable buffer, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
URLSpan[] links = buffer.getSpans(off, off, URLSpan.class);
if (links.length != 0) {
String url = links[0].getURL();
if (handleLinkClick(url)) {
return true;
} }
} }
}
return super.onTouchEvent(widget, buffer, event);
}
}
/*************************** 编辑操作处理 ***************************/ if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
/** }
*
*/
private void handleDeleteAction() {
if (mOnTextViewChangeListener == null) {
return;
}
// 处理空段落删除逻辑
if (mSelectionStartBeforeDelete == 0 && mParagraphIndex != NO_INDEX) {
// 只有整个段落为空时才删除
if (TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onParagraphDelete(mParagraphIndex);
}
}
}
/**
*
*/
private void handleEnterAction() {
if (mOnTextViewChangeListener == null) {
return;
}
int selectionStart = getSelectionStart();
String remainingText = "";
// 获取光标后的文本
if (selectionStart < length()) {
remainingText = getText().subSequence(selectionStart, length()).toString();
}
// 分割前的文本
String currentText = getText().subSequence(0, selectionStart).toString();
// 应用智能回车规则
if (currentText.endsWith("- - ") || currentText.endsWith("-- ")) {
// 智能列表结束
setText(currentText.substring(0, currentText.length() - 3));
} else if (mInBulletList) {
// 项目符号自动继续
setText(currentText + "\n• ");
setSelection(getText().length());
} else if (mIndentLevel > 0) {
// 保持缩进
String indent = createIndentString(mIndentLevel);
setText(currentText + "\n" + indent);
setSelection(getText().length());
} else {
// 普通回车
setText(currentText);
mOnTextViewChangeListener.onParagraphSplit(mParagraphIndex, remainingText, selectionStart);
}
}
/**
* Tab
*/
private void handleTabAction() {
int start = getSelectionStart();
int end = getSelectionEnd();
// 有选中文本时增加缩进
if (start != end) {
String selectedText = getText().subSequence(start, end).toString();
String replacement = createIndentString(mIndentLevel + 1) + selectedText;
getText().replace(start, end, replacement);
setSelection(start, start + replacement.length());
mIndentLevel++;
} else {
// 光标位置插入Tab
getText().insert(start, " ");
setSelection(start + 4);
}
}
/**
*
*/
private String createIndentString(int level) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < level; i++) {
sb.append(" "); // 4空格缩进
}
return sb.toString();
}
/**
*
*/
private void toggleBulletList() {
mInBulletList = !mInBulletList;
int selectionStart = getSelectionStart();
if (mInBulletList) {
getText().insert(0, "• ");
setSelection(selectionStart + 2);
} else {
String text = getText().toString();
if (text.startsWith("• ")) {
getText().replace(0, 2, "");
setSelection(Math.max(0, selectionStart - 2));
}
}
}
/**
*
*/
public void indent() {
if (mIndentLevel < 5) {
mIndentLevel++;
updateIndent();
}
}
/**
*
*/
public void dedent() {
if (mIndentLevel > 0) {
mIndentLevel--;
updateIndent();
}
}
/**
*
*/
private void updateIndent() {
setText(getText()); // 触发重绘
// 实际应用中可以设置段落缩进
}
/*************************** 辅助功能 ***************************/ menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
/** public boolean onMenuItemClick(MenuItem item) {
* // goto a new intent
*/ urls[0].onClick(NoteEditText.this);
private void copyToClipboard(String label, String url) { return true;
android.content.ClipboardManager clipboard = }
(android.content.ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); });
android.content.ClipData clip = android.content.ClipData.newPlainText(label, url);
clipboard.setPrimaryClip(clip);
Toast.makeText(getContext(), R.string.note_link_copied, Toast.LENGTH_SHORT).show();
}
/**
*
*/
private void callPhoneNumber(String phoneNumber) {
try {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse(SCHEME_TEL + phoneNumber));
getContext().startActivity(intent);
} catch (Exception e) {
showActionError(R.string.note_cant_call);
}
}
/**
*
*/
private void sendEmail(String email) {
try {
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse(SCHEME_MAILTO + email));
getContext().startActivity(intent);
} catch (Exception e) {
showActionError(R.string.note_cant_email);
}
}
/**
*
*/
private void openWebPage(String url) {
try {
// 确保有协议前缀
if (!url.startsWith(SCHEME_HTTP) && !url.startsWith(SCHEME_HTTPS)) {
url = "http://" + url;
} }
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
getContext().startActivity(intent);
} catch (Exception e) {
showActionError(R.string.note_cant_open_link);
} }
super.onCreateContextMenu(menu);
} }
}
/**
*
*/
private void openMap(String location) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("geo:0,0?q=" + Uri.encode(location)));
getContext().startActivity(intent);
} catch (Exception e) {
showActionError(R.string.note_cant_open_map);
}
}
/**
*
*/
private void openNoteReference(String noteRef) {
Toast.makeText(
getContext(),
getContext().getString(R.string.note_link_jump, noteRef),
Toast.LENGTH_SHORT
).show();
// 实际实现应该跳转到对应笔记
}
/**
*
*/
private void showActionError(int resId) {
Toast.makeText(getContext(), resId, Toast.LENGTH_SHORT).show();
}
/**
* (使)
*/
public int getParagraphIndex() {
return mParagraphIndex;
}
/**
* ()
*/
@Override
public ActionMode startActionMode(ActionMode.Callback callback, int type) {
// 添加自定义action mode支持
return super.startActionMode(callback, type);
}
}

File diff suppressed because it is too large Load Diff

@ -15,7 +15,6 @@
*/ */
package net.micode.notes.widget; package net.micode.notes.widget;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider; import android.appwidget.AppWidgetProvider;
@ -23,7 +22,6 @@ import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri;
import android.util.Log; import android.util.Log;
import android.widget.RemoteViews; import android.widget.RemoteViews;
@ -34,344 +32,101 @@ import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesListActivity;
/**
*
*
* :
* 1.
* 2.
* 3.
* 4.
*
*
* - getBgResourceId() : ID
* - getLayoutId() : ID
* - getWidgetType() :
*/
public abstract class NoteWidgetProvider extends AppWidgetProvider { public abstract class NoteWidgetProvider extends AppWidgetProvider {
public static final String [] PROJECTION = new String [] {
// 数据库查询字段
private static final String[] PROJECTION = new String[] {
NoteColumns.ID, NoteColumns.ID,
NoteColumns.BG_COLOR_ID, NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET, NoteColumns.SNIPPET
NoteColumns.WIDGET_ID // 添加字段用于数据一致性检查
}; };
// 查询字段索引 public static final int COLUMN_ID = 0;
private static final int COLUMN_ID = 0; public static final int COLUMN_BG_COLOR_ID = 1;
private static final int COLUMN_BG_COLOR_ID = 1; public static final int COLUMN_SNIPPET = 2;
private static final int COLUMN_SNIPPET = 2;
private static final int COLUMN_WIDGET_ID = 3; // 添加的索引
private static final String TAG = "NoteWidgetProvider"; private static final String TAG = "NoteWidgetProvider";
// ========================== 生命周期方法 ==========================
/**
* - widget_id
*
* @param context
* @param appWidgetIds ID
*/
@Override @Override
public void onDeleted(Context context, int[] appWidgetIds) { public void onDeleted(Context context, int[] appWidgetIds) {
try { ContentValues values = new ContentValues();
// 防止空指针异常 values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
if (context == null || appWidgetIds == null || appWidgetIds.length == 0) { for (int i = 0; i < appWidgetIds.length; i++) {
Log.w(TAG, "无效的小部件删除请求"); context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
return; values,
} NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
for (int widgetId : appWidgetIds) {
// 使用try-catch防止个别更新失败中断整个操作
try {
int rowsUpdated = context.getContentResolver().update(
Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(widgetId) }
);
Log.d(TAG, "已清理小部件 " + widgetId + " 关联的笔记引用,影响行数: " + rowsUpdated);
} catch (Exception e) {
Log.e(TAG, "清理小部件 " + widgetId + " 关联的笔记引用时出错", e);
}
}
} catch (Exception e) {
Log.e(TAG, "小部件删除处理发生未预期错误", e);
} }
} }
// ========================== 核心功能方法 ========================== private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
/**
*
*
* @param context
* @param widgetId ID
* @return Cursor
*/
protected Cursor getNoteWidgetInfo(Context context, int widgetId) {
if (context == null || widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
return null;
}
String selection = NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?";
String[] selectionArgs = new String[] {
String.valueOf(widgetId),
String.valueOf(Notes.ID_TRASH_FOLDER) // 修正: TRASH_FOLER -> TRASH_FOLDER
};
try {
return context.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
PROJECTION, PROJECTION,
selection, NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
selectionArgs, new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null null);
);
} catch (Exception e) {
Log.e(TAG, "查询小部件 " + widgetId + " 关联笔记信息时出错", e);
return null;
}
} }
/**
* -
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
*/
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false); update(context, appWidgetManager, appWidgetIds, false);
} }
/**
* -
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
* @param privacyMode
*/
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) { boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (context == null || appWidgetManager == null || appWidgetIds == null) { if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
Log.w(TAG, "无效的更新请求参数"); int bgId = ResourceParser.getDefaultBgId(context);
return; String snippet = "";
} Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
for (int widgetId : appWidgetIds) { intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
// 跳过无效小部件ID intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
continue; Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
} if (c != null && c.moveToFirst()) {
if (c.getCount() > 1) {
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close();
return;
}
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
intent.setAction(Intent.ACTION_VIEW);
} else {
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
// 初始化默认值 if (c != null) {
int bgId = ResourceParser.NoteColor.YELLOW.id; c.close();
String snippet = ""; }
long noteId = Notes.DEFAULT_NOTE_ID;
// 准备笔记编辑意图
Intent noteIntent = new Intent(context, NoteEditActivity.class);
noteIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
noteIntent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, widgetId);
noteIntent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
// 查询数据库获取笔记信息 RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
try (Cursor cursor = getNoteWidgetInfo(context, widgetId)) { rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
if (cursor != null && cursor.moveToFirst()) { intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
// 安全检查确保一个WidgetID只关联一个笔记 /**
if (cursor.getCount() > 1) { * Generate the pending intent to start host for the widget
Log.w(TAG, "小部件 " + widgetId + " 关联多个笔记,请检查数据一致性"); */
PendingIntent pendingIntent = null;
// 清除异常关联的笔记 if (privacyMode) {
cleanInvalidWidgetLinks(context, widgetId, cursor); rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
// 只处理第一条记录 pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
} context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
// 获取笔记数据
noteId = cursor.getLong(COLUMN_ID);
snippet = cursor.getString(COLUMN_SNIPPET);
bgId = cursor.getInt(COLUMN_BG_COLOR_ID);
noteIntent.putExtra(Intent.EXTRA_UID, noteId);
noteIntent.setAction(Intent.ACTION_VIEW);
// 检查数据一致性
int storedWidgetId = cursor.getInt(COLUMN_WIDGET_ID);
if (storedWidgetId != widgetId) {
Log.w(TAG, String.format(
"笔记%d的小部件ID不一致: 数据库=%d, 请求=%d",
noteId, storedWidgetId, widgetId
));
}
} else { } else {
// 未找到关联笔记 rv.setTextViewText(R.id.widget_text, snippet);
snippet = context.getString(R.string.widget_no_content); pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
noteIntent.setAction(Intent.ACTION_INSERT_OR_EDIT); PendingIntent.FLAG_UPDATE_CURRENT);
} }
} catch (Exception e) {
Log.e(TAG, "处理小部件 " + widgetId + " 时发生错误", e);
snippet = context.getString(R.string.widget_error_loading);
}
// 创建小部件视图 rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), getLayoutId()); appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
// 设置背景
int bgResId = getBgResourceId(bgId);
remoteViews.setImageViewResource(R.id.widget_bg_image, bgResId);
noteIntent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
// 创建点击意图
PendingIntent pendingIntent;
if (privacyMode) {
// 隐私模式:显示提示文本,点击进入主列表
remoteViews.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_privacy_mode));
Intent listIntent = new Intent(context, NotesListActivity.class);
pendingIntent = PendingIntent.getActivity(
context, widgetId, listIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
} else {
// 正常模式:显示笔记摘要,点击进入笔记编辑
remoteViews.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(
context, widgetId, noteIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
}
// 设置点击事件
remoteViews.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
// 应用更新
try {
appWidgetManager.updateAppWidget(widgetId, remoteViews);
Log.d(TAG, "成功更新小部件: " + widgetId);
} catch (Exception e) {
Log.e(TAG, "更新小部件 " + widgetId + " 失败", e);
} }
} }
} }
// ========================== 抽象方法 ==========================
/**
* ID
*
* @param bgId ID
* @return drawableID
*/
protected abstract int getBgResourceId(int bgId); protected abstract int getBgResourceId(int bgId);
/**
* ID
*
* @return ID
*/
protected abstract int getLayoutId(); protected abstract int getLayoutId();
/**
*
*
* @return
*/
protected abstract int getWidgetType(); protected abstract int getWidgetType();
}
// ========================== 新增功能方法 ==========================
/**
*
*
* @param context
* @param widgetId ID
* @param cursor
*/
private void cleanInvalidWidgetLinks(Context context, int widgetId, Cursor cursor) {
if (cursor == null) return;
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
try {
// 遍历所有关联笔记
cursor.moveToPosition(-1); // 移动到开始位置
while (cursor.moveToNext()) {
long currentNoteId = cursor.getLong(COLUMN_ID);
int currentWidgetId = cursor.getInt(COLUMN_WIDGET_ID);
// 只保留最新关联的笔记
if (currentWidgetId == widgetId) {
Log.i(TAG, "保留笔记 " + currentNoteId + " 关联的小部件 " + widgetId);
continue;
}
// 解除其他笔记的关联
Uri noteUri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, currentNoteId);
int rows = context.getContentResolver().update(
noteUri, values, null, null);
if (rows > 0) {
Log.w(TAG, "已清理笔记 " + currentNoteId + " 上的小部件关联");
}
}
} catch (Exception e) {
Log.e(TAG, "清理无效小部件关联时出错", e);
}
}
// ========================== 新增功能:定时刷新支持 ==========================
/**
*
*
* @param context
* @param appWidgetId ID
* @param intervalMinutes
*/
public static void setUpdateInterval(Context context, int appWidgetId, int intervalMinutes) {
// 最小间隔限制
if (intervalMinutes < 15) {
Log.w(TAG, "刷新间隔不能小于15分钟");
intervalMinutes = 60; // 默认1小时
}
// TODO: 实现AlarmManager定时刷新逻辑
}
// ========================== 新增功能:多尺寸小部件支持 ==========================
/**
*
*/
public enum WidgetSize {
SIZE_1X1,
SIZE_2X1,
SIZE_2X2,
SIZE_3X3
}
/**
*
*
* @param context
* @param appWidgetId ID
* @return
*/
protected WidgetSize getWidgetSize(Context context, int appWidgetId) {
// TODO: 实现小部件尺寸检测逻辑
return WidgetSize.SIZE_2X1; // 默认返回2x1
}
}

Loading…
Cancel
Save