新增云同步功能,支持便签在不同设备间的自动同步 #28

Merged
pvexk5qol merged 1 commits from caoweiqiong_branch into master 4 weeks ago

@ -68,7 +68,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.IS_PRIVATE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.VIEW_MODE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CUSTOM_BG_URI + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.EXPAND_1 + " TEXT NOT NULL DEFAULT ''" +
NoteColumns.EXPAND_1 + " TEXT NOT NULL DEFAULT ''," +
// [新增字段 v9]
NoteColumns.SYNC_STATE + " INTEGER DEFAULT 1," +
NoteColumns.SERVER_ID + " TEXT," +

@ -54,7 +54,7 @@ public class NotesProvider extends ContentProvider {
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "note/*", URI_NOTE_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
@ -235,16 +235,32 @@ public class NotesProvider extends ContentProvider {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
String id = null; // <--- 注意:这是原有的定义,保持不动
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false;
switch (mMatcher.match(uri)) {
// [新增手术位置 1]:定义一个标志,用来识别是否为系统受限项
boolean skipSyncTag = false;
int match = mMatcher.match(uri);
switch (match) {
case URI_NOTE:
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
id = uri.getPathSegments().get(1); // <--- 原有的 id 赋值
// [新增手术位置 2]:利用解析出的 id 进行判断
try {
long numericId = Long.parseLong(id);
if (numericId <= 0) {
skipSyncTag = true; // ID 为 -3, -2, -1, 0 的都是系统项,不打同步标
}
} catch (NumberFormatException e) {
skipSyncTag = true; // 如果不是数字,也跳过标记
}
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
@ -252,17 +268,34 @@ public class NotesProvider extends ContentProvider {
case URI_DATA:
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
// 更新 DATA 表通常意味着正文改了,标记位应该打在对应的 Note 上
// 这里的简化处理是不在此处加标记,而是让 note 表的更新触发
skipSyncTag = true;
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
updateData = true;
skipSyncTag = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// [核心手术位置 3]:统一处理同步标记
// 如果不是系统项,且 values 里没有主动设置过同步状态,则强制设为 1 (待同步)
if (!skipSyncTag) {
if (!values.containsKey(NoteColumns.SYNC_STATE)) {
values.put(NoteColumns.SYNC_STATE, 1);
// 注意:因为上面已经执行过 db.update 了,
// 所以对于单条 Note 记录,我们需要补丁式地更新一下这个状态
if (id != null && match == URI_NOTE_ITEM) {
db.execSQL("UPDATE " + TABLE.NOTE + " SET " + NoteColumns.SYNC_STATE + "=1 WHERE " + NoteColumns.ID + "=" + id);
}
}
}
if (count > 0) {
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);

@ -73,7 +73,6 @@ import net.micode.notes.tool.DataUtils.AppWidgetAttribute;
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;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
@ -328,6 +327,12 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
super.onResume();
applyCustomBackground();
updateViewMode();
// [新增] 触发一次性的异步同步任务
androidx.work.OneTimeWorkRequest syncRequest =
new androidx.work.OneTimeWorkRequest.Builder(net.micode.notes.sync.SyncWorker.class)
.build();
androidx.work.WorkManager.getInstance(this).enqueue(syncRequest);
}
private void applyCustomBackground() {
@ -1323,12 +1328,9 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
// 移除之前的 menu.clear(),因为这会清空 onCreate 加载的内容
// 仅保留原有的动态逻辑,例如同步按钮的状态切换
if (mState == ListEditState.NOTE_LIST) {
// getMenuInflater().inflate(R.menu.note_list, menu); // 已在 onCreate 中加载
// set sync or sync_cancel
MenuItem syncItem = menu.findItem(R.id.menu_sync);
if (syncItem != null) {
syncItem.setTitle(
GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync);
syncItem.setTitle(R.string.menu_sync); // 始终显示“同步”
}
} else if (mState == ListEditState.SUB_FOLDER) {
menu.clear(); // 子文件夹模式下可能需要清除并重新加载
@ -1353,18 +1355,19 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
exportNoteToText();
break;
}
case R.id.menu_sync: {
if (isSyncMode()) {
if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {
GTaskSyncService.startSync(this);
} else {
GTaskSyncService.cancelSync(this);
}
} else {
startPreferenceActivity();
}
case R.id.menu_sync:
// [新增] 弹出提示,告知用户正在同步
Toast.makeText(this, "正在同步云端便签...", Toast.LENGTH_SHORT).show();
// [核心] 触发 WorkManager 立即执行同步
androidx.work.OneTimeWorkRequest syncRequest =
new androidx.work.OneTimeWorkRequest.Builder(net.micode.notes.sync.SyncWorker.class)
.build();
androidx.work.WorkManager.getInstance(this).enqueue(syncRequest);
// 刷新列表显示
startAsyncNotesListQuery();
break;
}
case R.id.menu_setting: {
startPreferenceActivity();
break;

@ -49,7 +49,6 @@ import android.net.Uri; // [新增] 修复错误 1 & 2
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 {
@ -69,8 +68,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
private PreferenceCategory mAccountCategory;
private GTaskReceiver mReceiver;
private Account[] mOriAccounts;
private boolean mHasAddedAccount;
@ -86,10 +83,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
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);
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
@ -150,9 +143,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy();
}
@ -187,159 +177,61 @@ public class NotesPreferenceActivity extends PreferenceActivity {
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);
// [核心修改] 使用 Firebase 获取当前用户信息,不再使用 getSyncAccountName
com.google.firebase.auth.FirebaseUser user = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
// 已登录状态
accountPref.setTitle("当前登录账号");
accountPref.setSummary(user.getEmail());
accountPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
// 点击后弹出简单的信息确认,或者不做操作(注销已在主页菜单实现)
android.widget.Toast.makeText(NotesPreferenceActivity.this,
"账号已通过 Firebase 认证", android.widget.Toast.LENGTH_SHORT).show();
return true;
}
});
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this);
// 未登录状态
accountPref.setTitle("账号未登录");
accountPref.setSummary("点击跳转至登录页面");
accountPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(NotesPreferenceActivity.this, LoginActivity.class);
startActivity(intent);
return true;
}
});
}
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();
mAccountCategory.addPreference(accountPref);
}
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);
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
final AlertDialog dialog = dialogBuilder.show();
addAccountView.setOnClickListener(new View.OnClickListener() {
// 始终显示“立即同步”
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setEnabled(true);
syncButton.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();
// 点击后执行我们的新同步
androidx.work.OneTimeWorkRequest syncRequest =
new androidx.work.OneTimeWorkRequest.Builder(net.micode.notes.sync.SyncWorker.class).build();
androidx.work.WorkManager.getInstance(NotesPreferenceActivity.this).enqueue(syncRequest);
Toast.makeText(NotesPreferenceActivity.this, "开始同步...", Toast.LENGTH_SHORT).show();
}
});
// 暂时隐藏旧的状态文字
lastSyncTimeView.setVisibility(View.GONE);
}
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 void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
private Account[] getGoogleAccounts() {
@ -361,16 +253,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
// 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();
@ -419,20 +301,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
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:

Loading…
Cancel
Save