diff --git a/doc/小米便签的质量分析报告.docx b/doc/小米便签的质量分析报告.docx
deleted file mode 100644
index 6149195..0000000
Binary files a/doc/小米便签的质量分析报告.docx and /dev/null differ
diff --git a/src/data/Notes.java b/src/data/Notes.java
index 6273380..8142bef 100644
--- a/src/data/Notes.java
+++ b/src/data/Notes.java
@@ -17,40 +17,24 @@
package net.micode.notes.data;
import android.net.Uri;
-
-/**
- *
- * @Package: net.micode.notes.data
- * @ClassName: Notes
- * @Description: 集中定义一些常量
- */
public class Notes {
- //定义此应用ContentProvider的唯一标识
- public static final String AUTHORITY = "net.micode.notes.provider";
+ public static final String AUTHORITY = "micode_notes";
public static final String TAG = "Notes";
-
- /**
- *{@link Notes#TYPE_NOTE } 普通便签
- *{@link Notes#TYPE_FOLDER }文件夹
- *{@link Notes#TYPE_SYSTEM } 系统
- */
public static final int TYPE_NOTE = 0;
public static final int TYPE_FOLDER = 1;
public static final int TYPE_SYSTEM = 2;
/**
* Following IDs are system folders' identifiers
- * {@link Notes#ID_ROOT_FOLDER } is default folder 默认文件夹
- * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder 不属于文件夹的便签
- * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records 存储通话记录
- * {@link Notes#ID_TRASH_FOLER} 垃圾文件夹
+ * {@link Notes#ID_ROOT_FOLDER } is default folder
+ * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
+ * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0;
public static final int ID_TEMPARAY_FOLDER = -1;
public static final int ID_CALL_RECORD_FOLDER = -2;
public static final int ID_TRASH_FOLER = -3;
- //定义Intent Extra 的常量,用于在不同组件之间安全地传递数据
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
@@ -62,10 +46,10 @@ public class Notes {
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
- // 将不同数据类型对应到其MIMEitem类型字符串
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
+ public static final String ENCRYPTED_FOLDER = "vnd.android.cursor.item/encrypted_folder";
}
/**
@@ -78,7 +62,6 @@ public class Notes {
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
- //Note表数据库列名常量接口
public interface NoteColumns {
/**
* The unique ID for a row
@@ -113,8 +96,6 @@ public class Notes {
/**
* Folder's name or text content of note
- * 如果是文件夹,存储文件夹名字
- * 如果是便签,存储便签摘要
*
Type: TEXT
*/
public static final String SNIPPET = "snippet";
@@ -187,7 +168,6 @@ public class Notes {
public static final String VERSION = "version";
}
- //Data表数据库列名常量接口
public interface DataColumns {
/**
* The unique ID for a row
@@ -262,12 +242,10 @@ public class Notes {
public static final String DATA5 = "data5";
}
- //DataColumns接口的实现方式1:文本便签的数据模型
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* Type: Integer 1:check list mode 0: normal mode
- * data1表示是否是清单模式
*/
public static final String MODE = DATA1;
@@ -280,19 +258,16 @@ public class Notes {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
- //DataColumns接口的实现方式2:通话便签的数据模型
public static final class CallNote implements DataColumns {
/**
* Call date for this record
* Type: INTEGER (long)
- * data1表示通话时间戳
*/
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* Type: TEXT
- * data3表示电话号码
*/
public static final String PHONE_NUMBER = DATA3;
diff --git a/src/data/NotesDatabaseHelper.java b/src/data/NotesDatabaseHelper.java
index 12b1b3e..e1e055f 100644
--- a/src/data/NotesDatabaseHelper.java
+++ b/src/data/NotesDatabaseHelper.java
@@ -35,7 +35,7 @@ import net.micode.notes.data.Notes.NoteColumns;
public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db";
- private static final int DB_VERSION = 5;
+ private static final int DB_VERSION = 4;
public interface TABLE {
public static final String NOTE = "note";
@@ -68,9 +68,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
- NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," +
- NoteColumns.IS_HABIT + " INTEGER NOT NULL DEFAULT 0," +
- NoteColumns.HABIT_CONFIG + " TEXT NOT NULL DEFAULT ''" +
+ NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
/**创建data表的sql语句
@@ -406,11 +404,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
oldVersion++;
}
- if (oldVersion == 4) {
- upgradeToV5(db);
- oldVersion++;
- }
-
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
@@ -451,12 +444,4 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
-
- //更新到版本5:增加习惯打卡支持字段
- private void upgradeToV5(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.IS_HABIT
- + " INTEGER NOT NULL DEFAULT 0");
- db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.HABIT_CONFIG
- + " TEXT NOT NULL DEFAULT ''");
- }
}
diff --git a/src/tool/DataUtils.java b/src/tool/DataUtils.java
index 3c3e580..93157d8 100644
--- a/src/tool/DataUtils.java
+++ b/src/tool/DataUtils.java
@@ -37,7 +37,6 @@ import java.util.HashSet;
public class DataUtils {
public static final String TAG = "DataUtils";
- //批量操作删除便签,发生错误就返回flase,删除成功就返回true
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) {
if (ids == null) {
Log.d(TAG, "the ids is null");
@@ -47,42 +46,41 @@ public class DataUtils {
Log.d(TAG, "no id is in the hashset");
return true;
}
- // 构建删除操作:拼接标签ID到标签ContentUR
+
ArrayList operationList = new ArrayList();
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
-
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
- //创建一个 ContentProviderOperation.Builder,删除指定id的标签 只查询 ID 为 id 的那一条记录
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
- if (results == null || results.length == 0 || results[0] == null) {////上面的一条执行数据库操作删除并返回结果数组
+ if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
- } catch (RemoteException e) { //记录错误信息
+ } catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
- //便签转进新文件夹
+
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
- //批量操作便签转进新文件夹
+
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids,
long folderId) {
if (ids == null) {
@@ -93,9 +91,10 @@ public class DataUtils {
ArrayList operationList = new ArrayList();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
- .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); //创建更新,
+ .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
+ builder.withValue(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
operationList.add(builder.build());
}
@@ -113,21 +112,11 @@ public class DataUtils {
}
return false;
}
+
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
- public static int getUserFolderCount(ContentResolver resolver) {
- //查询设备中「类型为文件夹(TYPE_FOLDER)」且「父文件夹 ID 不等于回收站 ID(ID_TRASH_FOLER)」的文件夹总数。
-'''
-触发了一次跨进程的数据库查询操作
-Cursor query(
- Uri uri, // 要查询的数据 URI
- String[] projection, // 要返回的列(字段),null 表示所有列
- String selection, // WHERE 子句(不含 "WHERE" 关键字)
- String[] selectionArgs, // WHERE 子句中占位符 ? 的实际值
- String sortOrder // 排序方式(如 "date DESC")
-);
-'''
+ public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
@@ -138,7 +127,7 @@ Cursor query(
if(cursor != null) {
if(cursor.moveToFirst()) {
try {
- count = cursor.getInt(0);//从当前游标指向的行中,读取第 0 列(第一列)的整数类型数据
+ count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
@@ -148,11 +137,11 @@ Cursor query(
}
return count;
}
- //变迁是否可见(没进回收站)
+
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
- NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, //笔记的 parent_id 不能等于回收站的 ID(即不在回收站中)
+ NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);
@@ -165,7 +154,7 @@ Cursor query(
}
return exist;
}
- //便签还存在否
+
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
@@ -179,7 +168,7 @@ Cursor query(
}
return exist;
}
- //便签数据还存在否
+
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
@@ -193,12 +182,12 @@ Cursor query(
}
return exist;
}
- //查看是否存在一个名字是XX的文件夹
+
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
- NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + //类型是文件夹
- " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +//没进回收站
- " AND " + NoteColumns.SNIPPET + "=?", //存储在snippet的名字和下面的name一样
+ NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
+ " AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
@@ -209,9 +198,9 @@ Cursor query(
}
return exist;
}
- //获得文件夹下的便签,并仅返回它们的 Widget ID 和 Widget 类型
+
public static HashSet 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 },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
@@ -236,7 +225,7 @@ Cursor query(
}
return set;
}
- //获得电话号
+
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
@@ -246,7 +235,7 @@ Cursor query(
if (cursor != null && cursor.moveToFirst()) {
try {
- return cursor.getString(0); //返回电话号
+ return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
@@ -255,7 +244,7 @@ Cursor query(
}
return "";
}
-//通过电话号和日期查找便签
+
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },
@@ -276,7 +265,7 @@ Cursor query(
}
return 0;
}
-//通过id获得名字
+
public static String getSnippetById(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET },
@@ -294,15 +283,89 @@ Cursor query(
}
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
-//从一段文本(snippet)中提取作为标题
+
public static String getFormattedSnippet(String snippet) {
if (snippet != null) {
- snippet = snippet.trim();//清空无用制表符
- int index = snippet.indexOf('\n');//index即录字符串中第一个换行符 \n 出现的位置
+ snippet = snippet.trim();
+ int index = snippet.indexOf('\n');
if (index != -1) {
- snippet = snippet.substring(0, index);//只获取换行前的部分(核心)。
+ snippet = snippet.substring(0, index);
}
}
return snippet;
}
+
+ /**
+ * 获取指定文件夹中的所有便签ID
+ * @param resolver ContentResolver实例
+ * @param folderId 文件夹ID
+ * @return 便签ID的HashSet
+ */
+ public static HashSet getNotesInFolder(ContentResolver resolver, long folderId) {
+ Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
+ new String[] { NoteColumns.ID },
+ NoteColumns.PARENT_ID + "=?",
+ new String[] { String.valueOf(folderId) },
+ null);
+
+ HashSet ids = new HashSet();
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ do {
+ try {
+ ids.add(cursor.getLong(0));
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(TAG, "Get note id fails " + e.toString());
+ }
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ return ids;
+ }
+ public static boolean batchMoveToTrash(ContentResolver resolver, HashSet ids,
+ long originFolderId) {
+ if (ids == null) {
+ Log.d(TAG, "the ids is null");
+ return true;
+ }
+ ArrayList operationList = new ArrayList();
+ long now = System.currentTimeMillis();
+ for (long id : ids) {
+ ContentProviderOperation.Builder builder = ContentProviderOperation
+ .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
+ builder.withValue(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
+ builder.withValue(NoteColumns.ORIGIN_PARENT_ID, originFolderId);
+ builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
+ builder.withValue(NoteColumns.MODIFIED_DATE, now);
+ operationList.add(builder.build());
+ }
+ try {
+ ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
+ if (results == null || results.length == 0 || results[0] == null) {
+ Log.d(TAG, "move to trash failed, ids:" + ids.toString());
+ return false;
+ }
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ }
+ return false;
+ }
+
+ public static void moveNotesToTrashForFolder(ContentResolver resolver, long folderId) {
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
+ values.put(NoteColumns.ORIGIN_PARENT_ID, folderId);
+ values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
+ resolver.update(Notes.CONTENT_NOTE_URI, values,
+ NoteColumns.PARENT_ID + "=?",
+ new String[] { String.valueOf(folderId) });
+ }
+
}
+
+
diff --git a/src/tool/TranslateUtils.java b/src/tool/TranslateUtils.java
deleted file mode 100644
index 6d74a64..0000000
--- a/src/tool/TranslateUtils.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package net.micode.notes.tool;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.os.Build;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.security.MessageDigest;
-import java.util.Locale;
-
-public class TranslateUtils {
- private static final String TAG = "TranslateUtils";
-
-
- private static final String YOUDAO_APP_KEY = "3abfa533dbdc44d1";
- private static final String YOUDAO_APP_SECRET = "aliNHKWhhTlaLjRAkOce4cHTubriEl0c";
- private static final String YOUDAO_URL = "https://openapi.youdao.com/api";
-
- public static boolean isOnline(Context ctx) {
- ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (cm == null) return false;
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- Network network = cm.getActiveNetwork();
- if (network == null) return false;
-
- NetworkCapabilities capabilities = cm.getNetworkCapabilities(network);
- return capabilities != null && (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
- || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
- || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
- } else {
- // 兼容低版本
- NetworkInfo ni = cm.getActiveNetworkInfo();
- return ni != null && ni.isConnected();
- }
- }
-
- public static String translateParagraph(String text, String targetLang) {
- if (TextUtils.isEmpty(YOUDAO_APP_KEY) || TextUtils.isEmpty(YOUDAO_APP_SECRET)) {
- Log.w(TAG, "Youdao app key/secret not configured");
- return null;
- }
- try {
- Log.d(TAG, "Starting translation: text=" + text + ", targetLang=" + targetLang);
-
- String q = text;
- String from = "auto";
- String to = targetLang == null ? "en" : targetLang;
- String salt = String.valueOf(System.currentTimeMillis());
- String sign = md5(YOUDAO_APP_KEY + q + salt + YOUDAO_APP_SECRET);
-
- StringBuilder sb = new StringBuilder();
- sb.append("appKey=").append(urlEncode(YOUDAO_APP_KEY));
- sb.append("&q=").append(urlEncode(q));
- sb.append("&salt=").append(urlEncode(salt));
- sb.append("&from=").append(urlEncode(from));
- sb.append("&to=").append(urlEncode(to));
- sb.append("&sign=").append(urlEncode(sign));
-
- URL url = new URL(YOUDAO_URL);
- Log.d(TAG, "Connecting to: " + YOUDAO_URL);
-
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("POST");
- conn.setConnectTimeout(10000);
- conn.setReadTimeout(10000);
- conn.setDoOutput(true);
- conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
-
- OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
- writer.write(sb.toString());
- writer.flush();
- writer.close();
-
- int code = conn.getResponseCode();
- Log.d(TAG, "Response code: " + code);
-
- if (code != 200) {
- Log.w(TAG, "Youdao response code:" + code);
- // 读取错误响应
- try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "UTF-8"))) {
- StringBuilder errorResp = new StringBuilder();
- String line;
- while ((line = br.readLine()) != null) {
- errorResp.append(line);
- }
- Log.w(TAG, "Error response: " + errorResp.toString());
- } catch (Exception e) {
- Log.w(TAG, "Failed to read error response", e);
- }
- return null;
- }
-
- BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
- StringBuilder resp = new StringBuilder();
- String line;
- while ((line = br.readLine()) != null) {
- resp.append(line);
- }
- br.close();
- conn.disconnect();
-
- Log.d(TAG, "Response: " + resp.toString());
-
- JSONObject json = new JSONObject(resp.toString());
- if (json.has("translation")) {
- JSONArray arr = json.getJSONArray("translation");
- if (arr.length() > 0) {
- String result = arr.getString(0);
- Log.d(TAG, "Translation result: " + result);
- return result;
- }
- }
- // fallback: try webdict or basic
- if (json.has("web") && json.getJSONArray("web").length() > 0) {
- JSONObject w = json.getJSONArray("web").getJSONObject(0);
- if (w.has("value")) {
- JSONArray v = w.getJSONArray("value");
- if (v.length() > 0) {
- String result = v.getString(0);
- Log.d(TAG, "Web dict fallback result: " + result);
- return result;
- }
- }
- }
- Log.w(TAG, "No translation found in response");
- return null;
- } catch (Exception e) {
- Log.w(TAG, "translate error: " + e.getMessage(), e);
- return null;
- }
- }
-
- private static String urlEncode(String s) throws Exception {
- return java.net.URLEncoder.encode(s, "UTF-8");
- }
-
- private static String md5(String s) throws Exception {
- MessageDigest md = MessageDigest.getInstance("MD5");
- byte[] bytes = md.digest(s.getBytes("UTF-8"));
- StringBuilder sb = new StringBuilder();
- for (byte b : bytes) {
- String hex = Integer.toHexString(b & 0xff);
- if (hex.length() == 1) sb.append('0');
- sb.append(hex);
- }
- return sb.toString();
- }
-}
diff --git a/src/ui/AlarmAlertActivity.java b/src/ui/AlarmAlertActivity.java
index 11798f8..6e80b45 100644
--- a/src/ui/AlarmAlertActivity.java
+++ b/src/ui/AlarmAlertActivity.java
@@ -18,16 +18,11 @@ package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
-import android.app.PendingIntent;
-import android.app.AlarmManager;
-import android.content.ContentUris;
-import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
-import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
@@ -35,29 +30,17 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
-import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
-import android.view.View;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import java.util.Calendar;
//继承Activity //承诺实现click,dismiss接口,不继承功能
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
- private static final String TAG = "AlarmAlertActivity";
private long mNoteId;
private String mSnippet;
private static final int SNIPPET_PREW_MAX_LEN = 60;
@@ -100,12 +83,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
mPlayer = new MediaPlayer();
//如果不在回收站就显示文字并发出声音
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
- int isHabit = intent.getIntExtra("habit_alarm", 0);
- if (isHabit == 1) {
- showHabitDialog(intent);
- } else {
- showActionDialog();
- }
+ showActionDialog();
playAlarmSound();
} else {
finish();
@@ -156,190 +134,11 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this); //setPositive/Negative/NeutralButton()设置:确定,取消,中立按钮;
if (isScreenOn()) {
- dialog.setNegativeButton(R.string.notealert_enter, this);
+ dialog.setNegativeButton(R.string.notealert_enter, this); //取消
}
- dialog.show().setOnDismissListener(this);
+ dialog.show().setOnDismissListener(this); //设置关闭监听
}
-
- // Habit specific dialog with actions: complete, snooze, skip, abandon
- private void showHabitDialog(Intent intent) {
- View v = getLayoutInflater().inflate(R.layout.habit_alert_dialog, null);
- TextView tvTitle = (TextView) v.findViewById(R.id.habit_alert_title);
- TextView tvSnippet = (TextView) v.findViewById(R.id.habit_alert_snippet);
- final Button btnComplete = (Button) v.findViewById(R.id.habit_btn_complete);
- final Button btnSnooze10 = (Button) v.findViewById(R.id.habit_btn_snooze10);
- final Button btnSnooze30 = (Button) v.findViewById(R.id.habit_btn_snooze30);
- final Button btnSkip = (Button) v.findViewById(R.id.habit_btn_skip);
- final Button btnAbandon = (Button) v.findViewById(R.id.habit_btn_abandon);
-
- tvTitle.setText(getString(R.string.app_name));
- tvSnippet.setText(mSnippet);
-
- final AlertDialog d = new AlertDialog.Builder(this)
- .setView(v)
- .create();
-
- btnComplete.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- recordHabitHistory(mNoteId, "completed", "");
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_record_complete, Toast.LENGTH_SHORT).show();
- d.dismiss();
- }
- });
-
- btnSnooze10.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- scheduleSnooze(mNoteId, 10);
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_snoozed, Toast.LENGTH_SHORT).show();
- d.dismiss();
- }
- });
-
- btnSnooze30.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- scheduleSnooze(mNoteId, 30);
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_snoozed, Toast.LENGTH_SHORT).show();
- d.dismiss();
- }
- });
-
- btnSkip.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showSkipReasonDialog();
- // 不要立即关闭对话框,让用户选择原因
- }
- });
-
- btnAbandon.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- abandonHabit(mNoteId);
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_abandoned, Toast.LENGTH_SHORT).show();
- d.dismiss();
- }
- });
-
- d.setOnDismissListener(this);
- d.show();
- }
-
- private void showSkipReasonDialog() {
- final String[] reasons = new String[] { getString(R.string.skip_reason_busy),
- getString(R.string.skip_reason_sick), getString(R.string.skip_reason_other) };
- AlertDialog.Builder b = new AlertDialog.Builder(this);
- b.setTitle(R.string.habit_skip_title);
- b.setItems(reasons, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String reason = reasons[which];
- recordHabitHistory(mNoteId, "skipped", reason);
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_record_skipped, Toast.LENGTH_SHORT).show();
- // 选择原因后关闭主对话框
- dialog.dismiss();
- finish();
- }
- });
- b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- b.show();
- }
-
- private void scheduleSnooze(long noteId, int minutes) {
- try {
- Intent intent = new Intent(this, AlarmReceiver.class);
- intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId));
- intent.putExtra("habit_alarm", 1);
- int req = (int) (noteId ^ 0x100000) + minutes; // unique-ish
- PendingIntent pi = PendingIntent.getBroadcast(this, req, intent, 0);
- AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
- long trigger = System.currentTimeMillis() + minutes * 60 * 1000L;
- am.set(AlarmManager.RTC_WAKEUP, trigger, pi);
- } catch (Exception e) {
- Log.e(TAG, "Schedule snooze error", e);
- }
- }
-
- private void abandonHabit(long noteId) {
- try {
- ContentValues values = new ContentValues();
- values.put(Notes.NoteColumns.IS_HABIT, 0);
- values.put(Notes.NoteColumns.HABIT_CONFIG, "");
- getContentResolver().update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), values, null, null);
- // cancel repeating alarm
- Intent intent = new Intent(this, AlarmReceiver.class);
- intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId));
- intent.putExtra("habit_alarm", 1);
- PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, 0);
- AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
- am.cancel(pi);
- } catch (Exception e) {
- Log.e(TAG, "Abandon habit error", e);
- }
- }
-
- // Record history into habit_config.history (append object {date,status,reason})
- private void recordHabitHistory(long noteId, String status, String reason) {
- try {
- Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
- Cursor c = getContentResolver().query(uri, new String[]{Notes.NoteColumns.HABIT_CONFIG}, null, null, null);
- String cfg = "";
- if (c != null) {
- if (c.moveToFirst()) cfg = c.getString(0);
- c.close();
- }
- JSONObject jo = cfg != null && cfg.length() > 0 ? new JSONObject(cfg) : new JSONObject();
- JSONArray history = jo.has("history") ? jo.getJSONArray("history") : new JSONArray();
-
- // 获取当前时间戳
- long recordTime = System.currentTimeMillis();
-
- // 创建新记录
- JSONObject newEntry = new JSONObject();
- newEntry.put("date", recordTime);
- newEntry.put("status", status);
- newEntry.put("reason", reason == null ? "" : reason);
-
- // 检查是否已经存在该日期的记录,如果有则替换
- java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault());
- String recordKey = sdf.format(new java.util.Date(recordTime));
- boolean foundRecord = false;
-
- for (int i = 0; i < history.length(); i++) {
- JSONObject entry = history.getJSONObject(i);
- long entryDate = entry.optLong("date", 0);
- String entryKey = sdf.format(new java.util.Date(entryDate));
- if (recordKey.equals(entryKey)) {
- // 替换该日期的记录
- history.put(i, newEntry);
- foundRecord = true;
- break;
- }
- }
-
- // 如果没有该日期的记录,添加新记录
- if (!foundRecord) {
- history.put(newEntry);
- }
-
- jo.put("history", history);
- ContentValues values = new ContentValues();
- values.put(Notes.NoteColumns.HABIT_CONFIG, jo.toString());
- getContentResolver().update(uri, values, null, null);
- // 通知数据变化,以便日历视图刷新
- getContentResolver().notifyChange(uri, null);
- } catch (JSONException e) {
- Log.e(TAG, "Record habit history json error", e);
- }
- }
-
+ //选择negative时执行跳转到笔记 (那可能positive仅关闭弹窗)
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
diff --git a/src/ui/BackgroundManager.java b/src/ui/BackgroundManager.java
deleted file mode 100644
index 7b8d7b2..0000000
--- a/src/ui/BackgroundManager.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package net.micode.notes.ui;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.preference.PreferenceManager;
-import android.widget.Toast;
-import android.view.View;
-
-import net.micode.notes.R;
-
-import java.io.InputStream;
-
-public class BackgroundManager {
- public static final int REQUEST_CODE_PICK_IMAGE = 2001;
-
- private static final String PREF_BG_TYPE = "pref_bg_type"; // 0=color,1=builtin,2=uri
- private static final String PREF_BG_COLOR = "pref_bg_color";
- private static final String PREF_BG_RES_ID = "pref_bg_res_id";
- private static final String PREF_BG_URI = "pref_bg_uri";
-
- private static final int BG_TYPE_COLOR = 0;
- private static final int BG_TYPE_BUILTIN = 1;
- private static final int BG_TYPE_URI = 2;
-
- private Activity mActivity;
- private View mRootView;
-
- public BackgroundManager(Activity activity, int rootViewId) {
- mActivity = activity;
- mRootView = activity.findViewById(rootViewId);
- if (mRootView == null) {
- mRootView = activity.getWindow().getDecorView();
- }
- }
-
- public void applyBackgroundFromPrefs() {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
- int type = sp.getInt(PREF_BG_TYPE, -1);
- if (type == BG_TYPE_COLOR) {
- int color = sp.getInt(PREF_BG_COLOR, 0xFFFFFFFF);
- applyColorBackground(color);
- } else if (type == BG_TYPE_BUILTIN) {
- int resId = sp.getInt(PREF_BG_RES_ID, R.drawable.list_background);
- applyBuiltinBackground(resId);
- } else if (type == BG_TYPE_URI) {
- String uriStr = sp.getString(PREF_BG_URI, null);
- if (uriStr != null) {
- applyUriBackground(Uri.parse(uriStr));
- }
- } else {
- mRootView.setBackgroundResource(R.drawable.list_background);
- }
- }
-
- public void applyColorAndSave(int color) {
- applyColorBackground(color);
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
- sp.edit().putInt(PREF_BG_TYPE, BG_TYPE_COLOR).putInt(PREF_BG_COLOR, color).commit();
- }
-
- public void applyBuiltinAndSave(int resId) {
- applyBuiltinBackground(resId);
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
- sp.edit().putInt(PREF_BG_TYPE, BG_TYPE_BUILTIN).putInt(PREF_BG_RES_ID, resId).commit();
- }
-
- public void pickImageFromGallery() {
- Intent intent = new Intent(Intent.ACTION_PICK);
- intent.setType("image/*");
- mActivity.startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);
- }
-
- public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_CODE_PICK_IMAGE && resultCode == Activity.RESULT_OK && data != null) {
- Uri uri = data.getData();
- if (uri != null) {
- applyUriAndSave(uri);
- }
- return true;
- }
- return false;
- }
-
- public void resetToDefaultAndClear() {
- mRootView.setBackgroundResource(R.drawable.list_background);
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
- sp.edit().remove(PREF_BG_TYPE).remove(PREF_BG_COLOR).remove(PREF_BG_RES_ID).remove(PREF_BG_URI).commit();
- }
-
- private void applyColorBackground(int color) {
- mRootView.setBackgroundColor(color);
- }
-
- private void applyBuiltinBackground(int resId) {
- mRootView.setBackgroundResource(resId);
- }
-
- private void applyUriBackground(Uri uri) {
- try {
- InputStream is = mActivity.getContentResolver().openInputStream(uri);
- if (is != null) {
- Drawable d = Drawable.createFromStream(is, uri.toString());
- mRootView.setBackgroundDrawable(d);
- is.close();
- }
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(mActivity, "无法加载图片", Toast.LENGTH_SHORT).show();
- }
- }
-
- private void applyUriAndSave(Uri uri) {
- try {
- InputStream is = mActivity.getContentResolver().openInputStream(uri);
- if (is != null) {
- Drawable d = Drawable.createFromStream(is, uri.toString());
- mRootView.setBackgroundDrawable(d);
- is.close();
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
- sp.edit().putInt(PREF_BG_TYPE, BG_TYPE_URI).putString(PREF_BG_URI, uri.toString()).commit();
- }
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(mActivity, "无法加载图片", Toast.LENGTH_SHORT).show();
- }
- }
-}
diff --git a/src/ui/CallRecordReceiver.java b/src/ui/CallRecordReceiver.java
deleted file mode 100644
index 8683e9d..0000000
--- a/src/ui/CallRecordReceiver.java
+++ /dev/null
@@ -1,102 +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.ui;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import net.micode.notes.data.Notes;
-
-/**
- * 通话记录广播接收器,用于监听系统通话状态变化
- */
-public class CallRecordReceiver extends BroadcastReceiver {
- private static final String TAG = "CallRecordReceiver";
- private static String mLastPhoneNumber;// 记录最后一个电话号码
- private static long mCallStartTime;// 记录通话开始时间
- private static boolean mIsOutgoingCall;// 是否为拨出电话
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction() != null) {
- Log.d(TAG, "Received broadcast: " + intent.getAction());
-
- if (intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
- String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
- String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
-
- Log.d(TAG, "Phone state changed: " + state + ", number: " + phoneNumber);
-
- if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
- // 电话响铃,记录来电号码
- mLastPhoneNumber = phoneNumber;
- mCallStartTime = System.currentTimeMillis();
- mIsOutgoingCall = false;
- Log.d(TAG, "Incoming call ringing: " + phoneNumber);
- } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) {
- // 电话接通,记录开始时间
- if (mCallStartTime == 0) {
- mCallStartTime = System.currentTimeMillis();
- }
- if (phoneNumber != null) {
- mLastPhoneNumber = phoneNumber;
- }
- Log.d(TAG, "Call answered");
- } else if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) {
- // 电话挂断,创建通话记录便签
- if (mLastPhoneNumber != null && mCallStartTime > 0) {
- long callEndTime = System.currentTimeMillis();
- long callDuration = callEndTime - mCallStartTime;
-
- // 创建通话记录便签
- Log.d(TAG, "Call ended, creating call note for: " + mLastPhoneNumber);
- Intent serviceIntent = new Intent(context, CallRecordService.class);
- serviceIntent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, mLastPhoneNumber);
- serviceIntent.putExtra("call_date", callEndTime);
- serviceIntent.putExtra("call_duration", callDuration);
-
- // 启动服务创建通话记录便签
- try {
- context.startService(serviceIntent);
- Log.d(TAG, "Successfully started CallRecordService");
- } catch (Exception e) {
- Log.e(TAG, "Error starting CallRecordService", e);
- }
- }
-
- // 重置状态
- mLastPhoneNumber = null;
- mCallStartTime = 0;
- mIsOutgoingCall = false;
- Log.d(TAG, "Call state reset to idle");
- }
- } else if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
- // 拨出电话
- String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
- if (phoneNumber != null) {
- mLastPhoneNumber = phoneNumber;
- mCallStartTime = System.currentTimeMillis();
- mIsOutgoingCall = true;
- Log.d(TAG, "Outgoing call started: " + phoneNumber);
- }
- }
- }
- }
-}
diff --git a/src/ui/CallRecordService.java b/src/ui/CallRecordService.java
deleted file mode 100644
index 15451ef..0000000
--- a/src/ui/CallRecordService.java
+++ /dev/null
@@ -1,105 +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.ui;
-
-import android.app.IntentService;
-import android.content.Intent;
-import android.util.Log;
-
-import net.micode.notes.data.Notes;
-import net.micode.notes.model.WorkingNote;
-import net.micode.notes.tool.ResourceParser;
-
-/**
- * 通话记录服务,用于创建通话记录便签
- */
-public class CallRecordService extends IntentService {
- private static final String TAG = "CallRecordService";
-
- public CallRecordService() {
- super("CallRecordService");
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- if (intent != null) {
- String phoneNumber = intent.getStringExtra(android.telephony.TelephonyManager.EXTRA_INCOMING_NUMBER);
- long callDate = intent.getLongExtra("call_date", System.currentTimeMillis());
- long callDuration = intent.getLongExtra("call_duration", 0);
-
- if (phoneNumber != null) {
- Log.d(TAG, "Creating call note for number: " + phoneNumber + ", date: " + callDate);
- createCallNote(phoneNumber, callDate, callDuration);
- }
- }
- }
-
- /**
- * 创建通话记录便签并跳转到编辑视图
- * @param phoneNumber 电话号码
- * @param callDate 通话时间
- * @param callDuration 通话时长
- */
- private void createCallNote(String phoneNumber, long callDate, long callDuration) {
- try {
- Log.d(TAG, "Starting createCallNote for number: " + phoneNumber);
-
- // 创建空便签
- WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_CALL_RECORD_FOLDER,
- 0, Notes.TYPE_WIDGET_INVALIDE, ResourceParser.BLUE);
-
- // 转换为通话便签
- note.convertToCallNote(phoneNumber, callDate);
-
- // 设置空格作为内容,确保能保存
- note.setWorkingText(" ");
-
- // 保存便签获取ID
- long noteId = -1;
- if (note.saveNote()) {
- noteId = note.getNoteId();
- Log.d(TAG, "Call note created successfully for: " + phoneNumber + ", noteId: " + noteId);
- } else {
- Log.e(TAG, "Failed to create call note for: " + phoneNumber);
- return;
- }
-
- // 跳转到刚刚创建的通话记录便签的编辑视图
- if (noteId > 0) {
- Log.d(TAG, "Attempting to start NoteEditActivity for noteId: " + noteId);
- Intent intent = new Intent(this, NoteEditActivity.class);
- // 使用 ACTION_VIEW 以便 NoteEditActivity 正确处理并打开已有便签
- intent.setAction(Intent.ACTION_VIEW);
- // 使用正确的键名传递便签ID
- intent.putExtra(Intent.EXTRA_UID, noteId);
- intent.putExtra(Notes.INTENT_EXTRA_CALL_DATE, callDate);
- intent.putExtra("phone_number", phoneNumber);
- // 从服务启动 Activity,需要 NEW_TASK 标志;避免清除整个任务栈
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- startActivity(intent);
- Log.d(TAG, "Successfully started NoteEditActivity for call note: " + noteId);
- } catch (Exception e) {
- Log.e(TAG, "Error starting NoteEditActivity", e);
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "Error creating call note", e);
- }
- }
-
-}
diff --git a/src/ui/EncryptedFolderManager.java b/src/ui/EncryptedFolderManager.java
new file mode 100644
index 0000000..326177c
--- /dev/null
+++ b/src/ui/EncryptedFolderManager.java
@@ -0,0 +1,218 @@
+
+ /*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.micode.notes.ui;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.DataColumns;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.tool.DataUtils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class EncryptedFolderManager {
+ private static final String TAG = "EncryptedFolderManager";
+ private final Context mContext;
+ private final ContentResolver mResolver;
+ private final Callback mCallback;
+
+ public interface Callback {
+ void onEncryptedFolderCreated();
+
+ void onEncryptedFolderUnlocked(NoteItemData data);
+ }
+
+ public EncryptedFolderManager(Context context, ContentResolver resolver, Callback callback) {
+ mContext = context;
+ mResolver = resolver;
+ mCallback = callback;
+ }
+
+ public void showCreateEncryptedFolderDialog() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_encrypted_folder, null);
+ final EditText etName = (EditText) view.findViewById(R.id.et_encrypted_folder_name);
+ final EditText etQuestion = (EditText) view.findViewById(R.id.et_encrypted_question);
+ final EditText etAnswer = (EditText) view.findViewById(R.id.et_encrypted_answer);
+ etAnswer.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ builder.setTitle(R.string.encrypted_folder_title);
+ builder.setView(view);
+ builder.setPositiveButton(android.R.string.ok, null);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ final Dialog dialog = builder.show();
+ final Button positive = (Button) dialog.findViewById(android.R.id.button1);
+ positive.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String name = etName.getText().toString().trim();
+ String question = etQuestion.getText().toString().trim();
+ String answer = etAnswer.getText().toString().trim();
+ if (TextUtils.isEmpty(name)) {
+ etName.setError(mContext.getString(R.string.hint_foler_name));
+ return;
+ }
+ if (TextUtils.isEmpty(question)) {
+ etQuestion.setError(mContext.getString(R.string.encrypted_question_empty));
+ return;
+ }
+ if (TextUtils.isEmpty(answer)) {
+ etAnswer.setError(mContext.getString(R.string.encrypted_answer_empty));
+ return;
+ }
+ if (DataUtils.checkVisibleFolderName(mResolver, name)) {
+ Toast.makeText(mContext,
+ mContext.getString(R.string.folder_exist, name), Toast.LENGTH_LONG).show();
+ return;
+ }
+ long folderId = createEncryptedFolder(name, question, answer);
+ if (folderId > 0) {
+ dialog.dismiss();
+ if (mCallback != null) {
+ mCallback.onEncryptedFolderCreated();
+ }
+ }
+ }
+ });
+ }
+
+ public EncryptedFolderInfo getEncryptedFolderInfo(long folderId) {
+ Cursor cursor = mResolver.query(Notes.CONTENT_DATA_URI,
+ new String[] { DataColumns.DATA3, DataColumns.DATA4 },
+ DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?",
+ new String[] { String.valueOf(folderId), Notes.DataConstants.ENCRYPTED_FOLDER },
+ null);
+ if (cursor == null) {
+ return null;
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ String question = cursor.getString(0);
+ String answerHash = cursor.getString(1);
+ if (!TextUtils.isEmpty(question) && !TextUtils.isEmpty(answerHash)) {
+ return new EncryptedFolderInfo(folderId, question, answerHash);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ return null;
+ }
+
+ public void showEncryptedUnlockDialog(final EncryptedFolderInfo info, final NoteItemData data) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_encrypted_unlock, null);
+ TextView tvQuestion = (TextView) view.findViewById(R.id.tv_encrypted_question);
+ final EditText etAnswer = (EditText) view.findViewById(R.id.et_encrypted_answer);
+ tvQuestion.setText(info.question);
+ builder.setTitle(R.string.encrypted_unlock_title);
+ builder.setView(view);
+ builder.setPositiveButton(android.R.string.ok, null);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ final Dialog dialog = builder.show();
+ final Button positive = (Button) dialog.findViewById(android.R.id.button1);
+ positive.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String answer = etAnswer.getText().toString().trim();
+ if (TextUtils.isEmpty(answer)) {
+ etAnswer.setError(mContext.getString(R.string.encrypted_answer_empty));
+ return;
+ }
+ if (!TextUtils.equals(hashAnswer(answer), info.answerHash)) {
+ Toast.makeText(mContext, R.string.encrypted_answer_wrong,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ dialog.dismiss();
+ if (mCallback != null) {
+ mCallback.onEncryptedFolderUnlocked(data);
+ }
+ }
+ });
+ }
+
+ private long createEncryptedFolder(String name, String question, String answer) {
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.SNIPPET, name);
+ values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
+ values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ Uri uri = mResolver.insert(Notes.CONTENT_NOTE_URI, values);
+ if (uri == null) {
+ return -1;
+ }
+ long folderId = -1;
+ try {
+ folderId = Long.parseLong(uri.getPathSegments().get(1));
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Create encrypted folder failed", e);
+ return -1;
+ }
+ ContentValues dataValues = new ContentValues();
+ dataValues.put(DataColumns.NOTE_ID, folderId);
+ dataValues.put(DataColumns.MIME_TYPE, Notes.DataConstants.ENCRYPTED_FOLDER);
+ dataValues.put(DataColumns.DATA3, question);
+ dataValues.put(DataColumns.DATA4, hashAnswer(answer));
+ mResolver.insert(Notes.CONTENT_DATA_URI, dataValues);
+ return folderId;
+ }
+
+ private String hashAnswer(String answer) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] result = digest.digest(answer.getBytes());
+ StringBuilder sb = new StringBuilder();
+ for (byte b : result) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, "Hash error", e);
+ return "";
+ }
+ }
+
+ public static class EncryptedFolderInfo {
+ private final long folderId;
+ private final String question;
+ private final String answerHash;
+
+ private EncryptedFolderInfo(long folderId, String question, String answerHash) {
+ this.folderId = folderId;
+ this.question = question;
+ this.answerHash = answerHash;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ui/ImageInsertHelper.java b/src/ui/ImageInsertHelper.java
new file mode 100644
index 0000000..cdc2431
--- /dev/null
+++ b/src/ui/ImageInsertHelper.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.ui;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import net.micode.notes.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import jp.wasabeef.richeditor.RichEditor;
+
+public class ImageInsertHelper {
+ private static final String TAG = "ImageInsertHelper";
+ private final Activity mActivity;
+ private final int mRequestCode;
+
+ public static class Result {
+ public final boolean success;
+ public final String localPath;
+ public final String html;
+
+ private Result(boolean success, String localPath, String html) {
+ this.success = success;
+ this.localPath = localPath;
+ this.html = html;
+ }
+ }
+
+ public ImageInsertHelper(Activity activity, int requestCode) {
+ mActivity = activity;
+ mRequestCode = requestCode;
+ }
+
+ public void startPickImage() {
+ try {
+ Intent intent;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ } else {
+ intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ }
+ intent.setType("image/*");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ }
+ mActivity.startActivityForResult(intent, mRequestCode);
+ } catch (ActivityNotFoundException e) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ try {
+ mActivity.startActivityForResult(intent, mRequestCode);
+ } catch (ActivityNotFoundException ex) {
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "No image picker available", ex);
+ }
+ }
+ }
+
+ public Result handleActivityResult(int requestCode, int resultCode, Intent data, RichEditor editor) {
+ if (requestCode != mRequestCode) {
+ return null;
+ }
+ if (resultCode != Activity.RESULT_OK || data == null) {
+ return new Result(false, null, null);
+ }
+ Uri uri = data.getData();
+ if (uri == null) {
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ return new Result(false, null, null);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ final int takeFlags = data.getFlags()
+ & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ try {
+ mActivity.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Persistable uri permission not granted", e);
+ }
+ }
+ String localImagePath = saveImageToLocal(uri);
+ if (TextUtils.isEmpty(localImagePath)) {
+ return new Result(false, null, null);
+ }
+ String newHtml = appendImageHtml(editor, localImagePath);
+ return new Result(true, localImagePath, newHtml);
+ }
+
+ private String appendImageHtml(RichEditor editor, String localImagePath) {
+ String imgHtmlTag = buildImageHtmlTag(localImagePath);
+ String curHtml = normalizeEditorHtml(editor.getHtml());
+ String newHtml = curHtml + imgHtmlTag;
+ editor.setHtml(newHtml);
+ editor.focusEditor();
+ return newHtml;
+ }
+
+ String buildImageHtmlTag(String localImagePath) {
+ String imgUrl = Uri.fromFile(new File(localImagePath)).toString();
+ return "
";
+ }
+
+ private String normalizeEditorHtml(String html) {
+ if (TextUtils.isEmpty(html) || "null".equalsIgnoreCase(html)) {
+ return "";
+ }
+ return html;
+ }
+
+ private String saveImageToLocal(Uri uri) {
+ try {
+ File baseDir = mActivity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+ if (baseDir == null) {
+ baseDir = mActivity.getFilesDir();
+ }
+ File appDir = new File(baseDir, "note_images");
+ if (!appDir.exists() && !appDir.mkdirs()) {
+ Log.e(TAG, "Create image directory failed: " + appDir.getAbsolutePath());
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ return null;
+ }
+ String fileName = "note_" + System.currentTimeMillis() + ".jpg";
+ File targetFile = new File(appDir, fileName);
+ try (InputStream is = mActivity.getContentResolver().openInputStream(uri);
+ OutputStream os = new FileOutputStream(targetFile)) {
+ if (is == null) {
+ Log.e(TAG, "Open image stream failed: " + uri);
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ return null;
+ }
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = is.read(buffer)) > 0) {
+ os.write(buffer, 0, len);
+ }
+ }
+ return targetFile.getAbsolutePath();
+ } catch (Exception e) {
+ Log.e(TAG, "Save image failed", e);
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ return null;
+ }
+ }
+}
diff --git a/src/ui/MemoryBottleDialog.java b/src/ui/MemoryBottleDialog.java
new file mode 100644
index 0000000..897b312
--- /dev/null
+++ b/src/ui/MemoryBottleDialog.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.ui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.appwidget.AppWidgetManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.preference.PreferenceManager;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.DataColumns;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.data.Notes.TextNote;
+import net.micode.notes.model.WorkingNote;
+import net.micode.notes.tool.DataUtils;
+import net.micode.notes.tool.ResourceParser;
+
+import java.lang.ref.WeakReference;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+import java.util.HashSet;
+
+public class MemoryBottleDialog extends Dialog implements View.OnClickListener {
+ private static final String PREF_MEMORY_FOLDER_ID = "pref_memory_bottle_folder_id";
+ private static final List sAllEntries = new ArrayList<>();
+ private static final List sRemainingEntries = new ArrayList<>();
+ private static final Random sRandom = new Random();
+ private static long sFolderId = Long.MIN_VALUE;
+
+ private final Activity mActivity;
+ private Button mAddButton;
+ private Button mBrowseButton;
+ private long mMemoryFolderId = -1;
+ private boolean mEntriesLoaded;
+ private boolean mLoading;
+ private boolean mBrowseLoading;
+ private LoadTask mLoadTask;
+ private BrowseTask mBrowseTask;
+ private PendingAction mPendingAction = PendingAction.NONE;
+
+ public MemoryBottleDialog(Activity activity) {
+ super(activity, android.R.style.Theme_Light_NoTitleBar);
+ mActivity = activity;
+ }
+
+ @Override
+ protected void onCreate(android.os.Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.memory_bottle);
+ getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT);
+ initResources();
+ }
+
+ @Override
+ public void dismiss() {
+ if (mLoadTask != null) {
+ mLoadTask.cancel(true);
+ mLoadTask = null;
+ }
+ if (mBrowseTask != null) {
+ mBrowseTask.cancel(true);
+ mBrowseTask = null;
+ }
+ super.dismiss();
+ }
+
+ private void initResources() {
+ mAddButton = (Button) findViewById(R.id.btn_memory_add);
+ mBrowseButton = (Button) findViewById(R.id.btn_memory_browse);
+ mAddButton.setOnClickListener(this);
+ mBrowseButton.setOnClickListener(this);
+ updateButtonState();
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.btn_memory_add:
+ requestAction(PendingAction.ADD);
+ break;
+ case R.id.btn_memory_browse:
+ requestAction(PendingAction.BROWSE);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void requestAction(PendingAction action) {
+ if (mLoading || mBrowseLoading) {
+ Toast.makeText(mActivity, R.string.memory_bottle_loading, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if (mMemoryFolderId <= 0) {
+ mPendingAction = action;
+ Toast.makeText(mActivity, R.string.memory_bottle_loading, Toast.LENGTH_SHORT).show();
+ startLoadTask(action == PendingAction.BROWSE);
+ return;
+ }
+ if (action == PendingAction.BROWSE && !mEntriesLoaded) {
+ mPendingAction = action;
+ Toast.makeText(mActivity, R.string.memory_bottle_loading, Toast.LENGTH_SHORT).show();
+ startLoadTask(true);
+ return;
+ }
+ if (action == PendingAction.ADD) {
+ showAddDialog();
+ } else if (action == PendingAction.BROWSE) {
+ browseMemory();
+ }
+ }
+
+ private void startLoadTask(boolean loadEntries) {
+ if (mLoadTask != null) {
+ return;
+ }
+ setLoading(true);
+ mLoadTask = new LoadTask(this, loadEntries);
+ mLoadTask.execute();
+ }
+
+ private void setLoading(boolean loading) {
+ mLoading = loading;
+ updateButtonState();
+ }
+
+ private void setBrowseLoading(boolean loading) {
+ mBrowseLoading = loading;
+ updateButtonState();
+ }
+
+ private void updateButtonState() {
+ boolean enabled = !(mLoading || mBrowseLoading);
+ if (mAddButton != null) {
+ mAddButton.setEnabled(enabled);
+ }
+ if (mBrowseButton != null) {
+ mBrowseButton.setEnabled(enabled);
+ }
+ }
+
+ private void showAddDialog() {
+ final EditText editText = new EditText(mActivity);
+ int padding = (int) (mActivity.getResources().getDisplayMetrics().density * 16);
+ editText.setPadding(padding, padding, padding, padding);
+ editText.setGravity(Gravity.TOP | Gravity.START);
+ editText.setMinLines(4);
+ editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
+ editText.setHint(R.string.memory_bottle_add_hint);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
+ builder.setTitle(R.string.memory_bottle_add_title);
+ builder.setView(editText);
+ builder.setPositiveButton(android.R.string.ok, null);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ final AlertDialog dialog = builder.show();
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String content = editText.getText().toString().trim();
+ if (TextUtils.isEmpty(content)) {
+ Toast.makeText(mActivity, R.string.memory_bottle_empty_input,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if (createMemoryNote(content)) {
+ dialog.dismiss();
+ }
+ }
+ });
+ }
+
+ private boolean createMemoryNote(String content) {
+ if (mMemoryFolderId <= 0) {
+ Toast.makeText(mActivity, R.string.memory_bottle_folder_error, Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ WorkingNote note = WorkingNote.createEmptyNote(mActivity, mMemoryFolderId,
+ AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
+ ResourceParser.getDefaultBgId(mActivity));
+ note.setWorkingText(content);
+ if (!note.saveNote()) {
+ Toast.makeText(mActivity, R.string.memory_bottle_save_failed, Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ long noteId = note.getNoteId();
+ long createdDate = queryNoteCreatedDate(noteId);
+ MemoryEntry entry = new MemoryEntry(noteId, createdDate, content);
+ sAllEntries.add(entry);
+ sRemainingEntries.add(entry);
+ mEntriesLoaded = true;
+ Toast.makeText(mActivity, R.string.memory_bottle_save_success, Toast.LENGTH_SHORT).show();
+ return true;
+ }
+
+ private long queryNoteCreatedDate(long noteId) {
+ Cursor cursor = mActivity.getContentResolver().query(
+ ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
+ new String[] { NoteColumns.CREATED_DATE },
+ null, null, null);
+ if (cursor == null) {
+ return System.currentTimeMillis();
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ return System.currentTimeMillis();
+ }
+
+ private void browseMemory() {
+ if (sAllEntries.isEmpty()) {
+ Toast.makeText(mActivity, R.string.memory_bottle_empty, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if (sRemainingEntries.isEmpty()) {
+ showBrowseFinishedDialog();
+ return;
+ }
+ showRandomEntry();
+ }
+
+ private void showBrowseFinishedDialog() {
+ new AlertDialog.Builder(mActivity)
+ .setMessage(R.string.memory_bottle_browse_done)
+ .setPositiveButton(R.string.memory_bottle_restart, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ resetRemainingEntries();
+ showRandomEntry();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ private void resetRemainingEntries() {
+ sRemainingEntries.clear();
+ sRemainingEntries.addAll(sAllEntries);
+ }
+
+ private void showRandomEntry() {
+ int index = sRandom.nextInt(sRemainingEntries.size());
+ MemoryEntry entry = sRemainingEntries.remove(index);
+ startBrowseTask(entry);
+ }
+
+ private void startBrowseTask(MemoryEntry entry) {
+ if (mBrowseTask != null) {
+ return;
+ }
+ setBrowseLoading(true);
+ mBrowseTask = new BrowseTask(this, entry);
+ mBrowseTask.execute();
+ }
+
+ private String formatEntryMessage(long createdDate, String content) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
+ String date = format.format(new Date(createdDate));
+ return mActivity.getString(R.string.memory_bottle_entry_format, date, content);
+ }
+
+ private long ensureMemoryFolder() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ long storedId = sp.getLong(PREF_MEMORY_FOLDER_ID, Long.MIN_VALUE);
+ if (storedId > 0 && DataUtils.visibleInNoteDatabase(mActivity.getContentResolver(),
+ storedId, Notes.TYPE_FOLDER)) {
+ return storedId;
+ }
+ String folderName = mActivity.getString(R.string.memory_bottle_folder_name);
+ long folderId = queryFolderIdByName(folderName);
+ if (folderId > 0) {
+ sp.edit().putLong(PREF_MEMORY_FOLDER_ID, folderId).commit();
+ return folderId;
+ }
+ folderId = createMemoryFolder(folderName);
+ if (folderId > 0) {
+ sp.edit().putLong(PREF_MEMORY_FOLDER_ID, folderId).commit();
+ return folderId;
+ }
+ return -1;
+ }
+
+ private long queryFolderIdByName(String name) {
+ ContentResolver resolver = mActivity.getContentResolver();
+ Cursor cursor = resolver.query(
+ Notes.CONTENT_NOTE_URI,
+ new String[] { NoteColumns.ID },
+ NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?"
+ + " AND " + NoteColumns.SNIPPET + "=?",
+ new String[] { String.valueOf(Notes.TYPE_FOLDER),
+ String.valueOf(Notes.ID_TRASH_FOLER), name },
+ null);
+ if (cursor == null) {
+ return 0;
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ return 0;
+ }
+
+ private long createMemoryFolder(String name) {
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.SNIPPET, name);
+ values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
+ values.put(NoteColumns.PARENT_ID, Notes.ID_ROOT_FOLDER);
+ values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ android.net.Uri uri = mActivity.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
+ if (uri == null) {
+ return 0;
+ }
+ try {
+ return Long.parseLong(uri.getPathSegments().get(1));
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ private String queryNoteContent(ContentResolver resolver, long noteId) {
+ Cursor cursor = resolver.query(
+ Notes.CONTENT_DATA_URI,
+ new String[] { DataColumns.CONTENT },
+ DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?",
+ new String[] { String.valueOf(noteId), TextNote.CONTENT_ITEM_TYPE },
+ null);
+ if (cursor == null) {
+ return "";
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getString(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ return "";
+ }
+
+ private List loadEntriesFromDatabase(long folderId) {
+ List entries = new ArrayList<>();
+ ContentResolver resolver = mActivity.getContentResolver();
+ Cursor cursor = resolver.query(
+ Notes.CONTENT_NOTE_URI,
+ new String[] { NoteColumns.ID, NoteColumns.CREATED_DATE },
+ NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "=?",
+ new String[] { String.valueOf(Notes.TYPE_NOTE), String.valueOf(folderId) },
+ NoteColumns.CREATED_DATE + " DESC");
+ if (cursor == null) {
+ return entries;
+ }
+ try {
+ while (cursor.moveToNext()) {
+ long noteId = cursor.getLong(0);
+ long createdDate = cursor.getLong(1);
+ entries.add(new MemoryEntry(noteId, createdDate, ""));
+ }
+ } finally {
+ cursor.close();
+ }
+ return entries;
+ }
+
+ private static final class MemoryEntry {
+ private final long id;
+ private final long createdDate;
+ private final String content;
+
+ private MemoryEntry(long id, long createdDate, String content) {
+ this.id = id;
+ this.createdDate = createdDate;
+ this.content = content;
+ }
+ }
+
+ private static final class LoadResult {
+ private final long folderId;
+ private final List entries;
+ private final boolean loadedEntries;
+
+ private LoadResult(long folderId, List entries, boolean loadedEntries) {
+ this.folderId = folderId;
+ this.entries = entries;
+ this.loadedEntries = loadedEntries;
+ }
+ }
+
+ private static final class LoadTask extends AsyncTask {
+ private final WeakReference mRef;
+ private final boolean mLoadEntries;
+
+ private LoadTask(MemoryBottleDialog dialog, boolean loadEntries) {
+ mRef = new WeakReference<>(dialog);
+ mLoadEntries = loadEntries;
+ }
+
+ @Override
+ protected LoadResult doInBackground(Void... params) {
+ MemoryBottleDialog dialog = mRef.get();
+ if (dialog == null) {
+ return null;
+ }
+ long folderId = dialog.ensureMemoryFolder();
+ List entries = new ArrayList<>();
+ if (folderId > 0 && mLoadEntries) {
+ entries = dialog.loadEntriesFromDatabase(folderId);
+ }
+ return new LoadResult(folderId, entries, mLoadEntries);
+ }
+
+ @Override
+ protected void onPostExecute(LoadResult result) {
+ MemoryBottleDialog dialog = mRef.get();
+ if (dialog == null || !dialog.isShowing()) {
+ return;
+ }
+ dialog.mLoadTask = null;
+ dialog.setLoading(false);
+ if (result == null || result.folderId <= 0) {
+ Toast.makeText(dialog.mActivity, R.string.memory_bottle_folder_error,
+ Toast.LENGTH_SHORT).show();
+ dialog.mPendingAction = PendingAction.NONE;
+ return;
+ }
+ dialog.mMemoryFolderId = result.folderId;
+ sFolderId = result.folderId;
+ if (result.loadedEntries) {
+ sAllEntries.clear();
+ sAllEntries.addAll(result.entries);
+ sRemainingEntries.clear();
+ sRemainingEntries.addAll(result.entries);
+ dialog.mEntriesLoaded = true;
+ } else if (sFolderId == result.folderId) {
+ dialog.mEntriesLoaded = !sAllEntries.isEmpty();
+ }
+ PendingAction pending = dialog.mPendingAction;
+ dialog.mPendingAction = PendingAction.NONE;
+ if (pending == PendingAction.ADD) {
+ dialog.showAddDialog();
+ } else if (pending == PendingAction.BROWSE) {
+ dialog.browseMemory();
+ }
+ }
+ }
+
+ private static final class BrowseResult {
+ private final MemoryEntry entry;
+ private final String content;
+
+ private BrowseResult(MemoryEntry entry, String content) {
+ this.entry = entry;
+ this.content = content;
+ }
+ }
+
+ private static final class BrowseTask extends AsyncTask {
+ private final WeakReference mRef;
+ private final MemoryEntry mEntry;
+
+ private BrowseTask(MemoryBottleDialog dialog, MemoryEntry entry) {
+ mRef = new WeakReference<>(dialog);
+ mEntry = entry;
+ }
+
+ @Override
+ protected BrowseResult doInBackground(Void... params) {
+ MemoryBottleDialog dialog = mRef.get();
+ if (dialog == null) {
+ return null;
+ }
+ String content = mEntry.content;
+ if (TextUtils.isEmpty(content)) {
+ content = dialog.queryNoteContent(dialog.mActivity.getContentResolver(), mEntry.id);
+ }
+ if (TextUtils.isEmpty(content)) {
+ content = dialog.mActivity.getString(R.string.memory_bottle_missing_content);
+ }
+ return new BrowseResult(mEntry, content);
+ }
+
+ @Override
+ protected void onPostExecute(BrowseResult result) {
+ MemoryBottleDialog dialog = mRef.get();
+ if (dialog == null || !dialog.isShowing()) {
+ return;
+ }
+ dialog.mBrowseTask = null;
+ dialog.setBrowseLoading(false);
+ if (result == null) {
+ return;
+ }
+ String message = dialog.formatEntryMessage(result.entry.createdDate, result.content);
+ new AlertDialog.Builder(dialog.mActivity)
+ .setTitle(R.string.memory_bottle_title)
+ .setMessage(message)
+ .setPositiveButton(R.string.memory_bottle_close, null)
+ .setNegativeButton(R.string.memory_bottle_delete, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int which) {
+ dialog.deleteMemoryEntry(result.entry);
+ }
+ })
+ .show();
+ }
+ }
+ private void deleteMemoryEntry(MemoryEntry entry) {
+ if (mMemoryFolderId <= 0) {
+ Toast.makeText(mActivity, R.string.memory_bottle_folder_error, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ HashSet ids = new HashSet();
+ ids.add(entry.id);
+ if (DataUtils.batchMoveToTrash(mActivity.getContentResolver(), ids, mMemoryFolderId)) {
+ sAllEntries.remove(entry);
+ sRemainingEntries.remove(entry);
+ Toast.makeText(mActivity, R.string.memory_bottle_delete_success, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(mActivity, R.string.memory_bottle_delete_failed, Toast.LENGTH_SHORT).show();
+ }
+ }
+ private enum PendingAction {
+ NONE,
+ ADD,
+ BROWSE
+ }
+}
diff --git a/src/ui/NoteEditActivity.java b/src/ui/NoteEditActivity.java
deleted file mode 100644
index 945ec2d..0000000
--- a/src/ui/NoteEditActivity.java
+++ /dev/null
@@ -1,1832 +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.ui;
-
-import android.app.Activity;
-import android.app.AlarmManager;
-import android.app.AlertDialog;
-import android.app.PendingIntent;
-import android.app.SearchManager;
-import android.appwidget.AppWidgetManager;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.style.BackgroundColorSpan;
-import android.text.TextWatcher;
-import android.text.Editable;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.app.ProgressDialog;
-import android.os.AsyncTask;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.Spinner;
-import android.view.WindowManager;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-import android.widget.AdapterView;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.TimePicker;
-import android.widget.Toast;
-
-import net.micode.notes.R;
-import net.micode.notes.data.Notes;
-import net.micode.notes.data.Notes.TextNote;
-import net.micode.notes.model.WorkingNote;
-import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
-import net.micode.notes.tool.DataUtils;
-import net.micode.notes.tool.ResourceParser;
-import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
-import net.micode.notes.tool.TranslateUtils;
-import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
-import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
-import net.micode.notes.widget.NoteWidgetProvider_2x;
-import net.micode.notes.widget.NoteWidgetProvider_4x;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import android.widget.Button;
-import android.widget.CompoundButton;
-import android.widget.Spinner;
-import android.widget.ArrayAdapter;
-import android.widget.EditText;
-import java.util.Calendar;
-import java.util.Date;
-import android.app.PendingIntent;
-import android.app.AlarmManager;
-import android.widget.GridLayout;
-import android.widget.ProgressBar;
-import android.widget.LinearLayout;
-import java.text.SimpleDateFormat;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.Map;
-import java.util.HashMap;
-
-
-public class NoteEditActivity extends Activity implements OnClickListener,
-
- NoteSettingChangedListener, OnTextViewChangeListener, OnSelectionChangeListener {
- private class HeadViewHolder {
- public TextView tvModified;
-
- public ImageView ivAlertIcon;
- public TextView tvAlertDate;//文本提醒日期?
- public ImageView ibSetBgColor;//设置背景颜色图像视图
- public TextView tvCharNum;//新增字符数显示控件
- }
-
- // 显示习惯配置对话框(简单表单),将结果保存为 JSON 到 habit_config
- private void showHabitConfigDialog() {
- LayoutInflater inflater = LayoutInflater.from(this);
- View view = inflater.inflate(R.layout.habit_config_dialog, null);
- final Spinner spinnerPeriod = (Spinner) view.findViewById(R.id.spinner_period);
- final TimePicker tpRemindTime = (TimePicker) view.findViewById(R.id.tp_remind_time);
- final Spinner spinnerTargetType = (Spinner) view.findViewById(R.id.spinner_target_type);
- final EditText etTargetValue = (EditText) view.findViewById(R.id.et_target_value);
- final LinearLayout llWeeklyTimes = (LinearLayout) view.findViewById(R.id.ll_weekly_times);
- final EditText etWeeklyTimes = (EditText) view.findViewById(R.id.et_weekly_times);
-
- // 设置 TimePicker 为 24 小时制
- tpRemindTime.setIs24HourView(true);
-
- // setup spinners
- ArrayAdapter periodAdapter = ArrayAdapter.createFromResource(this,
- R.array.habit_period_options, android.R.layout.simple_spinner_item);
- periodAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinnerPeriod.setAdapter(periodAdapter);
-
- ArrayAdapter targetAdapter = ArrayAdapter.createFromResource(this,
- R.array.habit_target_type_options, android.R.layout.simple_spinner_item);
- targetAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinnerTargetType.setAdapter(targetAdapter);
-
- // 当选择周期变化时,显示/隐藏每周次数设置
- spinnerPeriod.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView> parent, View view, int position, long id) {
- String period = spinnerPeriod.getSelectedItem().toString();
- if (period.equals("每周 X 次")) {
- llWeeklyTimes.setVisibility(View.VISIBLE);
- } else {
- llWeeklyTimes.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView> parent) {
- }
- });
-
- // prefill if existing
- try {
- String cfg = mWorkingNote.getHabitConfig();
- if (cfg != null && cfg.length() > 0) {
- JSONObject jo = new JSONObject(cfg);
- String period = jo.optString("period", "每日");
- spinnerPeriod.setSelection(periodAdapter.getPosition(period));
-
- // 设置提醒时间
- String remindTime = jo.optString("remind_time", "08:00");
- if (!remindTime.isEmpty()) {
- String[] parts = remindTime.split(":");
- int hour = Integer.parseInt(parts[0]);
- int minute = Integer.parseInt(parts[1]);
- tpRemindTime.setHour(hour);
- tpRemindTime.setMinute(minute);
- }
-
- // 设置每周次数
- if (period.equals("每周 X 次")) {
- llWeeklyTimes.setVisibility(View.VISIBLE);
- etWeeklyTimes.setText(String.valueOf(jo.optInt("weekly_times", 3)));
- }
-
- spinnerTargetType.setSelection(targetAdapter.getPosition(jo.optString("target_type", "连续天数")));
- etTargetValue.setText(String.valueOf(jo.optInt("target_value", 0)));
- }
- } catch (Exception e) {
- // ignore
- }
-
- AlertDialog.Builder b = new AlertDialog.Builder(this);
- b.setTitle(R.string.habit_config_title);
- b.setView(view);
- b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String period = spinnerPeriod.getSelectedItem().toString();
-
- // 获取提醒时间
- int hour = tpRemindTime.getHour();
- int minute = tpRemindTime.getMinute();
- String remind = String.format("%02d:%02d", hour, minute);
-
- String targetType = spinnerTargetType.getSelectedItem().toString();
- int targetValue = 0;
- try {
- targetValue = Integer.parseInt(etTargetValue.getText().toString());
- } catch (Exception e) {
- targetValue = 0;
- }
-
- // 获取每周次数
- int weeklyTimes = 3;
- if (period.equals("每周 X 次")) {
- try {
- weeklyTimes = Integer.parseInt(etWeeklyTimes.getText().toString());
- } catch (Exception e) {
- weeklyTimes = 3;
- }
- }
-
- JSONObject jo = new JSONObject();
- try {
- jo.put("period", period);
- jo.put("remind_time", remind);
- jo.put("target_type", targetType);
- jo.put("target_value", targetValue);
- jo.put("weekly_times", weeklyTimes);
- } catch (JSONException e) {
- // ignore
- }
- if (mWorkingNote != null) {
- mWorkingNote.setHabit(true, jo.toString());
- // schedule alarm according to remind_time
- scheduleHabitAlarm(mWorkingNote);
- // refresh the menu to update the habit settings button
- invalidateOptionsMenu();
- }
- }
- });
- b.setNegativeButton(android.R.string.cancel, null);
- b.show();
- }
-
- // Schedule or cancel habit alarm according to current WorkingNote.habit_config
- private void scheduleHabitAlarm(WorkingNote note) {
- if (note == null || !note.isHabit()) {
- // cancel any existing alarm
- cancelHabitAlarm(note);
- return;
- }
- String cfg = note.getHabitConfig();
- if (cfg == null || cfg.length() == 0) {
- return;
- }
- try {
- JSONObject jo = new JSONObject(cfg);
- String remind = jo.optString("remind_time", "");
- if (remind == null || remind.length() == 0) {
- // no remind time provided
- return;
- }
- String[] parts = remind.split(":");
- int hour = Integer.parseInt(parts[0]);
- int minute = Integer.parseInt(parts[1]);
-
- // ensure note saved and has id
- if (!note.existInDatabase()) {
- note.saveNote();
- }
- if (!note.existInDatabase()) return;
-
- long noteId = note.getNoteId();
- Intent intent = new Intent(this, AlarmReceiver.class);
- intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId));
- intent.putExtra("habit_alarm", 1);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
- AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
-
- Calendar c = Calendar.getInstance();
- c.set(Calendar.HOUR_OF_DAY, hour);
- c.set(Calendar.MINUTE, minute);
- c.set(Calendar.SECOND, 0);
- long trigger = c.getTimeInMillis();
- long now = System.currentTimeMillis();
- if (trigger <= now) {
- // schedule for next day
- trigger += AlarmManager.INTERVAL_DAY;
- }
-
- alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, trigger,
- AlarmManager.INTERVAL_DAY, pendingIntent);
- } catch (Exception e) {
- Log.e(TAG, "Schedule habit alarm error", e);
- }
- }
-
- private void cancelHabitAlarm(WorkingNote note) {
- try {
- if (note == null || !note.existInDatabase()) return;
- long noteId = note.getNoteId();
- Intent intent = new Intent(this, AlarmReceiver.class);
- intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId));
- intent.putExtra("habit_alarm", 1);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
- AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
- alarmManager.cancel(pendingIntent);
- } catch (Exception e) {
- Log.e(TAG, "Cancel habit alarm error", e);
- }
- }
-
- private static final Map sBgSelectorBtnsMap = new HashMap();
- static {
- sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
- sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
- sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
- sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
- sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
- }
- //上下是反的为啥?构建双向印射关系,方便根据颜色值获取对应的选择按钮ID(比如当前背景是红的,红的按钮就是按下去的),或者根据选择按钮ID获取对应的颜色值
- private static final Map sBgSelectorSelectionMap = new HashMap();
- static {
- sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
- sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
- sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
- sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
- sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
- }
-
- private static final Map sFontSizeBtnsMap = new HashMap();//字体大小选择按钮映射
- static {
- sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
- sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
- sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
- sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
- }
-//同颜色的映射关系
- private static final Map sFontSelectorSelectionMap = new HashMap();
- static {
- sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
- sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
- sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
- sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
- }
-
- private static final String TAG = "NoteEditActivity";
-
- private HeadViewHolder mNoteHeaderHolder;
-
- private View mHeadViewPanel;
-
- private View mNoteBgColorSelector;
-
- private View mFontSizeSelector;
-
- private NoteEditText mNoteEditor;
-
- private View mNoteEditorPanel;
-
- private WorkingNote mWorkingNote;
-
- // habit calendar UI
- private LinearLayout mHabitPanel;
- private Button mBtnPrevMonth;
- private Button mBtnNextMonth;
- private TextView mTvHabitMonth;
- private GridLayout mHabitCalendarGrid;
- private TextView mTvHabitTotal;
- private ProgressBar mProgressHabitGoal;
-
- // calendar state
- private Calendar mRenderCalendar = Calendar.getInstance();
-
- // habit ui
-
- private SharedPreferences mSharedPrefs;
- private int mFontSizeId;
-
- private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
-
- private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
-
- public static final String TAG_CHECKED = String.valueOf('\u221A');
- public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
-
- private LinearLayout mEditTextList;
-
- // translation state
- private boolean mHasTranslation = false;
- private String mOriginalContent = null;
- private ProgressDialog mProgressDialog;
- private String mTargetLangCode = "en"; // default
-
- private String mUserQuery;
- private Pattern mPattern;
- //新增字符统计方法,排除空白字符
- //计算可见字符数,排除空白字符
- private int calcVisibleCharCount(CharSequence s) {
- if (s == null) return 0;
- String filtered = s.toString().replaceAll("\\s+", "");
- return filtered.length();
- }
- //根据选区更新字符统计显示
- private void updateCharNumForSelection(CharSequence text, int selStart, int selEnd) {
- if (text == null) {
- mNoteHeaderHolder.tvCharNum.setText("字符数:0");
- return;
- }
- if (selStart == selEnd) {
- mNoteHeaderHolder.tvCharNum.setText("字符数:" + calcVisibleCharCount(text));
- } else {
- int min = Math.min(selStart, selEnd);
- int max = Math.max(selStart, selEnd);
- CharSequence sub = text.subSequence(min, max);
- mNoteHeaderHolder.tvCharNum.setText("字符数:" + calcVisibleCharCount(sub));
- }
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {//创建活动时调用
- super.onCreate(savedInstanceState);
- this.setContentView(R.layout.note_edit);
-
- if (savedInstanceState == null && !initActivityState(getIntent())) {
- finish();
- return;
- }
- initResources();//初始化资源
- }
-
- /**
- * Current activity may be killed when the memory is low. Once it is killed, for another time
- * user load this activity, we should restore the former state/
- */
- @Override
- protected void onRestoreInstanceState(Bundle savedInstanceState) {//存储uid便于恢复活动状态时调用
- super.onRestoreInstanceState(savedInstanceState); //使用 Intent Extra 在组件之间传递uid
- if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
- Intent intent = new Intent(Intent.ACTION_VIEW); //创建一个意图,用于显示笔记详情,大小是(action_view显示数据功能)
- intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));//将uid添加到意图中,用于之后再调出来
- if (!initActivityState(intent)) {
- finish();
- return;
- }
- Log.d(TAG, "Restoring from killed activity");
- }
- }
-
- private boolean initActivityState(Intent intent) {
- /**
- * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
- * then jump to the NotesListActivity
- */
- mWorkingNote = null;
- if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {//如果意图是查看操作
- long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);//获取uid
- mUserQuery = "";//初始化用户查询,用于后续搜索
-
- /**
- * Starting from the searched result
- */
- if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {//如果意图包含搜索数据键
- noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));//解析出笔记id
- mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);//解析出用户查询
- }
-
- if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {//如果笔记数据库中不存在该笔记
- Intent jump = new Intent(this, NotesListActivity.class);
- startActivity(jump);//执行跳转
- showToast(R.string.error_note_not_exist);//显示笔记不存在的提示
- finish();
- return false;
- } else {
- mWorkingNote = WorkingNote.load(this, noteId);//加载笔记
- if (mWorkingNote == null) {
- Log.e(TAG, "load note failed with note id" + noteId);
- finish();
- return false;
- }
- }
- getWindow().setSoftInputMode( //控制软键盘的操作?
- WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN //隐藏
- | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);//调整布局
- } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {//如果意图是插入或编辑操作
- // New note
- long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);//获取文件夹id
- int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,
- AppWidgetManager.INVALID_APPWIDGET_ID);//获取小部件id
- int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,
- Notes.TYPE_WIDGET_INVALIDE);//获取小部件类型
- int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
- ResourceParser.getDefaultBgId(this));//获取背景资源id
-
- // Parse call-record note
- String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);//获取电话号码
- long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);//获取调用日期
- if (callDate != 0 && phoneNumber != null) {//如果调用日期和电话号码都不为空
- if (TextUtils.isEmpty(phoneNumber)) {
- Log.w(TAG, "The call record number is null");
- }
- long noteId = 0;//初始化笔记id
- if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
- phoneNumber, callDate)) > 0) {//如果记录中根据电话号码和调用日期查询到笔记id
- mWorkingNote = WorkingNote.load(this, noteId);//加载笔记
- if (mWorkingNote == null) {
- Log.e(TAG, "load call note failed with note id" + noteId);
- finish();
- return false;
- }
- } else {
- mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId,
- widgetType, bgResId);//新建笔记
- mWorkingNote.convertToCallNote(phoneNumber, callDate);//将笔记转换为call笔记
- }
- } else {
- mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
- bgResId);//新建笔记
- }
-
- getWindow().setSoftInputMode(//设置软键盘操作
- WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
- | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
- } else {
- Log.e(TAG, "Intent not specified action, should not support");
- finish();
- return false;
- }
- mWorkingNote.setOnSettingStatusChangedListener(this);//设置笔记设置状态改变监听器
- return true;
- }
-
- @Override
- protected void onResume() {//已经oncreate后切出,再次调用时,初始化笔记界面
- super.onResume();
- initNoteScreen();
- }
-
- private void initNoteScreen() {
- mNoteEditor.setTextAppearance(this, TextAppearanceResources
- .getTexAppearanceResource(mFontSizeId));//设置文本字体外观资源
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//根据笔记模式( 清单模式 或 普通模式 )初始化编辑器内容
- switchToListMode(mWorkingNote.getContent());//切换到列表清单模式
- } else {
- mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));//设置文本内容为高亮查询结果
- mNoteEditor.setSelection(mNoteEditor.getText().length());//将光标定位到文本末尾,方便用户继续编辑
- }
- for (Integer id : sBgSelectorSelectionMap.keySet()) {//隐藏所有背景色选择器的 选中状态指示器
- findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
- }
- mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());//设置笔记标题界面的 背景资源
- mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());设置笔记编辑区界面的 背景资源
-
- mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, //显示笔记的 最后修改日期 ,并格式化显示
- mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
- | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
- | DateUtils.FORMAT_SHOW_YEAR));
-
- /**
- * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
- * is not ready
- */
- showAlertHeader();
- // render habit calendar if this note is a habit
- if (mWorkingNote.isHabit()) {
- mHabitPanel.setVisibility(View.VISIBLE);
- // ensure calendar shows current month
- mRenderCalendar = Calendar.getInstance();
- renderHabitPanel();
- } else {
- mHabitPanel.setVisibility(View.GONE);
- }
- }
-
- // Render calendar and statistics from habit_config.history
- private void renderHabitPanel() {
- if (mWorkingNote == null || !mWorkingNote.isHabit()) return;
- Map dayStatus = new HashMap(); // yyyy-MM-dd -> status
- int totalCompleted = 0;
-
- // 优先使用 WorkingNote 中的 habit_config,避免日历点击改状态后 DB 缓存导致视图不刷新
- String cfg = (mWorkingNote != null) ? mWorkingNote.getHabitConfig() : null;
- if (cfg == null || cfg.length() == 0) {
- if (mWorkingNote != null && mWorkingNote.existInDatabase()) {
- try {
- Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId());
- Cursor c = getContentResolver().query(uri, new String[]{Notes.NoteColumns.HABIT_CONFIG}, null, null, null);
- if (c != null) {
- if (c.moveToFirst()) {
- String fromDb = c.getString(0);
- if (fromDb != null && fromDb.length() > 0) cfg = fromDb;
- }
- c.close();
- }
- } catch (Exception e) {
- Log.e(TAG, "Failed to read latest habit config", e);
- }
- }
- }
- if (cfg == null) cfg = "";
-
- try {
- if (cfg != null && cfg.length() > 0) {
- JSONObject jo = new JSONObject(cfg);
- if (jo.has("history")) {
- JSONArray history = jo.getJSONArray("history");
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
- for (int i = 0; i < history.length(); i++) {
- JSONObject e = history.getJSONObject(i);
- long date = e.optLong("date", 0);
- String status = e.optString("status", "");
- if (date > 0) {
- Calendar c = Calendar.getInstance();
- c.setTimeInMillis(date);
- String key = sdf.format(c.getTime());
- dayStatus.put(key, status);
- if ("completed".equals(status)) totalCompleted++;
- }
- }
- }
- }
- } catch (Exception e) {
- // ignore parse errors
- Log.e(TAG, "Failed to parse habit config", e);
- }
-
- // prepare month display
- Calendar cal = (Calendar) mRenderCalendar.clone();
- cal.set(Calendar.DAY_OF_MONTH, 1);
- int month = cal.get(Calendar.MONTH);
- int year = cal.get(Calendar.YEAR);
- SimpleDateFormat monthFmt = new SimpleDateFormat("yyyy年MM月", Locale.getDefault());
- mTvHabitMonth.setText(monthFmt.format(cal.getTime()));
-
- // clear grid
- mHabitCalendarGrid.removeAllViews();
-
- // add weekday headers
- String[] w = new String[] {"日","一","二","三","四","五","六"};
- for (int i = 0; i < 7; i++) {
- TextView tv = new TextView(this);
- tv.setText(w[i]);
- tv.setGravity(android.view.Gravity.CENTER);
- tv.setPadding(6,6,6,6);
- mHabitCalendarGrid.addView(tv);
- }
-
- int firstWeekday = cal.get(Calendar.DAY_OF_WEEK) - 1; // 0..6
- int daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
-
- // fill blanks
- for (int i = 0; i < firstWeekday; i++) {
- TextView tv = new TextView(this);
- mHabitCalendarGrid.addView(tv);
- }
-
- SimpleDateFormat sdfKey = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
- for (int d = 1; d <= daysInMonth; d++) {
- cal.set(Calendar.DAY_OF_MONTH, d);
- String key = sdfKey.format(cal.getTime());
- TextView cell = new TextView(this);
- cell.setText(String.valueOf(d));
- cell.setGravity(android.view.Gravity.CENTER);
- cell.setPadding(12,12,12,12);
- String status = dayStatus.get(key);
- String icon = "";
- boolean isToday = isSameDay(cal, Calendar.getInstance());
- if (status != null) {
- if ("completed".equals(status)) {
- cell.setBackgroundResource(isToday ? R.drawable.habit_day_today_bg : R.drawable.habit_day_completed_bg);
- icon = "✅ ";
- } else if ("skipped".equals(status)) {
- cell.setBackgroundResource(isToday ? R.drawable.habit_day_today_bg : R.drawable.habit_day_skipped_bg);
- icon = "➖ ";
- } else {
- cell.setBackgroundResource(isToday ? R.drawable.habit_day_today_bg : R.drawable.habit_day_pending_bg);
- icon = "🔄 ";
- }
- } else {
- // future or empty
- if (isToday) {
- cell.setBackgroundResource(R.drawable.habit_day_today_bg);
- icon = "🔄 ";
- }
- }
- // 设置文本为图标+日期
- cell.setText(icon + String.valueOf(d));
- final String selKey = key;
- final Calendar clickedCal = (Calendar) cal.clone();
- final String cellStatus = status;
- cell.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // 检查点击的日期是否是过去的日期或今天
- Calendar today = Calendar.getInstance();
- today.set(Calendar.HOUR_OF_DAY, 0);
- today.set(Calendar.MINUTE, 0);
- today.set(Calendar.SECOND, 0);
- today.set(Calendar.MILLISECOND, 0);
-
- if (clickedCal.before(today) || isSameDay(clickedCal, today)) {
- // 过去的日期或今天,显示三个选项
- AlertDialog.Builder builder = new AlertDialog.Builder(NoteEditActivity.this);
- builder.setTitle("设置打卡状态");
- builder.setItems(new CharSequence[]{"设为✅ 已完成", "设为➖ 跳过", "设为🔄 待打卡"}, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String status = "";
- String reason = "";
-
- switch (which) {
- case 0:
- // 设为已完成
- status = "completed";
- reason = clickedCal.before(today) ? "补打卡" : "";
- break;
- case 1:
- // 设为跳过
- status = "skipped";
- reason = "手动设置跳过";
- break;
- case 2:
- // 设为待打卡
- status = "pending";
- reason = "手动设置待打卡";
- break;
- }
-
- // 记录点击日期的打卡状态,传入点击的Calendar对象
- recordHabitHistory(mWorkingNote.getNoteId(), status, reason, clickedCal);
- renderHabitPanel();
- }
- });
- builder.show();
- } else {
- // 未来日期,不允许打卡
- Toast.makeText(NoteEditActivity.this, "未来日期暂不允许设置状态", Toast.LENGTH_SHORT).show();
- }
- }
- });
- mHabitCalendarGrid.addView(cell);
- }
-
- int goal = 0;
- try { if (cfg != null && cfg.length() > 0) { JSONObject j = new JSONObject(cfg); goal = j.optInt("target_value", 0); } } catch (Exception e) {}
-
- mTvHabitTotal.setText("总计" + totalCompleted + "天");
- if (goal > 0) {
- int prog = Math.min(100, (int) ((totalCompleted * 100L) / goal));
- mProgressHabitGoal.setProgress(prog);
- } else {
- mProgressHabitGoal.setProgress(0);
- }
- }
-
- private boolean isSameDay(Calendar a, Calendar b) {
- return a.get(Calendar.YEAR) == b.get(Calendar.YEAR) && a.get(Calendar.DAY_OF_YEAR) == b.get(Calendar.DAY_OF_YEAR);
- }
-
- // 计算完成的天数
- private int calculateCompletedDays(JSONArray history) {
- int count = 0;
- try {
- Log.d(TAG, "Calculating completed days from history with length: " + history.length());
- for (int i = 0; i < history.length(); i++) {
- JSONObject entry = history.getJSONObject(i);
- String status = entry.optString("status", "");
- long date = entry.optLong("date", 0);
- Log.d(TAG, "History entry " + i + ": date=" + date + ", status=" + status);
- if ("completed".equals(status)) {
- count++;
- }
- }
- Log.d(TAG, "Calculated completed days: " + count);
- } catch (JSONException e) {
- Log.e(TAG, "Calculate completed days error", e);
- }
- return count;
- }
-
- // 检查并显示目标达成弹窗
- private void checkAndShowGoalAchievement(JSONObject config, int completedBefore, int completedAfter) {
- try {
- int goal = config.optInt("target_value", 0);
- Log.d(TAG, "Goal achievement check - completedBefore: " + completedBefore + ", completedAfter: " + completedAfter + ", goal: " + goal);
- if (goal > 0) {
- // 检查是否刚刚达成目标(更新后达到或超过目标,更新前未达到)
- boolean shouldShowDialog = completedAfter >= goal && completedBefore < goal;
- Log.d(TAG, "Should show celebration dialog: " + shouldShowDialog);
- if (shouldShowDialog) {
- // 显示喝彩弹窗
- showCelebrationDialog();
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "Check goal achievement error", e);
- }
- }
-
- // 显示喝彩弹窗
- private void showCelebrationDialog() {
- // 确保Activity处于可见状态,避免Window handle错误
- if (isFinishing() || isDestroyed()) {
- Log.d(TAG, "Activity is not visible, skipping celebration dialog");
- return;
- }
-
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle("🎉 恭喜你!");
- builder.setMessage("你已经达成了习惯目标!继续保持,加油!");
- builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- builder.setCancelable(true);
- AlertDialog dialog = builder.create();
- dialog.show();
- }
-
- // Record history into habit_config.history (same logic as AlarmAlertActivity)
- private void recordHabitHistory(long noteId, String status, String reason, Calendar recordCal) {
- try {
- Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
- Cursor c = getContentResolver().query(uri, new String[]{Notes.NoteColumns.HABIT_CONFIG}, null, null, null);
- String cfg = "";
- if (c != null) {
- if (c.moveToFirst()) cfg = c.getString(0);
- c.close();
- }
- JSONObject jo = cfg != null && cfg.length() > 0 ? new JSONObject(cfg) : new JSONObject();
- JSONArray history = jo.has("history") ? jo.getJSONArray("history") : new JSONArray();
-
- // 计算更新前的完成天数
- int completedBefore = calculateCompletedDays(history);
-
- // 获取点击的日期的时间戳
- long recordTime = recordCal.getTimeInMillis();
-
- // 创建新记录
- JSONObject newEntry = new JSONObject();
- newEntry.put("date", recordTime);
- newEntry.put("status", status);
- newEntry.put("reason", reason == null ? "" : reason);
-
- // 检查是否已经存在该日期的记录,如果有则替换
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
- String recordKey = sdf.format(new Date(recordTime));
- boolean foundRecord = false;
-
- for (int i = 0; i < history.length(); i++) {
- JSONObject entry = history.getJSONObject(i);
- long entryDate = entry.optLong("date", 0);
- String entryKey = sdf.format(new Date(entryDate));
- if (recordKey.equals(entryKey)) {
- // 替换该日期的记录
- history.put(i, newEntry);
- foundRecord = true;
- break;
- }
- }
-
- // 如果没有该日期的记录,添加新记录
- if (!foundRecord) {
- history.put(newEntry);
- }
-
- // 计算更新后的完成天数
- int completedAfter = calculateCompletedDays(history);
-
- jo.put("history", history);
- String updatedConfig = jo.toString();
-
- // 更新数据库
- ContentValues values = new ContentValues();
- values.put(Notes.NoteColumns.HABIT_CONFIG, updatedConfig);
- getContentResolver().update(uri, values, null, null);
- getContentResolver().notifyChange(uri, null);
-
- // 同时更新本地 WorkingNote 缓存,确保 renderHabitPanel 使用最新数据、日历视图立即刷新
- if (mWorkingNote != null) {
- mWorkingNote.setHabit(true, updatedConfig);
- }
-
- // 检查是否达成目标
- checkAndShowGoalAchievement(jo, completedBefore, completedAfter);
- } catch (JSONException e) {
- Log.e(TAG, "Record habit history json error", e);
- }
- }
-
- // 兼容原有调用的重载方法
- private void recordHabitHistory(long noteId, String status, String reason) {
- recordHabitHistory(noteId, status, reason, Calendar.getInstance());
- }
-
- private void showAlertHeader() {
- if (mWorkingNote.hasClockAlert()) {
- long time = System.currentTimeMillis();
- if (time > mWorkingNote.getAlertDate()) {
- mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired);//显示已过期信息
- } else {
- mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(
- mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));//显示剩余时间信息
- }
- mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);
- mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);
- } else {
- mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);
- mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);
- };
- }
-
- @Override
- protected void onNewIntent(Intent intent) {//处理新的intent
- super.onNewIntent(intent);
- initActivityState(intent);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {//保存活动状态
- super.onSaveInstanceState(outState);
- /**
- * For new note without note id, we should firstly save it to
- * generate a id. If the editing note is not worth saving, there
- * is no id which is equivalent to create new note
- */
- if (!mWorkingNote.existInDatabase()) {
- saveNote();
- }
- outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
- Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {//处理触摸事件
- if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
- && !inRangeOfView(mNoteBgColorSelector, ev)) {//如果背景色选择器可见,且触摸事件不在范围内?
- mNoteBgColorSelector.setVisibility(View.GONE);//隐藏背景色选择器
- return true;
- }
-
- if (mFontSizeSelector.getVisibility() == View.VISIBLE
- && !inRangeOfView(mFontSizeSelector, ev)) {//如果字体大小选择器可见,且触摸事件不在范围内?
- mFontSizeSelector.setVisibility(View.GONE);//隐藏字体大小选择器
- return true;
- }
- return super.dispatchTouchEvent(ev);//将事件传递给父类处理
- }
-
- private boolean inRangeOfView(View view, MotionEvent ev) {//判断触摸事件是否在视图范围内
- int []location = new int[2];//获取视图在屏幕上的坐标,长度为2的整数数组
- view.getLocationOnScreen(location);
- int x = location[0];
- int y = location[1];
- if (ev.getX() < x
- || ev.getX() > (x + view.getWidth())
- || ev.getY() < y
- || ev.getY() > (y + view.getHeight())) {
- return false;
- }
- return true;
- }
-
- private void initResources() {//初始化资源
- mHeadViewPanel = findViewById(R.id.note_title);
- mNoteHeaderHolder = new HeadViewHolder();
- mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
- mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
- mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
- mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
- mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
- mNoteHeaderHolder.tvCharNum=(TextView)findViewById(R.id.tv_char_num);//将tvCharNum绑定到TextView
- mNoteEditor = (NoteEditText) findViewById(R.id.note_edit_view);
- // 注册选区变化监听
- mNoteEditor.setOnSelectionChangeListener(this);
- //设置文本监听(统计时排除空白字符)
- mNoteEditor.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- int displayCount = calcVisibleCharCount(s);
- mNoteHeaderHolder.tvCharNum.setText("字符数:" + displayCount);
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- }
- });
- mNoteHeaderHolder.tvCharNum.setText("字符数:" + calcVisibleCharCount(mNoteEditor.getText()));
-
-
- mNoteEditorPanel = findViewById(R.id.sv_note_edit);
- mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
- for (int id : sBgSelectorBtnsMap.keySet()) {
- ImageView iv = (ImageView) findViewById(id);
- iv.setOnClickListener(this);
- }
-
- mFontSizeSelector = findViewById(R.id.font_size_selector);
- for (int id : sFontSizeBtnsMap.keySet()) {
- View view = findViewById(id);
- view.setOnClickListener(this);
- };
- mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
- /**
- * 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(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
- mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
- }
- mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
- // habit UI binds
- mHabitPanel = (LinearLayout) findViewById(R.id.habit_panel);
- mBtnPrevMonth = (Button) findViewById(R.id.btn_prev_month);
- mBtnNextMonth = (Button) findViewById(R.id.btn_next_month);
- mTvHabitMonth = (TextView) findViewById(R.id.tv_habit_month);
- mHabitCalendarGrid = (GridLayout) findViewById(R.id.habit_calendar_grid);
- mTvHabitTotal = (TextView) findViewById(R.id.tv_habit_total);
- mProgressHabitGoal = (ProgressBar) findViewById(R.id.progress_habit_goal);
-
- mBtnPrevMonth.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mRenderCalendar.add(Calendar.MONTH, -1);
- renderHabitPanel();
- }
- });
- mBtnNextMonth.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mRenderCalendar.add(Calendar.MONTH, 1);
- renderHabitPanel();
- }
- });
- }
-
- @Override
- protected void onPause() {//在活动暂停时调用(切出去了)
- super.onPause();//调用父类的onPause方法
- if(saveNote()) {
- Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
- }
- clearSettingState();//清除设置状态
- }
-
- private void updateWidget() {//更新应用小部件
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);//接受意图
- if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
- intent.setClass(this, NoteWidgetProvider_2x.class);
- } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
- intent.setClass(this, NoteWidgetProvider_4x.class);
- } else {
- Log.e(TAG, "Unspported widget type");
- return;
- }
-
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {//添加应用小部件id
- mWorkingNote.getWidgetId()
- });
-
- sendBroadcast(intent);//发送广播更新应用小部件
- setResult(RESULT_OK, intent);//设置结果为成功,返回意图
- }
-
- public void onClick(View v) {//点击事件处理,语法细节没看
- int id = v.getId();
- if (id == R.id.btn_set_bg_color) {//如果点击的是设置背景颜色按钮
- mNoteBgColorSelector.setVisibility(View.VISIBLE);
- findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- - View.VISIBLE);
- } else if (sBgSelectorBtnsMap.containsKey(id)) {//如果点击的是背景颜色选择按钮
- findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.GONE);
- mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
- mNoteBgColorSelector.setVisibility(View.GONE);
- } else if (sFontSizeBtnsMap.containsKey(id)) {//如果点击的是字体大小选择按钮
- findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
- mFontSizeId = sFontSizeBtnsMap.get(id);
- mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
- findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
- //区分两种模式,列表模式和普通模式
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- getWorkingText();
- switchToListMode(mWorkingNote.getContent());
- } else {
- mNoteEditor.setTextAppearance(this,
- TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
- }
- mFontSizeSelector.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onBackPressed() {//返回键处理
- if(clearSettingState()) {
- return;
- }
- if (mHasTranslation) {
- AlertDialog.Builder b = new AlertDialog.Builder(this);
- b.setTitle(R.string.translate_confirm_keep_title);
- b.setPositiveButton(R.string.translate_keep, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- saveNote();
- finish();
- }
- });
- b.setNeutralButton(R.string.translate_discard, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (mOriginalContent != null) {
- mNoteEditor.setText(mOriginalContent);
- mHasTranslation = false;
- }
- saveNote();
- finish();
- }
- });
- b.setNegativeButton(R.string.translate_cancel_action, null);
- b.show();
- return;
- }
-
- saveNote();//保存笔记
- super.onBackPressed();
- }
-
- private boolean clearSettingState() {//清除设置状态
- if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {//
- mNoteBgColorSelector.setVisibility(View.GONE);
- return true;
- } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
- mFontSizeSelector.setVisibility(View.GONE);
- return true;
- }
- return false;
- }
-
- public void onBackgroundColorChanged() {//
- findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);//显示选中的背景颜色
- mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());//设置笔记编辑面板的背景颜色
- mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());//设置标题栏的背景颜色
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {//准备选项菜单
- if (isFinishing()) {
- return true;
- }
- clearSettingState();
- menu.clear();
- if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {//判断类型
- getMenuInflater().inflate(R.menu.call_note_edit, menu);//如果是通话记录文件夹, 通话记录转化为菜单
- } else {
- getMenuInflater().inflate(R.menu.note_edit, menu);
- }
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);//如果是列表模式, 菜单标题为普通模式
- } else {
- menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);//如果不是列表模式, 菜单标题为列表模式
- }
- if (mWorkingNote.hasClockAlert()) {
- menu.findItem(R.id.menu_alert).setVisible(false);
- } else {
- menu.findItem(R.id.menu_delete_remind).setVisible(false);
- }
- // 习惯相关菜单处理
- boolean isHabit = mWorkingNote.isHabit();
- menu.findItem(R.id.menu_set_habit).setVisible(!isHabit);
- menu.findItem(R.id.menu_habit_settings).setVisible(isHabit);
- menu.findItem(R.id.menu_stop_habit).setVisible(isHabit);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {//处理选项菜单点击事件
- switch (item.getItemId()) {
- case R.id.menu_new_note://新建笔记
- createNewNote();
- break;
- case R.id.menu_delete://删除笔记
- AlertDialog.Builder builder = new AlertDialog.Builder(this);//创建删除确认对话框
- builder.setTitle(getString(R.string.alert_title_delete));
- builder.setIcon(android.R.drawable.ic_dialog_alert);
- builder.setMessage(getString(R.string.alert_message_delete_note));
- builder.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- deleteCurrentNote();
- finish();
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- break;
- case R.id.menu_font_size://字体大小选择
- mFontSizeSelector.setVisibility(View.VISIBLE);
- findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
- break;
- case R.id.menu_list_mode://列表模式切换
- mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
- TextNote.MODE_CHECK_LIST : 0);
- break;
- case R.id.menu_share://分享笔记
- getWorkingText();
- sendTo(this, mWorkingNote.getContent());
- break;
- case R.id.menu_translate:
- showTranslateDialog();
- break;
- case R.id.menu_send_to_desktop://发送到桌面
- sendToDesktop();
- break;
- case R.id.menu_alert://设置提醒
- setReminder();
- break;
- case R.id.menu_delete_remind://删除提醒
- mWorkingNote.setAlertDate(0, false);
- break;
- case R.id.menu_set_habit:
- // 设置为习惯便签并显示设置对话框
- mWorkingNote.setHabit(true, "");
- showHabitConfigDialog();
- break;
- case R.id.menu_habit_settings:
- // 显示习惯设置对话框
- showHabitConfigDialog();
- break;
- case R.id.menu_stop_habit:
- // 停止习惯,转换为普通便签
- AlertDialog.Builder stopHabitBuilder = new AlertDialog.Builder(this);
- stopHabitBuilder.setTitle(R.string.habit_config_title);
- stopHabitBuilder.setMessage("确定要停止这个习惯吗?");
- stopHabitBuilder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // 将习惯便签转换为普通便签
- mWorkingNote.setHabit(false, "");
- // 取消习惯提醒
- cancelHabitAlarm(mWorkingNote);
- // 隐藏习惯面板
- mHabitPanel.setVisibility(View.GONE);
- // 刷新菜单
- invalidateOptionsMenu();
- // 刷新UI
- renderHabitPanel();
- }
- });
- stopHabitBuilder.setNegativeButton(android.R.string.cancel, null);
- stopHabitBuilder.show();
- break;
- default:
- break;
- }
- return true;
- }
-//下面是上文相关函数的具体实现
- private void setReminder() {
- DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
- d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
- public void OnDateTimeSet(AlertDialog dialog, long date) {
- mWorkingNote.setAlertDate(date , true);
- }
- });
- d.show();
- }
-
- /**
- * Share note to apps that support {@link Intent#ACTION_SEND} action
- * and {@text/plain} type
- */
- private void sendTo(Context context, String info) {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.putExtra(Intent.EXTRA_TEXT, info);//再intetn中添加标签为EXTRA_TEXT, 值为info
- intent.setType("text/plain");
- context.startActivity(intent);
- }
-
-
- private void showTranslateDialog() {
- View v = LayoutInflater.from(this).inflate(R.layout.translate_dialog, null);
- final Spinner spinner = (Spinner) v.findViewById(R.id.spinner_target_lang);
- final String[] langNames = new String[] {"英语", "中文", "日语", "韩语", "法语", "德语", "西班牙语"};
- final String[] langCodes = new String[] {"en", "zh-CHS", "ja", "ko", "fr", "de", "es"};
- ArrayAdapter adapter = new ArrayAdapter(this,
- android.R.layout.simple_spinner_item, langNames);
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinner.setAdapter(adapter);
-
- AlertDialog.Builder b = new AlertDialog.Builder(this);
- b.setTitle(R.string.translate_dialog_title);
- b.setView(v);
- final AlertDialog d = b.create();
-
- Button btnCancel = (Button) v.findViewById(R.id.btn_cancel_translate);
- Button btnConfirm = (Button) v.findViewById(R.id.btn_confirm_translate);
- btnCancel.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- d.dismiss();
- }
- });
- btnConfirm.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- int pos = spinner.getSelectedItemPosition();
- String code = "en";
- switch (pos) {
- case 1: code = "zh-CHS"; break;
- case 2: code = "ja"; break;
- case 3: code = "ko"; break;
- case 4: code = "fr"; break;
- case 5: code = "de"; break;
- case 6: code = "es"; break;
- default: code = "en"; break;
- }
- d.dismiss();
- startTranslate(code);
- }
- });
- d.show();
- }
-
- private void startTranslate(String targetLang) {
- if (!TranslateUtils.isOnline(this)) {
- showToast(R.string.translate_offline_hint);
- return;
- }
- // backup original content
- if (!mHasTranslation) {
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- // 清单模式下获取文本内容
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mEditTextList.getChildCount(); i++) {
- View view = mEditTextList.getChildAt(i);
- NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
- if (!TextUtils.isEmpty(edit.getText())) {
- if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
- sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
- } else {
- sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
- }
- }
- }
- mOriginalContent = sb.toString();
- } else {
- // 普通模式下获取文本内容
- mOriginalContent = mNoteEditor.getText().toString();
- }
- }
- mTargetLangCode = targetLang;
- mProgressDialog = ProgressDialog.show(this, "", getString(R.string.translate_progress), true, false);
-
- // 根据当前模式获取文本内容
- String content;
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mEditTextList.getChildCount(); i++) {
- View view = mEditTextList.getChildAt(i);
- NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
- if (!TextUtils.isEmpty(edit.getText())) {
- if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
- sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
- } else {
- sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
- }
- }
- }
- content = sb.toString();
- } else {
- content = mNoteEditor.getText().toString();
- }
-
- new TranslateTask().execute(content);
- }
-
- private class TranslateTask extends AsyncTask {
- @Override
- protected String doInBackground(String... params) {
- String content = params[0] == null ? "" : params[0];
- String[] paragraphs = content.split("\\n");
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < paragraphs.length; i++) {
- String p = paragraphs[i];
- sb.append(p);
- sb.append('\n');
- if (!p.trim().isEmpty()) {
- String t = TranslateUtils.translateParagraph(p, mTargetLangCode);
- if (t != null) {
- sb.append(t);
- sb.append('\n');
- } else {
- sb.append("[翻译失败]");
- sb.append('\n');
- }
- }
- }
- return sb.toString();
- }
-
- @Override
- protected void onPostExecute(String result) {
- if (mProgressDialog != null && mProgressDialog.isShowing()) {
- mProgressDialog.dismiss();
- }
- if (result != null) {
- mHasTranslation = true;
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- // 清单模式下,将翻译结果转换为清单格式
- mEditTextList.removeAllViews();
- String[] items = result.split("\n");
- int index = 0;
- for (String item : items) {
- if(!TextUtils.isEmpty(item)) {
- mEditTextList.addView(getListItem(item, index));
- index++;
- }
- }
- mEditTextList.addView(getListItem("", index));
- // 修改
- View focused = mEditTextList.getChildAt(index);
- focused.findViewById(R.id.et_edit_text).requestFocus();
- } else {
- // 普通模式下,直接显示翻译结果
- SpannableString spannable = new SpannableString(result);
- // 分析文本结构:原文和翻译交替出现
- // 格式:原文1\n翻译1\n原文2\n翻译2\n...
- String[] lines = result.split("\n");
- int currentPosition = 0;
- boolean isTranslation = false;
-
- for (String line : lines) {
- if (!TextUtils.isEmpty(line)) {
- if (isTranslation) {
- // 设置翻译结果为浅灰色
- int start = currentPosition;
- int end = currentPosition + line.length();
- spannable.setSpan(new ForegroundColorSpan(Color.parseColor("#999999")), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- isTranslation = !isTranslation;
- }
- currentPosition += line.length() + 1; // +1 for newline
- }
-
- mNoteEditor.setText(spannable);
- // scroll to first translation
- int firstTranslationIdx = result.indexOf('\n');
- if (firstTranslationIdx >= 0 && firstTranslationIdx + 1 < result.length()) {
- mNoteEditor.setSelection(firstTranslationIdx + 1);
- }
- }
- } else {
- showToast(R.string.error_sync_network);
- }
- }
- }
- private void createNewNote() {
- // Firstly, save current editing notes
- saveNote();
-
- // For safety, start a new NoteEditActivity
- finish();
- Intent intent = new Intent(this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
- startActivity(intent);
- }
-
- private void deleteCurrentNote() {
- if (mWorkingNote.existInDatabase()) {
- HashSet ids = new HashSet();
- long id = mWorkingNote.getNoteId();
- if (id != Notes.ID_ROOT_FOLDER) {//如果不是根文件夹
- ids.add(id);//将笔记id添加到集合中
- } else {
- Log.d(TAG, "Wrong note id, should not happen");
- }
- if (!isSyncMode()) {//如果不是同步模式
- if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {//批量删除笔记
- Log.e(TAG, "Delete Note error");
- }
- } else {
- if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {//将笔记移动到回收站文件夹
- Log.e(TAG, "Move notes to trash folder error, should not happens");
- }
- }
- }
- mWorkingNote.markDeleted(true);
- }
-
- private boolean isSyncMode() {
- return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
- }
-
- public void onClockAlertChanged(long date, boolean set) {//设置提醒时间
- /**
- * User could set clock to an unsaved note, so before setting the
- * alert clock, we should save the note first
- */
- if (!mWorkingNote.existInDatabase()) {
- saveNote();
- }
- if (mWorkingNote.getNoteId() > 0) {
- Intent intent = new Intent(this, AlarmReceiver.class);
- intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
- PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
- AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
- showAlertHeader();
- if(!set) {
- alarmManager.cancel(pendingIntent);
- } else {
- alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
- }
- } else {
- /**
- * There is the condition that user has input nothing (the note is
- * not worthy saving), we have no note id, remind the user that he
- * should input something
- */
- Log.e(TAG, "Clock alert setting error");
- showToast(R.string.error_note_empty_for_clock);
- }
- }
-
- public void onWidgetChanged() {
- updateWidget();
- }
-
- public void onEditTextDelete(int index, String text) {//处理清单模式下删除列表项 的操作
- int childCount = mEditTextList.getChildCount();//获取编辑框的子视图数量
- if (childCount == 1) {
- return;
- }
-
- for (int i = index + 1; i < childCount; i++) {//遍历删除项(index处)之后的所有列表项 ,将它们的索引减1,不会影响到后续的列表项的索引,从而避免了索引错乱的问题
- ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
- .setIndex(i - 1);
- }
-
- mEditTextList.removeViewAt(index);//删除index处的子图
- //亮点:小米笔记清单模式下的特殊设计 ,属于防误删的安全机制 ,主要为了保护用户内容,这是啥意思?
- NoteEditText edit = null;
- if(index == 0) {
- edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
- R.id.et_edit_text);
- } else {
- edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
- R.id.et_edit_text);//
- }
- int length = edit.length();//获取删除项(index处)之前的列表项的文本长度
- edit.append(text);//将被删除项的文本追加到目标项末尾
- edit.requestFocus();//将焦点自动切换到目标项
- edit.setSelection(length);//将光标定位到合并前的文本末尾
- }
-
- public void onEditTextEnter(int index, String text) {//处理清单模式下添加列表项的操作
- /**
- * Should not happen, check for debug
- */
- if(index > mEditTextList.getChildCount()) {
- Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
- }
-
- View view = getListItem(text, index);
- mEditTextList.addView(view, index);//将新的列表项视图添加到编辑框中,位置为index
- NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);//获取新添加的列表项的编辑框
- edit.requestFocus();
- edit.setSelection(0);//将光标定位到新添加的列表项的文本开头
- for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {//索引加一
- ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
- .setIndex(i);
- }
- }
-
- private void switchToListMode(String text) {//将编辑框切换到清单模式
- mEditTextList.removeAllViews();
- String[] items = text.split("\n");
- int index = 0;
- for (String item : items) {
- if(!TextUtils.isEmpty(item)) {
- mEditTextList.addView(getListItem(item, index));//将新的列表项视图添加到编辑框中,位置为index
- index++;
- }
- }
- mEditTextList.addView(getListItem("", index));//添加一个空列表项
- View focused = mEditTextList.getChildAt(index);
- focused.findViewById(R.id.et_edit_text).requestFocus();
- // 更新字符统计,显示整条便签的字符数
- mNoteHeaderHolder.tvCharNum.setText("字符数:" + calcVisibleCharCount(text));
-
- mNoteEditor.setVisibility(View.GONE);//隐藏编辑框
- mEditTextList.setVisibility(View.VISIBLE);
- }
-
- private Spannable getHighlightQueryResult(String fullText, String userQuery) {//高亮显示用户查询的文本,这里用了spannable,是比较好的视图管理器
- SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);//存储高亮显示的文本
- if (!TextUtils.isEmpty(userQuery)) {
- mPattern = Pattern.compile(userQuery, Pattern.CASE_INSENSITIVE);//编译正则表达式
- Matcher m = mPattern.matcher(fullText);//创建一个 Matcher 对象,用于对输入字符串进行匹配操作
- int start = 0;
- while (m.find(start)) { //构建span字符串
- spannable.setSpan(
- new BackgroundColorSpan(this.getResources().getColor(
- R.color.user_query_highlight)), m.start(), m.end(),
- Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
- start = m.end();
- }
- }
- return spannable;
- }
-
- private View getListItem(String item, int index) { //获取清单模式下的列表项视图
- View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
- final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);//获取列表项的编辑框
- edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
- CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); //复选框用于让用户从有限数量的选项中选择一个或多个选项。
- cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {//复选框变化监听
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {//
- if (isChecked) {//PaintFlags是Android中用于控制文本绘制样式的位掩码 ,通过位运算组合多个绘制标志
- edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);//添加删除线效果
- } else {
- edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);//清除删除线效果
- }
- }
- });
-
- if (item.startsWith(TAG_CHECKED)) {//如果列表项以(TAG_CHECKED)开头,则将复选框设置为选中状态
- cb.setChecked(true);
- edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);//
- item = item.substring(TAG_CHECKED.length(), item.length()).trim();//
- } else if (item.startsWith(TAG_UNCHECKED)) {
- cb.setChecked(false);
- edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);//
- item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
- }
-
- edit.setOnTextViewChangeListener(this);
- //新增选区变化回调接口设置
- edit.setOnSelectionChangeListener(this);
- edit.setIndex(index);
- edit.setText(getHighlightQueryResult(item, mUserQuery));
- return view;
- }
- //新增选区变化回调接口实现
-
- @Override
- public void onSelectionChanged(int index, int selStart, int selEnd) {
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- //列表模式下的选区变化事件
- if (index >= 0 && index < mEditTextList.getChildCount()) {
- //如果索引在范围内
- View view = mEditTextList.getChildAt(index);
- NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
- updateCharNumForSelection(edit.getText(), selStart, selEnd);
- }
- } else {
- if (mNoteEditor != null && mNoteEditor.isFocused()) {
- updateCharNumForSelection(mNoteEditor.getText(), selStart, selEnd);
- }
- }
- }
-
- public void onTextChange(int index, boolean hasText) {// 列表项文本变化事件
- if (index >= mEditTextList.getChildCount()) {//检查索引是否超出范围
- Log.e(TAG, "Wrong index, should not happen");
- return;
- }
- if(hasText) {//如果列表项有文本
- mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
- } else {
- mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
- }
- }
-
- public void onCheckListModeChanged(int oldMode, int newMode) {// 检查列表模式变化事件
- if (newMode == TextNote.MODE_CHECK_LIST) {//如果切换到检查列表模式
- switchToListMode(mNoteEditor.getText().toString());
- } else {
- if (!getWorkingText()) {
- mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
- ""));//如果没有选中项,则将所有未选中项的文本删除
- }
- mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
- mEditTextList.setVisibility(View.GONE);
- mNoteEditor.setVisibility(View.VISIBLE);
- // 更新字符统计为整条便签字符数
- mNoteHeaderHolder.tvCharNum.setText("字符数:" + calcVisibleCharCount(mWorkingNote.getContent()));
- }
- }
-
- private boolean getWorkingText() {//获取选中项的文本
- boolean hasChecked = false;
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//如果是检查列表模式
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mEditTextList.getChildCount(); i++) {//遍历所有列表项
- View view = mEditTextList.getChildAt(i);
- NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
- if (!TextUtils.isEmpty(edit.getText())) {
- if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
- sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");//如果列表项选中,则将其文本添加到 StringBuilder 中
- hasChecked = true;
- } else {
- sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");//如果列表项未选中,则将其文本添加到 StringBuilder 中
- }
- }
- }
- mWorkingNote.setWorkingText(sb.toString());
- } else {
- mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
- }
- return hasChecked;
- }
-
- private boolean saveNote() {//保存笔记
- getWorkingText();
- boolean saved = mWorkingNote.saveNote();
- if (saved) {
- /**
- * There are two modes from List view to edit view, open one note,
- * create/edit a node. Opening node requires to the original
- * position in the list when back from edit view, while creating a
- * new node requires to the top of the list. This code
- * {@link #RESULT_OK} is used to identify the create/edit state
- */
- setResult(RESULT_OK);
- }
- return saved;
- }
-
- private void sendToDesktop() {//将笔记发送到桌面
- /**
- * Before send message to home, we should make sure that current
- * editing note is exists in databases. So, for new note, firstly
- * save it
- */
- if (!mWorkingNote.existInDatabase()) {//如果笔记不存在于数据库中
- saveNote();
- }
-
- if (mWorkingNote.getNoteId() > 0) {//如果笔记存在于数据库中
- Intent sender = new Intent();
- Intent shortcutIntent = new Intent(this, NoteEditActivity.class);
- shortcutIntent.setAction(Intent.ACTION_VIEW);//设置意图操作行为为查看
- shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
- shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
- // 获取快捷方式名称
- String shortcutName = makeShortcutIconTitle(mWorkingNote.getContent());
-
- // 检查Android版本,使用相应的API
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
- // Android 8.0+ 使用ShortcutManager API
- android.content.pm.ShortcutManager shortcutManager = getSystemService(android.content.pm.ShortcutManager.class);
- if (shortcutManager != null && shortcutManager.isRequestPinShortcutSupported()) {
- // 创建ShortcutInfo对象
- android.content.pm.ShortcutInfo.Builder builder = new android.content.pm.ShortcutInfo.Builder(this, "note_" + mWorkingNote.getNoteId());
- builder.setShortLabel(shortcutName);
- builder.setLongLabel(shortcutName);
- builder.setIntent(shortcutIntent);
- // 设置图标
- builder.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.icon_app));
-
- // 创建PendingIntent用于确认
- Intent intent = new Intent(this, NoteEditActivity.class);
- intent.setAction("android.intent.action.CREATE_SHORTCUT");
- intent.putExtra("note_id", mWorkingNote.getNoteId());
- android.app.PendingIntent pendingIntent = android.app.PendingIntent.getActivity(this, 0, intent, 0);
-
- // 请求创建快捷方式
- shortcutManager.requestPinShortcut(builder.build(), pendingIntent.getIntentSender());
- showToast(R.string.info_note_enter_desktop);
- } else {
- // 如果ShortcutManager不可用,使用旧方式
- sendShortcutBroadcast(shortcutIntent, shortcutName);
- }
- } else {
- // Android 7.1及以下使用旧的广播方式
- sendShortcutBroadcast(shortcutIntent, shortcutName);
- }
- } else {
- /**
- * There is the condition that user has input nothing (the note is
- * not worthy saving), we have no note id, remind the user that he
- * should input something
- */
- Log.e(TAG, "Send to desktop error");
- showToast(R.string.error_note_empty_for_send_to_desktop);
- }
- }
-
- /**
- * 使用旧的广播方式创建快捷方式(兼容Android 7.1及以下)
- */
- private void sendShortcutBroadcast(Intent shortcutIntent, String shortcutName) {
- Intent sender = new Intent();
- sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
- sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, shortcutName);
- sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
- Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
- sender.putExtra("duplicate", true);
- sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
- showToast(R.string.info_note_enter_desktop);
- sendBroadcast(sender);
- }
-
- private String makeShortcutIconTitle(String content) {
- content = content.replace(TAG_CHECKED, "");
- content = content.replace(TAG_UNCHECKED, "");
- return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
- SHORTCUT_ICON_TITLE_MAX_LEN) : content;
- }
-
- private void showToast(int resId) {//显示短时间的提示消息
- showToast(resId, Toast.LENGTH_SHORT);//
- }
-
- private void showToast(int resId, int duration) {//显示自定义时间长度的 Toast 消息
- Toast.makeText(this, resId, duration).show();
- }
-}
diff --git a/src/ui/NoteEditText.java b/src/ui/NoteEditText.java
index b2ff0a0..e0fe20a 100644
--- a/src/ui/NoteEditText.java
+++ b/src/ui/NoteEditText.java
@@ -86,8 +86,6 @@ public class NoteEditText extends EditText {
}
private OnTextViewChangeListener mOnTextViewChangeListener; // 文本变化监听器实例
- //新增选区变化回调接口
- private OnSelectionChangeListener mOnSelectionChangeListener;
/**
* 构造函数,创建NoteEditText实例
@@ -113,10 +111,6 @@ public class NoteEditText extends EditText {
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
- //新增选区变化回调接口设置方法
- public void setOnSelectionChangeListener(OnSelectionChangeListener listener) {
- mOnSelectionChangeListener = listener;
- }
/**
* 构造函数,从XML布局创建NoteEditText实例
@@ -291,19 +285,4 @@ public class NoteEditText extends EditText {
}
super.onCreateContextMenu(menu);
}
-
- //新增选区变化回调接口实现
- @Override
- protected void onSelectionChanged(int selStart, int selEnd) {
- super.onSelectionChanged(selStart, selEnd);
- if (mOnSelectionChangeListener != null) {
- mOnSelectionChangeListener.onSelectionChanged(mIndex, selStart, selEnd);
- }
- }
}
-
-
-// 新增选区变化回调接口
-interface OnSelectionChangeListener {
- void onSelectionChanged(int index, int selStart, int selEnd);
-}
\ No newline at end of file
diff --git a/src/ui/NoteItemData.java b/src/ui/NoteItemData.java
index c01ff2f..7021ee6 100644
--- a/src/ui/NoteItemData.java
+++ b/src/ui/NoteItemData.java
@@ -110,13 +110,8 @@ public class NoteItemData {
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
- // habit flag, may not exist on older DBs
- try {
- mIsHabit = cursor.getInt(IS_HABIT_COLUMN) > 0;
- } catch (Exception e) {
- mIsHabit = false;
- }
+ // 处理通话记录相关属性
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
// 获取通话记录的电话号码
@@ -331,11 +326,6 @@ public class NoteItemData {
return (mAlertDate > 0);
}
- public boolean isHabit() {
- return mIsHabit;
- }
-
-
/**
* 是否为通话记录
* @return true如果是通话记录,否则false
diff --git a/src/ui/NotesListActivity.java b/src/ui/NotesListActivity.java
deleted file mode 100644
index 8f55201..0000000
--- a/src/ui/NotesListActivity.java
+++ /dev/null
@@ -1,1191 +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.ui;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.appwidget.AppWidgetManager;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.ActionMode;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.Display;
-import android.view.HapticFeedbackConstants;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnCreateContextMenuListener;
-import android.view.View.OnTouchListener;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemLongClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.PopupMenu;
-import android.widget.TextView;
-import android.widget.Toast;
-import net.micode.notes.ui.BackgroundManager;
-import net.micode.notes.R;
-import net.micode.notes.data.Notes;
-import net.micode.notes.data.Notes.NoteColumns;
-import net.micode.notes.gtask.remote.GTaskSyncService;
-import net.micode.notes.model.WorkingNote;
-import net.micode.notes.tool.BackupUtils;
-import net.micode.notes.tool.DataUtils;
-import net.micode.notes.tool.ResourceParser;
-import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
-import net.micode.notes.widget.NoteWidgetProvider_2x;
-import net.micode.notes.widget.NoteWidgetProvider_4x;
-
-import android.net.Uri;
-import android.app.Activity;
-import android.content.Intent;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.HashSet;
-
-/**
- * 小米笔记的主界面类,负责管理笔记列表、文件夹、搜索和批量操作
- * 实现了笔记的创建、编辑、删除、移动等核心功能
- */
-public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
- /**
- * 异步查询的Token常量,用于区分不同类型的查询请求
- */
- private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; // 查询文件夹下的笔记列表
- private static final int FOLDER_LIST_QUERY_TOKEN = 1; // 查询文件夹列表
-
- /**
- * 文件夹上下文菜单的菜单项ID
- */
- private static final int MENU_FOLDER_DELETE = 0; // 删除文件夹
- private static final int MENU_FOLDER_VIEW = 1; // 查看文件夹内容
- private static final int MENU_FOLDER_CHANGE_NAME = 2; // 重命名文件夹
-
- /**
- * SharedPreferences键名,用于标记是否显示过添加笔记的介绍
- */
- private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
-
- /**
- * 列表编辑状态枚举,用于管理不同的列表视图状态
- */
- private enum ListEditState {
- NOTE_LIST, // 根目录笔记列表
- SUB_FOLDER, // 子文件夹笔记列表
- CALL_RECORD_FOLDER // 通话记录文件夹列表
- };
-
- private ListEditState mState; // 当前列表状态
- private BackgroundQueryHandler mBackgroundQueryHandler; // 异步查询处理器,用于后台加载数据
-
- private NotesListAdapter mNotesListAdapter; // 笔记列表适配器,负责数据与UI的绑定
- private ListView mNotesListView; // 显示笔记列表的ListView组件
- private Button mAddNewNote; // 添加新笔记按钮
- private boolean mDispatch; // 触摸事件分发标志
- private int mOriginY; // 触摸事件起始Y坐标
- private int mDispatchY; // 触摸事件分发Y坐标
- private TextView mTitleBar; // 标题栏视图
- private long mCurrentFolderId; // 当前文件夹ID
- private ContentResolver mContentResolver; // 内容解析器,用于访问数据库
- private ModeCallback mModeCallBack; // 多选模式回调,处理批量操作
- private static final String TAG = "NotesListActivity"; // 日志标签
- public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; // ListView滚动速率
- private NoteItemData mFocusNoteDataItem; // 当前聚焦的笔记数据项
-
- /**
- * 数据库查询条件
- */
- private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; // 普通文件夹查询条件
- private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
- + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
- + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
- + NoteColumns.NOTES_COUNT + ">0)"; // 根文件夹查询条件
-
- /**
- * startActivityForResult的请求码
- */
- private final static int REQUEST_CODE_OPEN_NODE = 102; // 打开笔记的请求码
- private final static int REQUEST_CODE_NEW_NODE = 103; // 新建笔记的请求码
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {// 初始化活动,设置布局和资源
- super.onCreate(savedInstanceState);
- setContentView(R.layout.note_list);
- initResources();
-
- // 注册Android 13+的返回键回调
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
- getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
- android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- new android.window.OnBackInvokedCallback() {
- @Override
- public void onBackInvoked() {
- handleBackPress();
- }
- }
- );
- }
- /**
- * Insert an introduction when user firstly use this application
- */
- setAppInfoFromRawRes();
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {// 处理子活动返回结果
- //如果是背景管理器的结果,交给背景管理器处理
- if (mBackgroundManager != null && mBackgroundManager.handleActivityResult(requestCode, resultCode, data)) {
- return;
- }
- if (resultCode == RESULT_OK
- && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
- mNotesListAdapter.changeCursor(null);
- } else {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-
- /**
- * 首次使用应用时插入介绍笔记
- */
- private void setAppInfoFromRawRes() {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);// 亮点:SharedPreferences 是Android平台上一个轻量级的存储类,主要用于保存应用的一些常用配置。它通过键值对的形式将数据保存在XML文件中
- if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {// 检查是否已添加介绍笔记
- StringBuilder sb = new StringBuilder();
- InputStream in = null;
- try {
- in = getResources().openRawResource(R.raw.introduction);
- if (in != null) {
- InputStreamReader isr = new InputStreamReader(in, "UTF-8");// 亮点:指定字符编码为UTF-8,确保能够正确读取包含特殊字符的文本文件
- BufferedReader br = new BufferedReader(isr);// 亮点:BufferedReader 是Java IO库中的一个字符流类,用于读取字符数据。它提供了缓冲区的功能,能够一次读取多个字符,提高了读取效率。
- char [] buf = new char[1024];
- int len = 0;
- while ((len = br.read(buf)) > 0) {
- sb.append(buf, 0, len);//将读取到的字符数组buf中的0到len-1位置的字符添加到StringBuilder中
- }
- } else {
- Log.e(TAG, "Read introduction file error");
- return;
- }
- } catch (IOException e) {
- e.printStackTrace();
- return;
- } finally {
- if(in != null) {
- try {
- in.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
-
- WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
- AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
- ResourceParser.RED);
- note.setWorkingText(sb.toString());
- if (note.saveNote()) {
- sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
- } else {
- Log.e(TAG, "Save introduction note error");
- return;
- }
- }
- }
-
- /**
- * 活动启动时执行,开始异步查询笔记列表
- */
- @Override
- protected void onStart() {
- super.onStart();
- startAsyncNotesListQuery();
- }
-
- /**
- * 初始化资源和UI组件
- */
- private void initResources() {
- mContentResolver = this.getContentResolver();
- mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
- mCurrentFolderId = Notes.ID_ROOT_FOLDER;
- mNotesListView = (ListView) findViewById(R.id.notes_list);
- mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
- null, false);
- mNotesListView.setOnItemClickListener(new OnListItemClickListener());
- mNotesListView.setOnItemLongClickListener(this);
- mNotesListAdapter = new NotesListAdapter(this);
- mNotesListView.setAdapter(mNotesListAdapter);
- mAddNewNote = (Button) findViewById(R.id.btn_new_note);
- mAddNewNote.setOnClickListener(this);
- mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
- mDispatch = false;
- mDispatchY = 0;
- mOriginY = 0;
- mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
- mState = ListEditState.NOTE_LIST;
- mModeCallBack = new ModeCallback();
- //初始化背景管理器
- mNotesRootView = findViewById(R.id.notes_root);
-
- mBackgroundManager = new BackgroundManager(this, R.id.notes_root);
- mBackgroundManager.applyBackgroundFromPrefs();
- }
-
-
-
- //显示背景设置对话框
- private void showBackgroundSettingsDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- View view = LayoutInflater.from(this).inflate(R.layout.background_settings_dialog, null);
- builder.setView(view);
-
- Button btnSolidColor = (Button) view.findViewById(R.id.btn_solid_color);
- Button btnBuiltinImage = (Button) view.findViewById(R.id.btn_builtin_image);
- Button btnGalleryImage = (Button) view.findViewById(R.id.btn_gallery_image);
- Button btnResetDefault = (Button) view.findViewById(R.id.btn_reset_default);
-
- final AlertDialog dialog = builder.create();
-
- btnSolidColor.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- showColorChoiceDialog();
- }
- });
-
- btnBuiltinImage.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- showBuiltinChoiceDialog();
- }
- });
-
- btnGalleryImage.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.pickImageFromGallery();
- }
- });
-
- btnResetDefault.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.resetToDefaultAndClear();
- }
- });
-
- dialog.show();
- }
-
- private void showColorChoiceDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- View view = LayoutInflater.from(this).inflate(R.layout.color_choice_dialog, null);
- builder.setView(view);
-
- View colorWhite = view.findViewById(R.id.color_white);
- View colorYellow = view.findViewById(R.id.color_yellow);
- View colorRed = view.findViewById(R.id.color_red);
- View colorGreen = view.findViewById(R.id.color_green);
- View colorBlue = view.findViewById(R.id.color_blue);
-
- final AlertDialog dialog = builder.create();
-
- colorWhite.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.applyColorAndSave(0xFFFFFFFF);
- }
- });
-
- colorYellow.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.applyColorAndSave(0xFFFFFFCC);
- }
- });
-
- colorRed.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.applyColorAndSave(0xFFFFF0F0);
- }
- });
-
- colorGreen.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.applyColorAndSave(0xFFE8FFF0);
- }
- });
-
- colorBlue.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.applyColorAndSave(0xFFDDE8FF);
- }
- });
-
- dialog.show();
- }
-
- private void showBuiltinChoiceDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- View view = LayoutInflater.from(this).inflate(R.layout.builtin_image_choice_dialog, null);
- builder.setView(view);
-
- ImageView imageBuiltin1 = (ImageView) view.findViewById(R.id.image_builtin_1);
- ImageView imageBuiltin2 = (ImageView) view.findViewById(R.id.image_builtin_2);
- ImageView imageBuiltin3 = (ImageView) view.findViewById(R.id.image_builtin_3);
- ImageView imageBuiltin4 = (ImageView) view.findViewById(R.id.image_builtin_4);
-
- final AlertDialog dialog = builder.create();
-
- imageBuiltin1.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.applyBuiltinAndSave(R.drawable.background_1);
- }
- });
-
- imageBuiltin2.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.applyBuiltinAndSave(R.drawable.background_2);
- }
- });
-
- imageBuiltin3.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.applyBuiltinAndSave(R.drawable.background_3);
- }
- });
-
- imageBuiltin4.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- if (mBackgroundManager != null) mBackgroundManager.applyBuiltinAndSave(R.drawable.background_4);
- }
- });
-
- dialog.show();
- }
-
-
- /**
- * 多选模式回调类,处理批量操作和菜单项点击事件
- */
- private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
- /** 下拉菜单,用于显示选择操作选项 */
- private DropdownMenu mDropDownMenu;
- /** ActionMode实例,用于管理多选模式的生命周期 */
- private ActionMode mActionMode;
- /** 移动菜单项,用于批量移动笔记 */
- private MenuItem mMoveMenu;
-
- /**
- * 创建多选模式时的初始化操作
- * @param mode ActionMode实例
- * @param menu 菜单对象
- * @return 是否成功创建多选模式
- */
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- getMenuInflater().inflate(R.menu.note_list_options, menu);
- menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
- mMoveMenu = menu.findItem(R.id.move);
- if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
- || DataUtils.getUserFolderCount(mContentResolver) == 0) {
- mMoveMenu.setVisible(false);
- } else {
- mMoveMenu.setVisible(true);
- mMoveMenu.setOnMenuItemClickListener(this);
- }
- mActionMode = mode;
- mNotesListAdapter.setChoiceMode(true);
- mNotesListView.setLongClickable(false);
- mAddNewNote.setVisibility(View.GONE);
-
- View customView = LayoutInflater.from(NotesListActivity.this).inflate(
- R.layout.note_list_dropdown_menu, null);
- mode.setCustomView(customView);
- mDropDownMenu = new DropdownMenu(NotesListActivity.this,
- (Button) customView.findViewById(R.id.selection_menu),
- R.menu.note_list_dropdown);
- mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
- public boolean onMenuItemClick(MenuItem item) {
- mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
- updateMenu();
- return true;
- }
-
- });
- return true;
- }
-
- private void updateMenu() {// 更新下拉菜单显示的选中项数量
- int selectedCount = mNotesListAdapter.getSelectedCount();
- // Update dropdown menu
- String format = getResources().getString(R.string.menu_select_title, selectedCount);
- mDropDownMenu.setTitle(format);
- MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);// 查找选择所有项的菜单项
- if (item != null) {
- if (mNotesListAdapter.isAllSelected()) {
- item.setChecked(true);
- item.setTitle(R.string.menu_deselect_all);
- } else {
- item.setChecked(false);
- item.setTitle(R.string.menu_select_all);
- }
- }
- }
-
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {// 准备多选模式时的操作
- // TODO Auto-generated method stub
- return false;
- }
-
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {// 处理下拉菜单项点击事件
- // TODO Auto-generated method stub
- return false;
- }
-
- public void onDestroyActionMode(ActionMode mode) {// 多选模式结束时的操作
- mNotesListAdapter.setChoiceMode(false);
- mNotesListView.setLongClickable(true);
- mAddNewNote.setVisibility(View.VISIBLE);
- }
-
- public void finishActionMode() {// 结束多选模式
- mActionMode.finish();
- }
-
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
- boolean checked) {// 处理列表项选中状态改变事件,注意actionmode的生命周期
- mNotesListAdapter.setCheckedItem(position, checked);
- updateMenu();
- }
-
- public boolean onMenuItemClick(MenuItem item) {// 处理下拉菜单项点击事件
- if (mNotesListAdapter.getSelectedCount() == 0) {
- Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
- Toast.LENGTH_SHORT).show();
- return true;
- }
-
- switch (item.getItemId()) {
- case R.id.delete:
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
- builder.setTitle(getString(R.string.alert_title_delete));
- builder.setIcon(android.R.drawable.ic_dialog_alert);
- builder.setMessage(getString(R.string.alert_message_delete_notes,
- mNotesListAdapter.getSelectedCount()));
- builder.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int which) {
- batchDelete();
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- break;
- case R.id.move:
- startQueryDestinationFolders();
- break;
- default:
- return false;
- }
- return true;
- }
- }
-
- private class NewNoteOnTouchListener implements OnTouchListener {// 处理"新建笔记"按钮的触摸事件
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN: {
- Display display = getWindowManager().getDefaultDisplay();
- int screenHeight = display.getHeight();
- int newNoteViewHeight = mAddNewNote.getHeight();
- int start = screenHeight - newNoteViewHeight;
- int eventY = start + (int) event.getY();
- /**
- * Minus TitleBar's height
- */
- if (mState == ListEditState.SUB_FOLDER) {// 如果当前状态是子文件夹
- eventY -= mTitleBar.getHeight();
- start -= mTitleBar.getHeight();
- }
- /**
- * HACKME:When click the transparent part of "New Note" button, dispatch
- * the event to the list view behind this button. The transparent part of
- * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel)
- * and the line top of the button. The coordinate based on left of the "New
- * Note" button. The 94 represents maximum height of the transparent part.
- * Notice that, if the background of the button changes, the formula should
- * also change. This is very bad, just for the UI designer's strong requirement.
- */
- if (event.getY() < (event.getX() * (-0.12) + 94)) {// 如果点击位置在"新建笔记"按钮的透明部分
- View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- - mNotesListView.getFooterViewsCount());// 获取列表项的底部视图
- if (view != null && view.getBottom() > start
- && (view.getTop() < (start + 94))) {// 如果点击位置在"新建笔记"按钮的透明部分且在列表项范围内
- mOriginY = (int) event.getY();
- mDispatchY = eventY;
- event.setLocation(event.getX(), mDispatchY);
- mDispatch = true;
- return mNotesListView.dispatchTouchEvent(event);
- }
- }
- break;
- }
- case MotionEvent.ACTION_MOVE: {// 处理"新建笔记"按钮的移动事件
- if (mDispatch) {
- mDispatchY += (int) event.getY() - mOriginY;
- event.setLocation(event.getX(), mDispatchY);
- return mNotesListView.dispatchTouchEvent(event);
- }
- break;
- }
- default: {// 处理"新建笔记"按钮的其他事件
- if (mDispatch) {
- event.setLocation(event.getX(), mDispatchY);
- mDispatch = false;
- return mNotesListView.dispatchTouchEvent(event);
- }
- break;
- }
- }
- return false;
- }
-
- };
-
- /**
- * 启动异步查询笔记列表的方法
- * 根据当前文件夹ID构建查询条件,使用异步查询处理器加载数据
- */
- private void startAsyncNotesListQuery() {
- String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
- : NORMAL_SELECTION;
- mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
- Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
- String.valueOf(mCurrentFolderId)
- }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC" );// 按类型和修改日期排序
- }
-
- /**
- * 异步查询处理器,用于在后台执行数据库查询操作
- */
- private final class BackgroundQueryHandler extends AsyncQueryHandler {
- /**
- * 构造函数
- * @param contentResolver 内容解析器,用于访问数据库
- */
- public BackgroundQueryHandler(ContentResolver contentResolver) {
- super(contentResolver);
- }
-
- /**
- * 查询完成后的回调处理
- * @param token 查询标识,区分不同类型的查询
- * @param cookie 额外数据
- * @param cursor 查询结果游标
- */
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- switch (token) {
- case FOLDER_NOTE_LIST_QUERY_TOKEN:// 查询笔记列表
- mNotesListAdapter.changeCursor(cursor);
- break;
- case FOLDER_LIST_QUERY_TOKEN:// 查询文件夹列表
- if (cursor != null && cursor.getCount() > 0) {
- showFolderListMenu(cursor);
- } else {
- Log.e(TAG, "Query folder failed");
- }
- break;
- default:
- return;
- }
- }
- }
-
- private void showFolderListMenu(Cursor cursor) {// 显示文件夹列表菜单
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
- builder.setTitle(R.string.menu_title_select_folder);
- final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
- builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
-
- public void onClick(DialogInterface dialog, int which) {
- DataUtils.batchMoveToFolder(mContentResolver,
- mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
- Toast.makeText(
- NotesListActivity.this,
- getString(R.string.format_move_notes_to_folder,
- mNotesListAdapter.getSelectedCount(),
- adapter.getFolderName(NotesListActivity.this, which)),
- Toast.LENGTH_SHORT).show();
- mModeCallBack.finishActionMode();
- }
- });
- builder.show();
- }
-
- private void createNewNote() {// 创建新笔记
- Intent intent = new Intent(this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
- this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
- }
-
- private void batchDelete() {// 批量删除笔记
- new AsyncTask>() {
- protected HashSet doInBackground(Void... unused) {
- HashSet widgets = mNotesListAdapter.getSelectedWidget();
- if (!isSyncMode()) {
- // if not synced, delete notes directly
- if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
- .getSelectedItemIds())) {
- } else {
- Log.e(TAG, "Delete notes error, should not happens");
- }
- } else {
- // in sync mode, we'll move the deleted note into the trash
- // folder
- if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
- .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
- Log.e(TAG, "Move notes to trash folder error, should not happens");
- }
- }
- return widgets;
- }
-
- @Override
- protected void onPostExecute(HashSet widgets) {// 批量删除笔记完成后的回调处理
- if (widgets != null) {
- for (AppWidgetAttribute widget : widgets) {
- if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
- && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
- updateWidget(widget.widgetId, widget.widgetType);
- }
- }
- }
- mModeCallBack.finishActionMode();
- }
- }.execute();
- }
-
- private void deleteFolder(long folderId) {// 删除文件夹
- if (folderId == Notes.ID_ROOT_FOLDER) {
- Log.e(TAG, "Wrong folder id, should not happen " + folderId);
- return;
- }
-
- HashSet ids = new HashSet();
- ids.add(folderId);
- HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver,
- folderId);
- if (!isSyncMode()) {
- // if not synced, delete folder directly
- DataUtils.batchDeleteNotes(mContentResolver, ids);
- } else {
- // in sync mode, we'll move the deleted folder into the trash folder
- DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
- }
- if (widgets != null) {
- for (AppWidgetAttribute widget : widgets) {
- if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
- && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
- updateWidget(widget.widgetId, widget.widgetType);
- }
- }
- }
- }
-
- private void openNode(NoteItemData data) {// 打开笔记
- Intent intent = new Intent(this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_VIEW);
- intent.putExtra(Intent.EXTRA_UID, data.getId());
- this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
- }
-
- private void openFolder(NoteItemData data) {// 打开文件夹
- mCurrentFolderId = data.getId();
- startAsyncNotesListQuery();
- if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
- mState = ListEditState.CALL_RECORD_FOLDER;
- mAddNewNote.setVisibility(View.GONE);
- } else {
- mState = ListEditState.SUB_FOLDER;
- }
- if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
- mTitleBar.setText(R.string.call_record_folder_name);
- } else {
- mTitleBar.setText(data.getSnippet());
- }
- mTitleBar.setVisibility(View.VISIBLE);
- }
-
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.btn_new_note:
- createNewNote();
- break;
- default:
- break;
- }
- }
-
- private void showSoftInput() {// 显示软键盘
- InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- if (inputMethodManager != null) {
- inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
- }
- }
-
- private void hideSoftInput(View view) {// 隐藏软键盘
- InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
- }
-
- private void showCreateOrModifyFolderDialog(final boolean create) {// 显示创建或修改文件夹对话框
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
- final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
- showSoftInput();
- if (!create) {
- if (mFocusNoteDataItem != null) {
- etName.setText(mFocusNoteDataItem.getSnippet());
- builder.setTitle(getString(R.string.menu_folder_change_name));
- } else {
- Log.e(TAG, "The long click data item is null");
- return;
- }
- } else {
- etName.setText("");
- builder.setTitle(this.getString(R.string.menu_create_folder));
- }
-
- builder.setPositiveButton(android.R.string.ok, null);
- builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- hideSoftInput(etName);
- }
- });
-
- final Dialog dialog = builder.setView(view).show();
- final Button positive = (Button)dialog.findViewById(android.R.id.button1);
- positive.setOnClickListener(new OnClickListener() {// 确认创建或修改文件夹
- public void onClick(View v) {
- hideSoftInput(etName);
- String name = etName.getText().toString();
- if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
- Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
- Toast.LENGTH_LONG).show();
- etName.setSelection(0, etName.length());
- return;
- }
- if (!create) {
- if (!TextUtils.isEmpty(name)) {// 修改文件夹名称
- ContentValues values = new ContentValues();
- values.put(NoteColumns.SNIPPET, name);
- values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
- values.put(NoteColumns.LOCAL_MODIFIED, 1);
- mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
- + "=?", new String[] {
- String.valueOf(mFocusNoteDataItem.getId())
- });
- }
- } else if (!TextUtils.isEmpty(name)) {// 创建新文件夹
- ContentValues values = new ContentValues();
- values.put(NoteColumns.SNIPPET, name);
- values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
- mContentResolver.insert(Notes.CONTENT_NOTE_URI, values);
- }
- dialog.dismiss();
- }
- });
-
- if (TextUtils.isEmpty(etName.getText())) {// 输入框为空时禁用确认按钮
- positive.setEnabled(false);
- }
- /**
- * When the name edit text is null, disable the positive button
- */
- etName.addTextChangedListener(new TextWatcher() {// 监听输入框文本变化
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // TODO Auto-generated method stub
-
- }
-
- public void onTextChanged(CharSequence s, int start, int before, int count) {// 输入框文本变化时更新确认按钮状态
- if (TextUtils.isEmpty(etName.getText())) {
- positive.setEnabled(false);
- } else {
- positive.setEnabled(true);
- }
- }
-
- public void afterTextChanged(Editable s) {// 输入框文本变化后更新确认按钮状态
- // TODO Auto-generated method stub
-
- }
- });
- }
-
- @Override
- public void onBackPressed() {
- handleBackPress();
- }
- public void handleBackPress() {// 处理返回键按下事件,根据当前状态执行相应操作
- switch (mState) {
- case SUB_FOLDER:// 返回上一级文件夹
- mCurrentFolderId = Notes.ID_ROOT_FOLDER;
- mState = ListEditState.NOTE_LIST;
- mTitleBar.setVisibility(View.GONE);
- startAsyncNotesListQuery();
- invalidateOptionsMenu();
- break;
- case CALL_RECORD_FOLDER:// 返回上一级通话记录文件夹
- mCurrentFolderId = Notes.ID_ROOT_FOLDER;
- mState = ListEditState.NOTE_LIST;
- mAddNewNote.setVisibility(View.VISIBLE);
- mTitleBar.setVisibility(View.GONE);
- startAsyncNotesListQuery();
- break;
- case NOTE_LIST:// 返回主界面
- super.onBackPressed();
- break;
- default:
- break;
- }
- }
-
- private void updateWidget(int appWidgetId, int appWidgetType) {// 更新指定应用小部件
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- if (appWidgetType == Notes.TYPE_WIDGET_2X) {
- intent.setClass(this, NoteWidgetProvider_2x.class);
- } else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
- intent.setClass(this, NoteWidgetProvider_4x.class);
- } else {
- Log.e(TAG, "Unspported widget type");
- return;
- }
-
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
- appWidgetId
- });
-
- sendBroadcast(intent);
- setResult(RESULT_OK, intent);
- }
-
- private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {// 文件夹上下文菜单创建监听
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- if (mFocusNoteDataItem != null) {
- menu.setHeaderTitle(mFocusNoteDataItem.getSnippet());
- menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view);
- menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete);
- menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name);
- }
- }
- };
-
- @Override
- public void onContextMenuClosed(Menu menu) {// 上下文菜单关闭时移除监听
- if (mNotesListView != null) {
- mNotesListView.setOnCreateContextMenuListener(null);
- }
- super.onContextMenuClosed(menu);
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- if (mFocusNoteDataItem == null) {
- Log.e(TAG, "The long click data item is null");
- return false;
- }
- switch (item.getItemId()) {
- case MENU_FOLDER_VIEW:// 查看文件夹
- openFolder(mFocusNoteDataItem);
- break;
- case MENU_FOLDER_DELETE:// 删除文件夹
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(getString(R.string.alert_title_delete));
- builder.setIcon(android.R.drawable.ic_dialog_alert);
- builder.setMessage(getString(R.string.alert_message_delete_folder));
- builder.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- deleteFolder(mFocusNoteDataItem.getId());
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- break;
- case MENU_FOLDER_CHANGE_NAME:// 重命名文件夹
- showCreateOrModifyFolderDialog(false);
- break;
- default:
- break;
- }
-
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {// 准备选项菜单
- menu.clear();
- if (mState == ListEditState.NOTE_LIST) {
- getMenuInflater().inflate(R.menu.note_list, menu);
- // set sync or sync_cancel
- menu.findItem(R.id.menu_sync).setTitle(
- GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync);
- } else if (mState == ListEditState.SUB_FOLDER) {
- getMenuInflater().inflate(R.menu.sub_folder, menu);
- } else if (mState == ListEditState.CALL_RECORD_FOLDER) {
- getMenuInflater().inflate(R.menu.call_record_folder, menu);
- } else {
- Log.e(TAG, "Wrong state:" + mState);
- }
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {// 选项菜单项点击事件处理
- switch (item.getItemId()) {
- case R.id.menu_new_folder: {// 新建文件夹
- showCreateOrModifyFolderDialog(true);
- break;
- }
- case R.id.menu_export_text: {// 导出笔记为文本文件
- exportNoteToText();
- break;
- }
- case R.id.menu_sync: {// 同步笔记
- if (isSyncMode()) {
- if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {// 同步笔记
- GTaskSyncService.startSync(this);
- } else {
- GTaskSyncService.cancelSync(this);
- }
- } else {
- startPreferenceActivity();
- }
- break;
- }
- case R.id.menu_setting: {// 打开偏好设置
- startPreferenceActivity();
- break;
- }
- case R.id.menu_new_note: {// 新建笔记
- createNewNote();
- break;
- }
- case R.id.menu_search: {// 搜索笔记
- onSearchRequested();
- break;
- }
-
- //如果是背景设置菜单项
- case R.id.menu_background_settings: {
- showBackgroundSettingsDialog();
- break;
- }
- default:
- break;
- }
- return true;
- }
-
- @Override
- public boolean onSearchRequested() {// 搜索笔记
- startSearch(null, false, null /* appData */, false);
- return true;
- }
-
- private void exportNoteToText() {// 导出笔记为文本文件
- final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
- new AsyncTask() {
-
- @Override
- protected Integer doInBackground(Void... unused) {// 后台导出笔记为文本文件
- return backup.exportToText();
- }
-
- @Override
- protected void onPostExecute(Integer result) {// 导出笔记为文本文件完成后的回调
- if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);// 导出笔记为文本文件失败对话框
- builder.setTitle(NotesListActivity.this
- .getString(R.string.failed_sdcard_export));
- builder.setMessage(NotesListActivity.this
- .getString(R.string.error_sdcard_unmounted));
- builder.setPositiveButton(android.R.string.ok, null);
- builder.show();
- } else if (result == BackupUtils.STATE_SUCCESS) {
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);// 导出笔记为文本文件成功对话框
- builder.setTitle(NotesListActivity.this
- .getString(R.string.success_sdcard_export));
- builder.setMessage(NotesListActivity.this.getString(
- R.string.format_exported_file_location, backup
- .getExportedTextFileName(), backup.getExportedTextFileDir()));
- builder.setPositiveButton(android.R.string.ok, null);
- builder.show();
- } else if (result == BackupUtils.STATE_SYSTEM_ERROR) {// 导出笔记为文本文件系统错误对话框
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
- builder.setTitle(NotesListActivity.this
- .getString(R.string.failed_sdcard_export));
- builder.setMessage(NotesListActivity.this
- .getString(R.string.error_sdcard_export));
- builder.setPositiveButton(android.R.string.ok, null);
- builder.show();
- }
- }
-
- }.execute();
- }
-
- private boolean isSyncMode() {// 判断是否为同步模式
- return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
- }
-
- private void startPreferenceActivity() {// 打开偏好设置活动
- Activity from = getParent() != null ? getParent() : this;
- Intent intent = new Intent(from, NotesPreferenceActivity.class);
- from.startActivityIfNeeded(intent, -1);
- }
-
- private class OnListItemClickListener implements OnItemClickListener {// 列表项点击监听器
-
- public void onItemClick(AdapterView> parent, View view, int position, long id) {// 列表项点击事件
- if (view instanceof NotesListItem) {
- NoteItemData item = ((NotesListItem) view).getItemData();
- if (mNotesListAdapter.isInChoiceMode()) {
- if (item.getType() == Notes.TYPE_NOTE) {
- position = position - mNotesListView.getHeaderViewsCount();
- mModeCallBack.onItemCheckedStateChanged(null, position, id,
- !mNotesListAdapter.isSelectedItem(position));
- }
- return;
- }
-
- switch (mState) {// 根据当前状态处理点击事件
- case NOTE_LIST:
- if (item.getType() == Notes.TYPE_FOLDER// 点击文件夹项
- || item.getType() == Notes.TYPE_SYSTEM) {
- openFolder(item);
- } else if (item.getType() == Notes.TYPE_NOTE) {
- openNode(item);
- } else {
- Log.e(TAG, "Wrong note type in NOTE_LIST");
- }
- break;
- case SUB_FOLDER:
- case CALL_RECORD_FOLDER:
- if (item.getType() == Notes.TYPE_NOTE) {
- openNode(item);
- } else {
- Log.e(TAG, "Wrong note type in SUB_FOLDER");
- }
- break;
- default:
- break;
- }
- }
- }
-
- }
-
- private void startQueryDestinationFolders() {// 查询目标文件夹
- String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
- selection = (mState == ListEditState.NOTE_LIST) ? selection:
- "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
-
- mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
- null,
- Notes.CONTENT_NOTE_URI,
- FoldersListAdapter.PROJECTION,
- selection,
- new String[] {
- String.valueOf(Notes.TYPE_FOLDER),
- String.valueOf(Notes.ID_TRASH_FOLER),
- String.valueOf(mCurrentFolderId)
- },
- NoteColumns.MODIFIED_DATE + " DESC");
- }
-
- public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {// 列表项长按事件
- if (view instanceof NotesListItem) {
- mFocusNoteDataItem = ((NotesListItem) view).getItemData();
- if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
- if (mNotesListView.startActionMode(mModeCallBack) != null) {
- mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
- mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- } else {
- Log.e(TAG, "startActionMode fails");
- }
- } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
- mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
- }
- }
- return false;
- }
-}
diff --git a/src/ui/NotesListAdapter.java b/src/ui/NotesListAdapter.java
index 98106be..5159005 100644
--- a/src/ui/NotesListAdapter.java
+++ b/src/ui/NotesListAdapter.java
@@ -31,80 +31,89 @@ import java.util.HashSet;
import java.util.Iterator;
-public class NotesListAdapter extends CursorAdapter {//Adapter作为Activity和ListView之间的适配器,将Cursor数据转换为ListView可以理解的视图格式
- private static final String TAG = "NotesListAdapter"; // 日志标签
- private Context mContext; // 上下文对象
- private HashMap mSelectedIndex; // 选中项索引映射
- private int mNotesCount; // 笔记数量(排除文件夹)
- private boolean mChoiceMode; // 是否处于选择模式
-
- public static class AppWidgetAttribute { // 小部件属性类,用于存储小部件的ID和类型
- public int widgetId; // 小部件ID
- public int widgetType; // 小部件类型
+public class NotesListAdapter extends CursorAdapter {
+ private static final String TAG = "NotesListAdapter";
+ private Context mContext;
+ private HashMap mSelectedIndex;
+ private int mNotesCount;
+ private boolean mChoiceMode;
+
+ private boolean mIncludeFolders;
+
+ public static class AppWidgetAttribute {
+ public int widgetId;
+ public int widgetType;
};
-
- public NotesListAdapter(Context context) { // 构造函数,创建NotesListAdapter实例
+ public NotesListAdapter(Context context) {
super(context, null);
- mSelectedIndex = new HashMap(); // 初始化选中项映射
+ mSelectedIndex = new HashMap();
mContext = context;
mNotesCount = 0;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return new NotesListItem(context); // 创建新的NotesListItem
+ return new NotesListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
- NoteItemData itemData = new NoteItemData(context, cursor); // 创建数据模型
- ((NotesListItem) view).bind(context, itemData, mChoiceMode, // 绑定数据到视图
+ NoteItemData itemData = new NoteItemData(context, cursor);
+ ((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
public void setCheckedItem(final int position, final boolean checked) {
- mSelectedIndex.put(position, checked); // 更新选中状态
- notifyDataSetChanged(); // 通知数据变化,刷新UI
+ mSelectedIndex.put(position, checked);
+ notifyDataSetChanged();
}
public boolean isInChoiceMode() {
- return mChoiceMode; // 检查是否处于选择模式
+ return mChoiceMode;
}
public void setChoiceMode(boolean mode) {
- mSelectedIndex.clear(); // 清除之前的选中状态
- mChoiceMode = mode; // 更新模式标志
+ mSelectedIndex.clear();
+ mChoiceMode = mode;
+ mIncludeFolders = false;
+ }
+ public void setChoiceMode(boolean mode, boolean includeFolders) {
+ mSelectedIndex.clear();
+ mChoiceMode = mode;
+ mIncludeFolders = includeFolders;
}
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
- if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { // 只处理普通笔记,跳过文件夹
- setCheckedItem(i, checked); // 设置选中状态
+ int type = NoteItemData.getNoteType(cursor);
+ if (type == Notes.TYPE_NOTE || (mIncludeFolders && type == Notes.TYPE_FOLDER)) {
+ setCheckedItem(i, checked);
}
}
}
}
- public HashSet getSelectedItemIds() {// 获取选中项的ID集合
+ public HashSet getSelectedItemIds() {
HashSet itemSet = new HashSet();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
- Long id = getItemId(position); // 获取选中项的ID
+ Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
- itemSet.add(id); // 添加到集合
+ itemSet.add(id);
}
}
}
+
return itemSet;
}
- public HashSet getSelectedWidget() {// 获取选中项的小部件属性集合
+ public HashSet getSelectedWidget() {
HashSet itemSet = new HashSet();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
@@ -112,10 +121,12 @@ public class NotesListAdapter extends CursorAdapter {//Adapter作为Activity和L
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
- widget.widgetId = item.getWidgetId(); // 获取小部件ID
- widget.widgetType = item.getWidgetType(); // 获取小部件类型
+ widget.widgetId = item.getWidgetId();
+ widget.widgetType = item.getWidgetType();
itemSet.add(widget);
- // Don't close cursor here, only the adapter could close it
+ /**
+ * Don't close cursor here, only the adapter could close it
+ */
} else {
Log.e(TAG, "Invalid cursor");
return null;
@@ -125,7 +136,7 @@ public class NotesListAdapter extends CursorAdapter {//Adapter作为Activity和L
return itemSet;
}
- public int getSelectedCount() {// 获取选中项数量
+ public int getSelectedCount() {
Collection values = mSelectedIndex.values();
if (null == values) {
return 0;
@@ -137,39 +148,39 @@ public class NotesListAdapter extends CursorAdapter {//Adapter作为Activity和L
count++;
}
}
- return count; // 返回选中项数量
+ return count;
}
- public boolean isAllSelected() {// 检查是否所有笔记都被选中
+ public boolean isAllSelected() {
int checkedCount = getSelectedCount();
- return (checkedCount != 0 && checkedCount == mNotesCount);
+ return (checkedCount != 0 && checkedCount == mNotesCount);
}
- public boolean isSelectedItem(final int position) {// 检查指定位置的项是否被选中
+ public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
- return mSelectedIndex.get(position); // 检查指定位置的项是否被选中
+ return mSelectedIndex.get(position);
}
@Override
protected void onContentChanged() {
super.onContentChanged();
- calcNotesCount(); // 当内容变化时,重新计算笔记数量
+ calcNotesCount();
}
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
- calcNotesCount(); // 当游标变化时,重新计算笔记数量
+ calcNotesCount();
}
- private void calcNotesCount() {// 计算普通笔记的数量
+ private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c != null) {
- if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { // 只统计普通笔记,排除文件夹
+ if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} else {
diff --git a/src/ui/NotesListItem.java b/src/ui/NotesListItem.java
index 60c91cd..b41f8e7 100644
--- a/src/ui/NotesListItem.java
+++ b/src/ui/NotesListItem.java
@@ -31,16 +31,13 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout {
- private ImageView mAlert; // 提醒图标(时钟或通话记录图标)
- private TextView mTitle; // 笔记/文件夹标题或内容摘要
- private TextView mTime; // 最后修改时间
- private TextView mCallName; // 通话记录来电者名称(仅通话记录显示)
- private NoteItemData mItemData; // 当前列表项的数据模型
- private CheckBox mCheckBox; // 选择模式下的复选框(用于批量操作)
+ private ImageView mAlert;
+ private TextView mTitle;
+ private TextView mTime;
+ private TextView mCallName;
+ private NoteItemData mItemData;
+ private CheckBox mCheckBox;
- /**
- * 构造函数,初始化视图组件
- */
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
@@ -51,13 +48,7 @@ public class NotesListItem extends LinearLayout {
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
- /**
- * 绑定数据到视图,根据数据类型动态调整UI样式
- * @param data 笔记数据模型
- * @param checked 选择模式下是否选中
- */
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
- // 处理选择模式显示
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
@@ -66,7 +57,6 @@ public class NotesListItem extends LinearLayout {
}
mItemData = data;
- // 处理通话记录文件夹
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
@@ -74,9 +64,14 @@ public class NotesListItem extends LinearLayout {
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
- }
- // 处理通话记录笔记
- else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
+ } else if (data.getId() == Notes.ID_TRASH_FOLER) {
+ mCallName.setVisibility(View.GONE);
+ mAlert.setVisibility(View.VISIBLE);
+ mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
+ mTitle.setText(context.getString(R.string.trash_folder_name)
+ + context.getString(R.string.format_folder_files_count, data.getNotesCount()));
+ mAlert.setImageResource(android.R.drawable.ic_menu_delete);
+ } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
@@ -87,9 +82,14 @@ public class NotesListItem extends LinearLayout {
} else {
mAlert.setVisibility(View.GONE);
}
- }
- // 处理普通文件夹和笔记
- else {
+ } else if (data.getId() == Notes.ID_TRASH_FOLER) { //为回收站添加图标和显示逻辑
+ mCallName.setVisibility(View.GONE);
+ mAlert.setVisibility(View.VISIBLE);
+ mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
+ mTitle.setText(context.getString(R.string.trash_folder_name)
+ + context.getString(R.string.format_folder_files_count, data.getNotesCount()));
+ mAlert.setImageResource(R.drawable.baseline_restore_from_trash_24);
+ } else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
@@ -108,20 +108,14 @@ public class NotesListItem extends LinearLayout {
}
}
}
- // 设置最后修改时间(相对时间格式)
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
- // 根据数据类型和位置设置背景
setBackground(data);
}
- /**
- * 根据笔记类型和位置设置不同的背景资源
- */
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) {
- // 根据笔记在列表中的位置设置不同背景
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
@@ -132,14 +126,10 @@ public class NotesListItem extends LinearLayout {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
- // 文件夹使用统一背景
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
- /**
- * 获取当前列表项的数据模型
- */
public NoteItemData getItemData() {
return mItemData;
}
diff --git a/src/ui/NotesPreferenceActivity.java b/src/ui/NotesPreferenceActivity.java
index 341b589..f9569c8 100644
--- a/src/ui/NotesPreferenceActivity.java
+++ b/src/ui/NotesPreferenceActivity.java
@@ -91,9 +91,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
- registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
- } else {
registerReceiver(mReceiver, filter);
mOriAccounts = null;
diff --git a/src/ui/TrashManager.java b/src/ui/TrashManager.java
new file mode 100644
index 0000000..e900b8e
--- /dev/null
+++ b/src/ui/TrashManager.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.ui;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.tool.DataUtils;
+import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
+
+import java.util.HashSet;
+
+public class TrashManager {
+ private static final String TAG = "TrashManager";
+ private final Context mContext;
+ private final ContentResolver mResolver;
+ private final Callback mCallback;
+
+ public interface Callback {
+ void onWidgetsNeedUpdate(HashSet widgets);
+
+ void onListChanged();
+
+ void onActionModeFinished();
+
+ void onRestoreInvalid();
+ }
+
+ public TrashManager(Context context, ContentResolver resolver, Callback callback) {
+ mContext = context;
+ mResolver = resolver;
+ mCallback = callback;
+ }
+
+ public void cleanupExpiredTrash() {
+ long expireTime = System.currentTimeMillis() - 24L * 60L * 60L * 1000L;
+ mResolver.delete(Notes.CONTENT_NOTE_URI,
+ NoteColumns.PARENT_ID + "=? AND " + NoteColumns.MODIFIED_DATE + "",
+ new String[] { String.valueOf(Notes.ID_TRASH_FOLER), String.valueOf(expireTime) });
+ }
+
+ public void batchDelete(final boolean inTrash, final HashSet ids,
+ final HashSet widgets, final long originFolderId) {
+ new AsyncTask>() {
+ protected HashSet doInBackground(Void... unused) {
+ if (inTrash) {
+ if (!DataUtils.batchDeleteNotes(mResolver, ids)) {
+ Log.e(TAG, "Delete notes error, should not happens");
+ }
+ } else {
+ if (!DataUtils.batchMoveToTrash(mResolver, ids, originFolderId)) {
+ Log.e(TAG, "Move notes to trash folder error");
+ }
+ }
+ return widgets;
+ }
+
+ @Override
+ protected void onPostExecute(HashSet resultWidgets) {
+ if (mCallback != null) {
+ mCallback.onWidgetsNeedUpdate(resultWidgets);
+ mCallback.onListChanged();
+ mCallback.onActionModeFinished();
+ }
+ }
+ }.execute();
+ }
+
+ public void restoreSelected(final HashSet ids, final HashSet widgets) {
+ new AsyncTask() {
+ protected Boolean doInBackground(Void... params) {
+ boolean hasInvalid = false;
+ long now = System.currentTimeMillis();
+ for (long id : ids) {
+ Cursor cursor = mResolver.query(
+ ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
+ new String[] { NoteColumns.ORIGIN_PARENT_ID, NoteColumns.TYPE },
+ null, null, null);
+ if (cursor == null) {
+ continue;
+ }
+ long originParent = Notes.ID_ROOT_FOLDER;
+ int type = Notes.TYPE_NOTE;
+ try {
+ if (cursor.moveToFirst()) {
+ originParent = cursor.getLong(0);
+ type = cursor.getInt(1);
+ }
+ } finally {
+ cursor.close();
+ }
+ long targetParent = resolveRestoreParent(originParent);
+ if (targetParent != originParent) {
+ hasInvalid = true;
+ }
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.PARENT_ID, targetParent);
+ values.put(NoteColumns.ORIGIN_PARENT_ID, 0);
+ values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ values.put(NoteColumns.MODIFIED_DATE, now);
+ mResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
+ values, null, null);
+ if (type == Notes.TYPE_FOLDER) {
+ restoreNotesForFolder(id, now);
+ }
+ }
+ return hasInvalid;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean hasInvalid) {
+ if (mCallback != null) {
+ if (hasInvalid != null && hasInvalid) {
+ mCallback.onRestoreInvalid();
+ }
+ mCallback.onWidgetsNeedUpdate(widgets);
+ mCallback.onListChanged();
+ mCallback.onActionModeFinished();
+ }
+ }
+ }.execute();
+ }
+
+ public HashSet moveFolderToTrash(long folderId, long originFolderId) {
+ HashSet widgets = DataUtils.getFolderNoteWidget(mResolver, folderId);
+ DataUtils.moveNotesToTrashForFolder(mResolver, folderId);
+ HashSet ids = new HashSet();
+ ids.add(folderId);
+ if (!DataUtils.batchMoveToTrash(mResolver, ids, originFolderId)) {
+ Log.e(TAG, "Move folder to trash error");
+ }
+ return widgets;
+ }
+
+ private void restoreNotesForFolder(long folderId, long now) {
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.PARENT_ID, folderId);
+ values.put(NoteColumns.ORIGIN_PARENT_ID, 0);
+ values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ values.put(NoteColumns.MODIFIED_DATE, now);
+ mResolver.update(Notes.CONTENT_NOTE_URI, values,
+ NoteColumns.PARENT_ID + "=? AND " + NoteColumns.ORIGIN_PARENT_ID + "=?",
+ new String[] { String.valueOf(Notes.ID_TRASH_FOLER), String.valueOf(folderId) });
+ }
+
+ private long resolveRestoreParent(long originParentId) {
+ if (originParentId == Notes.ID_ROOT_FOLDER || originParentId == Notes.ID_CALL_RECORD_FOLDER) {
+ return originParentId;
+ }
+ if (originParentId <= 0) {
+ return Notes.ID_ROOT_FOLDER;
+ }
+ Cursor cursor = mResolver.query(
+ ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, originParentId),
+ new String[] { NoteColumns.ID, NoteColumns.PARENT_ID },
+ NoteColumns.PARENT_ID + "<>?",
+ new String[] { String.valueOf(Notes.ID_TRASH_FOLER) },
+ null);
+ if (cursor == null) {
+ return Notes.ID_ROOT_FOLDER;
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ return originParentId;
+ }
+ } finally {
+ cursor.close();
+ }
+ return Notes.ID_ROOT_FOLDER;
+ }
+}