代码 #3

Merged
ps5hzkxwg merged 8 commits from liuzhilong_branch into master 4 weeks ago

@ -0,0 +1,24 @@
package net.micode.notes;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}

@ -0,0 +1,158 @@
/*
* 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;
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId;
private String mSnippet;
private static final int SNIPPET_PREW_MAX_LEN = 60;
MediaPlayer mPlayer;
@Override
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);
}
Intent intent = getIntent();
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
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 {
finish();
}
}
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
private void playAlarmSound() {
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) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
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);
}
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
break;
default:
break;
}
}
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
}
}

@ -0,0 +1,65 @@
/*
* 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;
public class AlarmInitReceiver extends BroadcastReceiver {
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
@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);
Intent sender = new Intent(context, AlarmReceiver.class);
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, PendingIntent.FLAG_IMMUTABLE);
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
c.close();
}
}
}

@ -0,0 +1,30 @@
/*
* 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;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
intent.setClass(context, AlarmAlertActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

@ -0,0 +1,485 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.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 {
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 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) {
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
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();
} else {
mIsAm = true;
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
}
};
public interface OnDateTimeChangedListener {
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;
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);
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
updateDateControl();
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
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);
mIsEnabled = enabled;
}
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
* Set the current date
*
* @param date The current date in millis
*/
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));
}
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
/**
* Get current year
*
* @return The current year
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
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;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
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);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.invalidate();
}
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
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);
}
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,90 @@
/*
* 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;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;
private DateTimePicker mDateTimePicker;
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
public DateTimePickerDialog(Context context, long date) {
super(context);
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
updateTitle(mDate.getTimeInMillis());
}
});
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
set24HourView(DateFormat.is24HourFormat(this.getContext()));
updateTitle(mDate.getTimeInMillis());
}
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,61 @@
/*
* 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;
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu;
private Menu mMenu;
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});
}
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}

@ -0,0 +1,80 @@
/*
* 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;
public class FoldersListAdapter extends CursorAdapter {
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
private class FolderListItem extends LinearLayout {
private TextView mName;
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
}
public void bind(String name) {
mName.setText(name);
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,411 @@
/*
* 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.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
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 androidx.appcompat.widget.AppCompatEditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends AppCompatEditText {
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
public NoteEditText(Context context) {
super(context, null);
mIndex = 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;
}
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:
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) {
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);
if (urls.length == 1) {
int defaultResId = 0;
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;
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}
/**
*
*/
public boolean hasStyle(int style) {
if (!(getText() instanceof Spanned)) {
return false;
}
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
if (selStart >= selEnd) {
return false;
}
Spanned spanned = (Spanned) getText();
StyleSpan[] spans = spanned.getSpans(selStart, selEnd, StyleSpan.class);
for (StyleSpan span : spans) {
if (span.getStyle() == style) {
return true;
}
}
return false;
}
/**
* 线
*/
public boolean hasUnderline() {
if (!(getText() instanceof Spanned)) {
return false;
}
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
if (selStart >= selEnd) {
return false;
}
Spanned spanned = (Spanned) getText();
UnderlineSpan[] spans = spanned.getSpans(selStart, selEnd, UnderlineSpan.class);
return spans.length > 0;
}
/**
* 线
*/
public boolean hasStrikethrough() {
if (!(getText() instanceof Spanned)) {
return false;
}
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
if (selStart >= selEnd) {
return false;
}
Spanned spanned = (Spanned) getText();
StrikethroughSpan[] spans = spanned.getSpans(selStart, selEnd, StrikethroughSpan.class);
return spans.length > 0;
}
/**
*
*/
public void applyStyle(int style) {
if (!(getText() instanceof Spanned)) {
return;
}
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
if (selStart >= selEnd) {
return;
}
Spanned spanned = (Spanned) getText();
StyleSpan[] spans = spanned.getSpans(selStart, selEnd, StyleSpan.class);
boolean hasStyle = false;
for (StyleSpan span : spans) {
if (span.getStyle() == style) {
hasStyle = true;
break;
}
}
if (!hasStyle) {
SpannableStringBuilder builder = new SpannableStringBuilder(spanned);
builder.setSpan(new StyleSpan(style), selStart, selEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setText(builder);
setSelection(selStart, selEnd);
}
}
/**
*
*/
public void removeStyle(int style) {
if (!(getText() instanceof Spanned)) {
return;
}
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
if (selStart >= selEnd) {
return;
}
Spanned spanned = (Spanned) getText();
StyleSpan[] spans = spanned.getSpans(selStart, selEnd, StyleSpan.class);
SpannableStringBuilder builder = new SpannableStringBuilder(spanned);
for (StyleSpan span : spans) {
if (span.getStyle() == style) {
builder.removeSpan(span);
}
}
setText(builder);
setSelection(selStart, selEnd);
}
/**
* 线
*/
public void toggleUnderline() {
if (!(getText() instanceof Spanned)) {
return;
}
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
if (selStart >= selEnd) {
return;
}
Spanned spanned = (Spanned) getText();
UnderlineSpan[] spans = spanned.getSpans(selStart, selEnd, UnderlineSpan.class);
SpannableStringBuilder builder = new SpannableStringBuilder(spanned);
if (spans.length > 0) {
// 移除下划线
for (UnderlineSpan span : spans) {
builder.removeSpan(span);
}
} else {
// 添加下划线
builder.setSpan(new UnderlineSpan(), selStart, selEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
setText(builder);
setSelection(selStart, selEnd);
}
/**
* 线
*/
public void toggleStrikethrough() {
if (!(getText() instanceof Spanned)) {
return;
}
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
if (selStart >= selEnd) {
return;
}
Spanned spanned = (Spanned) getText();
StrikethroughSpan[] spans = spanned.getSpans(selStart, selEnd, StrikethroughSpan.class);
SpannableStringBuilder builder = new SpannableStringBuilder(spanned);
if (spans.length > 0) {
// 移除删除线
for (StrikethroughSpan span : spans) {
builder.removeSpan(span);
}
} else {
// 添加删除线
builder.setSpan(new StrikethroughSpan(), selStart, selEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
setText(builder);
setSelection(selStart, selEnd);
}
}

@ -0,0 +1,293 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.PINNED // 添加置顶字段
};
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
private static final int CREATED_DATE_COLUMN = 3;
private static final int HAS_ATTACHMENT_COLUMN = 4;
private static final int MODIFIED_DATE_COLUMN = 5;
private static final int NOTES_COUNT_COLUMN = 6;
private static final int PARENT_ID_COLUMN = 7;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
private static final int PINNED_COLUMN = 12; // 新增置顶字段索引
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private int mPinned; // 新增置顶状态字段
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
public NoteItemData(Context context, Cursor cursor) {
try {
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);
// 加载置顶状态,添加异常处理
try {
mPinned = cursor.getInt(PINNED_COLUMN);
} catch (Exception e) {
mPinned = 0; // 默认未置顶
}
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
try {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
}
}
} catch (Exception e) {
mPhoneNumber = "";
mName = "";
}
}
if (mName == null) {
mName = "";
}
checkPostion(cursor);
} catch (Exception e) {
e.printStackTrace();
// 初始化默认值
mId = 0;
mAlertDate = 0;
mBgColorId = 0;
mCreatedDate = 0;
mHasAttachment = false;
mModifiedDate = 0;
mNotesCount = 0;
mParentId = 0;
mSnippet = "";
mType = 0;
mWidgetId = 0;
mWidgetType = 0;
mPinned = 0;
mPhoneNumber = "";
mName = "";
}
}
private void checkPostion(Cursor cursor) {
try {
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()) {
try {
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;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
cursor.moveToNext();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
// 初始化默认值
mIsLastItem = false;
mIsFirstItem = false;
mIsOnlyOneItem = false;
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
}
}
/**
*
*
* @return true-false-
*/
public boolean isPinned() {
return mPinned == 1;
}
/**
*
*
* @return 0-1-
*/
public int getPinned() {
return mPinned;
}
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
public boolean isLast() {
return mIsLastItem;
}
public String getCallName() {
return mName;
}
public boolean isFirst() {
return mIsFirstItem;
}
public boolean isSingle() {
return mIsOnlyOneItem;
}
public long getId() {
return mId;
}
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() {
return mParentId;
}
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,352 @@
/*
* 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.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.PriorityQueue;
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter";
private Context mContext;
private HashMap<Integer, Boolean> mSelectedIndex;
private int mNotesCount;
private boolean mChoiceMode;
private boolean mSortByPinned; // 新增:是否按置顶排序标志
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
};
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
mSortByPinned = true; // 默认按置顶排序
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
// 优化减少NoteItemData对象创建使用缓存
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
/**
*
*
* @param sortByPinned true-false-
*/
public void setSortByPinned(boolean sortByPinned) {
if (mSortByPinned != sortByPinned) {
mSortByPinned = sortByPinned;
notifyDataSetChanged();
}
}
/**
*
*
* @return true-false-
*/
public boolean isSortByPinned() {
return mSortByPinned;
}
/**
*
*
* @return
*/
public int getPinnedNotesCount() {
int pinnedCount = 0;
Cursor cursor = getCursor();
if (cursor != null && cursor.moveToFirst()) {
do {
NoteItemData itemData = new NoteItemData(mContext, cursor);
if (itemData.isPinned() && itemData.getType() == Notes.TYPE_NOTE) {
pinnedCount++;
}
} while (cursor.moveToNext());
}
return pinnedCount;
}
/**
* ID
*
* @return ID
*/
public HashSet<Long> getPinnedNoteIds() {
HashSet<Long> pinnedIds = new HashSet<Long>();
Cursor cursor = getCursor();
if (cursor != null && cursor.moveToFirst()) {
do {
NoteItemData itemData = new NoteItemData(mContext, cursor);
if (itemData.isPinned() && itemData.getType() == Notes.TYPE_NOTE) {
pinnedIds.add(itemData.getId());
}
} while (cursor.moveToNext());
}
return pinnedIds;
}
/**
*
*
* @param noteIds ID
* @param pinned true-false-
*/
public void setNotesPinned(HashSet<Long> noteIds, boolean pinned) {
// 这里只是更新适配器状态实际数据库更新需要在Activity中处理
notifyDataSetChanged();
}
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
public boolean isInChoiceMode() {
return mChoiceMode;
}
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
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);
}
}
}
}
/**
*
*
* @param checked true-false-
*/
public void selectAllPinned(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
NoteItemData itemData = new NoteItemData(mContext, cursor);
if (itemData.getType() == Notes.TYPE_NOTE && itemData.isPinned()) {
setCheckedItem(i, checked);
}
}
}
}
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
/**
* ID
*
* @return ID
*/
public HashSet<Long> getSelectedPinnedItemIds() {
HashSet<Long> pinnedItemSet = new HashSet<Long>();
Cursor cursor = getCursor();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true && cursor.moveToPosition(position)) {
NoteItemData itemData = new NoteItemData(mContext, cursor);
if (itemData.isPinned() && itemData.getType() == Notes.TYPE_NOTE) {
Long id = getItemId(position);
if (id != Notes.ID_ROOT_FOLDER) {
pinnedItemSet.add(id);
}
}
}
}
return pinnedItemSet;
}
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
}
}
return count;
}
/**
*
*
* @return
*/
public int getSelectedPinnedCount() {
int count = 0;
Cursor cursor = getCursor();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true && cursor.moveToPosition(position)) {
NoteItemData itemData = new NoteItemData(mContext, cursor);
if (itemData.isPinned() && itemData.getType() == Notes.TYPE_NOTE) {
count++;
}
}
}
return count;
}
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
/**
*
*
* @return true-false-
*/
public boolean isAllPinnedSelected() {
int pinnedCount = getPinnedNotesCount();
int selectedPinnedCount = getSelectedPinnedCount();
return (pinnedCount > 0 && selectedPinnedCount == pinnedCount);
}
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
}
@Override
public long getItemId(int position) {
Cursor cursor = (Cursor) getItem(position);
if (cursor != null) {
return cursor.getLong(0); // ID列在PROJECTION中的索引是0
}
return 0;
}
private void calcNotesCount() {
mNotesCount = 0;
Cursor cursor = getCursor();
if (cursor != null) {
int count = cursor.getCount();
for (int i = 0; i < count; i++) {
if (cursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
mNotesCount++;
}
}
}
}
}
/**
* -
*/
public static class NoteComparator implements Comparator<NoteItemData> {
@Override
public int compare(NoteItemData item1, NoteItemData item2) {
// 首先按置顶状态排序:置顶的在前
if (item1.isPinned() && !item2.isPinned()) {
return -1;
} else if (!item1.isPinned() && item2.isPinned()) {
return 1;
} else {
// 置顶状态相同,按修改时间倒序排序
return Long.compare(item2.getModifiedDate(), item1.getModifiedDate());
}
}
}
}

