diff --git a/doc/代码正确无误.PNG b/doc/代码正确无误.PNG deleted file mode 100644 index 0d7995e..0000000 Binary files a/doc/代码正确无误.PNG and /dev/null differ diff --git a/doc/小米便签开源代码的泛读报告.docx b/doc/小米便签开源代码的泛读报告.docx new file mode 100644 index 0000000..792fff8 Binary files /dev/null and b/doc/小米便签开源代码的泛读报告.docx differ diff --git a/doc/李凡.png b/doc/李凡.png new file mode 100644 index 0000000..8f5cafb Binary files /dev/null and b/doc/李凡.png differ diff --git a/doc/成功运行.PNG b/doc/窦志伟.PNG similarity index 100% rename from doc/成功运行.PNG rename to doc/窦志伟.PNG diff --git a/src/代码注释/1.java b/src/代码注释/1.java new file mode 100644 index 0000000..9977fa8 --- /dev/null +++ b/src/代码注释/1.java @@ -0,0 +1,450 @@ +/* + * 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 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 { + + // 默认启用状态为 true + private static final boolean DEFAULT_ENABLE_STATE = true; + + // 一天中的小时数(12小时制) + private static final int HOURS_IN_HALF_DAY = 12; + // 一天中的小时数(24小时制) + 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; + // 24小时制小时选择器的最小和最大值 + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + // 12小时制小时选择器的最小和最大值 + 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; + // AM/PM选择器的最小和最大值 + 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]; + + // 标记是否是AM + private boolean mIsAm; + + // 是否为24小时制 + private boolean mIs24HourView; + + // 控件是否启用 + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + + // 初始化标记 + private boolean mInitialising; + + // 日期时间变化监听器 + private OnDateTimeChangedListener mOnDateTimeChangedListener; + + // 日期选择变化监听器 + private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); // 改变日期 + updateDateControl(); // 更新日期显示 + onDateTimeChanged(); // 触发日期时间变化事件 + } + }; + + // 小时选择变化监听器 + 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小时制,处理AM/PM转换 + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); // 日期增加一天 + isDateChanged = true; + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); // 日期减少一天 + isDateChanged = true; + } + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; // 切换AM/PM + updateAmPmControl(); // 更新AM/PM控件 + } + } else { + // 如果是24小时制,处理跨日的变化 + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + } + // 设置新的小时 + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + mDate.set(Calendar.HOUR_OF_DAY, newHour); + onDateTimeChanged(); // 触发日期时间变化事件 + if (isDateChanged) { + // 如果日期变化,更新日期 + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + + // 分钟选择变化监听器 + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0; + if (oldVal == maxValue && newVal == minValue) { + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { + offset -= 1; + } + if (offset != 0) { + mDate.add(Calendar.HOUR_OF_DAY, offset); // 根据分钟变化调整小时 + mHourSpinner.setValue(getCurrentHour()); // 更新小时控件的值 + updateDateControl(); // 更新日期 + int newHour = getCurrentHourOfDay(); + if (newHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + updateAmPmControl(); // 更新AM/PM控件 + } else { + mIsAm = true; + updateAmPmControl(); // 更新AM/PM控件 + } + } + mDate.set(Calendar.MINUTE, newVal); // 设置分钟 + onDateTimeChanged(); // 触发日期时间变化事件 + } + }; + + // AM/PM选择变化监听器 + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mIsAm = !mIsAm; // 切换AM/PM + if (mIsAm) { + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); // 如果是AM,减少12小时 + } else { + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); // 如果是PM,增加12小时 + } + updateAmPmControl(); // 更新AM/PM控件 + onDateTimeChanged(); // 触发日期时间变化事件 + } + }; + + // 日期时间变化监听器接口 + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } + + public DateTimePicker(Context context) { + this(context, System.currentTimeMillis()); + } + + public DateTimePicker(Context context, long date) { + this(context, date, DateFormat.is24HourFormat(context)); + } + + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context); + mDate = Calendar.getInstance(); // 获取当前时间 + mInitialising = true; // 标记初始化状态 + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; // 判断当前时间是否为AM + inflate(context, R.layout.datetime_picker, this); // 加载布局文件 + + mDateSpinner = (NumberPicker) findViewById(R.id.date); // 获取日期选择器 + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); // 设置日期选择器最小值 + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); // 设置日期选择器最大值 + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); // 设置日期变化监听器 + + mHourSpinner = (NumberPicker) findViewById(R.id.hour); // 获取小时选择器 + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); // 设置小时变化监听器 + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); // 获取分钟选择器 + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); // 设置分钟选择器最小值 + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); // 设置分钟选择器最大值 + mMinuteSpinner.setOnLongPressUpdateInterval(100); // 设置长按更新间隔 + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); // 设置分钟变化监听器 + + // 获取AM/PM字符串数组 + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); // 获取AM/PM选择器 + mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); // 设置AM/PM选择器最小值 + mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); // 设置AM/PM选择器最大值 + mAmPmSpinner.setDisplayedValues(stringsForAmPm); // 设置AM/PM的显示值 + mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); // 设置AM/PM变化监听器 + + // 初始化时更新控件状态 + updateDateControl(); + updateHourControl(); + updateAmPmControl(); + + set24HourView(is24HourView); // 设置是否为24小时制 + + // 设置当前时间 + setCurrentDate(date); + + setEnabled(isEnabled()); // 设置是否启用 + + mInitialising = false; // 标记初始化完成 + } + + @Override + public void setEnabled(boolean enabled) { + if (mIsEnabled == enabled) { + return; + } + super.setEnabled(enabled); + mDateSpinner.setEnabled(enabled); // 设置日期选择器启用状态 + mMinuteSpinner.setEnabled(enabled); // 设置分钟选择器启用状态 + mHourSpinner.setEnabled(enabled); // 设置小时选择器启用状态 + mAmPmSpinner.setEnabled(enabled); // 设置AM/PM选择器启用状态 + mIsEnabled = enabled; // 更新启用状态 + } + + @Override + public boolean isEnabled() { + return mIsEnabled; // 返回当前是否启用 + } + + // 获取当前时间的毫秒值 + public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); + } + + // 设置当前时间(以毫秒为单位) + public void setCurrentDate(long date) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); // 将毫秒值设置为时间 + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); // 更新日期时间 + } + + // 设置当前时间(年、月、日、时、分) + public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + setCurrentYear(year); // 设置当前年份 + setCurrentMonth(month); // 设置当前月份 + setCurrentDay(dayOfMonth); // 设置当前日期 + setCurrentHour(hourOfDay); // 设置当前小时 + setCurrentMinute(minute); // 设置当前分钟 + } + + // 获取当前年份 + public int getCurrentYear() { + return mDate.get(Calendar.YEAR); + } + + // 设置当前年份 + public void setCurrentYear(int year) { + if (!mInitialising && year == getCurrentYear()) { + return; // 如果年份没变化,则不更新 + } + mDate.set(Calendar.YEAR, year); // 更新年份 + updateDateControl(); // 更新日期显示 + onDateTimeChanged(); // 触发日期时间变化事件 + } + + // 获取当前月份(0-11) + public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); + } + + // 设置当前月份(0-11) + public void setCurrentMonth(int month) { + if (!mInitialising && month == getCurrentMonth()) { + return; // 如果月份没变化,则不更新 + } + mDate.set(Calendar.MONTH, month); // 更新月份 + updateDateControl(); // 更新日期显示 + onDateTimeChanged(); // 触发日期时间变化事件 + } + + // 获取当前日期 + public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); + } + + // 设置当前日期 + public void setCurrentDay(int dayOfMonth) { + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; // 如果日期没变化,则不更新 + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); // 更新日期 + updateDateControl(); // 更新日期显示 + onDateTimeChanged(); // 触发日期时间变化事件 + } + + // 获取当前小时(24小时制) + public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); + } + + private int getCurrentHour() { + if (mIs24HourView){ + return getCurrentHourOfDay(); // 如果是24小时制,直接返回当前小时 + } else { + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; // 如果是PM,减去12小时 + } else { + return hour == 0 ? HOURS_IN_HALF_DAY : hour; // 如果是0点,设置为12 + } + } + } + + // 设置当前小时(24小时制) + public void setCurrentHour(int hourOfDay) { + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { + return; // 如果小时没变化,则不更新 + } + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); // 设置小时 + if (!mIs24HourView) { + if (hourOfDay >= HOURS_IN_HALF_DAY) { + mIsAm = false; // 设置为PM + if (hourOfDay > HOURS_IN_HALF_DAY) { + hourOfDay -= HOURS_IN_HALF_DAY; // 减去12小时 + } + } else { + mIsAm = true; // 设置为AM + if (hourOfDay == 0) { + hourOfDay = HOURS_IN_HALF_DAY; // 如果是0点,设置为12 + } + } + updateAmPmControl(); // 更新AM/PM控件 + } + mHourSpinner.setValue(hourOfDay); // 更新小时选择器的值 + onDateTimeChanged(); // 触发日期时间变化事件 + } + + // 获取当前分钟 + public int getCurrentMinute() { + return mDate.get(Calendar.MINUTE); + } + + // 设置当前分钟 + public void setCurrentMinute(int minute) { + if (!mInitialising && minute == getCurrentMinute()) { + return; // 如果分钟没变化,则不更新 + } + mMinuteSpinner.setValue(minute); // 更新分钟选择器的值 + mDate.set(Calendar.MINUTE, minute); // 设置分钟 + onDateTimeChanged(); // 触发日期时间变化事件 + } + + // 返回是否为24小时制 + public boolean is24HourView () { + return mIs24HourView; + } + + // 设置是否为24小时制 + public void set24HourView(boolean is24HourView) { + if (mIs24HourView == is24HourView) { + return; // 如果模式没变化,则不更新 + } + mIs24HourView = is24HourView; // 设置为24小时制 + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); // 隐藏AM/PM控件 + int hour = getCurrentHourOfDay(); + updateHourControl(); // 更新小时控件 + setCurrentHour(hour); // 设置小时 + updateAmPmControl(); // 更新AM/PM控件 + } + + // 更新日期控件的显示值 + private void updateDateControl() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + 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); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); // 格式化日期为“MM.dd 星期” + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); // 设置日期选择器的显示值 + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 设置初始值为当前日期 + mDateSpinner.invalidate(); // 刷新日期选择器 + } + + // 更新AM/PM控件的显示 + private void updateAmPmControl() { + if (mIs24HourView) { + mAmPmSpinner.setVisibility(View.GONE); // 隐藏AM/PM控件 + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; + mAmPmSpinner.setValue(index); // 设置AM/PM选择器的值 + mAmPmSpinner.setVisibility(View.VISIBLE); // 显示AM/PM控件 + } + } + + // 更新小时控件的显示 + private void updateHourControl() { + if (mIs24HourView) { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + } + } + + // 设置日期时间变化的回调接口 + public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { + mOnDateTimeChangedListener = callback; + } + + // 触发日期时间变化事件 + private void onDateTimeChanged() { + if (mOnDateTimeChangedListener != null) { + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } + } +} diff --git a/src/代码注释/2.java b/src/代码注释/2.java new file mode 100644 index 0000000..802e7b4 --- /dev/null +++ b/src/代码注释/2.java @@ -0,0 +1,386 @@ +/* + * 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; + + +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; // 账户类别设置 + + private GTaskReceiver mReceiver; // 广播接收器 + + private Account[] mOriAccounts; // 原始账户列表 + + private boolean mHasAddedAccount; // 是否添加了账户标志 + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + /* 使用应用图标作为导航 */ + getActionBar().setDisplayHomeAsUpEnabled(true); // 显示向上按钮,允许导航回到上级界面 + + addPreferencesFromResource(R.xml.preferences); // 加载偏好设置资源文件 + mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); // 获取账户设置类别 + mReceiver = new GTaskReceiver(); // 创建广播接收器 + 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); // 将头部视图添加到列表视图中 + } + + @Override + protected void onResume() { + super.onResume(); + + // 如果用户添加了新的账户,需要自动设置同步账户 + if (mHasAddedAccount) { + Account[] accounts = getGoogleAccounts(); // 获取Google账户列表 + 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; + } + } + } + } + + refreshUI(); // 刷新UI界面 + } + + @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); // 如果没有同步记录,隐藏显示 + } + } + } + + private void refreshUI() { + loadAccountPreference(); // 加载账户偏好设置 + loadSyncButton(); // 加载同步按钮和状态 + } + + private void showSelectAccountAlertDialog() { + 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); // 设置空的正面按钮 + + Account[] accounts = getGoogleAccounts(); // 获取Google账户列表 + 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(); // 关闭对话框 + refreshUI(); // 刷新UI + } + }); + } + + 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.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) { + removeSyncAccount(); // 移除同步账户 + refreshUI(); // 刷新UI + } + } + }); + dialogBuilder.show(); // 显示对话框 + } + + private Account[] getGoogleAccounts() { + AccountManager accountManager = AccountManager.get(this); // 获取账户管理器 + return accountManager.getAccountsByType("com.google"); // 获取Google账户 + } + + 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(); // 创建内容值 + values.put(NoteColumns.GTASK_ID, ""); // 清空任务ID + values.put(NoteColumns.SYNC_ID, 0); // 清空同步ID + 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 settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); // 获取共享偏好设置 + SharedPreferences.Editor editor = settings.edit(); // 获取编辑器 + if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { + editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); // 移除同步账户名称 + } + if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { + editor.remove(PREFERENCE_LAST_SYNC_TIME); // 移除最后同步时间 + } + editor.commit(); // 提交更改 + + // 清除本地与gtask相关的信息 + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); // 创建内容值 + values.put(NoteColumns.GTASK_ID, ""); // 清空任务ID + values.put(NoteColumns.SYNC_ID, 0); // 清空同步ID + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); // 更新数据库 + } + }).start(); + } + + 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) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); // 获取共享偏好设置 + SharedPreferences.Editor editor = settings.edit(); // 获取编辑器 + editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); // 设置最后同步时间 + editor.commit(); // 提交更改 + } + + 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 { + + @Override + public void onReceive(Context context, Intent intent) { + refreshUI(); // 刷新UI + if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { + 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) { + switch (item.getItemId()) { + case android.R.id.home: + Intent intent = new Intent(this, NotesListActivity.class); // 创建返回到笔记列表的意图 + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 设置清除栈标志 + startActivity(intent); // 启动活动 + return true; + default: + return false; + } + } +} diff --git a/src/代码注释/3.java b/src/代码注释/3.java new file mode 100644 index 0000000..fccd4e2 --- /dev/null +++ b/src/代码注释/3.java @@ -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.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; + +public class NoteEditText extends EditText { + private static final String TAG = "NoteEditText"; // 定义日志标签 + private int mIndex; // 当前编辑框的索引 + private int mSelectionStartBeforeDelete; // 删除前的选择开始位置 + + // 定义常见的URL schema + private static final String SCHEME_TEL = "tel:" ; + private static final String SCHEME_HTTP = "http:" ; + private static final String SCHEME_EMAIL = "mailto:" ; + + // 存储URL schema对应的操作资源ID + private static final Map sSchemaActionResMap = new HashMap(); + 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); // 邮件链接 + } + + /** + * 用于{@link NoteEditActivity}调用,删除或添加编辑框内容 + */ + public interface OnTextViewChangeListener { + /** + * 当按下删除键({@link KeyEvent#KEYCODE_DEL})且文本为空时删除当前编辑框内容 + */ + void onEditTextDelete(int index, String text); + + /** + * 当按下回车键({@link KeyEvent#KEYCODE_ENTER})时,在当前编辑框后添加新的文本框 + */ + void onEditTextEnter(int index, String text); + + /** + * 当文本变化时隐藏或显示选项 + */ + void onTextChange(int index, boolean hasText); + } + + private OnTextViewChangeListener mOnTextViewChangeListener; // 文本变化监听器 + + // 构造函数 + public NoteEditText(Context context) { + super(context, null); + mIndex = 0; // 默认索引为0 + } + + // 设置索引 + public void setIndex(int index) { + mIndex = index; + } + + // 设置文本变化监听器 + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; + } + + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + // 处理触摸事件 + @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(); + 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); // 继续处理其他触摸事件 + } + + // 处理按键按下事件 + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener != null) { + return false; // 如果监听器已设置,且按下回车键,返回false + } + break; + case KeyEvent.KEYCODE_DEL: + mSelectionStartBeforeDelete = getSelectionStart(); // 记录删除前的光标位置 + break; + default: + break; + } + return super.onKeyDown(keyCode, event); // 继续处理其他按键按下事件 + } + + // 处理按键抬起事件 + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch(keyCode) { + case KeyEvent.KEYCODE_DEL: + // 如果设置了文本变化监听器,且删除键按下时光标位置为0且不是第一个编辑框,调用删除方法 + if (mOnTextViewChangeListener != null) { + if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + return true; + } + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + case KeyEvent.KEYCODE_ENTER: + // 按下回车键时,将当前文本分割为两部分,调用回车事件方法 + 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); // 继续处理其他按键抬起事件 + } + + // 当焦点变化时调用 + @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); // 调用父类方法 + } + + // 创建上下文菜单 + @Override + protected void onCreateContextMenu(ContextMenu menu) { + if (getText() instanceof Spanned) { // 如果文本是Spanned类型(包含格式化信息) + int selStart = getSelectionStart(); // 获取当前选中的起始位置 + int selEnd = getSelectionEnd(); // 获取当前选中的结束位置 + + int min = Math.min(selStart, selEnd); + int max = Math.max(selStart, selEnd); + + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); // 获取选中的URLSpan + if (urls.length == 1) { // 如果选中的只有一个URLSpan + int defaultResId = 0; // 默认资源ID为0 + // 根据URL的schema选择相应的菜单项 + for (String schema : sSchemaActionResMap.keySet()) { + if (urls[0].getURL().indexOf(schema) >= 0) { + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) { + defaultResId = R.string.note_link_other; // 如果没有匹配到schema,使用默认资源 + } + + // 添加菜单项,并设置点击事件 + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // 打开链接对应的Intent + urls[0].onClick(NoteEditText.this); + return true; + } + }); + } + } + super.onCreateContextMenu(menu); // 调用父类方法,继续创建菜单项 + } +}