UI代码注释

UI代码注释
main
pecvhqap2 2 months ago
commit 13123e9736

@ -1 +0,0 @@
加油

@ -0,0 +1 @@
Subproject commit 87902d310e6c672d3d3a95ead30c1beb0754068f

@ -0,0 +1,229 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.Window;
import android.view.WindowManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
/**
* AlarmAlertActivity
*
*/
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
// 存储笔记的 ID
private long mNoteId;
// 存储笔记的摘要信息
private String mSnippet;
// 摘要信息显示的最大长度
private static final int SNIPPET_PREW_MAX_LEN = 60;
// 用于播放提醒声音的媒体播放器
MediaPlayer mPlayer;
/**
* Activity
* @param savedInstanceState Activity
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 请求不显示标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 获取当前窗口
final Window win = getWindow();
// 添加标志,使窗口在锁屏状态下也能显示
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// 如果屏幕处于关闭状态
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);
}
// 获取启动该 Activity 的 Intent
Intent intent = getIntent();
try {
// 从 Intent 的数据路径中获取笔记的 ID
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();
return;
}
// 创建媒体播放器实例
mPlayer = new MediaPlayer();
// 检查笔记是否在数据库中可见
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
// 显示提醒对话框
showActionDialog();
// 播放提醒声音
playAlarmSound();
} else {
// 如果笔记不可见,则关闭该 Activity
finish();
}
}
/**
*
* @return true false
*/
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.prepare();
// 设置循环播放
mPlayer.setLooping(true);
// 开始播放
mPlayer.start();
} catch (IllegalArgumentException e) {
// 处理异常,打印堆栈信息
e.printStackTrace();
} catch (SecurityException e) {
// 处理异常,打印堆栈信息
e.printStackTrace();
} catch (IllegalStateException e) {
// 处理异常,打印堆栈信息
e.printStackTrace();
} catch (IOException e) {
// 处理异常,打印堆栈信息
e.printStackTrace();
}
}
/**
*
*/
private void showActionDialog() {
// 创建对话框构建器
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
// 设置对话框标题
dialog.setTitle(R.string.app_name);
// 设置对话框显示的消息,即笔记摘要信息
dialog.setMessage(mSnippet);
// 设置确认按钮及其点击监听器
dialog.setPositiveButton(R.string.notealert_ok, this);
// 如果屏幕处于开启状态,设置进入笔记编辑页面的按钮
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
// 显示对话框并设置对话框关闭监听器
dialog.show().setOnDismissListener(this);
}
/**
*
* @param dialog
* @param which
*/
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
// 创建一个 Intent 用于启动笔记编辑 Activity
Intent intent = new Intent(this, NoteEditActivity.class);
// 设置 Intent 的操作类型为查看
intent.setAction(Intent.ACTION_VIEW);
// 传递笔记的 ID 到笔记编辑 Activity
intent.putExtra(Intent.EXTRA_UID, mNoteId);
// 启动笔记编辑 Activity
startActivity(intent);
break;
default:
break;
}
}
/**
*
* @param dialog
*/
public void onDismiss(DialogInterface dialog) {
// 停止播放提醒声音
stopAlarmSound();
// 关闭当前 Activity
finish();
}
/**
*
*/
private void stopAlarmSound() {
if (mPlayer != null) {
// 停止播放
mPlayer.stop();
// 释放媒体播放器资源
mPlayer.release();
mPlayer = null;
}
}
}

@ -0,0 +1,91 @@
/*
* 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.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
/**
* AlarmInitReceiver 广
* 便便
*/
public class AlarmInitReceiver extends BroadcastReceiver {
// 定义查询便签时需要返回的列
private static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 便签的 ID
NoteColumns.ALERTED_DATE // 便签的提醒日期
};
// 定义查询结果集中各列的索引
private static final int COLUMN_ID = 0; // 便签 ID 列的索引
private static final int COLUMN_ALERTED_DATE = 1; // 便签提醒日期列的索引
/**
* 广
* 便便
*
* @param context
* @param intent 广
*/
@Override
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);
// 创建一个新的意图,用于触发 AlarmReceiver
Intent sender = new Intent(context, AlarmReceiver.class);
// 设置意图的数据为当前便签的 URI
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建一个 PendingIntent用于在提醒日期触发广播
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取系统的闹钟服务
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
// 设置闹钟,在提醒日期唤醒设备并触发 PendingIntent
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
// 关闭游标,释放资源
c.close();
}
}
}

@ -0,0 +1,42 @@
/*
* 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;
/**
* AlarmReceiver BroadcastReceiver广
* 广 AlarmAlertActivity
*/
public class AlarmReceiver extends BroadcastReceiver {
/**
* 广
* @param context
* @param intent 广
*/
@Override
public void onReceive(Context context, Intent intent) {
// 设置意图的目标类为 AlarmAlertActivity
intent.setClass(context, AlarmAlertActivity.class);
// 添加新任务标志,确保可以在新任务中启动 Activity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 启动 AlarmAlertActivity
context.startActivity(intent);
}
}