@ -0,0 +1,194 @@
/*
* 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.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.data.Notes.TagColumns;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout {
private ImageView mAlert;
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
private NoteItemData mItemData;
private CheckBox mCheckBox;
private ImageView mPinnedIcon; // 新增:置顶图标
private LinearLayout mTagsLayout; // 新增:标签显示区域
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
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);
mPinnedIcon = (ImageView) findViewById(R.id.iv_pin_icon); // 新增:获取置顶图标引用
mTagsLayout = (LinearLayout) findViewById(R.id.ll_note_tags); // 新增:获取标签显示区域引用
}
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data;
// 设置置顶图标显示状态
if (data.isPinned() && data.getType() == Notes.TYPE_NOTE) {
mPinnedIcon.setVisibility(View.VISIBLE);
mPinnedIcon.setImageResource(R.drawable.ic_pinned);
} else {
mPinnedIcon.setVisibility(View.GONE);
}
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
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);
// 文件夹不显示置顶图标
mPinnedIcon.setVisibility(View.GONE);
// 隐藏标签显示区域
mTagsLayout.setVisibility(View.GONE);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
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);
}
// 通话记录不显示置顶图标
mPinnedIcon.setVisibility(View.GONE);
// 隐藏标签显示区域
mTagsLayout.setVisibility(View.GONE);
} else {
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.VISIBLE);
mAlert.setImageResource(R.drawable.list_folder);
// 文件夹不显示置顶图标
mPinnedIcon.setVisibility(View.GONE);
// 隐藏标签显示区域
mTagsLayout.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);
}
// 普通笔记:根据置顶状态显示图标
if (data.isPinned()) {
mPinnedIcon.setVisibility(View.VISIBLE);
} else {
mPinnedIcon.setVisibility(View.GONE);
}
// 显示笔记的标签
showTags(context, data.getId());
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
setTag(data);
}
/**
*
* @param context
* @param noteId ID
*/
private void showTags(Context context, long noteId) {
// 清空现有标签
mTagsLayout.removeAllViews();
// 隐藏标签显示区域,避免空布局占用空间
mTagsLayout.setVisibility(View.GONE);
// TODO: 从数据库中获取实际的标签数据
// 暂时不添加标签,避免每次绑定都创建新视图导致卡顿
}
public NoteItemData getItemData() {
return mItemData;
}
/**
*
*
* @param visible
*/
public void setPinnedIconVisibility(boolean visible) {
if (mPinnedIcon != null) {
mPinnedIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
}
}
/**
*
*
* @return ImageView
*/
public ImageView getPinnedIcon() {
return mPinnedIcon;
}
/**
*
*
* @param pinned
*/
public void updatePinnedStatus(boolean pinned) {
if (mPinnedIcon != null) {
if (pinned) {
mPinnedIcon.setVisibility(View.VISIBLE);
mPinnedIcon.setImageResource(R.drawable.ic_pinned);
} else {
mPinnedIcon.setVisibility(View.GONE);
}
}
}
}

