田绍君 2 months ago
parent 188db56c47
commit d47444dc8b

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MiNotes"
tools:targetApi="31">
<activity
android:name=".ui.NotesListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustPan"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:exported="true">
<intent-filter >
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter >>
<intent-filter >
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<provider
android:name="net.micode.notes.data.NotesProvider"
android:authorities="micode_notes"
android:multiprocess="true" />
<receiver
android:name=".widget.NoteWidgetProvider_2x"
android:label="@string/app_widget2x2"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver>
<receiver
android:name=".widget.NoteWidgetProvider_4x"
android:label="@string/app_widget4x4"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_4x_info" />
</receiver>
<receiver android:name=".ui.AlarmInitReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" >
</receiver>
<activity
android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
</activity>
<activity
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
</activity>
<service
android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" >
</service>
<meta-data
android:name="android.app.default_searchable"
android:value=".ui.NoteEditActivity" />
<!-- <activity-->
<!-- android:name=".MainActivity"-->
<!-- android:exported="true">-->
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->
<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
<!-- </activity>-->
</application>
</manifest>

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

@ -165,6 +165,12 @@ public class Notes {
* <P> Type : INTEGER (long) </P> * <P> Type : INTEGER (long) </P>
*/ */
public static final String VERSION = "version"; public static final String VERSION = "version";
/**
* Sign to indicate whether the note is pinned
* <P> Type: INTEGER </P>
*/
public static final String IS_PINNED = "is_pinned";
} }
public interface DataColumns { public interface DataColumns {

@ -30,7 +30,7 @@ import net.micode.notes.data.Notes.NoteColumns;
public class NotesDatabaseHelper extends SQLiteOpenHelper { public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db"; private static final String DB_NAME = "note.db";
private static final int DB_VERSION = 4; private static final int DB_VERSION = 5;
public interface TABLE { public interface TABLE {
public static final String NOTE = "note"; public static final String NOTE = "note";
@ -60,7 +60,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.IS_PINNED + " INTEGER NOT NULL DEFAULT 0" +
")"; ")";
private static final String CREATE_DATA_TABLE_SQL = private static final String CREATE_DATA_TABLE_SQL =
@ -302,34 +303,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; if (oldVersion < 2) {
boolean skipV2 = false;
if (oldVersion == 1) {
upgradeToV2(db); upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++;
} }
if (oldVersion < 3) {
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db); upgradeToV3(db);
reCreateTriggers = true;
oldVersion++;
} }
if (oldVersion < 4) {
if (oldVersion == 3) {
upgradeToV4(db); upgradeToV4(db);
oldVersion++;
}
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
} }
if (oldVersion < 5) {
if (oldVersion != newVersion) { upgradeToV5(db);
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
} }
} }
@ -359,4 +343,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0"); + " INTEGER NOT NULL DEFAULT 0");
} }
private void upgradeToV5(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.IS_PINNED + " INTEGER NOT NULL DEFAULT 0");
}
} }