@ -0,0 +1,412 @@
/*
* 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 java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
public class DateTimePicker extends FrameLayout {
private static final boolean DEFAULT_ENABLE_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12;
private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7;
private static final int DATE_SPINNER_MIN_VAL = 0;
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUT_SPINNER_MIN_VAL = 0;
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;
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;
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener;
// 移除同步账户相关设置
private void removeSyncAccount() {
// 获取指定名称的 SharedPreferences 对象,使用私有模式,确保只有本应用可以访问这些设置
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
// 获取 SharedPreferences 的编辑器,用于修改存储的设置
SharedPreferences.Editor editor = settings.edit();
// 检查 SharedPreferences 中是否包含同步账户名称的键
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
// 如果包含,则从编辑器中移除该键值对
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
// 检查 SharedPreferences 中是否包含最后同步时间的键
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
// 如果包含,则从编辑器中移除该键值对
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
// 提交编辑器中的修改,将更改保存到 SharedPreferences 中
editor.commit();
// 清理本地与 Google 任务GTask相关的信息由于数据库操作可能会阻塞主线程因此在新线程中执行
new Thread(new Runnable() {
public void run() {
// 创建一个 ContentValues 对象,用于存储要更新的数据
ContentValues values = new ContentValues();
// 将 GTask ID 字段设置为空字符串
values.put(NoteColumns.GTASK_ID, "");
// 将同步 ID 字段设置为 0
values.put(NoteColumns.SYNC_ID, 0);
// 使用 ContentResolver 更新 Notes 内容提供者中的笔记数据,将 GTask ID 和同步 ID 字段清空
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
}
// 获取当前设置的同步账户名称
public static String getSyncAccountName(Context context) {
// 获取指定名称的 SharedPreferences 对象,使用私有模式
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 从 SharedPreferences 中获取同步账户名称,如果不存在则返回空字符串
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
// 设置最后一次同步的时间
public static void setLastSyncTime(Context context, long time) {
// 获取指定名称的 SharedPreferences 对象,使用私有模式
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 获取 SharedPreferences 的编辑器,用于修改存储的设置
SharedPreferences.Editor editor = settings.edit();
// 将最后同步时间存储到编辑器中
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
// 提交编辑器中的修改,将更改保存到 SharedPreferences 中
editor.commit();
}
// 获取最后一次同步的时间
public static long getLastSyncTime(Context context) {
// 获取指定名称的 SharedPreferences 对象,使用私有模式
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 从 SharedPreferences 中获取最后同步时间,如果不存在则返回 0
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
// 自定义广播接收器,用于接收 GTask 同步服务的广播
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 刷新界面,更新账户设置和同步按钮状态等信息
refreshUI();
// 检查广播意图中是否包含正在同步的标志,并且标志为 true
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
// 查找用于显示同步状态的 TextView 控件
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 设置同步状态文本为广播意图中携带的进度消息
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
// 处理选项菜单的点击事件
public boolean onOptionsItemSelected(MenuItem item) {
// 根据点击的菜单项 ID 进行不同的处理
switch (item.getItemId()) {
// 如果点击的是应用图标(即返回按钮)
case android.R.id.home:
// 创建一个意图,用于启动 NotesListActivity
Intent intent = new Intent(this, NotesListActivity.class);
// 添加标志,确保启动的 NotesListActivity 位于任务栈的顶部,并清除之前的所有活动
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// 启动 NotesListActivity
startActivity(intent);
// 返回 true 表示事件已处理
return true;
// 如果点击的是其他菜单项
default:
// 返回 false 表示事件未处理
return false;
}
}
// 获取当前月份
public int getCurrentMonth() {
// 通过Calendar对象获取当前月份
return mDate.get(Calendar.MONTH);
}
/**
*
*
* @param month
*/
public void setCurrentMonth(int month) {
// 如果不是初始化阶段且设置的月份与当前月份相同,则直接返回
if (!mInitialising && month == getCurrentMonth()) {
return;
}
// 设置Calendar对象的月份
mDate.set(Calendar.MONTH, month);
// 更新日期控件显示
updateDateControl();
// 触发日期时间改变的回调
onDateTimeChanged();
}
/**
*
*
* @return
*/
public int getCurrentDay() {
// 通过Calendar对象获取当前月份中的日期
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
*
*
* @param dayOfMonth
*/
public void setCurrentDay(int dayOfMonth) {
// 如果不是初始化阶段且设置的日期与当前日期相同,则直接返回
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
// 设置Calendar对象的日期
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
// 更新日期控件显示
updateDateControl();
// 触发日期时间改变的回调
onDateTimeChanged();
}
/**
* 24(0~23)
* @return 24
*/
public int getCurrentHourOfDay() {
// 通过Calendar对象获取24小时制的当前小时
return mDate.get(Calendar.HOUR_OF_DAY);
}
// 获取当前小时根据是否为24小时制进行不同处理
private int getCurrentHour() {
if (mIs24HourView){
// 如果是24小时制直接返回24小时制的当前小时
return getCurrentHourOfDay();
} else {
// 如果是12小时制
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
// 如果小时数大于12减去12
return hour - HOURS_IN_HALF_DAY;
} else {
// 如果小时数为0则显示为12否则直接显示
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* 24(0~23)
*
* @param hourOfDay 24
*/
public void setCurrentHour(int hourOfDay) {
// 如果不是初始化阶段且设置的小时与当前小时相同,则直接返回
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
// 设置Calendar对象的小时
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
// 如果是12小时制
if (hourOfDay >= HOURS_IN_HALF_DAY) {
// 如果小时数大于等于12设置为下午
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
// 如果小时数大于12减去12
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
// 如果小时数小于12设置为上午
mIsAm = true;
if (hourOfDay == 0) {
// 如果小时数为0则显示为12
hourOfDay = HOURS_IN_HALF_DAY;
}
}
// 更新上午/下午控件显示
updateAmPmControl();
}
// 设置小时选择器的值
mHourSpinner.setValue(hourOfDay);
// 触发日期时间改变的回调
onDateTimeChanged();
}
/**
*
*
* @return
*/
public int getCurrentMinute() {
// 通过Calendar对象获取当前分钟
return mDate.get(Calendar.MINUTE);
}
/**
*
*/
public void setCurrentMinute(int minute) {
// 如果不是初始化阶段且设置的分钟与当前分钟相同,则直接返回
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
// 设置分钟选择器的值
mMinuteSpinner.setValue(minute);
// 设置Calendar对象的分钟
mDate.set(Calendar.MINUTE, minute);
// 触发日期时间改变的回调
onDateTimeChanged();
}
/**
* @return 24truefalse
*/
public boolean is24HourView () {
// 返回是否为24小时制视图的标志
return mIs24HourView;
}
/**
* 24/
*
* @param is24HourView true24false/
*/
public void set24HourView(boolean is24HourView) {
// 如果当前模式与要设置的模式相同,则直接返回
if (mIs24HourView == is24HourView) {
return;
}
// 更新是否为24小时制视图的标志
mIs24HourView = is24HourView;
// 根据是否为24小时制视图显示或隐藏上午/下午选择器
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
// 获取当前24小时制的小时
int hour = getCurrentHourOfDay();
// 更新小时选择器的范围
updateHourControl();
// 设置当前小时
setCurrentHour(hour);
// 更新上午/下午控件显示
updateAmPmControl();
}
// 更新日期控件的显示
private void updateDateControl() {
// 创建一个新的Calendar对象
Calendar cal = Calendar.getInstance();
// 设置Calendar对象的时间为当前时间
cal.setTimeInMillis(mDate.getTimeInMillis());
// 将日期向前移动DAYS_IN_ALL_WEEK / 2 + 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) {
// 日期向后移动1天
cal.add(Calendar.DAY_OF_YEAR, 1);
// 将日期格式化为"MM.dd EEEE"的字符串
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
// 设置日期选择器的显示值
mDateSpinner.setDisplayedValues(mDateDisplayValues);
// 设置日期选择器的当前值为中间值
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
// 使日期选择器重绘
mDateSpinner.invalidate();
}
// 更新上午/下午控件的显示
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);
}
}
// 更新小时控件的显示
private void updateHourControl() {
if (mIs24HourView) {
// 如果是24小时制视图设置小时选择器的最小值和最大值
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
// 如果是12小时制视图设置小时选择器的最小值和最大值
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
*
* @param callback null
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
// 设置日期时间改变的回调函数
mOnDateTimeChangedListener = callback;
}
// 触发日期时间改变的回调
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
// 调用回调函数,传递当前的年、月、日、小时和分钟
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,146 @@
/*
* 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 java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
/**
* DateTimePickerDialog AlertDialog
* DateTimePicker
*
*/
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
// 用于存储用户选择的日期和时间
private Calendar mDate = Calendar.getInstance();
// 标记是否使用 24 小时制显示时间
private boolean mIs24HourView;
// 日期时间选择确定的回调接口
private OnDateTimeSetListener mOnDateTimeSetListener;
// 自定义的日期时间选择器组件
private DateTimePicker mDateTimePicker;
/**
*
*/
public interface OnDateTimeSetListener {
/**
*
* @param dialog
* @param date
*/
void OnDateTimeSet(AlertDialog dialog, long date);
}
/**
*
* @param context
* @param date
*/
public DateTimePickerDialog(Context context, long date) {
super(context);
// 创建 DateTimePicker 实例
mDateTimePicker = new DateTimePicker(context);
// 将 DateTimePicker 设置为对话框的视图
setView(mDateTimePicker);
// 为 DateTimePicker 设置日期时间改变的监听器
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
@Override
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 更新 Calendar 对象的日期和时间
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());
}
});
// 设置 Calendar 对象的时间为传入的日期时间
mDate.setTimeInMillis(date);
// 将秒数设置为 0
mDate.set(Calendar.SECOND, 0);
// 设置 DateTimePicker 的当前日期时间
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框的确定按钮及其点击监听器
setButton(context.getString(R.string.datetime_dialog_ok), this);
// 设置对话框的取消按钮
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 根据系统设置确定是否使用 24 小时制
set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 更新对话框的标题显示初始日期时间
updateTitle(mDate.getTimeInMillis());
}
/**
* 使 24
* @param is24HourView true 使 24 false 使 12
*/
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
/**
*
* @param callBack OnDateTimeSetListener
*/
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
/**
*
* @param date
*/
private void updateTitle(long date) {
// 设置日期时间显示的标志
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
// 根据是否使用 24 小时制添加相应的标志
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
// 设置对话框的标题为格式化后的日期时间
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
/**
*
* @param arg0
* @param arg1
*/
@Override
public void onClick(DialogInterface arg0, int arg1) {
// 如果设置了回调接口,则调用回调方法
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,101 @@
/*
* 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.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
/**
* DropdownMenu
*
*/
public class DropdownMenu {
// 用于触发弹出菜单的按钮
private Button mButton;
// 弹出菜单对象
private PopupMenu mPopupMenu;
// 弹出菜单的菜单对象
private Menu mMenu;
/**
*
*
* @param context
* @param button
* @param menuId ID
*/
public DropdownMenu(Context context, Button button, int menuId) {
// 保存传入的按钮对象
mButton = button;
// 设置按钮的背景资源为下拉图标
mButton.setBackgroundResource(R.drawable.dropdown_icon);
// 创建一个弹出菜单,将其关联到按钮上
mPopupMenu = new PopupMenu(context, mButton);
// 获取弹出菜单的菜单对象
mMenu = mPopupMenu.getMenu();
// 从指定的菜单资源 ID 中加载菜单内容到菜单对象中
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
// 为按钮设置点击监听器,当按钮被点击时显示弹出菜单
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});
}
/**
*
*
* @param listener
*/
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
// 检查弹出菜单是否为空
if (mPopupMenu != null) {
// 设置弹出菜单的菜单项点击监听器
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
/**
* ID
*
* @param id ID
* @return null
*/
public MenuItem findItem(int id) {
// 在菜单对象中查找指定 ID 的菜单项
return mMenu.findItem(id);
}
/**
*
*
* @param title
*/
public void setTitle(CharSequence title) {
// 设置按钮的文本为传入的标题文本
mButton.setText(title);
}
}

@ -0,0 +1,132 @@
/*
* 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.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
/**
* FoldersListAdapter CursorAdapter
*
*/
public class FoldersListAdapter extends CursorAdapter {
// 定义查询数据库时需要投影的列,即需要从数据库中获取的列
public static final String [] PROJECTION = {
NoteColumns.ID, // 文件夹的 ID 列
NoteColumns.SNIPPET // 文件夹的名称列
};
// 定义投影列在数组中的索引位置,方便后续使用
public static final int ID_COLUMN = 0; // ID 列的索引
public static final int NAME_COLUMN = 1; // 名称列的索引
/**
* FoldersListAdapter
*
* @param context 访
* @param c
*/
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
/**
*
*
* @param context 访
* @param cursor
* @param parent
* @return FolderListItem
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
/**
*
*
* @param view
* @param context 访
* @param cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
// 检查视图是否为 FolderListItem 类型
if (view instanceof FolderListItem) {
// 获取文件夹名称,如果文件夹的 ID 是根文件夹的 ID则显示特定的字符串
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
// 调用 FolderListItem 的 bind 方法将文件夹名称绑定到视图上
((FolderListItem) view).bind(folderName);
}
}
/**
*
*
* @param context 访
* @param position
* @return
*/
public String getFolderName(Context context, int position) {
// 获取指定位置的游标
Cursor cursor = (Cursor) getItem(position);
// 获取文件夹名称,如果文件夹的 ID 是根文件夹的 ID则显示特定的字符串
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
/**
* FolderListItem 线
*/
private class FolderListItem extends LinearLayout {
private TextView mName; // 用于显示文件夹名称的文本视图
/**
* FolderListItem
*
* @param context 访
*/
public FolderListItem(Context context) {
super(context);
// 加载文件夹列表项的布局文件
inflate(context, R.layout.folder_list_item, this);
// 获取布局文件中的文本视图
mName = (TextView) findViewById(R.id.tv_folder_name);
}
/**
*
*
* @param name
*/
public void bind(String name) {
mName.setText(name);
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,317 @@
/*
* 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.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
/**
* NoteEditText EditText
*
*/
public class NoteEditText extends EditText {
// 日志标签,用于在日志中标识该类的信息
private static final String TAG = "NoteEditText";
// 该编辑文本框的索引,用于在列表中定位
private int mIndex;
// 删除操作前的选择起始位置
private int mSelectionStartBeforeDelete;
// 电话链接的协议前缀
private static final String SCHEME_TEL = "tel:" ;
// HTTP 链接的协议前缀
private static final String SCHEME_HTTP = "http:" ;
// 邮件链接的协议前缀
private static final String SCHEME_EMAIL = "mailto:" ;
// 存储协议前缀和对应的菜单项资源 ID 的映射
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
// 初始化映射,将电话协议前缀映射到对应的菜单项资源 ID
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
// 初始化映射,将 HTTP 协议前缀映射到对应的菜单项资源 ID
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
// 初始化映射,将邮件协议前缀映射到对应的菜单项资源 ID
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* OnTextViewChangeListener
*
*/
public interface OnTextViewChangeListener {
/**
*
* @param index
* @param text
*/
void onEditTextDelete(int index, String text);
/**
*
* @param index
* @param text
*/
void onEditTextEnter(int index, String text);
/**
*
* @param index
* @param hasText
*/
void onTextChange(int index, boolean hasText);
}
// 文本变化监听器实例
private OnTextViewChangeListener mOnTextViewChangeListener;
/**
* 使 NoteEditText
* @param context
*/
public NoteEditText(Context context) {
super(context, null);
// 初始化索引为 0
mIndex = 0;
}
/**
*
* @param index
*/
public void setIndex(int index) {
mIndex = index;
}
/**
*
* @param listener OnTextViewChangeListener
*/
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
/**
* 使 NoteEditText
* @param context
* @param attrs
*/
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
/**
* 使 NoteEditText
* @param context
* @param attrs
* @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:
// 获取触摸点的 x 坐标
int x = (int) event.getX();
// 获取触摸点的 y 坐标
int y = (int) event.getY();
// 减去左内边距
x -= getTotalPaddingLeft();
// 减去上内边距
y -= getTotalPaddingTop();
// 加上水平滚动距离
x += getScrollX();
// 加上垂直滚动距离
y += getScrollY();
// 获取文本布局对象
Layout layout = getLayout();
// 根据垂直位置获取所在行
int line = layout.getLineForVertical(y);
// 根据行和水平位置获取偏移量
int off = layout.getOffsetForHorizontal(line, x);
// 设置光标位置
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:
if (mOnTextViewChangeListener != null) {
// 如果有监听器,不处理回车键事件,交由监听器处理
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 记录删除操作前的选择起始位置
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
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) {
// 当删除操作从文本开头开始且索引不为 0 时,调用删除回调方法
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
// 记录日志,提示监听器未设置
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
// 获取当前选择的起始位置
int selectionStart = getSelectionStart();
// 获取光标之后的文本内容
String text = getText().subSequence(selectionStart, length()).toString();
// 设置文本为光标之前的内容
setText(getText().subSequence(0, selectionStart));
// 调用回车回调方法
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
// 记录日志,提示监听器未设置
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
/**
*
* @param focused
* @param direction
* @param previouslyFocusedRect
*/
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
if (!focused && TextUtils.isEmpty(getText())) {
// 失去焦点且文本为空时,调用文本变化回调方法,标记为无文本
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
// 其他情况,调用文本变化回调方法,标记为有文本
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
/**
*
* @param menu
*/
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
// 获取选择的起始位置
int selStart = getSelectionStart();
// 获取选择的结束位置
int selEnd = getSelectionEnd();
// 获取选择范围的最小值
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) {
// 如果只有一个 URLSpan 对象
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
// 根据链接协议前缀获取对应的菜单项资源 ID
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) {
// 如果没有匹配的协议前缀,使用默认的菜单项资源 ID
defaultResId = R.string.note_link_other;
}
// 添加菜单项,并设置点击监听器
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 点击菜单项时,触发链接的点击事件
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}
}

@ -0,0 +1,360 @@
/*
* 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;
/**
* NoteItemData
* Cursor
*/
public class NoteItemData {
// 定义从数据库查询笔记数据时使用的列名数组
static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 笔记的唯一标识符
NoteColumns.ALERTED_DATE, // 提醒日期
NoteColumns.BG_COLOR_ID, // 背景颜色 ID
NoteColumns.CREATED_DATE, // 创建日期
NoteColumns.HAS_ATTACHMENT, // 是否有附件
NoteColumns.MODIFIED_DATE, // 修改日期
NoteColumns.NOTES_COUNT, // 笔记数量
NoteColumns.PARENT_ID, // 父文件夹 ID
NoteColumns.SNIPPET, // 笔记摘要
NoteColumns.TYPE, // 笔记类型
NoteColumns.WIDGET_ID, // 小部件 ID
NoteColumns.WIDGET_TYPE, // 小部件类型
};
// 定义每个列在 PROJECTION 数组中的索引,方便从 Cursor 中获取数据
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 long mId; // 笔记的唯一标识符
private long mAlertDate; // 提醒日期
private int mBgColorId; // 背景颜色 ID
private long mCreatedDate; // 创建日期
private boolean mHasAttachment; // 是否有附件
private long mModifiedDate; // 修改日期
private int mNotesCount; // 笔记数量
private long mParentId; // 父文件夹 ID
private String mSnippet; // 笔记摘要
private int mType; // 笔记类型
private int mWidgetId; // 小部件 ID
private int mWidgetType; // 小部件类型
private String mName; // 联系人姓名
private String mPhoneNumber; // 联系人电话号码
// 笔记在列表中的位置相关标志
private boolean mIsLastItem; // 是否是列表中的最后一项
private boolean mIsFirstItem; // 是否是列表中的第一项
private boolean mIsOnlyOneItem; // 是否是列表中唯一的一项
private boolean mIsOneNoteFollowingFolder; // 是否是文件夹后面的单条笔记
private boolean mIsMultiNotesFollowingFolder; // 是否是文件夹后面的多条笔记
/**
* Cursor
*
* @param context
* @param cursor Cursor
*/
public NoteItemData(Context context, Cursor 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);
// 移除笔记摘要中的特定标签
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);
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);
}
/**
*
*
* @param cursor 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;
}
}
// 将 Cursor 移动回原来的位置
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
/**
*
*
* @return true false
*/
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
/**
*
*
* @return true false
*/
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
/**
*
*
* @return true false
*/
public boolean isLast() {
return mIsLastItem;
}
/**
*
*
* @return
*/
public String getCallName() {
return mName;
}
/**
*
*
* @return true false
*/
public boolean isFirst() {
return mIsFirstItem;
}
/**
*
*
* @return true false
*/
public boolean isSingle() {
return mIsOnlyOneItem;
}
/**
*
*
* @return
*/
public long getId() {
return mId;
}
/**
*
*
* @return
*/
public long getAlertDate() {
return mAlertDate;
}
/**
*
*
* @return
*/
public long getCreatedDate() {
return mCreatedDate;
}
/**
*
*
* @return true false
*/
public boolean hasAttachment() {
return mHasAttachment;
}
/**
*
*
* @return
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* ID
*
* @return ID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* ID
*
* @return ID
*/
public long getParentId() {
return mParentId;
}
/**
*
*
* @return
*/
public int getNotesCount() {
return mNotesCount;
}
/**
* ID
*
* @return ID
*/
public long getFolderId () {
return mParentId;
}
/**
*
*
* @return
*/
public int getType() {
return mType;
}
/**
*
*
* @return
*/
public int getWidgetType() {
return mWidgetType;
}
/**
* ID
*
* @return ID
*/
public int getWidgetId() {
return mWidgetId;
}
/**
*
*
* @return
*/
public String getSnippet() {
return mSnippet;
}
/**
*
*
* @return true false
*/
public boolean hasAlert() {
return (mAlertDate > 0);
}
/**
*
*
* @return true false
*/
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
/**
* Cursor
*
* @param cursor Cursor
* @return
*/
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,312 @@
/*
* 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.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
/**
* NotesListAdapter CursorAdapter
*
*/
public class NotesListAdapter extends CursorAdapter {
// 日志标签,用于在调试时标识该类的日志信息
private static final String TAG = "NotesListAdapter";
// 上下文对象,用于获取资源和执行与 UI 相关的操作
private Context mContext;
// 用于存储每个笔记项的选择状态,键为笔记项的位置,值为是否选中的布尔值
private HashMap<Integer, Boolean> mSelectedIndex;
// 笔记的总数
private int mNotesCount;
// 是否处于选择模式的标志
private boolean mChoiceMode;
/**
* ID
*/
public static class AppWidgetAttribute {
// 小部件的 ID
public int widgetId;
// 小部件的类型
public int widgetType;
};
/**
*
* @param context
*/
public NotesListAdapter(Context context) {
// 调用父类的构造函数,传入上下文和初始的 Cursor 为 null
super(context, null);
// 初始化选择状态的 HashMap
mSelectedIndex = new HashMap<Integer, Boolean>();
// 保存上下文对象
mContext = context;
// 初始化笔记总数为 0
mNotesCount = 0;
}
/**
*
* @param context
* @param cursor
* @param parent
* @return NotesListItem
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
/**
*
* @param view
* @param context
* @param cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
// 检查视图是否为 NotesListItem 类型
if (view instanceof NotesListItem) {
// 创建一个 NoteItemData 对象,用于封装笔记数据
NoteItemData itemData = new NoteItemData(context, cursor);
// 调用 NotesListItem 的 bind 方法,将数据绑定到视图上,并传入选择模式和当前项的选择状态
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
/**
*
* @param position
* @param checked
*/
public void setCheckedItem(final int position, final boolean checked) {
// 将指定位置的选择状态保存到 HashMap 中
mSelectedIndex.put(position, checked);
// 通知适配器数据发生变化,刷新视图
notifyDataSetChanged();
}
/**
*
* @return true false
*/
public boolean isInChoiceMode() {
return mChoiceMode;
}
/**
*
* @param mode
*/
public void setChoiceMode(boolean mode) {
// 清空选择状态的 HashMap
mSelectedIndex.clear();
// 更新选择模式标志
mChoiceMode = mode;
}
/**
*
* @param checked
*/
public void selectAll(boolean checked) {
// 获取数据库游标
Cursor cursor = getCursor();
// 遍历所有笔记项
for (int i = 0; i < getCount(); i++) {
// 将游标移动到指定位置
if (cursor.moveToPosition(i)) {
// 检查当前笔记项的类型是否为普通笔记
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
// 设置当前笔记项的选择状态
setCheckedItem(i, checked);
}
}
}
}
/**
* ID
* @return ID HashSet
*/
public HashSet<Long> getSelectedItemIds() {
// 创建一个 HashSet 用于存储选中笔记项的 ID
HashSet<Long> itemSet = new HashSet<Long>();
// 遍历选择状态的 HashMap
for (Integer position : mSelectedIndex.keySet()) {
// 检查当前位置的笔记项是否被选中
if (mSelectedIndex.get(position) == true) {
// 获取当前位置的笔记项的 ID
Long id = getItemId(position);
// 检查 ID 是否为根文件夹的 ID如果是则记录日志否则将 ID 添加到 HashSet 中
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
/**
*
* @return HashSet
*/
public HashSet<AppWidgetAttribute> getSelectedWidget() {
// 创建一个 HashSet 用于存储选中小部件的属性
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
// 遍历选择状态的 HashMap
for (Integer position : mSelectedIndex.keySet()) {
// 检查当前位置的笔记项是否被选中
if (mSelectedIndex.get(position) == true) {
// 获取当前位置的数据库游标
Cursor c = (Cursor) getItem(position);
// 检查游标是否有效
if (c != null) {
// 创建一个 AppWidgetAttribute 对象,用于存储小部件的属性
AppWidgetAttribute widget = new AppWidgetAttribute();
// 创建一个 NoteItemData 对象,用于封装笔记数据
NoteItemData item = new NoteItemData(mContext, c);
// 获取小部件的 ID 并保存到 AppWidgetAttribute 对象中
widget.widgetId = item.getWidgetId();
// 获取小部件的类型并保存到 AppWidgetAttribute 对象中
widget.widgetType = item.getWidgetType();
// 将 AppWidgetAttribute 对象添加到 HashSet 中
itemSet.add(widget);
/**
*
*/
} else {
// 记录游标无效的错误日志
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
/**
*
* @return
*/
public int getSelectedCount() {
// 获取选择状态的 HashMap 中的所有值
Collection<Boolean> values = mSelectedIndex.values();
// 检查值集合是否为空
if (null == values) {
return 0;
}
// 创建一个迭代器,用于遍历值集合
Iterator<Boolean> iter = values.iterator();
// 初始化选中笔记项的数量为 0
int count = 0;
// 遍历值集合
while (iter.hasNext()) {
// 检查当前值是否为 true如果是则增加选中笔记项的数量
if (true == iter.next()) {
count++;
}
}
return count;
}
/**
*
* @return true false
*/
public boolean isAllSelected() {
// 获取选中的笔记项的数量
int checkedCount = getSelectedCount();
// 检查选中的笔记项数量是否不为 0 且等于笔记总数
return (checkedCount != 0 && checkedCount == mNotesCount);
}
/**
*
* @param position
* @return true false
*/
public boolean isSelectedItem(final int position) {
// 检查指定位置的选择状态是否为 null如果是则返回 false否则返回该位置的选择状态
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
/**
*
*/
@Override
protected void onContentChanged() {
// 调用父类的方法
super.onContentChanged();
// 重新计算笔记总数
calcNotesCount();
}
/**
* 使
* @param cursor
*/
@Override
public void changeCursor(Cursor cursor) {
// 调用父类的方法
super.changeCursor(cursor);
// 重新计算笔记总数
calcNotesCount();
}
/**
*
*/
private void calcNotesCount() {
// 初始化笔记总数为 0
mNotesCount = 0;
// 遍历所有笔记项
for (int i = 0; i < getCount(); i++) {
// 获取当前位置的数据库游标
Cursor c = (Cursor) getItem(i);
// 检查游标是否有效
if (c != null) {
// 检查当前笔记项的类型是否为普通笔记,如果是则增加笔记总数
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} else {
// 记录游标无效的错误日志
Log.e(TAG, "Invalid cursor");
return;
}
}
}
}

@ -0,0 +1,207 @@
/*
* 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.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
/**
* NotesListItem LinearLayout
*
*/
public class NotesListItem extends LinearLayout {
// 用于显示提醒图标的 ImageView
private ImageView mAlert;
// 用于显示标题的 TextView
private TextView mTitle;
// 用于显示修改时间的 TextView
private TextView mTime;
// 用于显示通话记录中联系人姓名的 TextView
private TextView mCallName;
// 存储当前笔记项数据的对象
private NoteItemData mItemData;
// 用于选择笔记项的 CheckBox
private CheckBox mCheckBox;
/**
* NotesListItem
*
* @param context
*/
public NotesListItem(Context context) {
super(context);
// 将 note_item 布局文件填充到当前视图中
inflate(context, R.layout.note_item, this);
// 通过 findViewById 方法获取布局中的各个视图组件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
/**
*
*
*
* @param context
* @param data
* @param choiceMode
* @param checked
*/
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 如果处于选择模式且笔记类型为普通笔记
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
// 显示 CheckBox 并设置其选中状态
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
// 否则隐藏 CheckBox
mCheckBox.setVisibility(View.GONE);
}
// 保存当前笔记项数据
mItemData = data;
// 如果笔记项的 ID 为通话记录文件夹的 ID
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 隐藏通话记录联系人姓名的 TextView
mCallName.setVisibility(View.GONE);
// 显示提醒图标
mAlert.setVisibility(View.VISIBLE);
// 设置标题的文本样式
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
// 设置标题文本,显示通话记录文件夹名称和其中的笔记数量
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
// 设置提醒图标为通话记录图标
mAlert.setImageResource(R.drawable.call_record);
}
// 如果笔记项的父 ID 为通话记录文件夹的 ID
else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
// 显示通话记录联系人姓名的 TextView 并设置其文本
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
// 设置标题的文本样式
mTitle.setTextAppearance(context, R.style.TextAppearanceSecondaryItem);
// 设置标题文本,显示格式化后的笔记摘要
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
// 如果笔记有提醒
if (data.hasAlert()) {
// 设置提醒图标为时钟图标并显示
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
// 否则隐藏提醒图标
mAlert.setVisibility(View.GONE);
}
}
// 其他情况
else {
// 隐藏通话记录联系人姓名的 TextView
mCallName.setVisibility(View.GONE);
// 设置标题的文本样式
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
// 如果笔记类型为文件夹
if (data.getType() == Notes.TYPE_FOLDER) {
// 设置标题文本,显示文件夹名称和其中的笔记数量
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
// 隐藏提醒图标
mAlert.setVisibility(View.GONE);
}
// 普通笔记类型
else {
// 设置标题文本,显示格式化后的笔记摘要
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
// 如果笔记有提醒
if (data.hasAlert()) {
// 设置提醒图标为时钟图标并显示
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
// 否则隐藏提醒图标
mAlert.setVisibility(View.GONE);
}
}
}
// 设置修改时间的文本,显示相对时间
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 设置当前笔记项的背景
setBackground(data);
}
/**
*
*
*
* @param data
*/
private void setBackground(NoteItemData data) {
// 获取笔记项的背景颜色 ID
int id = data.getBgColorId();
// 如果笔记类型为普通笔记
if (data.getType() == Notes.TYPE_NOTE) {
// 如果笔记是单独的一项或者后面跟着一个文件夹
if (data.isSingle() || data.isOneFollowingFolder()) {
// 设置背景资源为单独笔记项的背景
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
}
// 如果笔记是列表中的最后一项
else if (data.isLast()) {
// 设置背景资源为最后一个笔记项的背景
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
}
// 如果笔记是列表中的第一项或者后面跟着多个文件夹
else if (data.isFirst() || data.isMultiFollowingFolder()) {
// 设置背景资源为第一个笔记项的背景
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
}
// 其他情况
else {
// 设置背景资源为普通笔记项的背景
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
}
// 文件夹类型
else {
// 设置背景资源为文件夹的背景
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
/**
*
*
* @return
*/
public NoteItemData getItemData() {
return mItemData;
}
}

@ -0,0 +1,530 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
/**
* NotesPreferenceActivity
*
*/
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;
// 用于接收 GTaskSyncService 广播的接收器
private GTaskReceiver mReceiver;
// 原始的 Google 账户数组
private Account[] mOriAccounts;
// 标记是否添加了新账户
private boolean mHasAddedAccount;
/**
*
* @param icicle
*/
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// 设置应用图标可用于导航返回
getActionBar().setDisplayHomeAsUpEnabled(true);
// 从 XML 文件中加载偏好设置
addPreferencesFromResource(R.xml.preferences);
// 查找同步账户偏好类别
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// 创建 GTask 广播接收器
mReceiver = new GTaskReceiver();
// 创建意图过滤器,监听 GTaskSyncService 的广播
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
// 注册广播接收器
registerReceiver(mReceiver, filter);
// 初始化原始账户数组
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();
// 如果添加了新账户,自动设置同步账户
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
for (Account accountNew : accounts) {
boolean found = false;
for (Account accountOld : mOriAccounts) {
if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true;
break;
}
}
if (!found) {
setSyncAccount(accountNew.name);
break;
}
}
}
}
// 刷新 UI
refreshUI();
}
/**
* 广
*/
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy();
}
/**
*
*/
private void loadAccountPreference() {
// 清空同步账户偏好类别中的所有偏好
mAccountCategory.removeAll();
// 创建一个新的偏好项
Preference accountPref = new Preference(this);
// 获取当前的默认同步账户名称
final String defaultAccount = getSyncAccountName(this);
// 设置偏好项的标题
accountPref.setTitle(getString(R.string.preferences_account_title));
// 设置偏好项的摘要
accountPref.setSummary(getString(R.string.preferences_account_summary));
// 设置偏好项的点击监听器
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// 如果没有设置同步账户,显示选择账户的对话框
showSelectAccountAlertDialog();
} else {
// 如果已经设置了同步账户,显示更改账户的确认对话框
showChangeAccountConfirmAlertDialog();
}
} else {
// 如果正在同步,显示提示信息
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
// 将偏好项添加到同步账户偏好类别中
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);
// 根据同步状态设置按钮状态和文本
if (GTaskSyncService.isSyncing()) {
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 取消同步操作
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
}
});
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 立即开始同步操作
GTaskSyncService.startSync(NotesPreferenceActivity.this);
}
});
}
// 只有在设置了同步账户时,同步按钮才可用
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// 根据同步状态设置最后一次同步时间的显示
if (GTaskSyncService.isSyncing()) {
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
/**
* UI
*/
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
/**
*
*/
private void showSelectAccountAlertDialog() {
// 创建一个 AlertDialog 构建器
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 加载自定义标题视图
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
// 查找标题文本视图
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
// 设置标题文本
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
// 查找副标题文本视图
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
// 设置副标题文本
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
// 设置自定义标题
dialogBuilder.setCustomTitle(titleView);
// 设置确定按钮为空
dialogBuilder.setPositiveButton(null, null);
// 获取所有 Google 账户
Account[] accounts = getGoogleAccounts();
// 获取当前的默认同步账户名称
String defAccount = getSyncAccountName(this);
// 记录原始账户数组
mOriAccounts = accounts;
// 标记未添加新账户
mHasAddedAccount = false;
if (accounts.length > 0) {
// 创建账户名称数组
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
}
items[index++] = account.name;
}
// 设置单选列表项
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 设置选择的账户为同步账户
setSyncAccount(itemMapping[which].toString());
// 关闭对话框
dialog.dismiss();
// 刷新 UI
refreshUI();
}
});
}
// 加载添加账户的视图
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
// 设置对话框的视图
dialogBuilder.setView(addAccountView);
// 显示对话框
final AlertDialog dialog = dialogBuilder.show();
// 设置添加账户视图的点击监听器
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 标记添加了新账户
mHasAddedAccount = true;
// 创建添加账户的意图
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
// 设置账户权限过滤器
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
});
// 启动添加账户的活动
startActivityForResult(intent, -1);
// 关闭对话框
dialog.dismiss();
}
});
}
/**
*
*/
private void showChangeAccountConfirmAlertDialog() {
// 创建一个 AlertDialog 构建器
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 加载自定义标题视图
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
// 查找标题文本视图
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
// 设置标题文本,显示当前的同步账户名称
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this)));
// 查找副标题文本视图
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
// 设置副标题文本,显示更改账户的警告信息
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
// 设置自定义标题
dialogBuilder.setCustomTitle(titleView);
// 创建菜单项数组
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
};
// 设置菜单项的点击监听器
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
// 选择更改账户,显示选择账户的对话框
showSelectAccountAlertDialog();
} else if (which == 1) {
// 选择移除账户,移除同步账户并刷新 UI
removeSyncAccount();
refreshUI();
}
}
});
// 显示对话框
dialogBuilder.show();
}
/**
* Google
* @return Google
*/
private Account[] getGoogleAccounts() {
// 获取账户管理器
AccountManager accountManager = AccountManager.get(this);
// 获取所有 Google 账户
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);
// 获取偏好编辑器
SharedPreferences.Editor editor = settings.edit();
if (account != null) {
// 保存同步账户名称
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
// 清空同步账户名称
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
// 提交更改
editor.commit();
// 清空最后一次同步时间
setLastSyncTime(this, 0);
// 清理本地与 GTask 相关的信息
new Thread(new Runnable() {
public void run() {
// 创建内容值对象
ContentValues values = new ContentValues();
// 清空 GTask ID
values.put(NoteColumns.GTASK_ID, "");
// 清空同步 ID
values.put(NoteColumns.SYNC_ID, 0);
// 更新笔记内容提供者中的数据
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
// 显示设置成功的提示信息
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
// 移除同步账户相关设置
private void removeSyncAccount() {
// 获取指定名称的 SharedPreferences 对象,使用私有模式,确保只有本应用可以访问这些设置
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
// 获取 SharedPreferences 的编辑器,用于修改存储的设置
SharedPreferences.Editor editor = settings.edit();
// 检查 SharedPreferences 中是否包含同步账户名称的键
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
// 如果包含,则从编辑器中移除该键值对
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
// 检查 SharedPreferences 中是否包含最后同步时间的键
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
// 如果包含,则从编辑器中移除该键值对
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
// 提交编辑器中的修改,将更改保存到 SharedPreferences 中
editor.commit();
// 清理本地与 Google 任务GTask相关的信息由于数据库操作可能会阻塞主线程因此在新线程中执行
new Thread(new Runnable() {
public void run() {
// 创建一个 ContentValues 对象,用于存储要更新的数据
ContentValues values = new ContentValues();
// 将 GTask ID 字段设置为空字符串
values.put(NoteColumns.GTASK_ID, "");
// 将同步 ID 字段设置为 0
values.put(NoteColumns.SYNC_ID, 0);
// 使用 ContentResolver 更新 Notes 内容提供者中的笔记数据,将 GTask ID 和同步 ID 字段清空
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
}
// 获取当前设置的同步账户名称
public static String getSyncAccountName(Context context) {
// 获取指定名称的 SharedPreferences 对象,使用私有模式
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 从 SharedPreferences 中获取同步账户名称,如果不存在则返回空字符串
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
// 设置最后一次同步的时间
public static void setLastSyncTime(Context context, long time) {
// 获取指定名称的 SharedPreferences 对象,使用私有模式
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 获取 SharedPreferences 的编辑器,用于修改存储的设置
SharedPreferences.Editor editor = settings.edit();
// 将最后同步时间存储到编辑器中
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
// 提交编辑器中的修改,将更改保存到 SharedPreferences 中
editor.commit();
}
// 获取最后一次同步的时间
public static long getLastSyncTime(Context context) {
// 获取指定名称的 SharedPreferences 对象,使用私有模式
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 从 SharedPreferences 中获取最后同步时间,如果不存在则返回 0
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
// 自定义广播接收器,用于接收 GTask 同步服务的广播
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 刷新界面,更新账户设置和同步按钮状态等信息
refreshUI();
// 检查广播意图中是否包含正在同步的标志,并且标志为 true
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
// 查找用于显示同步状态的 TextView 控件
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 设置同步状态文本为广播意图中携带的进度消息
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
// 处理选项菜单的点击事件
public boolean onOptionsItemSelected(MenuItem item) {
// 根据点击的菜单项 ID 进行不同的处理
switch (item.getItemId()) {
// 如果点击的是应用图标(即返回按钮)
case android.R.id.home:
// 创建一个意图,用于启动 NotesListActivity
Intent intent = new Intent(this, NotesListActivity.class);
// 添加标志,确保启动的 NotesListActivity 位于任务栈的顶部,并清除之前的所有活动
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// 启动 NotesListActivity
startActivity(intent);
// 返回 true 表示事件已处理
return true;
// 如果点击的是其他菜单项
default:
// 返回 false 表示事件未处理
return false;
}
}
Loading…
Cancel
Save