@ -0,0 +1,426 @@
/*
* 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 android.preference.CheckBoxPreference;
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_PASSWORD_LOCK_ENABLED_KEY = "pref_key_password_lock_enabled";
private static final String PREFERENCE_PASSWORD_SET_KEY = "pref_key_password_set";
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);
/* using the app icon for navigation */
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.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, Context.RECEIVER_EXPORTED);
// 设置密码锁相关逻辑
setupPasswordLockPreferences();
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
private void setupPasswordLockPreferences() {
// 获取密码锁复选框和设置密码选项
final CheckBoxPreference passwordLockEnabledPref = (CheckBoxPreference) findPreference(PREFERENCE_PASSWORD_LOCK_ENABLED_KEY);
final Preference passwordSetPref = findPreference(PREFERENCE_PASSWORD_SET_KEY);
// 设置密码锁复选框的监听器
passwordLockEnabledPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean isEnabled = (Boolean) newValue;
passwordSetPref.setEnabled(isEnabled);
return true;
}
});
// 设置当前状态
passwordSetPref.setEnabled(passwordLockEnabledPref.isChecked());
// 设置密码选项的点击事件
passwordSetPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(NotesPreferenceActivity.this, PasswordSetActivity.class);
startActivity(intent);
return true;
}
});
}
@Override
protected void onResume() {
super.onResume();
// need to set sync account automatically if user has added a new
// account
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;
}
}
}
}
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)) {
// the first time to set account
showSelectAccountAlertDialog();
} else {
// if the account has already been set, we need to promp
// user about the risk
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);
// set button state
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)));
// set last sync time
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();
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();
}
});
}
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();
}
}
});
dialogBuilder.show();
}
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.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();
// clean up last sync time
setLastSyncTime(this, 0);
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_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 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();
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
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();
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;
}
}
}