@ -63,24 +63,43 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
}); });
} }
private void showNotification(int tickerId, String content) { // private void showNotification(int tickerId, String content) {
Notification notification = new Notification(R.drawable.notification, mContext // Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis()); // .getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS; // notification.defaults = Notification.DEFAULT_LIGHTS;
notification.flags = Notification.FLAG_AUTO_CANCEL; // notification.flags = Notification.FLAG_AUTO_CANCEL;
PendingIntent pendingIntent; // PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) { // if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, // pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), 0); // NotesPreferenceActivity.class), 0);
} else { // } else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, // pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0); // NotesListActivity.class), 0);
} // }
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, // notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
pendingIntent); // pendingIntent);
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); // mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
// }
private void showNotification(int tickerId, String content) {
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);
} }
Notification.Builder builder = new Notification.Builder(mContext)
.setAutoCancel(true)
.setContentTitle(mContext.getString(R.string.app_name))
.setContentText(content)
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis())
.setOngoing(true);
Notification notification=builder.getNotification();
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
@Override @Override
protected Integer doInBackground(Void... unused) { protected Integer doInBackground(Void... unused) {

@ -30,9 +30,11 @@ import android.content.SharedPreferences;
import android.graphics.Paint; import android.graphics.Paint;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan; import android.text.style.BackgroundColorSpan;
import android.util.Log; import android.util.Log;
@ -149,6 +151,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private String mUserQuery; private String mUserQuery;
private Pattern mPattern; private Pattern mPattern;
private TextView mWordCountView;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -159,6 +163,28 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return; return;
} }
initResources(); initResources();
mWordCountView = (TextView) findViewById(R.id.tv_word_count);
// Add text change listener directly to the note editor
mNoteEditor.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mWordCountView != null) {
String text = s.toString();
int wordCount = text.replaceAll("\\s+", "").length();
mWordCountView.setText(wordCount + "字");
Log.d(TAG, "Text changed, new count: " + wordCount);
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
} }
/** /**
@ -870,4 +896,20 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private void showToast(int resId, int duration) { private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show(); Toast.makeText(this, resId, duration).show();
} }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mWordCountView != null) {
String text = s.toString();
// Count all non-whitespace characters
int wordCount = text.replaceAll("\\s+", "").length();
mWordCountView.setText(wordCount + "字");
Log.d(TAG, "Text changed, new count: " + wordCount);
}
}
@Override
public void onSelectionChanged(int selStart, int selEnd) {
// Not used for word count
}
} }

@ -22,6 +22,7 @@ import android.text.Layout;
import android.text.Selection; import android.text.Selection;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.URLSpan; import android.text.style.URLSpan;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
@ -73,6 +74,10 @@ public class NoteEditText extends EditText {
* Hide or show item option when text change * Hide or show item option when text change
*/ */
void onTextChange(int index, boolean hasText); void onTextChange(int index, boolean hasText);
void onTextChanged(CharSequence s, int start, int before, int count);
void onSelectionChanged(int selStart, int selEnd);
} }
private OnTextViewChangeListener mOnTextViewChangeListener; private OnTextViewChangeListener mOnTextViewChangeListener;
@ -96,7 +101,26 @@ public class NoteEditText extends EditText {
public NoteEditText(Context context, AttributeSet attrs, int defStyle) { public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
// TODO Auto-generated constructor stub // Add TextWatcher to track text changes
addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mOnTextViewChangeListener != null) {
mOnTextViewChangeListener.onTextChanged(s, start, before, count);
}
}
@Override
public void afterTextChanged(android.text.Editable s) {
if (mOnTextViewChangeListener != null) {
mOnTextViewChangeListener.onTextChanged(s, 0, 0, s.length());
}
}
});
} }
@Override @Override
@ -214,4 +238,20 @@ public class NoteEditText extends EditText {
} }
super.onCreateContextMenu(menu); super.onCreateContextMenu(menu);
} }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
super.onTextChanged(s, start, before, count);
if (mOnTextViewChangeListener != null) {
mOnTextViewChangeListener.onTextChanged(s, start, before, count);
}
}
@Override
public void onSelectionChanged(int selStart, int selEnd) {
super.onSelectionChanged(selStart, selEnd);
if (mOnTextViewChangeListener != null) {
mOnTextViewChangeListener.onSelectionChanged(selStart, selEnd);
}
}
} }

