上传res文件夹 #30

Merged
p6vxzahlf merged 2 commits from wangyijia_branch into master 4 weeks ago

@ -166,6 +166,17 @@ public class Notes {
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
/**
* Flag to indicate this note is a habit note
* <P> Type : INTEGER (0/1) </P>
*/
public static final String IS_HABIT = "is_habit";
/**
* Habit configuration stored as JSON string
* <P> Type : TEXT </P>
*/
public static final String HABIT_CONFIG = "habit_config";
}
public interface DataColumns {

@ -38,7 +38,7 @@ import net.micode.notes.data.NotesDatabaseHelper.TABLE;
*
* @Package: net.micode.notes.data
* @ClassName: NotesProvider
* @Description: 便
* @Description: java
*/
public class NotesProvider extends ContentProvider {
//uri匹配器

@ -25,11 +25,6 @@ import org.json.JSONException;
import org.json.JSONObject;
/**
* @Package: net.micode.notes.gtask.data
* @ClassName: MetaData
* @Description: Google Task便Google Task
*/
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();

@ -33,11 +33,7 @@ import java.util.ArrayList;
*
* @Package: net.micode.notes.gtask.data
* @ClassName: TaskList
* @Description: Node
* 1. Task
* 2. Google Task APIJSON
* 3.
* 4.
* @Description: java
*/
public class TaskList extends Node {
private static final String TAG = TaskList.class.getSimpleName();

@ -27,7 +27,32 @@ import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* GTaskASyncTask - Google Task
*
*
* 1. 线Google Task
* 2.
* 3.
* 4.
*
*
* - AsyncTask线
* -
* -
*
*
* 1. doInBackground()
* 2. onProgressUpdate()
* 3. onPostExecute()
* 4. cancelSync()
*
* 线
* 使AsyncTaskUI线线
*
* @author MiCode Open Source Community
* @version 1.0
*/
/**
*
* @Package: net.micode.notes.gtask.remote
@ -102,7 +127,16 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
/**
*
*
*
* 1.
* 2. GTaskManager
*
* @param unused 使Void
* @return
*/
/**
* @method doInBackground
* @description

@ -912,7 +912,7 @@ public class GTaskManager {
return GTaskClient.getInstance().getSyncAccount().name;
}
public void cancelSync() {
mCancelled = true;
}

@ -60,6 +60,10 @@ public class WorkingNote {
private boolean mIsDeleted;
// habit fields
private boolean mIsHabit;
private String mHabitConfig;
private NoteSettingChangedListener mNoteSettingStatusListener;// 设置变化监听器
//数据表查询字段投影 用于查询便签数据
@ -81,7 +85,10 @@ public class WorkingNote {
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
,
NoteColumns.IS_HABIT,
NoteColumns.HABIT_CONFIG
};
private static final int DATA_ID_COLUMN = 0;
@ -102,6 +109,8 @@ public class WorkingNote {
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
private static final int NOTE_HABIT_FLAG_COLUMN = 6;
private static final int NOTE_HABIT_CONFIG_COLUMN = 7;
// New note construct 创建新便签
private WorkingNote(Context context, long folderId) {
@ -141,6 +150,17 @@ public class WorkingNote {
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
// habit fields (may not exist on older DB versions)
try {
mIsHabit = cursor.getInt(NOTE_HABIT_FLAG_COLUMN) == 1;
} catch (Exception e) {
mIsHabit = false;
}
try {
mHabitConfig = cursor.getString(NOTE_HABIT_CONFIG_COLUMN);
} catch (Exception e) {
mHabitConfig = "";
}
}
cursor.close();
} else {
@ -285,6 +305,24 @@ public class WorkingNote {
}
}
// 设置/取消习惯便签并保存配置信息config 为 JSON 字符串,可为空)
public void setHabit(boolean isHabit, String config) {
if (mIsHabit != isHabit || (config != null && !config.equals(mHabitConfig))) {
mIsHabit = isHabit;
mHabitConfig = config == null ? "" : config;
mNote.setNoteValue(NoteColumns.IS_HABIT, String.valueOf(isHabit ? 1 : 0));
mNote.setNoteValue(NoteColumns.HABIT_CONFIG, mHabitConfig);
}
}
public boolean isHabit() {
return mIsHabit;
}
public String getHabitConfig() {
return mHabitConfig == null ? "" : mHabitConfig;
}
//设置清单模式
public void setCheckListMode(int mode) {
if (mMode != mode) {

@ -41,34 +41,38 @@ public class BackupUtils {
// Singleton stuff
private static BackupUtils sInstance;
//ynchronized 关键字,代表这个方法加锁,防止多进程混乱
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
//如果当前备份不存在,则新声明一个
sInstance = new BackupUtils(context);
}
return sInstance;
}
// 当前 SD 卡未挂载
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 文件不存在
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// 数据格式损坏(可能被其他程序修改)
// The data is not well formated, may be changed by other programs
public static final int STATE_DATA_DESTROIED = 2;
// 运行时异常导致备份/恢复失败
// Some run-time exception which causes restore or backup fails
public static final int STATE_SYSTEM_ERROR = 3;
// 备份或重新储存成功???
// Backup or restore success
public static final int STATE_SUCCESS = 4;
//文本提取
private TextExport mTextExport;
//初始化类将该类设置为context接口格式提供了访问资源、启动组件、获取系统服务、管理文件、执行权限检查等。
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
// 获取外部存储的根目录便于进行api操作
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// mTextExport是一个静态内部类,提供了导出功能
public int exportToText() {
return mTextExport.exportToText();
}
@ -82,20 +86,20 @@ public class BackupUtils {
}
private static class TextExport {
private static final String[] NOTE_PROJECTION = { //文本基础信息
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET, //文本片段
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
//定义类级别的常量.p s f下面的 数字代表在字符串中存储位置
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
private static final String[] DATA_PROJECTION = { //数据基础信息,这个数据是啥数据???
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
@ -106,7 +110,7 @@ public class BackupUtils {
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1; //导出笔记时,先查询这条笔记的 DATA_COLUMN_MIME_TYPE 值,判断格式
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
@ -122,7 +126,7 @@ public class BackupUtils {
private String mFileDirectory;
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);//从array.xml里获取字符串数组信息
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
@ -133,33 +137,33 @@ public class BackupUtils {
}
/**
* Export the folder identified by folder id to text 便
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId //引用的别的类的啥父母ID
folderId
}, null);
if (notesCursor != null) { //遍历cursor的数据
if (notesCursor.moveToFirst()) { //在开头
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm), //R.string.xxx 是一个引用字符串资源的标识符,它直接指向资源文件中的字符串,用gststring执行
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); //getXXXX是结构
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext()); //没有下一个就终止,代表数据遍历完毕
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream便
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
@ -171,14 +175,14 @@ public class BackupUtils {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) { //有电话的note
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), //在导出内容时把phoneNumber填充到 “笔记内容的格式模板” 并打印出来
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
@ -212,10 +216,9 @@ public class BackupUtils {
}
/**
* Note will be exported as text which is user readable ,
* Note will be exported as text which is user readable
*/
public int exportToText() {
//判断两个失败条件
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
@ -226,22 +229,22 @@ public class BackupUtils {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes 输出在文件夹中的便签
// First export folder and its notes
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); //xinxi
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { //这个判断何意味为啥一个cursor一个context
folderName = mContext.getString(R.string.call_record_folder_name); //Context.getString 是 Android 中用于从资源文件中获取字符串的方法
} else { //cursor.getString 是 Android 中用于从数据库中获取字符串类型数据的方法
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
@ -254,7 +257,7 @@ public class BackupUtils {
folderCursor.close();
}
// Export notes in root's folder,输出根目录中的便签
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
@ -276,16 +279,15 @@ public class BackupUtils {
}
ps.close();
return STATE_SUCCESS; //都能对应上
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format); //建一个指向 SD 卡指定目录的 TXT 导出文件
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
@ -293,9 +295,9 @@ public class BackupUtils {
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try { //try catch语法
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos); //返回操作该文件的 PrintStream 输出流
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
@ -308,25 +310,25 @@ public class BackupUtils {
}
/**
* Generate the text file to store imported data TXT
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
sb.append(context.getString(
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString()); //stringbuilder转成srting再写进file
File file = new File(sb.toString());
try {
if (!filedir.exists()) {
filedir.mkdir(); //文件目录
filedir.mkdir();
}
if (!file.exists()) {
file.createNewFile(); //文件
file.createNewFile();
}
return file;
} catch (SecurityException e) {

@ -323,14 +323,25 @@ public class DataUtils {
}
return ids;
}
<<<<<<< HEAD:src/notes/tool/DataUtils.java
public static boolean batchMoveToTrash(ContentResolver resolver, HashSet<Long> ids, long originFolderId) {
if (ids == null || ids.isEmpty()) {
Log.d(TAG, "the ids is null or empty");
=======
public static boolean batchMoveToTrash(ContentResolver resolver, HashSet<Long> ids,
long originFolderId) {
if (ids == null) {
Log.d(TAG, "the ids is null");
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/tool/DataUtils.java
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
long now = System.currentTimeMillis();
<<<<<<< HEAD:src/notes/tool/DataUtils.java
// 1. 更新便签的PARENT_ID为回收站ID
=======
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/tool/DataUtils.java
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
@ -340,9 +351,16 @@ public class DataUtils {
builder.withValue(NoteColumns.MODIFIED_DATE, now);
operationList.add(builder.build());
}
<<<<<<< HEAD:src/notes/tool/DataUtils.java
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0) {
=======
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/tool/DataUtils.java
Log.d(TAG, "move to trash failed, ids:" + ids.toString());
return false;
}
@ -365,7 +383,32 @@ public class DataUtils {
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) });
}
<<<<<<< HEAD:src/notes/tool/DataUtils.java
/**
*
* @param resolver ContentResolver
* @param folderId ID
* @return
*/
public static boolean isEncryptedFolder(ContentResolver resolver, long folderId) {
// 查询是否存在该文件夹的加密数据
String selection = Notes.DataColumns.NOTE_ID + "=? AND " + Notes.DataColumns.MIME_TYPE + "=?";
String[] selectionArgs = new String[] {
String.valueOf(folderId),
Notes.DataConstants.ENCRYPTED_FOLDER
};
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, null, selection, selectionArgs, null);
boolean isEncrypted = false;
if (cursor != null) {
isEncrypted = cursor.getCount() > 0;
cursor.close();
}
return isEncrypted;
}
=======
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/tool/DataUtils.java
}

@ -0,0 +1,238 @@
/*
* 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.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import net.micode.notes.R;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
public class DoodleDialog extends Dialog {
public interface OnDoodleSavedListener {
void onSaved(String localPath);
}
private final Context mContext;
private final OnDoodleSavedListener mListener;
private DoodleView mDoodleView;
public DoodleDialog(Context context, OnDoodleSavedListener listener) {
super(context);
mContext = context;
mListener = listener;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.doodle_title);
setContentView(createContentView());
WindowManager.LayoutParams params = getWindow().getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
getWindow().setAttributes(params);
}
private View createContentView() {
LinearLayout root = new LinearLayout(mContext);
root.setOrientation(LinearLayout.VERTICAL);
int padding = dpToPx(12);
root.setPadding(padding, padding, padding, padding);
mDoodleView = new DoodleView(mContext);
LinearLayout.LayoutParams canvasParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 0);
canvasParams.weight = 1f;
root.addView(mDoodleView, canvasParams);
LinearLayout actions = new LinearLayout(mContext);
actions.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams actionParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
actions.setLayoutParams(actionParams);
Button clearButton = new Button(mContext);
clearButton.setText(R.string.doodle_clear);
clearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDoodleView.clear();
}
});
Button cancelButton = new Button(mContext);
cancelButton.setText(android.R.string.cancel);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
Button saveButton = new Button(mContext);
saveButton.setText(R.string.doodle_save);
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String path = saveDoodle();
if (!TextUtils.isEmpty(path)) {
if (mListener != null) {
mListener.onSaved(path);
}
dismiss();
} else {
Toast.makeText(mContext, R.string.doodle_save_failed, Toast.LENGTH_SHORT).show();
}
}
});
actions.addView(clearButton, new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
actions.addView(cancelButton, new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
actions.addView(saveButton, new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
root.addView(actions);
return root;
}
private String saveDoodle() {
Bitmap bitmap = mDoodleView.exportBitmap();
if (bitmap == null) {
return null;
}
try {
File baseDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (baseDir == null) {
baseDir = mContext.getFilesDir();
}
File appDir = new File(baseDir, "note_images");
if (!appDir.exists() && !appDir.mkdirs()) {
return null;
}
File file = new File(appDir, "doodle_" + System.currentTimeMillis() + ".png");
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
return file.getAbsolutePath();
} catch (Exception e) {
return null;
}
}
private int dpToPx(int dp) {
DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
return Math.round(dm.density * dp);
}
private static class DoodleView extends View {
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final List<Path> mPaths = new ArrayList<>();
private Path mCurrentPath;
DoodleView(Context context) {
super(context);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(6f);
setBackgroundColor(Color.WHITE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Path path : mPaths) {
canvas.drawPath(path, mPaint);
}
if (mCurrentPath != null) {
canvas.drawPath(mCurrentPath, mPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mCurrentPath = new Path();
mCurrentPath.moveTo(x, y);
invalidate();
return true;
case MotionEvent.ACTION_MOVE:
if (mCurrentPath != null) {
mCurrentPath.lineTo(x, y);
invalidate();
}
return true;
case MotionEvent.ACTION_UP:
if (mCurrentPath != null) {
mCurrentPath.lineTo(x, y);
mPaths.add(mCurrentPath);
mCurrentPath = null;
invalidate();
}
return true;
default:
return false;
}
}
void clear() {
mPaths.clear();
mCurrentPath = null;
invalidate();
}
Bitmap exportBitmap() {
if (getWidth() <= 0 || getHeight() <= 0) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.WHITE);
for (Path path : mPaths) {
canvas.drawPath(path, mPaint);
}
return bitmap;
}
}
}

@ -15,7 +15,7 @@
*/
package net.micode.notes.tool;
//定义一些与 Google TasksGTaskJSON 数据交互相关的常量字符串,为与 Google Tasks 同步相关的 JSON 数据结构提供标准化的字段名常量便于在GTask上同步
public class GTaskStringUtils {
public final static String GTASK_JSON_ACTION_ID = "action_id";

@ -19,7 +19,7 @@ package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R; //资源文件夹中的文件被编译成R类的一部分R类在编译过程中会生成
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
public class ResourceParser {
@ -40,7 +40,6 @@ public class ResourceParser {
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
public static class NoteBgResources {
//引用背景颜色资源
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
@ -48,7 +47,7 @@ public class ResourceParser {
R.drawable.edit_green,
R.drawable.edit_red
};
//引用标题颜色资源
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
@ -56,26 +55,32 @@ public class ResourceParser {
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
public static int getNoteBgResource(int id) {
if (id < 0 || id >= BG_EDIT_RESOURCES.length) {
return BG_EDIT_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_EDIT_RESOURCES[id];
}
public static int getNoteTitleBgResource(int id) {
if (id < 0 || id >= BG_EDIT_TITLE_RESOURCES.length) {
return BG_EDIT_TITLE_RESOURCES[0]; // 默认返回第一个标题背景资源
}
return BG_EDIT_TITLE_RESOURCES[id];
}
}
public static int getDefaultBgId(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( //获得一个实例,指向该偏好框架在给定上下文中使用的默认文件
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { //如果没指定就随机给个颜色
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
return BG_DEFAULT_COLOR;
}
}
public static class NoteItemBgResources { //便签主题资源
public static class NoteItemBgResources {
private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
@ -109,18 +114,30 @@ public class ResourceParser {
};
public static int getNoteBgFirstRes(int id) {
if (id < 0 || id >= BG_FIRST_RESOURCES.length) {
return BG_FIRST_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_FIRST_RESOURCES[id];
}
public static int getNoteBgLastRes(int id) {
if (id < 0 || id >= BG_LAST_RESOURCES.length) {
return BG_LAST_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_LAST_RESOURCES[id];
}
public static int getNoteBgSingleRes(int id) {
if (id < 0 || id >= BG_SINGLE_RESOURCES.length) {
return BG_SINGLE_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_SINGLE_RESOURCES[id];
}
public static int getNoteBgNormalRes(int id) {
if (id < 0 || id >= BG_NORMAL_RESOURCES.length) {
return BG_NORMAL_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_NORMAL_RESOURCES[id];
}
@ -139,6 +156,9 @@ public class ResourceParser {
};
public static int getWidget2xBgResource(int id) {
if (id < 0 || id >= BG_2X_RESOURCES.length) {
return BG_2X_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_2X_RESOURCES[id];
}
@ -151,6 +171,9 @@ public class ResourceParser {
};
public static int getWidget4xBgResource(int id) {
if (id < 0 || id >= BG_4X_RESOURCES.length) {
return BG_4X_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_4X_RESOURCES[id];
}
}

@ -0,0 +1,162 @@
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();
}
}

@ -39,49 +39,41 @@ import net.micode.notes.tool.DataUtils;
import java.io.IOException;
//继承Activity //承诺实现clickdismiss接口不继承功能
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId;
private String mSnippet;
private static final int SNIPPET_PREW_MAX_LEN = 60;
MediaPlayer mPlayer;
@Override //创建闹钟行为
@Override
protected void onCreate(Bundle savedInstanceState) {
//调用其父类Activity的onCreate方法来实现对界面的图画绘制工作。
super.onCreate(savedInstanceState);
//onSaveInstanceState()来保存当前activity的状态信息
requestWindowFeature(Window.FEATURE_NO_TITLE);
//final类似stactic获得窗口对象
final Window win = getWindow();
//为窗口添加各种标志flags从而改变窗口的行为和外观
//屏幕锁定时显示窗口,便于锁屏时启动闹钟
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
//LayoutParams 布局参数,规定子视图如何放置
if (!isScreenOn()) {//不锁平时
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON//当这个window对用户是可见状态,则保持设备屏幕不关闭且不变暗
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON//当window被添加或者显示,系统会点亮屏幕,
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON//允许在屏幕开启的时候锁定屏幕
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);//插入window的矩形大小,来确保内容不会被装饰物(如状态栏)掩盖
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}
Intent intent = getIntent();
try { //传递到当前组件的 URI 数据截取Uri内的字符串的第二个“/”右边的字符及id
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
//根据ID从数据库中获取标签的内容
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
//判断是否超过最大长度,超过执行截取操作
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
} catch (IllegalArgumentException e) {
e.printStackTrace();// 将异常的详细信息输出到标准错误流
e.printStackTrace();
return;
}
mPlayer = new MediaPlayer();
//如果不在回收站就显示文字并发出声音
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog();
playAlarmSound();
@ -90,27 +82,26 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
}
private boolean isScreenOn() { //由电量判断屏幕状态
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
private void playAlarmSound() {
//由铃声管理器获得默认铃声的uri
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
//设置为有声铃声还是无声的参数
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
//无铃声模式且不报警就设置为无铃声
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
mPlayer.setDataSource(this, url);//指定播放文件的路径
mPlayer.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true);//设置循环播放
mPlayer.setLooping(true);
mPlayer.start();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
@ -128,11 +119,10 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
private void showActionDialog() {
//AlertDialog.Builder是对话框组件用于显示信息
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this); //setPositive/Negative/NeutralButton()设置:确定,取消,中立按钮;
dialog.setPositiveButton(R.string.notealert_ok, this);
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this); //取消
}
@ -143,20 +133,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);//执行安卓内置操作查看数据
intent.putExtra(Intent.EXTRA_UID, mNoteId);//向目标页面传递笔记的ID
startActivity(intent);//触发页面跳转
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
break;
default:
break;
}
}
//取消监听后操作
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
//取消声音
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();

@ -27,7 +27,7 @@ import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
//广播接收器用于响应来自其他应用程序或者系统的广播消息(可以使事件)
public class AlarmInitReceiver extends BroadcastReceiver {
private static final String [] PROJECTION = new String [] {
@ -39,31 +39,24 @@ public class AlarmInitReceiver extends BroadcastReceiver {
private static final int COLUMN_ALERTED_DATE = 1;
@Override
public void onReceive(Context context, Intent intent) { //设置接收装置
//获取当前时间的常用方法。它返回自 1970 年 1 月 1 日 00:00:00.000 到当前时刻的时间距离
public void onReceive(Context context, Intent intent) {
long currentDate = System.currentTimeMillis();
//查询未到响应时间且是有提醒类型的便签
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
null);
if (c != null) {
if (c.moveToFirst()) {
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
//显示intent格式明确启动应用中的AlarmReceiver类
Intent sender = new Intent(context, AlarmReceiver.class);
//指定要访问的资源URI(这里的uir是把id和contentUri连接后成一个新的Uri)
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
//(满足特定条件但这里是0即没有)向BroadcastReceiver发送执行sender的广播
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
//获取 AlarmManager
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
//set(int typelong startTimePendingIntent pi) ,设置一次性定时器,到达时间执行完就没了
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
//闹钟在睡眠状态下会唤醒系统并执行提示功能
} while (c.moveToNext());
}
c.close();

@ -20,11 +20,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {//闹钟响应广播接收者
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
intent.setClass(context, AlarmAlertActivity.class); //启动闹钟响应的行为
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//启动一个新的任务栈,为何要启动新的???
context.startActivity(intent);//执行
intent.setClass(context, AlarmAlertActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

@ -0,0 +1,137 @@
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"; // 背景类型key0=颜色1=内置资源2=自定义图片URI
private static final String PREF_BG_COLOR = "pref_bg_color"; // 背景颜色key
private static final String PREF_BG_RES_ID = "pref_bg_res_id"; // 内置背景资源ID key
private static final String PREF_BG_URI = "pref_bg_uri"; // 自定义图片URI key
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;//要设置背景的根View
public BackgroundManager(Activity activity, int rootViewId) {
mActivity = activity;
mRootView = activity.findViewById(rootViewId);//绑定指定的根View
if (mRootView == null) {
mRootView = activity.getWindow().getDecorView();//若传入的View ID无效取窗口根View
}
}
// 根据偏好设置应用背景
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);//读取资源ID
applyBuiltinBackground(resId);//应用内置背景
} else if (type == BG_TYPE_URI) {//自定义图片类型
String uriStr = sp.getString(PREF_BG_URI, null);//读取URI字符串
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);//获取默认SharedPreferences
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);
}
// 应用URI背景
private void applyUriBackground(Uri uri) {
try {
InputStream is = mActivity.getContentResolver().openInputStream(uri);//通过内容解析器打开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();
}
}
// 应用URI背景并保存偏好设置
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();
}
}
}

@ -0,0 +1,103 @@
/*
* 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);
}
}
}
}
}

@ -0,0 +1,105 @@
/*
* 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(Intent.EXTRA_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);
}
}
}

@ -27,7 +27,7 @@ import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
//FrameLayout 是 Android 布局容器之一,用于在屏幕上开辟一块区域,将子控件按照添加顺序进行层叠显示
public class DateTimePicker extends FrameLayout {
private static final boolean DEFAULT_ENABLE_STATE = true;
@ -45,56 +45,56 @@ public class DateTimePicker extends FrameLayout {
private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1;
//XX选择器设置
//这里定义的四个变量全部是在设置闹钟时需要选择的变量(如日期、时、分、上午或者下午)
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
private Calendar mDate;
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];//
private boolean mIsAm; //12小时制是否上午
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
private boolean mIs24HourView;//24小时制
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE; //是否启动?
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mInitialising; //是否正在初始化?
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener; //自定义接口,创建接口类型变量,外部实现功能可以调用onDateTimeChanged了
//系统接口,自己实现功能,当用户滑动日期滚轮(比如从 5 号滑到 6 号系统会自动调用onValueChange方法
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { //日期变更
private OnDateTimeChangedListener mOnDateTimeChangedListener;
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();//更新日期
onDateTimeChanged();//触发自定义接口的回调(把事件传给外部)[实现方法不在此类],作用未知??
updateDateControl();
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { //小时变更
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance(); //获取现在的日历
if (!mIs24HourView) { //12小时制
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { //从十一点多调整到第二天0点日期加一。
cal.setTimeInMillis(mDate.getTimeInMillis());// set time to 1970 年 1 月 1 日的 00:00:00.000到Calendar对象表示的时间之间的毫秒数 after january 1 1970
Calendar cal = Calendar.getInstance();
if (!mIs24HourView) {
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {//从0点多调整到昨天点日期加一。
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || //上下午切换
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
} else {//24小时制
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {//同上面日期切换逻辑
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
@ -104,8 +104,8 @@ public class DateTimePicker extends FrameLayout {
isDateChanged = true;
}
}
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);//24小时制的小时时间数
mDate.set(Calendar.HOUR_OF_DAY, newHour); //通过set函数将新的Hour值传给mDate
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
@ -115,18 +115,18 @@ public class DateTimePicker extends FrameLayout {
}
};
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {//分钟变更
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0; //分钟导致的的小时切换
int offset = 0;
if (oldVal == maxValue && newVal == minValue) {
offset += 1; //小时加一
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1; //小时减一
offset -= 1;
}
if (offset != 0) { //更新小时时间
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
@ -139,44 +139,44 @@ public class DateTimePicker extends FrameLayout {
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);//通过set函数将新的minutes值传给mDate
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {//上下午变更
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;//为啥要变成否定的
if (mIsAm) {//下午变成上午了24小时制减去12
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {//上午变下午就加12除余
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);//
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
}
};
public interface OnDateTimeChangedListener {//接口要求实现onDateTimeChanged
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
public DateTimePicker(Context context) {// 获得1970 年 1 月 1 日的 00:00:00.000到Calendar对象表示的时间之间的毫秒数
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));//把上一个函数的毫秒改成24小时制的日期格式
this(context, date, DateFormat.is24HourFormat(context));
}
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);//super是啥
super(context);
mDate = Calendar.getInstance();
mInitialising = true;
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);//inflate的作用
inflate(context, R.layout.datetime_picker, this);
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
@ -188,14 +188,14 @@ public class DateTimePicker extends FrameLayout {
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);//
mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);//设置展示的字符串
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
@ -208,7 +208,7 @@ public class DateTimePicker extends FrameLayout {
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());//?
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
@ -241,7 +241,6 @@ public class DateTimePicker extends FrameLayout {
return mDate.getTimeInMillis();
}
//两种不同的参数,对应不同的设置现在时间的方法
/**
* Set the current date
*
@ -427,87 +426,58 @@ public class DateTimePicker extends FrameLayout {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView; //spinner拉条 就是选am还是pm时候用的
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
//
/**
*
*
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
// 计算起始日期当前日期减去3天DAYS_IN_ALL_WEEK/2=3再减1天得到一周前的日期
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
// 每次循环增加一天,生成一周内的日期
cal.add(Calendar.DAY_OF_YEAR, 1);
// 格式化日期为"MM.dd EEEE"格式例如05.20 星期一)
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
// 设置日期显示值到选择器
mDateSpinner.setDisplayedValues(mDateDisplayValues);
// 将选择器默认值设置为中间位置(即当前日期)
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
// 刷新选择器显示
mDateSpinner.invalidate();
}
/**
* /
* 24/
*/
private void updateAmPmControl() {
if (mIs24HourView) {
// 24小时制下隐藏上午/下午选择器
mAmPmSpinner.setVisibility(View.GONE);
} else {
// 12小时制下根据当前是上午还是下午设置选择器值
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
// 显示上午/下午选择器
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
/**
*
* 24
*/
private void updateHourControl() {
if (mIs24HourView) {
// 24小时制小时范围0-23
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
// 12小时制小时范围1-12
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
*
*
* @param callback null
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
/**
*
*
*/
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
// 调用监听器的onDateTimeChanged方法传递当前选择的年、月、日、时、分
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}

@ -28,39 +28,37 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
//对话框弹出
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener; //设置时间的接口
private OnDateTimeSetListener mOnDateTimeSetListener;
private DateTimePicker mDateTimePicker;
public interface OnDateTimeSetListener {//实现接口的函数调用
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
public DateTimePickerDialog(Context context, long date) {
super(context);//访问AlertDialog的context
super(context);
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);//自定义对话框
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { //注册监听器,用户操作日期时间选择器时,这个监听器会被自动触发
setView(mDateTimePicker);
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
//用户修改时,实时更新时间
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
updateTitle(mDate.getTimeInMillis());//根据新的日期时间更新对话框标题
updateTitle(mDate.getTimeInMillis());
}
});
//下面三行是日期时间选择器初始化的核心逻辑
mDate.setTimeInMillis(date); //导入calendar时间
mDate.set(Calendar.SECOND, 0);//秒数设为0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());//设置为当前时间
setButton(context.getString(R.string.datetime_dialog_ok), this);//设置了确认和取消按钮
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
set24HourView(DateFormat.is24HourFormat(this.getContext()));
updateTitle(mDate.getTimeInMillis());
@ -70,21 +68,20 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener
mIs24HourView = is24HourView;
}
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { //设置时间的接口
mOnDateTimeSetListener = callBack;
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
private void updateTitle(long date) {
int flag = //亮点,位掩码设计 返回了结果是一个包含所有标志位的整数作为时间显示格式
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
//再并上24小时格式或12小时格式
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR; //错误应为12HOUR
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); //设置对话框标题
} //日期时间的本地化格式化
public void onClick(DialogInterface arg0, int arg1) {//点击确认按钮时调用
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}

@ -29,35 +29,33 @@ import net.micode.notes.R;
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu; //弹窗菜单
private PopupMenu mPopupMenu;
private Menu mMenu;
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);//绘制下拉菜单图标
mButton.setBackgroundResource(R.drawable.dropdown_icon);
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();//获取弹窗菜单的菜单对象
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);//获得菜单
//第一个参数用于指定我们通过哪一个资源文件来创建菜单menuId。
// 第二个参数用于指定我们的菜单项将添加到哪一个 Meau 对象中
mButton.setOnClickListener(new OnClickListener() {//设置下拉菜单图标点击事件
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});
}
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {//设置下拉菜单选项点击事件监听
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
public MenuItem findItem(int id) {//根据菜单项的ID查找菜单项
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
public void setTitle(CharSequence title) {//设置下拉菜单图标上的文本
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}

@ -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;
}
}
}

@ -28,8 +28,8 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
//将cursor对象数据加载到界面的适配器
public class FoldersListAdapter extends CursorAdapter {//将文件夹数据转换成视图
public class FoldersListAdapter extends CursorAdapter {
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
@ -39,43 +39,41 @@ public class FoldersListAdapter extends CursorAdapter {//将文件夹数据转
public static final int NAME_COLUMN = 1;
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);//调用父类构造方法,初始化上下文和游标
super(context, c);
// TODO Auto-generated constructor stub
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {//创建新的视图
return new FolderListItem(context);//创建一个新的FolderListItem视图
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {//绑定视图与数据
if (view instanceof FolderListItem) {//判断视图是否为FolderListItem类型
//根据文件夹类型动态显示不同的名称,当文件夹是文件夹时,显示移动到父文件夹
//当文件夹是 普通文件夹时,显示其实际名称
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);//将文件夹名称绑定到FolderListItem视图
((FolderListItem) view).bind(folderName);
}
}
public String getFolderName(Context context, int position) {//根据位置找到对应文件夹的名称
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
//布局管理器 之一,属于 ViewGroup 的子类,用于 将子View按照线性方向排列
private class FolderListItem extends LinearLayout {//文件夹列表项视图
private class FolderListItem extends LinearLayout {
private TextView mName;
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);//对 LayoutInflater.inflate()的简化
//将R.layout.folder_list_item布局文件实例化并添加到当前视图FolderListItem
mName = (TextView) findViewById(R.id.tv_folder_name);//获取布局文件中的TextView控件
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
}
public void bind(String name) {
mName.setText(name);//将文件夹名称设置到TextView中
mName.setText(name);
}
}

@ -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 "<img src=\"" + imgUrl + "\" width=\"200\" height=\"200\"/><br/>";
}
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;
}
}
}

File diff suppressed because it is too large Load Diff

@ -37,111 +37,87 @@ import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
/**
* AndroidEditText
* /
*/
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText"; // 日志标签
private int mIndex; // 当前编辑框在容器中的索引位置
private int mSelectionStartBeforeDelete; // 删除操作前的光标起始位置
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
// 支持的链接协议常量
private static final String SCHEME_TEL = "tel:" ; // 电话协议
private static final String SCHEME_HTTP = "http:" ; // HTTP协议
private static final String SCHEME_EMAIL = "mailto:" ; // 邮件协议
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
// 协议到字符串资源的映射表,用于上下文菜单显示
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); // 电话链接菜单
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); // 网页链接菜单
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); // 邮件链接菜单
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* NoteEditActivity
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
public interface OnTextViewChangeListener {
/**
*
* @param index
* @param text
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);
/**
*
* @param index
* @param text
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
*
* @param index
* @param hasText
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
<<<<<<< HEAD:src/notes/ui/NoteEditText.java
private OnTextViewChangeListener mOnTextViewChangeListener;
//新增选区变化回调接口
private OnSelectionChangeListener mOnSelectionChangeListener;
=======
private OnTextViewChangeListener mOnTextViewChangeListener; // 文本变化监听器实例
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NoteEditText.java
/**
* NoteEditText
* @param context
*/
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
/**
*
* @param index
*/
public void setIndex(int index) {
mIndex = index;
}
/**
* NoteEditActivity
* @param listener
*/
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
<<<<<<< HEAD:src/notes/ui/NoteEditText.java
//新增选区变化回调接口设置方法
public void setOnSelectionChangeListener(OnSelectionChangeListener listener) {
mOnSelectionChangeListener = listener;
}
=======
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NoteEditText.java
/**
* XMLNoteEditText
* @param context
* @param attrs XML
*/
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
/**
* XMLNoteEditText
* @param context
* @param attrs XML
* @param defStyle
*/
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
/**
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 计算触摸点在文本中的精确位置
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
@ -152,30 +128,22 @@ public class NoteEditText extends EditText {
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Selection.setSelection(getText(), off); // 设置光标位置
Selection.setSelection(getText(), off);
break;
}
return super.onTouchEvent(event);
}
/**
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 回车键按下时交由onKeyUp处理
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 记录删除前的光标位置
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
@ -184,17 +152,10 @@ public class NoteEditText extends EditText {
return super.onKeyDown(keyCode, event);
}
/**
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
// 删除键处理:当光标在开头且非第一个编辑框时,删除当前编辑框
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
@ -205,14 +166,10 @@ public class NoteEditText extends EditText {
}
break;
case KeyEvent.KEYCODE_ENTER:
// 回车键处理:在当前位置分割文本,创建新的编辑框
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
// 获取光标后的文本
String text = getText().subSequence(selectionStart, length()).toString();
// 保留光标前的文本
setText(getText().subSequence(0, selectionStart));
// 通知Activity添加新的编辑框
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
@ -224,16 +181,9 @@ public class NoteEditText extends EditText {
return super.onKeyUp(keyCode, event);
}
/**
*
* @param focused
* @param direction
* @param previouslyFocusedRect
*/
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
// 失去焦点且文本为空时通知Activity隐藏相关选项
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
@ -243,10 +193,8 @@ public class NoteEditText extends EditText {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
/**
*
* @param menu
*/
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
@ -256,10 +204,8 @@ public class NoteEditText extends EditText {
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
// 查找选中区域内的URLSpan
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
// 根据链接协议选择合适的菜单文本
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
@ -272,11 +218,10 @@ public class NoteEditText extends EditText {
defaultResId = R.string.note_link_other;
}
// 添加菜单选项并设置点击监听器
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 执行链接点击操作
// goto a new intent
urls[0].onClick(NoteEditText.this);
return true;
}
@ -286,3 +231,12 @@ public class NoteEditText extends EditText {
super.onCreateContextMenu(menu);
}
}
<<<<<<< HEAD:src/notes/ui/NoteEditText.java
// 新增选区变化回调接口
interface OnSelectionChangeListener {
void onSelectionChanged(int index, int selStart, int selEnd);
}
=======
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NoteEditText.java

@ -0,0 +1,240 @@
/*
* 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.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.IS_HABIT,
};
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
private static final int CREATED_DATE_COLUMN = 3;
private static final int HAS_ATTACHMENT_COLUMN = 4;
private static final int MODIFIED_DATE_COLUMN = 5;
private static final int NOTES_COUNT_COLUMN = 6;
private static final int PARENT_ID_COLUMN = 7;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
private static final int IS_HABIT_COLUMN = 12;
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private boolean mIsHabit;
private String mName;
private String mPhoneNumber;
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
public NoteItemData(Context context, Cursor cursor) {
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
// 移除HTML标签
mSnippet = mSnippet.replaceAll("<[^>]*>", "");
// 移除CHECKED和UNCHECKED标签
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
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) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
}
}
}
if (mName == null) {
mName = "";
}
checkPostion(cursor);
}
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
mIsOnlyOneItem = (cursor.getCount() == 1);
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
} else {
mIsOneNoteFollowingFolder = true;
}
}
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
public boolean isLast() {
return mIsLastItem;
}
public String getCallName() {
return mName;
}
public boolean isFirst() {
return mIsFirstItem;
}
public boolean isSingle() {
return mIsOnlyOneItem;
}
public long getId() {
return mId;
}
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() {
return mParentId;
}
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
public boolean isHabit() {
return mIsHabit;
}
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

File diff suppressed because it is too large Load Diff

@ -44,6 +44,10 @@ public class NotesListAdapter extends CursorAdapter {
public int widgetId;
public int widgetType;
};
<<<<<<< HEAD:src/notes/ui/NotesListAdapter.java
=======
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesListAdapter.java
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();

@ -32,6 +32,10 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout {
private ImageView mAlert;
<<<<<<< HEAD:src/notes/ui/NotesListItem.java
private ImageView mHabit;
=======
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesListItem.java
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
@ -42,6 +46,7 @@ public class NotesListItem extends LinearLayout {
super(context);
inflate(context, R.layout.note_item, this);
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mHabit = (ImageView) findViewById(R.id.iv_habit_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
@ -70,7 +75,11 @@ public class NotesListItem extends LinearLayout {
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()));
<<<<<<< HEAD:src/notes/ui/NotesListItem.java
mAlert.setImageResource(R.drawable.baseline_restore_from_trash_24);
=======
mAlert.setImageResource(android.R.drawable.ic_menu_delete);
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesListItem.java
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
@ -82,6 +91,14 @@ public class NotesListItem extends LinearLayout {
} else {
mAlert.setVisibility(View.GONE);
}
<<<<<<< HEAD:src/notes/ui/NotesListItem.java
// habit badge
if (data.isHabit()) {
mHabit.setVisibility(View.VISIBLE);
} else {
mHabit.setVisibility(View.GONE);
}
=======
} else if (data.getId() == Notes.ID_TRASH_FOLER) { //为回收站添加图标和显示逻辑
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
@ -89,6 +106,7 @@ public class NotesListItem extends LinearLayout {
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);
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesListItem.java
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
@ -97,7 +115,14 @@ public class NotesListItem extends LinearLayout {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
// 加密文件夹使用锁图标,与打卡图标区分
// 通过DataUtils检查是否为加密文件夹
if (DataUtils.isEncryptedFolder(context.getContentResolver(), data.getId())) {
mAlert.setImageResource(R.drawable.lock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
@ -106,6 +131,11 @@ public class NotesListItem extends LinearLayout {
} else {
mAlert.setVisibility(View.GONE);
}
if (data.isHabit()) {
mHabit.setVisibility(View.VISIBLE);
} else {
mHabit.setVisibility(View.GONE);
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));

@ -48,37 +48,27 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
/**
*
*
*/
public class NotesPreferenceActivity extends PreferenceActivity {
/** 偏好设置文件名 */
public static final String PREFERENCE_NAME = "notes_preferences";
/** 同步账户名称偏好键 */
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
/** 最后同步时间偏好键 */
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
/** 背景颜色随机显示偏好键 */
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
/** 同步账户分类键 */
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
/** 账户授权过滤器键 */
private static final String AUTHORITIES_FILTER_KEY = "authorities";
/** 账户设置分类 */
private PreferenceCategory mAccountCategory;
/** GTask同步广播接收器 */
private GTaskReceiver mReceiver;
/** 原始账户列表,用于检测是否添加了新账户 */
private Account[] mOriAccounts;
/** 是否添加了新账户的标志 */
private boolean mHasAddedAccount;
/**
*
* @param icicle
*/
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@ -91,16 +81,21 @@ public class NotesPreferenceActivity extends PreferenceActivity {
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
<<<<<<< HEAD:src/notes/ui/NotesPreferenceActivity.java
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(mReceiver, filter);
}
=======
registerReceiver(mReceiver, filter);
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesPreferenceActivity.java
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
/**
* UI
*/
@Override
protected void onResume() {
super.onResume();
@ -129,9 +124,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
refreshUI();
}
/**
*
*/
@Override
protected void onDestroy() {
if (mReceiver != null) {
@ -140,9 +132,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
super.onDestroy();
}
/**
*
*/
private void loadAccountPreference() {
mAccountCategory.removeAll();
@ -173,9 +162,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
mAccountCategory.addPreference(accountPref);
}
/**
*
*/
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
@ -215,17 +201,11 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
/**
* UI
*/
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
/**
* Google
*/
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
@ -282,9 +262,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
});
}
/**
*
*/
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
@ -314,19 +291,11 @@ public class NotesPreferenceActivity extends PreferenceActivity {
dialogBuilder.show();
}
/**
* Google
* @return Google
*/
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
/**
*
* @param account
*/
private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
@ -357,9 +326,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
/**
*
*/
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
@ -372,7 +338,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
editor.commit();
// clean up local gtask related info
new Thread(new Runnable() {//清空一系列数据thread多线程操作避免阻塞主线程
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
@ -382,20 +348,13 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}).start();
}
/**
*
* @return
*/
public static String getSyncAccountName(Context context) {//
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
/**
*
*/
public static void setLastSyncTime(Context context, long time) {//
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
@ -403,20 +362,16 @@ public class NotesPreferenceActivity extends PreferenceActivity {
editor.commit();
}
/**
*
* @return
*/
public static long getLastSyncTime(Context context) {//
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
private class GTaskReceiver extends BroadcastReceiver {// 处理同步状态更新广播
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {// 处理同步状态更新广播
public void onReceive(Context context, Intent intent) {
refreshUI();
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
@ -427,11 +382,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
/**
*
* @param item
*/
public boolean onOptionsItemSelected(MenuItem item) {//
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, NotesListActivity.class);

@ -32,11 +32,11 @@ import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
public abstract class NoteWidgetProvider extends AppWidgetProvider {//笔记小部件提供器
public abstract class NoteWidgetProvider extends AppWidgetProvider {
public static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET//笔记摘要
NoteColumns.SNIPPET
};
public static final int COLUMN_ID = 0;
@ -46,9 +46,9 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {//笔记小
private static final String TAG = "NoteWidgetProvider";
@Override
public void onDeleted(Context context, int[] appWidgetIds) {//删除小部件时调用
ContentValues values = new ContentValues();//创建内容值对象,用于更新数据库
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);//将小部件ID设置为无效值
public void onDeleted(Context context, int[] appWidgetIds) {
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
for (int i = 0; i < appWidgetIds.length; i++) {
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
@ -59,18 +59,18 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {//笔记小
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,//查询投影,指定要返回的列
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
}
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);//false
update(context, appWidgetManager, appWidgetIds, false);
}
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {//更新小部件,是上面的实现方式
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
int bgId = ResourceParser.getDefaultBgId(context);
@ -90,38 +90,36 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {//笔记小
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
intent.setAction(Intent.ACTION_VIEW);//设置意图操作,查看笔记
intent.setAction(Intent.ACTION_VIEW);
} else {
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);//设置意图操作,插入或编辑笔记
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
if (c != null) {
c.close();
}
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());//创建远程视图对象,用于更新小部件的布局
/*App Widget使RemoteViewsView? RemoteViews View
Android*/
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null;//创建挂起意图对象,用于启动主活动
PendingIntent pendingIntent = null;
if (privacyMode) {
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));//设置小部件文本为隐私模式下的提示文本
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);//用于动态更新
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);//设置小部件文本点击事件,启动挂起意图
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);//更新小部件布局
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}

@ -24,10 +24,10 @@ import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
public class NoteWidgetProvider_2x extends NoteWidgetProvider {//小部件2x布局类
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);//调用父类更新方法
super.update(context, appWidgetManager, appWidgetIds);
}
@Override

@ -24,14 +24,14 @@ import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
public class NoteWidgetProvider_4x extends NoteWidgetProvider {//小部件4x布局类
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);//调用父类更新方法
super.update(context, appWidgetManager, appWidgetIds);
}
protected int getLayoutId() {
return R.layout.widget_4x;//返回4x布局资源ID
return R.layout.widget_4x;
}
@Override

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#88555555" />
<item android:state_selected="true" android:color="#ff999999" />
<item android:color="#ff000000" />
</selector>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#50000000" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save