@ -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.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.tool.PasswordUtils;
/**
*
*
*/
public class PasswordLockActivity extends Activity implements View.OnClickListener {
/**
*
*/
private EditText mPasswordEditText;
/**
*
*/
private Button mConfirmButton;
/**
*
*/
private TextView mErrorTextView;
/**
*
*/
private TextView mForgetTextView;
/**
*
*/
private int mErrorCount = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_password_lock);
// 初始化视图
initViews();
// 设置点击事件监听器
setListeners();
}
/**
*
*/
private void initViews() {
mPasswordEditText = findViewById(R.id.et_password);
mConfirmButton = findViewById(R.id.btn_confirm);
mErrorTextView = findViewById(R.id.tv_error);
mForgetTextView = findViewById(R.id.tv_forget);
}
/**
*
*/
private void setListeners() {
mConfirmButton.setOnClickListener(this);
mForgetTextView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_confirm) {
// 处理确认按钮点击事件
verifyPassword();
} else if (id == R.id.tv_forget) {
// 处理忘记密码点击事件
handleForgetPassword();
}
}
/**
*
*/
private void verifyPassword() {
try {
// 获取用户输入的密码
String inputPassword = mPasswordEditText.getText().toString().trim();
// 检查密码是否为空
if (TextUtils.isEmpty(inputPassword)) {
showError(getString(R.string.password_error_empty));
return;
}
// 检查密码是否正确
if (PasswordUtils.verifyPassword(this, inputPassword)) {
// 密码正确,进入应用
enterApplication();
} else {
// 密码错误,显示错误信息
mErrorCount++;
showError(getString(R.string.password_error_wrong));
// 清空密码输入框
mPasswordEditText.setText("");
// 如果错误次数达到3次显示忘记密码提示
if (mErrorCount >= 3 && mForgetTextView != null) {
mForgetTextView.setVisibility(View.VISIBLE);
}
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, R.string.password_error_verify, Toast.LENGTH_SHORT).show();
}
}
/**
*
*/
private void enterApplication() {
try {
// 创建返回Intent设置结果为成功
Intent resultIntent = new Intent();
setResult(RESULT_OK, resultIntent);
// 关闭当前Activity返回应用主界面
finish();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
*/
private void handleForgetPassword() {
try {
// 这里可以实现忘记密码的逻辑,比如跳转到密码重置界面
// 或者直接清除密码锁
PasswordUtils.clearPasswordLock(this);
Toast.makeText(this, R.string.password_success_cancel, Toast.LENGTH_SHORT).show();
// 进入应用
enterApplication();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, R.string.password_error_cancel, Toast.LENGTH_SHORT).show();
}
}
/**
*
* @param errorMsg
*/
private void showError(String errorMsg) {
try {
if (mErrorTextView != null) {
mErrorTextView.setText(errorMsg);
mErrorTextView.setVisibility(View.VISIBLE);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
* @param context
* @return
*/
public static boolean isLockEnabled(android.content.Context context) {
return PasswordUtils.isLockEnabled(context);
}
/**
*
* @param context
* @return
*/
public static String getPassword(android.content.Context context) {
return PasswordUtils.getPassword(context);
}
/**
*
* @param context
* @param password
*/
public static void setPassword(android.content.Context context, String password) {
PasswordUtils.setPassword(context, password);
}
/**
*
* @param context
* @param enabled
*/
public static void setLockEnabled(android.content.Context context, boolean enabled) {
PasswordUtils.setLockEnabled(context, enabled);
}
}

@ -0,0 +1,315 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.tool.PasswordUtils;
/**
*
*
*/
public class PasswordSetActivity extends Activity implements View.OnClickListener, TextWatcher {
/**
*
*/
private TextView mTitleTextView;
/**
*
*/
private LinearLayout mOldPasswordLayout;
/**
*
*/
private EditText mOldPasswordEditText;
/**
*
*/
private EditText mNewPasswordEditText;
/**
*
*/
private EditText mConfirmPasswordEditText;
/**
*
*/
private TextView mStrengthTextView;
/**
*
*/
private TextView mErrorTextView;
/**
*
*/
private Button mCancelButton;
/**
*
*/
private Button mSaveButton;
/**
*
*/
private boolean mHasPassword = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_password_set);
// 初始化视图
initViews();
// 检查是否已有密码
checkIfHasPassword();
// 设置点击事件监听器
setListeners();
// 设置文本变化监听器
setTextWatchers();
}
/**
*
*/
private void initViews() {
mTitleTextView = findViewById(R.id.tv_title);
mOldPasswordLayout = findViewById(R.id.ll_old_password);
mOldPasswordEditText = findViewById(R.id.et_old_password);
mNewPasswordEditText = findViewById(R.id.et_new_password);
mConfirmPasswordEditText = findViewById(R.id.et_confirm_password);
mStrengthTextView = findViewById(R.id.tv_strength);
mErrorTextView = findViewById(R.id.tv_error);
mCancelButton = findViewById(R.id.btn_cancel);
mSaveButton = findViewById(R.id.btn_save);
}
/**
*
*/
private void checkIfHasPassword() {
mHasPassword = PasswordUtils.hasPassword(this);
// 如果已有密码,显示当前密码输入区域
if (mHasPassword) {
mOldPasswordLayout.setVisibility(View.VISIBLE);
mTitleTextView.setText(R.string.password_change_title);
} else {
mOldPasswordLayout.setVisibility(View.GONE);
mTitleTextView.setText(R.string.password_set_title);
}
}
/**
*
*/
private void setListeners() {
mCancelButton.setOnClickListener(this);
mSaveButton.setOnClickListener(this);
}
/**
*
*/
private void setTextWatchers() {
mNewPasswordEditText.addTextChangedListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_cancel) {
// 处理取消按钮点击事件
finish();
} else if (id == R.id.btn_save) {
// 处理保存按钮点击事件
savePassword();
}
}
/**
*
*/
private void savePassword() {
// 重置错误提示
hideError();
// 检查当前密码(如果已有密码)
if (mHasPassword) {
String oldPassword = mOldPasswordEditText.getText().toString().trim();
if (TextUtils.isEmpty(oldPassword)) {
showError(getString(R.string.password_error_empty));
return;
}
if (!PasswordUtils.verifyPassword(this, oldPassword)) {
showError(getString(R.string.password_error_old_wrong));
return;
}
}
// 检查新密码
String newPassword = mNewPasswordEditText.getText().toString().trim();
if (TextUtils.isEmpty(newPassword)) {
showError(getString(R.string.password_error_empty));
return;
}
if (newPassword.length() < 4 || newPassword.length() > 16) {
showError(getString(R.string.password_error_length));
return;
}
// 检查确认密码
String confirmPassword = mConfirmPasswordEditText.getText().toString().trim();
if (TextUtils.isEmpty(confirmPassword)) {
showError(getString(R.string.password_error_empty));
return;
}
if (!newPassword.equals(confirmPassword)) {
showError(getString(R.string.password_error_match));
return;
}
// 保存密码
savePasswordToPrefs(newPassword);
// 显示保存成功提示
String successMessage = mHasPassword ? getString(R.string.password_success_change) : getString(R.string.password_success_set);
Toast.makeText(this, successMessage, Toast.LENGTH_SHORT).show();
// 关闭当前Activity
finish();
}
/**
* SharedPreferences
* @param password
*/
private void savePasswordToPrefs(String password) {
try {
PasswordUtils.savePasswordAndEnableLock(this, password);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, R.string.password_error_save, Toast.LENGTH_SHORT).show();
}
}
/**
*
* @param errorMsg
*/
private void showError(String errorMsg) {
try {
if (mErrorTextView != null) {
mErrorTextView.setText(errorMsg);
mErrorTextView.setVisibility(View.VISIBLE);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
*/
private void hideError() {
try {
if (mErrorTextView != null) {
mErrorTextView.setVisibility(View.GONE);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 文本变化前的处理
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 文本变化中的处理
updatePasswordStrength(s.toString());
}
@Override
public void afterTextChanged(Editable s) {
// 文本变化后的处理
}
/**
*
* @param password
*/
private void updatePasswordStrength(String password) {
if (TextUtils.isEmpty(password)) {
mStrengthTextView.setVisibility(View.GONE);
return;
}
String strength = getPasswordStrength(password);
mStrengthTextView.setText(strength);
mStrengthTextView.setVisibility(View.VISIBLE);
}
/**
*
* @param password
* @return
*/
private String getPasswordStrength(String password) {
int length = password.length();
if (length < 4) {
return getString(R.string.password_strength_weak);
} else if (length < 8) {
return getString(R.string.password_strength_medium);
} else {
return getString(R.string.password_strength_strong);
}
}
}

@ -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.app.Activity;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.content.ContentUris;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.util.SparseBooleanArray;
import java.util.ArrayList;
import java.util.HashSet;
import java.lang.ref.WeakReference;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.DataUtils;
import java.util.HashSet;
public class TrashListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
private static final int TRASH_NOTE_LIST_QUERY_TOKEN = 0;
private static final String TAG = "TrashListActivity";
private BackgroundQueryHandler mBackgroundQueryHandler;
private NotesListAdapter mNotesListAdapter;
private ListView mNotesListView;
private ContentResolver mContentResolver;
private ModeCallback mModeCallBack;
private TextView mTitleBar;
private TextView mEmptyView;
private Button mBtnRestoreAll;
private Button mBtnDeleteAll;
private static final String TRASH_SELECTION = NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLDER;
private static final String SORT_ORDER = NoteColumns.MODIFIED_DATE + " DESC";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.trash_list);
initResources();
}
@Override
protected void onStart() {
super.onStart();
startAsyncTrashListQuery();
}
private void initResources() {
mContentResolver = this.getContentResolver();
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver(), this);
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
mTitleBar.setText(R.string.trash_title);
mNotesListView = (ListView) findViewById(R.id.trash_list);
mEmptyView = (TextView) findViewById(R.id.trash_empty);
mNotesListView.setEmptyView(mEmptyView);
mNotesListAdapter = new NotesListAdapter(this);
mNotesListView.setAdapter(mNotesListAdapter);
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
mNotesListView.setOnItemLongClickListener(this);
// 设置多选模式监听器和初始模式
mModeCallBack = new ModeCallback();
mNotesListView.setMultiChoiceModeListener(mModeCallBack);
mNotesListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
// 设置底部按钮
mBtnRestoreAll = (Button) findViewById(R.id.btn_restore_all);
mBtnDeleteAll = (Button) findViewById(R.id.btn_delete_all);
mBtnRestoreAll.setOnClickListener(this);
mBtnDeleteAll.setOnClickListener(this);
Button btnBack = (Button) findViewById(R.id.btn_back);
btnBack.setOnClickListener(this);
}
private void startAsyncTrashListQuery() {
mBackgroundQueryHandler.startQuery(TRASH_NOTE_LIST_QUERY_TOKEN, null, Notes.CONTENT_NOTE_URI,
NoteItemData.PROJECTION, TRASH_SELECTION, null, SORT_ORDER);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_back) {
finish();
} else if (v.getId() == R.id.btn_restore_all) {
showConfirmDialog(R.string.restore_all_confirm, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
restoreAllNotes();
}
});
} else if (v.getId() == R.id.btn_delete_all) {
showConfirmDialog(R.string.delete_all_confirm, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
deleteAllNotes();
}
});
}
}
private void showConfirmDialog(int messageId, DialogInterface.OnClickListener listener) {
new android.app.AlertDialog.Builder(this)
.setMessage(messageId)
.setPositiveButton("确定", listener)
.setNegativeButton("取消", null)
.show();
}
private void restoreAllNotes() {
try {
// 获取所有回收站中的笔记
Cursor cursor = mContentResolver.query(Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, TRASH_SELECTION, null, null);
int count = 0;
if (cursor != null) {
while (cursor.moveToNext()) {
long noteId = cursor.getLong(0);
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 直接更新不使用withSelection条件
int updated = mContentResolver.update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
values,
null, // 不使用额外条件只通过URI指定笔记ID
null);
if (updated > 0) {
count++;
}
}
cursor.close();
}
Toast.makeText(this, getString(R.string.restore_success, count), Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e(TAG, "Restore all failed: " + e.getMessage());
Toast.makeText(this, getString(R.string.restore_success, 0), Toast.LENGTH_SHORT).show();
}
startAsyncTrashListQuery();
}
private void deleteAllNotes() {
try {
// 获取所有回收站中的笔记
Cursor cursor = mContentResolver.query(Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, TRASH_SELECTION, null, null);
HashSet<Long> allIds = new HashSet<>();
if (cursor != null) {
while (cursor.moveToNext()) {
long noteId = cursor.getLong(0);
allIds.add(noteId);
}
cursor.close();
}
DataUtils.batchPermanentDeleteNotes(mContentResolver, allIds);
Toast.makeText(this, getString(R.string.delete_success, allIds.size()), Toast.LENGTH_SHORT).show();
startAsyncTrashListQuery();
} catch (Exception e) {
Log.e(TAG, "Delete all failed: " + e.getMessage());
Toast.makeText(this, getString(R.string.delete_success, 0), Toast.LENGTH_SHORT).show();
startAsyncTrashListQuery();
}
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
// 长按事件由系统自动处理因为ListView已经设置了CHOICE_MODE_MULTIPLE_MODAL
// 返回false让系统继续处理事件
return false;
}
private class OnListItemClickListener implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mNotesListView.isItemChecked(position)) {
mNotesListAdapter.setCheckedItem(position, true);
} else {
mNotesListAdapter.setCheckedItem(position, false);
}
}
}
private class ModeCallback implements ListView.MultiChoiceModeListener, MenuItem.OnMenuItemClickListener {
private ActionMode mActionMode;
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.trash_note_options, menu);
menu.findItem(R.id.restore).setOnMenuItemClickListener(this);
menu.findItem(R.id.permanent_delete).setOnMenuItemClickListener(this);
mActionMode = mode;
mNotesListAdapter.setChoiceMode(true);
// 隐藏底部按钮
mBtnRestoreAll.setVisibility(View.GONE);
mBtnDeleteAll.setVisibility(View.GONE);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (item.getItemId() == R.id.restore) {
restoreSelectedNotes();
mode.finish();
return true;
} else if (item.getItemId() == R.id.permanent_delete) {
deleteSelectedNotes();
mode.finish();
return true;
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
mNotesListAdapter.setChoiceMode(false);
// 显示底部按钮
mBtnRestoreAll.setVisibility(View.VISIBLE);
mBtnDeleteAll.setVisibility(View.VISIBLE);
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
int checkedCount = mNotesListView.getCheckedItemCount();
mode.setTitle(getString(R.string.selected_count, checkedCount));
// 更新adapter中的选择状态
mNotesListAdapter.setCheckedItem(position, checked);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
return onActionItemClicked(mActionMode, item);
}
}
private void restoreSelectedNotes() {
HashSet<Long> selectedIds = getSelectedNoteIds();
if (selectedIds.isEmpty()) {
Toast.makeText(this, R.string.no_note_selected, Toast.LENGTH_SHORT).show();
return;
}
int count = 0;
try {
for (Long noteId : selectedIds) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 直接更新不使用withSelection条件
int updated = mContentResolver.update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
values,
null, // 不使用额外条件只通过URI指定笔记ID
null);
if (updated > 0) {
count++;
}
}
Toast.makeText(this, getString(R.string.restore_success, count), Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e(TAG, "Restore selected failed: " + e.getMessage());
Toast.makeText(this, getString(R.string.restore_success, count), Toast.LENGTH_SHORT).show();
}
startAsyncTrashListQuery();
}
private void deleteSelectedNotes() {
HashSet<Long> selectedIds = getSelectedNoteIds();
if (selectedIds.isEmpty()) {
Toast.makeText(this, R.string.no_note_selected, Toast.LENGTH_SHORT).show();
return;
}
DataUtils.batchPermanentDeleteNotes(mContentResolver, selectedIds);
Toast.makeText(this, getString(R.string.delete_success, selectedIds.size()), Toast.LENGTH_SHORT).show();
startAsyncTrashListQuery();
}
private HashSet<Long> getSelectedNoteIds() {
HashSet<Long> selectedIds = new HashSet<Long>();
Cursor cursor = mNotesListAdapter.getCursor();
if (cursor != null) {
// 遍历ListView中的所有项检查是否被选中
for (int i = 0; i < mNotesListAdapter.getCount(); i++) {
if (mNotesListView.isItemChecked(i)) {
// 移动游标到指定位置
if (cursor.moveToPosition(i)) {
// 从Cursor中获取笔记ID使用NoteColumns.ID对应的列索引
// 由于我们使用NoteItemData.PROJECTION进行查询所以ID列的索引是0
long noteId = cursor.getLong(0);
selectedIds.add(noteId);
}
}
}
}
return selectedIds;
}
private static final class BackgroundQueryHandler extends AsyncQueryHandler {
private final WeakReference<TrashListActivity> mActivity;
public BackgroundQueryHandler(ContentResolver cr, TrashListActivity activity) {
super(cr);
mActivity = new WeakReference<>(activity);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
TrashListActivity activity = mActivity.get();
if (activity == null) {
return;
}
switch (token) {
case TRASH_NOTE_LIST_QUERY_TOKEN:
activity.mNotesListAdapter.changeCursor(cursor);
break;
default:
Log.w(TAG, "Unknown query token: " + token);
}
}
}
}

@ -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.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
import android.widget.RemoteViews;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
public abstract class NoteWidgetProvider extends AppWidgetProvider {
public static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
};
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2;
private static final String TAG = "NoteWidgetProvider";
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
for (int i = 0; i < appWidgetIds.length; i++) {
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
}
}
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLDER) },
null);
}
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
int bgId = ResourceParser.getDefaultBgId(context);
String snippet = "";
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) {
if (c.getCount() > 1) {
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close();
return;
}
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
intent.setAction(Intent.ACTION_VIEW);
} else {
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
if (c != null) {
c.close();
}
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null;
if (privacyMode) {
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
protected abstract int getBgResourceId(int bgId);
protected abstract int getLayoutId();
protected abstract int getWidgetType();
}

@ -0,0 +1,47 @@
/*
* 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.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
@Override
protected int getLayoutId() {
return R.layout.widget_2x;
}
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_2X;
}
}

@ -0,0 +1,46 @@
/*
* 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.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
protected int getLayoutId() {
return R.layout.widget_4x;
}
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_4X;
}
}
Loading…
Cancel
Save