@ -40,6 +40,7 @@ public class NoteItemData {
NoteColumns.TYPE, NoteColumns.TYPE,
NoteColumns.WIDGET_ID, NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE, NoteColumns.WIDGET_TYPE,
NoteColumns.IS_PINNED,
}; };
private static final int ID_COLUMN = 0; private static final int ID_COLUMN = 0;
@ -54,7 +55,9 @@ public class NoteItemData {
private static final int TYPE_COLUMN = 9; private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10; private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11; private static final int WIDGET_TYPE_COLUMN = 11;
private static final int IS_PINNED_COLUMN = 12;
private Context mContext;
private long mId; private long mId;
private long mAlertDate; private long mAlertDate;
private int mBgColorId; private int mBgColorId;
@ -69,6 +72,7 @@ public class NoteItemData {
private int mWidgetType; private int mWidgetType;
private String mName; private String mName;
private String mPhoneNumber; private String mPhoneNumber;
private boolean mIsPinned;
private boolean mIsLastItem; private boolean mIsLastItem;
private boolean mIsFirstItem; private boolean mIsFirstItem;
@ -77,6 +81,11 @@ public class NoteItemData {
private boolean mIsMultiNotesFollowingFolder; private boolean mIsMultiNotesFollowingFolder;
public NoteItemData(Context context, Cursor cursor) { public NoteItemData(Context context, Cursor cursor) {
mContext = context;
loadFromCursor(cursor);
}
private void loadFromCursor(Cursor cursor) {
mId = cursor.getLong(ID_COLUMN); mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
@ -91,12 +100,13 @@ public class NoteItemData {
mType = cursor.getInt(TYPE_COLUMN); mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
mIsPinned = cursor.getInt(IS_PINNED_COLUMN) == 1;
mPhoneNumber = ""; mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); mPhoneNumber = DataUtils.getCallNumberByNoteId(mContext.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) { if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber); mName = Contact.getContact(mContext, mPhoneNumber);
if (mName == null) { if (mName == null) {
mName = mPhoneNumber; mName = mPhoneNumber;
} }
@ -221,4 +231,12 @@ public class NoteItemData {
public static int getNoteType(Cursor cursor) { public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN); return cursor.getInt(TYPE_COLUMN);
} }
public boolean isPinned() {
return mIsPinned;
}
public void setPinned(boolean pinned) {
mIsPinned = pinned;
}
} }

@ -28,6 +28,7 @@ import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@ -59,6 +60,9 @@ import android.widget.ListView;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.content.ContentUris;
import android.widget.CursorAdapter;
import android.view.ViewGroup;
import net.micode.notes.R; import net.micode.notes.R;
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
@ -239,14 +243,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.note_list_options, menu); getMenuInflater().inflate(R.menu.note_list_options, menu);
menu.findItem(R.id.delete).setOnMenuItemClickListener(this); menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
mMoveMenu = menu.findItem(R.id.move); menu.findItem(R.id.move).setOnMenuItemClickListener(this);
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
mMoveMenu.setOnMenuItemClickListener(this);
}
mActionMode = mode; mActionMode = mode;
mNotesListAdapter.setChoiceMode(true); mNotesListAdapter.setChoiceMode(true);
mNotesListView.setLongClickable(false); mNotesListView.setLongClickable(false);
@ -264,7 +261,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
updateMenu(); updateMenu();
return true; return true;
} }
}); });
return true; return true;
} }
@ -287,12 +283,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
public boolean onPrepareActionMode(ActionMode mode, Menu menu) { public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false; return false;
} }
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false; return false;
} }
@ -411,10 +405,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private void startAsyncNotesListQuery() { private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION; : NORMAL_SELECTION;
String sortOrder = Notes.NoteColumns.IS_PINNED + " DESC, " +
Notes.NoteColumns.MODIFIED_DATE + " DESC";
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId) String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); }, sortOrder);
} }
private final class BackgroundQueryHandler extends AsyncQueryHandler { private final class BackgroundQueryHandler extends AsyncQueryHandler {
@ -446,17 +442,31 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
builder.setTitle(R.string.menu_title_select_folder); builder.setTitle(R.string.menu_title_select_folder);
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
builder.setAdapter(adapter, new DialogInterface.OnClickListener() { builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
DataUtils.batchMoveToFolder(mContentResolver, long folderId = adapter.getItemId(which);
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); if (mFocusNoteDataItem != null) {
Toast.makeText( // 单个便签移动
NotesListActivity.this, HashSet<Long> ids = new HashSet<Long>();
getString(R.string.format_move_notes_to_folder, ids.add(mFocusNoteDataItem.getId());
mNotesListAdapter.getSelectedCount(), DataUtils.batchMoveToFolder(mContentResolver, ids, folderId);
adapter.getFolderName(NotesListActivity.this, which)), Toast.makeText(
Toast.LENGTH_SHORT).show(); NotesListActivity.this,
mModeCallBack.finishActionMode(); getString(R.string.format_move_notes_to_folder,
1,
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
} else {
// 批量移动
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), folderId);
Toast.makeText(
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
mModeCallBack.finishActionMode();
}
} }
}); });
builder.show(); builder.show();
@ -877,10 +887,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
private class OnListItemClickListener implements OnItemClickListener { private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) { if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData(); NoteItemData item = ((NotesListItem) view).getItemData();
if (mNotesListAdapter.isInChoiceMode()) { if (mNotesListAdapter.isInChoiceMode()) {
if (item.getType() == Notes.TYPE_NOTE) { if (item.getType() == Notes.TYPE_NOTE) {
position = position - mNotesListView.getHeaderViewsCount(); position = position - mNotesListView.getHeaderViewsCount();
@ -914,7 +924,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
} }
} }
} }
private void startQueryDestinationFolders() { private void startQueryDestinationFolders() {
@ -938,17 +947,203 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) { if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData(); mFocusNoteDataItem = ((NotesListItem) view).getItemData();
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE) {
if (mNotesListView.startActionMode(mModeCallBack) != null) { if (!mNotesListAdapter.isInChoiceMode()) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true); // 显示便签的上下文菜单
showNoteContextMenu(mFocusNoteDataItem);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else { } else {
Log.e(TAG, "startActionMode fails"); if (mNotesListView.startActionMode(mModeCallBack) != null) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
Log.e(TAG, "startActionMode fails");
}
} }
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
} }
} }
return false; return true; // 返回 true 表示消费了长按事件,这样就不会触发单击事件
}
private void showNoteContextMenu(NoteItemData noteData) {
// 使用 ListView 作为锚点视图
PopupMenu popup = new PopupMenu(this, mNotesListView);
popup.getMenuInflater().inflate(R.menu.note_list_context_menu, popup.getMenu());
// 根据便签的置顶状态设置菜单项文本
MenuItem pinItem = popup.getMenu().findItem(R.id.menu_pin);
if (pinItem != null) {
pinItem.setTitle(noteData.isPinned() ? R.string.menu_unpin : R.string.menu_pin);
}
popup.setOnMenuItemClickListener(item -> {
int itemId = item.getItemId();
if (itemId == R.id.menu_pin) {
togglePinNote(noteData);
return true;
} else if (itemId == R.id.menu_share) {
shareNote(noteData);
return true;
} else if (itemId == R.id.menu_delete) {
deleteNote(noteData);
return true;
} else if (itemId == R.id.menu_move_to_folder) {
moveNoteToFolder(noteData);
return true;
}
return false;
});
popup.show();
}
private void togglePinNote(NoteItemData noteData) {
ContentValues values = new ContentValues();
values.put(Notes.NoteColumns.IS_PINNED, !noteData.isPinned() ? 1 : 0);
values.put(Notes.NoteColumns.LOCAL_MODIFIED, 1);
values.put(Notes.NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteData.getId()),
values,
null,
null
);
// 刷新列表视图
startAsyncNotesListQuery();
}
private void shareNote(NoteItemData noteData) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, noteData.getSnippet());
startActivity(Intent.createChooser(intent, getString(R.string.menu_share)));
}
private void deleteNote(NoteItemData noteData) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
HashSet<Long> ids = new HashSet<Long>();
ids.add(noteData.getId());
if (!isSyncMode()) {
// if not synced, delete note directly
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// in sync mode, we'll move the deleted note into the trash folder
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
private void moveNoteToFolder(NoteItemData noteData) {
// 查询所有可用的文件夹
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
selection = (mState == ListEditState.NOTE_LIST) ? selection:
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
FoldersListAdapter.PROJECTION,
selection,
new String[] {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
private class NoteListAdapter extends CursorAdapter {
private static final String TAG = "NoteListAdapter";
private final String[] PROJECTION = {
NoteColumns.ID,
NoteColumns.TYPE,
NoteColumns.SNIPPET,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.BG_COLOR_ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.ALERTED_DATE,
NoteColumns.PARENT_ID,
NoteColumns.HAS_ATTACHMENT,
};
private int mIdColumnIndex;
private int mTypeColumnIndex;
private int mSnippetColumnIndex;
private int mWidgetIdColumnIndex;
private int mWidgetTypeColumnIndex;
private int mBgColorColumnIndex;
private int mModifiedDateColumnIndex;
private int mAlertedDateColumnIndex;
private int mParentIdColumnIndex;
private int mHasAttachmentColumnIndex;
private LayoutInflater mInflater;
public NoteListAdapter(Context context) {
super(context, null);
mInflater = LayoutInflater.from(context);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.notes_list_item, parent, false);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view == null) {
return;
}
TextView tvTitle = (TextView) view.findViewById(R.id.tv_title);
TextView tvWordCount = (TextView) view.findViewById(R.id.tv_word_count);
String snippet = cursor.getString(mSnippetColumnIndex);
tvTitle.setText(snippet);
// Calculate word count
int wordCount = snippet.replaceAll("\\s+", "").length();
tvWordCount.setText(wordCount + "字");
tvWordCount.setVisibility(View.VISIBLE); // 确保字数显示可见
// Set background color
int bgColorId = cursor.getInt(mBgColorColumnIndex);
if (bgColorId == ResourceParser.BG_DEFAULT_COLOR) {
view.setBackgroundColor(0xFFFFFF); // 白色背景
} else {
view.setBackgroundColor(bgColorId);
}
}
@Override
public void changeCursor(Cursor cursor) {
if (cursor != null) {
mIdColumnIndex = cursor.getColumnIndex(NoteColumns.ID);
mTypeColumnIndex = cursor.getColumnIndex(NoteColumns.TYPE);
mSnippetColumnIndex = cursor.getColumnIndex(NoteColumns.SNIPPET);
mWidgetIdColumnIndex = cursor.getColumnIndex(NoteColumns.WIDGET_ID);
mWidgetTypeColumnIndex = cursor.getColumnIndex(NoteColumns.WIDGET_TYPE);
mBgColorColumnIndex = cursor.getColumnIndex(NoteColumns.BG_COLOR_ID);
mModifiedDateColumnIndex = cursor.getColumnIndex(NoteColumns.MODIFIED_DATE);
mAlertedDateColumnIndex = cursor.getColumnIndex(NoteColumns.ALERTED_DATE);
mParentIdColumnIndex = cursor.getColumnIndex(NoteColumns.PARENT_ID);
mHasAttachmentColumnIndex = cursor.getColumnIndex(NoteColumns.HAS_ATTACHMENT);
}
super.changeCursor(cursor);
}
} }
} }

