Merge remote-tracking branch 'origin/develop' into zhengkunpeng_branch

zhengkunpeng_branch
zkp 2 years ago
commit 83756503ad

@ -1,5 +1,4 @@
<?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");

@ -176,6 +176,14 @@ public class WorkingNote {
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
/**
* @method: createEmptyNote
* @description: 便便
* @date: 2023/12/20 23:55
* @author: zhoukexing
* @param: [context, folderId, widgetId, widgetType, defaultBgColorId]
* @return: net.micode.notes.model.WorkingNote WorkingNote
*/
WorkingNote note = new WorkingNote(context, folderId);
note.setBgColorId(defaultBgColorId);
note.setWidgetId(widgetId);
@ -187,16 +195,24 @@ public class WorkingNote {
return new WorkingNote(context, id, 0);
}
public synchronized boolean saveNote() {
if (isWorthSaving()) {
if (!existInDatabase()) {
public synchronized boolean saveNote() {// TODO: 2023/12/19 要仔细溯源看看
/**
* @method: saveNote
* @description: 便true
* @date: 2023/12/19 23:44
* @author: zhoukexing
* @param: []
* @return: boolean
*/
if (isWorthSaving()) { // 判断是否有新内容-->是否值得注释 @zhoukexing 2023/12/19 23:45
if (!existInDatabase()) { // 判断是否在数据库里存在==是新便签还是已有便签 @zhoukexing 2023/12/19 23:45
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
mNote.syncNote(mContext, mNoteId);
mNote.syncNote(mContext, mNoteId); // 和远程同步 @zhoukexing 2023/12/19 23:46
/**
* Update widget content if there exist any widget of this note
@ -208,7 +224,7 @@ public class WorkingNote {
}
return true;
} else {
return false;
return false; // 没有需要保存的就返回false @zhoukexing 2023/12/19 23:46
}
}
@ -225,7 +241,16 @@ public class WorkingNote {
}
}
/**
* @method: setOnSettingStatusChangedListener
* @description: NoteEditActivityNoteSettingChangedListener
* @date: 2023/12/21 0:10
* @author: zhoukexing
* @param: [l] NoteEditActivity
* @return: void
*/
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
//Q: 这里的l是怎么获取的。l是NoteEditActivity @zkx 2023/12/21
mNoteSettingStatusListener = l;
}
@ -249,7 +274,14 @@ public class WorkingNote {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
/**
* @method: markDeleted
* @description:
* @date: 2023/12/21 0:50
* @author: zhoukexing
* @param: [mark]
* @return: void
*/
public void markDeleted(boolean mark) {
mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID

@ -72,8 +72,12 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
public class NoteEditActivity extends Activity //NOTE: extends--单继承,但可多重继承 @zhoukexing 2023/12/17 23:29
implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { //NOTE: implements--实现接口 @zhoukexing 2023/12/17 23:24
/** NOTE:
*
* @zhoukexing 2023/12/17 23:39
*/
private class HeadViewHolder {
public TextView tvModified;
@ -149,12 +153,27 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private String mUserQuery;
private Pattern mPattern;
/*--- 以上是此类中的数据区,以下是方法区 ---*/
/**
* @zkx 2023/12/18 ActivityonCreate
*/
/**
* @method: onCreate
* @description: list便
* allinall
* @date: 2023/12/18 23:22
* @author: zhoukexing
* @param: [savedInstanceState]
* @return: void
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
if (savedInstanceState == null && !initActivityState(getIntent())) {
// savedInstanceState? @zhoukexing 2023/12/20 23:45
finish();
return;
}
@ -180,12 +199,21 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
private boolean initActivityState(Intent intent) {
/**
* @method: initActivityState
* @description: intentKey-Value便
* @date: 2023/12/18 23:31
* @author: zhoukexing
* @param: [intent]
* @return: boolean callloadintent
*/
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
* then jump to the NotesListActivity
*/
mWorkingNote = null;
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
// 进入场景:点进一个已有便签 @zhoukexing 2023/12/21 0:14
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = "";
@ -203,7 +231,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
showToast(R.string.error_note_not_exist);
finish();
return false;
} else {
} else {// 如果在数据库里存在就根据noteId从数据库中加载到工作便签里来 @zhoukexing 2023/12/21 0:16
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load note failed with note id" + noteId);
@ -211,20 +239,21 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return false;
}
}
getWindow().setSoftInputMode(
getWindow().setSoftInputMode(// 猜:平滑地展示便签内容 @zhoukexing 2023/12/21 0:18
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {
// New note
// 进入场景:一个New note
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,
Notes.TYPE_WIDGET_INVALIDE);
Notes.TYPE_WIDGET_INVALIDE); // widgetType=0: 新建的挂件,空的 @zhoukexing 2023/12/21 0:02
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
ResourceParser.getDefaultBgId(this));
ResourceParser.getDefaultBgId(this));// TODO: 2023/12/21 背景色的设置
// Parse call-record note
// Parse call-record note todo
// 解析文档,看是否有号码存在,以便展示的时候渲染 @zhoukexing 2023/12/20 23:49
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);
if (callDate != 0 && phoneNumber != null) {
@ -234,6 +263,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
long noteId = 0;
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {
// TODO: 2023/12/20
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load call note failed with note id" + noteId);
@ -245,7 +275,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
widgetType, bgResId);
mWorkingNote.convertToCallNote(phoneNumber, callDate);
}
} else {
} else { // 没有要显示的电话
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
bgResId);
}
@ -259,6 +289,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return false;
}
mWorkingNote.setOnSettingStatusChangedListener(this);
// this是WorkingNote @zhoukexing 2023/12/21 0:08
return true;
}
@ -381,6 +412,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
private void initResources() {
/**
* @method: initResources
* @description: onCreate
* @date: 2023/12/18 23:36
* @author: zhoukexing
* @param: []
* @return: void
*/
mHeadViewPanel = findViewById(R.id.note_title);
mNoteHeaderHolder = new HeadViewHolder();
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
@ -454,7 +493,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
@ -531,16 +570,26 @@ public class NoteEditActivity extends Activity implements OnClickListener,
@Override
public boolean onOptionsItemSelected(MenuItem item) {
/**
* @method: onOptionsItemSelected
* @description: Activity.javaonOptionsItemSelected线menu-->item
* item
* @date: 2023/12/19 23:35
* @author: zhoukexing
* @param: [item]
* @return: boolean
*/
int itemId = item.getItemId();
if (itemId == R.id.menu_new_note) {
if (itemId == R.id.menu_new_note) { // 从item到itemid用itemid导向对应的不同的动作 @zhoukexing 2023/12/19 23:38
createNewNote();
} else if (itemId == R.id.menu_delete) {
// 构建一个警告⚠对话框,让用户确认是否真的要删除便签 @zhoukexing 2023/12/21 0:41
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() {
new DialogInterface.OnClickListener() {// TODO: 2023/12/21 传入了一个函数作为参数?
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
finish();
@ -608,17 +657,34 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
private void createNewNote() {
/**
* @method: createNewNote
* @description: add note便
* 便NoteEditActivityintent
* intent
* @date: 2023/12/19 23:03
* @author: zhoukexing
* @param: []
* @return: void
*/
// Firstly, save current editing notes
saveNote();
// For safety, start a new NoteEditActivity
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
Intent intent = new Intent(this, NoteEditActivity.class); //Q: 在类的内部,还没有实现完全时,启动这个类自己?@zkx 2023/12/17
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 创建便签后要插入或者编辑。Q: 为什么要插入 @zhoukexing 2023/12/19 22:57
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
startActivity(intent);
}
/**
* @method: deleteCurrentNote
* @description: 便
* @date: 2023/12/21 0:48
* @author: zhoukexing
* @param: []
* @return: void
*/
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>();
@ -850,8 +916,16 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
private boolean saveNote() {
/**
* @method: saveNote
* @description: 便便list
* @date: 2023/12/19 23:50
* @author: zhoukexing
* @param: []
* @return: boolean
*/
getWorkingText();
boolean saved = mWorkingNote.saveNote();
boolean saved = mWorkingNote.saveNote();// TODO: 2023/12/19 工作便签下的saveNote
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
@ -860,7 +934,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
* new node requires to the top of the list. This code
* {@link #RESULT_OK} is used to identify the create/edit state
*/
setResult(RESULT_OK);
setResult(RESULT_OK); // RESULT_OK指示将该便签保存到list界面的顶端因为这是新建的便签 @zhoukexing 2023/12/19 23:41
//Q: 这个setResult只在这里调用了怎么实现的 @zkx 2023/12/19
}
return saved;
}
@ -927,4 +1002,4 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
}
} //NOTE: 这一整个文件就是这一个类 @zhoukexing 2023/12/17 23:41

@ -137,11 +137,19 @@ public class NoteEditText extends EditText {
}
return super.onKeyDown(keyCode, event);
}
/**
* @method: onKeyUp
* @description: deleteenter
* 退
* @date: 2023/12/21 0:28
* @author: zhoukexing
* @param: [keyCode, event]
* @return: boolean
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_DEL: // delete键的号为67 @zhoukexing 2023/12/21 0:31
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
@ -151,7 +159,7 @@ public class NoteEditText extends EditText {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_ENTER: // enter键的号为66 @zhoukexing 2023/12/21 0:31
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();

@ -405,15 +405,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
};
//TODO add comments of this method
/**
* @Method startAsyncNotesListQuery
* @Date 2023/12/19 8:34
* @Author lenovo
* @Return void
* @Description 便
*/
private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); // DESC 降序
}
private final class BackgroundQueryHandler extends AsyncQueryHandler {
@ -505,7 +510,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}.execute();
}
// TODO add comments of this method
/**
* @Method deleteFolder
* @Date 2023/12/19 8:37
* @param folderId
* @Author lenovo
* @Return void
* @Description (sync mode )
*/
private void deleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
@ -514,6 +526,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
HashSet<Long> ids = new HashSet<Long>();
ids.add(folderId);
// 所有与要删除文件夹相关的 widgets
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
if (!isSyncMode()) {
@ -523,6 +536,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
// in sync mode, we'll move the deleted folder into the trash folder
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
// 更新 widgets
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
@ -540,19 +554,29 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
// TODO add comments of this method
/**
* @Method openFolder
* @Date 2023/12/19 7:55
* @param data
* @Author lenovo
* @Return void
* @Description
*/
private void openFolder(NoteItemData data) {
mCurrentFolderId = data.getId();
startAsyncNotesListQuery();
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// TODO store all records 暂时没太搞明白这个代表什么
mState = ListEditState.CALL_RECORD_FOLDER;
mAddNewNote.setVisibility(View.GONE);
} else {
// 正常打开文件夹
mState = ListEditState.SUB_FOLDER;
}
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mTitleBar.setText(R.string.call_record_folder_name);
} else {
// 将顶部栏设置为 data.getSnippet 文件夹名称
mTitleBar.setText(data.getSnippet());
}
mTitleBar.setVisibility(View.VISIBLE);
@ -564,6 +588,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
/**
* @Method showSoftInput
* @Date 2023/12/17 23:46
* @Author lenovo
* @Return void
* @Description
*/
private void showSoftInput() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
@ -576,15 +607,23 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
// TODO add comments of this method
/**
* @Method showCreateOrModifyFolderDialog
* @Date 2023/12/17 22:32
* @param create true false
* @Author lenovo
* @Return void
* @Description
*/
private void showCreateOrModifyFolderDialog(final boolean create) {
// final 关键字, 初始化之后无法修改 (const)
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
showSoftInput();
if (!create) {
if (mFocusNoteDataItem != null) {
etName.setText(mFocusNoteDataItem.getSnippet());
etName.setText(mFocusNoteDataItem.getSnippet()); // 显示之前的名称
builder.setTitle(getString(R.string.menu_folder_change_name));
} else {
Log.e(TAG, "The long click data item is null");
@ -594,7 +633,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
etName.setText("");
builder.setTitle(this.getString(R.string.menu_create_folder));
}
/**
* OK
* Cancel
* lenovo 2023/12/18 0:03
*/
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@ -608,12 +651,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public void onClick(View v) {
hideSoftInput(etName);
String name = etName.getText().toString();
// 如果该名称已经存在
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
// 提示 exist, please rename
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show();
etName.setSelection(0, etName.length());
etName.setSelection(0, etName.length()); //全选输入文件名(准备修改/删除)
return;
}
// 更新数据库中文件夹名称 插入/修改
if (!create) {
if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues();
@ -631,6 +677,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
mContentResolver.insert(Notes.CONTENT_NOTE_URI, values);
}
//关闭对话框
dialog.dismiss();
}
});
@ -733,6 +780,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
super.onContextMenuClosed(menu);
}
/**
* @Method onContextItemSelected
* @Date 2023/12/17 23:51
* @param item
* @Author lenovo
* @Return boolean
* @Description
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mFocusNoteDataItem == null) {
@ -746,7 +801,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
case MENU_FOLDER_DELETE:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setIcon(android.R.drawable.divider_horizontal_dark);
builder.setMessage(getString(R.string.alert_message_delete_folder));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@ -785,6 +840,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
/**
* @Method onOptionsItemSelected
* @Date 2023/12/17 23:49
* @param item
* @Author lenovo
* @Return boolean
* @Description
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
@ -812,26 +875,46 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
//TODO add comments of this method
@Override
/**
* @Method onSearchRequested
* @Date 2023/12/19 8:53
* @Author lenovo
* @Return boolean
* @Description
*/
public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false);
return true;
}
//TODO add comments of this method
/**
* @Method exportNoteToText
* @Date 2023/12/19 8:44
* @Author lenovo
* @Return void
* @Description 便
*/
private void exportNoteToText() {
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
/**
* UI 线
* Warning: deprecated in API level 30
* would cause Context leaks, missed callbacks, or crashes on configuration changes.
* lenovo 2023/12/19 9:04
*/
new AsyncTask<Void, Void, Integer>() {
@Override
// 后台运行导出文本
protected Integer doInBackground(Void... unused) {
return backup.exportToText();
}
@Override
// 线程运行结束后的结果处理程序
protected void onPostExecute(Integer result) {
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { // 没插 SD 卡
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
@ -913,7 +996,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
// TODO add comments of this method
/**
* @Method startQueryDestinationFolders
* @Date 2023/12/19 8:59
* @Author lenovo
* @Return void
* @Description
*/
private void startQueryDestinationFolders() {
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
selection = (mState == ListEditState.NOTE_LIST) ? selection:

Loading…
Cancel
Save