@ -64,6 +64,24 @@ public class NotesListAdapter extends CursorAdapter {
} }
} }
@Override
public Cursor getCursor() {
Cursor cursor = super.getCursor();
if (cursor != null) {
// 对便签进行排序:置顶的在前,然后按修改时间倒序
String sortOrder = Notes.NoteColumns.IS_PINNED + " DESC, " +
Notes.NoteColumns.MODIFIED_DATE + " DESC";
cursor = mContext.getContentResolver().query(
cursor.getNotificationUri(),
NoteItemData.PROJECTION, // 使用 NoteItemData 中定义的投影
null, // 不使用选择条件
null, // 不使用选择参数
sortOrder
);
}
return cursor;
}
public void setCheckedItem(final int position, final boolean checked) { public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked); mSelectedIndex.put(position, checked);
notifyDataSetChanged(); notifyDataSetChanged();

@ -32,9 +32,11 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout { public class NotesListItem extends LinearLayout {
private ImageView mAlert; private ImageView mAlert;
private ImageView mPinned;
private TextView mTitle; private TextView mTitle;
private TextView mTime; private TextView mTime;
private TextView mCallName; private TextView mCallName;
private TextView mWordCount;
private NoteItemData mItemData; private NoteItemData mItemData;
private CheckBox mCheckBox; private CheckBox mCheckBox;
@ -42,9 +44,11 @@ public class NotesListItem extends LinearLayout {
super(context); super(context);
inflate(context, R.layout.note_item, this); inflate(context, R.layout.note_item, this);
mAlert = (ImageView) findViewById(R.id.iv_alert_icon); mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mPinned = (ImageView) findViewById(R.id.iv_pinned_icon);
mTitle = (TextView) findViewById(R.id.tv_title); mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time); mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name); mCallName = (TextView) findViewById(R.id.tv_name);
mWordCount = (TextView) findViewById(R.id.tv_word_count);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
} }
@ -60,6 +64,8 @@ public class NotesListItem extends LinearLayout {
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE); mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE); mAlert.setVisibility(View.VISIBLE);
mPinned.setVisibility(View.GONE);
mWordCount.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
mTitle.setText(context.getString(R.string.call_record_folder_name) mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount())); + context.getString(R.string.format_folder_files_count, data.getNotesCount()));
@ -75,6 +81,8 @@ public class NotesListItem extends LinearLayout {
} else { } else {
mAlert.setVisibility(View.GONE); mAlert.setVisibility(View.GONE);
} }
mPinned.setVisibility(View.GONE);
mWordCount.setVisibility(View.GONE);
} else { } else {
mCallName.setVisibility(View.GONE); mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
@ -84,6 +92,8 @@ public class NotesListItem extends LinearLayout {
+ context.getString(R.string.format_folder_files_count, + context.getString(R.string.format_folder_files_count,
data.getNotesCount())); data.getNotesCount()));
mAlert.setVisibility(View.GONE); mAlert.setVisibility(View.GONE);
mPinned.setVisibility(View.GONE);
mWordCount.setVisibility(View.GONE);
} else { } else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) { if (data.hasAlert()) {
@ -92,6 +102,15 @@ public class NotesListItem extends LinearLayout {
} else { } else {
mAlert.setVisibility(View.GONE); mAlert.setVisibility(View.GONE);
} }
if (data.isPinned()) {
mPinned.setImageResource(R.drawable.ic_pinned);
mPinned.setVisibility(View.VISIBLE);
} else {
mPinned.setVisibility(View.GONE);
}
mWordCount.setVisibility(View.VISIBLE);
int wordCount = data.getSnippet().replaceAll("\\s+", "").length();
mWordCount.setText(wordCount + "字");
} }
} }
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

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

Loading…
Cancel
Save