mirror of
https://github.com/nextcloud/notes-android.git
synced 2024-11-26 23:27:55 +03:00
Merge branch 'master' into 603-sorting-method
This commit is contained in:
commit
85342bbc2a
17 changed files with 267 additions and 39 deletions
|
@ -150,7 +150,7 @@ public class NotesDatabaseTest {
|
|||
String newContent = getCurDate() + " This is a even greater day my friend.";
|
||||
DBNote dbNote = new DBNote(newNoteID, 1, Calendar.getInstance(), "A Greater Day",
|
||||
newContent, true, "Best Friend's Record", null, DBStatus.VOID,
|
||||
accountID, NoteUtil.generateNoteExcerpt(newContent), 0);
|
||||
accountID, NoteUtil.generateNoteExcerpt(newContent, "Test-Title"), 0);
|
||||
|
||||
// Add a new note
|
||||
long noteID = db.addNote(accountID, dbNote);
|
||||
|
@ -165,7 +165,7 @@ public class NotesDatabaseTest {
|
|||
newContent = getCurDate() + " This is a even greater day my friend.";
|
||||
dbNote = new DBNote(0, 1, Calendar.getInstance(), "An Even Greater Day",
|
||||
newContent, true, "Sincere Friend's Record", null, DBStatus.VOID,
|
||||
accountID, NoteUtil.generateNoteExcerpt(newContent), 0);
|
||||
accountID, NoteUtil.generateNoteExcerpt(newContent, "Test-Title"), 0);
|
||||
// Add a new note
|
||||
noteID = db.addNote(accountID, dbNote);
|
||||
// Check if this note is added successfully
|
||||
|
|
|
@ -44,7 +44,7 @@ public class AppendToNoteActivity extends NotesListViewActivity {
|
|||
} else {
|
||||
newContent = receivedText;
|
||||
}
|
||||
db.updateNoteAndSync(ssoAccount, localAccount.getId(), note, newContent, () -> Toast.makeText(this, getString(R.string.added_content, receivedText), Toast.LENGTH_SHORT).show());
|
||||
db.updateNoteAndSync(ssoAccount, localAccount, note, newContent, () -> Toast.makeText(this, getString(R.string.added_content, receivedText), Toast.LENGTH_SHORT).show());
|
||||
} else {
|
||||
Toast.makeText(this, R.string.shared_text_empty, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import androidx.annotation.ColorInt;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
|
@ -31,7 +32,9 @@ import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
|||
import it.niedermann.owncloud.notes.R;
|
||||
import it.niedermann.owncloud.notes.android.activity.EditNoteActivity;
|
||||
import it.niedermann.owncloud.notes.android.fragment.CategoryDialogFragment.CategoryDialogListener;
|
||||
import it.niedermann.owncloud.notes.android.fragment.EditTitleDialogFragment.EditTitleListener;
|
||||
import it.niedermann.owncloud.notes.branding.BrandedFragment;
|
||||
import it.niedermann.owncloud.notes.model.ApiVersion;
|
||||
import it.niedermann.owncloud.notes.model.CloudNote;
|
||||
import it.niedermann.owncloud.notes.model.DBNote;
|
||||
import it.niedermann.owncloud.notes.model.DBStatus;
|
||||
|
@ -48,7 +51,7 @@ import static it.niedermann.owncloud.notes.branding.BrandingUtil.tintMenuIcon;
|
|||
import static it.niedermann.owncloud.notes.util.ColorUtil.isColorDark;
|
||||
import static it.niedermann.owncloud.notes.util.Notes.isDarkThemeActive;
|
||||
|
||||
public abstract class BaseNoteFragment extends BrandedFragment implements CategoryDialogListener {
|
||||
public abstract class BaseNoteFragment extends BrandedFragment implements CategoryDialogListener, EditTitleListener {
|
||||
|
||||
private static final String TAG = BaseNoteFragment.class.getSimpleName();
|
||||
|
||||
|
@ -70,6 +73,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
|
|||
private int originalScrollY;
|
||||
protected NotesDatabase db;
|
||||
private NoteFragmentListener listener;
|
||||
private boolean titleModified = false;
|
||||
|
||||
protected boolean isNew = true;
|
||||
|
||||
|
@ -195,6 +199,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
|
|||
MenuItem itemFavorite = menu.findItem(R.id.menu_favorite);
|
||||
prepareFavoriteOption(itemFavorite);
|
||||
|
||||
menu.findItem(R.id.menu_title).setVisible(localAccount.getPreferredApiVersion() != null && localAccount.getPreferredApiVersion().compareTo(new ApiVersion("1.0", 1, 0)) >= 0);
|
||||
menu.findItem(R.id.menu_delete).setVisible(!isNew);
|
||||
}
|
||||
|
||||
|
@ -214,7 +219,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
|
|||
if (originalNote == null) {
|
||||
db.deleteNoteAndSync(ssoAccount, note.getId());
|
||||
} else {
|
||||
db.updateNoteAndSync(ssoAccount, localAccount.getId(), originalNote, null, null);
|
||||
db.updateNoteAndSync(ssoAccount, localAccount, originalNote, null, null);
|
||||
}
|
||||
listener.close();
|
||||
return true;
|
||||
|
@ -230,6 +235,9 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
|
|||
case R.id.menu_category:
|
||||
showCategorySelector();
|
||||
return true;
|
||||
case R.id.menu_title:
|
||||
showEditTitleDialog();
|
||||
return true;
|
||||
case R.id.menu_move:
|
||||
MoveAccountDialogFragment.newInstance().show(requireActivity().getSupportFragmentManager(), BaseNoteFragment.class.getSimpleName());
|
||||
return true;
|
||||
|
@ -275,7 +283,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
|
|||
}
|
||||
|
||||
public void onCloseNote() {
|
||||
if (originalNote == null && getContent().isEmpty()) {
|
||||
if (!titleModified && originalNote == null && getContent().isEmpty()) {
|
||||
db.deleteNoteAndSync(ssoAccount, note.getId());
|
||||
}
|
||||
}
|
||||
|
@ -297,8 +305,9 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
|
|||
Log.v(TAG, "... not saving, since nothing has changed");
|
||||
}
|
||||
} else {
|
||||
note = db.updateNoteAndSync(ssoAccount, localAccount.getId(), note, newContent, callback);
|
||||
note = db.updateNoteAndSync(ssoAccount, localAccount, note, newContent, callback);
|
||||
listener.onNoteUpdated(note);
|
||||
requireActivity().invalidateOptionsMenu();
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "note is null");
|
||||
|
@ -326,12 +335,35 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
|
|||
categoryFragment.show(manager, fragmentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog in order to chose a category
|
||||
*/
|
||||
private void showEditTitleDialog() {
|
||||
final String fragmentId = "fragment_edit_title";
|
||||
FragmentManager manager = requireActivity().getSupportFragmentManager();
|
||||
Fragment frag = manager.findFragmentByTag(fragmentId);
|
||||
if (frag != null) {
|
||||
manager.beginTransaction().remove(frag).commit();
|
||||
}
|
||||
DialogFragment editTitleFragment = EditTitleDialogFragment.newInstance(note.getTitle());
|
||||
editTitleFragment.setTargetFragment(this, 0);
|
||||
editTitleFragment.show(manager, fragmentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCategoryChosen(String category) {
|
||||
db.setCategory(ssoAccount, note, category, null);
|
||||
listener.onNoteUpdated(note);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTitleEdited(String newTitle) {
|
||||
titleModified = true;
|
||||
note.setTitle(newTitle);
|
||||
note = db.updateNoteAndSync(ssoAccount, localAccount, note, note.getContent(), newTitle, null);
|
||||
listener.onNoteUpdated(note);
|
||||
}
|
||||
|
||||
public void moveNote(LocalAccount account) {
|
||||
db.moveNoteToAnotherAccount(ssoAccount, note.getAccountId(), note, account.getId());
|
||||
listener.close();
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package it.niedermann.owncloud.notes.android.fragment;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import it.niedermann.owncloud.notes.R;
|
||||
import it.niedermann.owncloud.notes.databinding.DialogEditTitleBinding;
|
||||
|
||||
public class EditTitleDialogFragment extends DialogFragment {
|
||||
|
||||
static final String PARAM_OLD_TITLE = "old_title";
|
||||
|
||||
private String oldTitle;
|
||||
private EditTitleListener listener;
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
final Bundle args = getArguments();
|
||||
if (args == null) {
|
||||
throw new IllegalArgumentException("Provide at least " + PARAM_OLD_TITLE);
|
||||
}
|
||||
oldTitle = args.getString(PARAM_OLD_TITLE);
|
||||
|
||||
if (getTargetFragment() instanceof EditTitleListener) {
|
||||
listener = (EditTitleListener) getTargetFragment();
|
||||
} else if (getActivity() instanceof EditTitleListener) {
|
||||
listener = (EditTitleListener) getActivity();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Calling activity or target fragment must implement " + EditTitleListener.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
View dialogView = View.inflate(getContext(), R.layout.dialog_edit_title, null);
|
||||
DialogEditTitleBinding binding = DialogEditTitleBinding.bind(dialogView);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (requireArguments().containsKey(PARAM_OLD_TITLE)) {
|
||||
binding.title.setText(requireArguments().getString(PARAM_OLD_TITLE));
|
||||
}
|
||||
}
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.change_note_title)
|
||||
.setView(dialogView)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.action_edit_save, (dialog, which) -> listener.onTitleEdited(binding.title.getText().toString()))
|
||||
.setNegativeButton(R.string.simple_cancel, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
public static DialogFragment newInstance(String title) {
|
||||
final DialogFragment fragment = new EditTitleDialogFragment();
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(PARAM_OLD_TITLE, title);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface that must be implemented by the calling Activity.
|
||||
*/
|
||||
public interface EditTitleListener {
|
||||
/**
|
||||
* This method is called after the user has changed the title of a note manually.
|
||||
*
|
||||
* @param newTitle the new title that a user submitted
|
||||
*/
|
||||
void onTitleEdited(String newTitle);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ public class LocalAccount {
|
|||
private String etag;
|
||||
private String capabilitiesETag;
|
||||
private long modified;
|
||||
@Nullable
|
||||
private ApiVersion preferredApiVersion;
|
||||
@ColorInt
|
||||
private int color;
|
||||
|
@ -78,6 +79,7 @@ public class LocalAccount {
|
|||
this.modified = modified;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ApiVersion getPreferredApiVersion() {
|
||||
return preferredApiVersion;
|
||||
}
|
||||
|
|
|
@ -72,9 +72,9 @@ public abstract class NotesClient {
|
|||
if (preferredApiVersion == null) {
|
||||
Log.i(TAG, "apiVersion is null, using " + NotesClientV02.class.getSimpleName());
|
||||
return new NotesClientV02(appContext);
|
||||
// } else if (preferredApiVersion.compareTo(SUPPORTED_API_VERSIONS[0]) == 0) {
|
||||
// Log.i(TAG, "Using " + NotesClient_1_0.class.getSimpleName());
|
||||
// return new NotesClient_1_0(appContext);
|
||||
} else if (preferredApiVersion.compareTo(SUPPORTED_API_VERSIONS[0]) == 0) {
|
||||
Log.i(TAG, "Using " + NotesClientV1.class.getSimpleName());
|
||||
return new NotesClientV1(appContext);
|
||||
} else if (preferredApiVersion.compareTo(SUPPORTED_API_VERSIONS[1]) == 0) {
|
||||
Log.i(TAG, "Using " + NotesClientV02.class.getSimpleName());
|
||||
return new NotesClientV02(appContext);
|
||||
|
@ -189,7 +189,7 @@ public abstract class NotesClient {
|
|||
String supportedApiVersions = null;
|
||||
final AidlNetworkRequest.PlainHeader supportedApiVersionsHeader = response.getPlainHeader(HEADER_KEY_X_NOTES_API_VERSIONS);
|
||||
if (supportedApiVersionsHeader != null) {
|
||||
supportedApiVersions = Objects.requireNonNull(supportedApiVersionsHeader.getValue()).replace("\"", "");
|
||||
supportedApiVersions = "[" + Objects.requireNonNull(supportedApiVersionsHeader.getValue()) + "]";
|
||||
}
|
||||
|
||||
// return these header fields since they should only be saved after successful processing the result!
|
||||
|
|
|
@ -7,6 +7,11 @@ import androidx.annotation.WorkerThread;
|
|||
|
||||
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import it.niedermann.owncloud.notes.model.CloudNote;
|
||||
import it.niedermann.owncloud.notes.util.ServerResponse.NoteResponse;
|
||||
import it.niedermann.owncloud.notes.util.ServerResponse.NotesResponse;
|
||||
|
@ -18,23 +23,34 @@ public class NotesClientV1 extends NotesClient {
|
|||
|
||||
NotesClientV1(@NonNull Context appContext) {
|
||||
super(appContext);
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
}
|
||||
|
||||
NotesResponse getNotes(SingleSignOnAccount ssoAccount, long lastModified, String lastETag) throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
Map<String, String> parameter = new HashMap<>();
|
||||
parameter.put(GET_PARAM_KEY_PRUNE_BEFORE, Long.toString(lastModified));
|
||||
return new NotesResponse(requestServer(ssoAccount, "notes", METHOD_GET, parameter, null, lastETag));
|
||||
}
|
||||
|
||||
private NoteResponse putNote(SingleSignOnAccount ssoAccount, CloudNote note, String path, String method) throws Exception {
|
||||
JSONObject paramObject = new JSONObject();
|
||||
paramObject.accumulate(JSON_TITLE, note.getTitle());
|
||||
paramObject.accumulate(JSON_CONTENT, note.getContent());
|
||||
paramObject.accumulate(JSON_MODIFIED, note.getModified().getTimeInMillis() / 1000);
|
||||
paramObject.accumulate(JSON_FAVORITE, note.isFavorite());
|
||||
paramObject.accumulate(JSON_CATEGORY, note.getCategory());
|
||||
return new NoteResponse(requestServer(ssoAccount, path, method, null, paramObject, null));
|
||||
}
|
||||
|
||||
NoteResponse createNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
return putNote(ssoAccount, note, "notes", METHOD_POST);
|
||||
}
|
||||
|
||||
NoteResponse editNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
return putNote(ssoAccount, note, "notes/" + note.getRemoteId(), METHOD_PUT);
|
||||
}
|
||||
|
||||
void deleteNote(SingleSignOnAccount ssoAccount, long noteId) throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
this.requestServer(ssoAccount, "notes/" + noteId, METHOD_DELETE, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -62,6 +62,7 @@ import static it.niedermann.owncloud.notes.android.activity.EditNoteActivity.ACT
|
|||
import static it.niedermann.owncloud.notes.android.appwidget.NoteListWidget.updateNoteListWidgets;
|
||||
import static it.niedermann.owncloud.notes.android.appwidget.SingleNoteWidget.updateSingleNoteWidgets;
|
||||
import static it.niedermann.owncloud.notes.model.NoteListsWidgetData.MODE_DISPLAY_CATEGORY;
|
||||
import static it.niedermann.owncloud.notes.util.NoteUtil.generateNoteExcerpt;
|
||||
|
||||
/**
|
||||
* Helps to add, get, update and delete Notes with the option to trigger a Resync with the Server.
|
||||
|
@ -98,7 +99,7 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
* @param note Note
|
||||
*/
|
||||
public long addNoteAndSync(SingleSignOnAccount ssoAccount, long accountId, CloudNote note) {
|
||||
DBNote dbNote = new DBNote(0, 0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), note.getEtag(), DBStatus.LOCAL_EDITED, accountId, NoteUtil.generateNoteExcerpt(note.getContent()), 0);
|
||||
DBNote dbNote = new DBNote(0, 0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), note.getEtag(), DBStatus.LOCAL_EDITED, accountId, generateNoteExcerpt(note.getContent(), note.getTitle()), 0);
|
||||
long id = addNote(accountId, dbNote);
|
||||
notifyWidgets();
|
||||
getNoteServerSyncHelper().scheduleSync(ssoAccount, true);
|
||||
|
@ -125,7 +126,7 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
} else {
|
||||
values.put(key_status, DBStatus.VOID.getTitle());
|
||||
values.put(key_account_id, accountId);
|
||||
values.put(key_excerpt, NoteUtil.generateNoteExcerpt(note.getContent()));
|
||||
values.put(key_excerpt, generateNoteExcerpt(note.getContent(), note.getTitle()));
|
||||
}
|
||||
if (note.getRemoteId() > 0) {
|
||||
values.put(key_remote_id, note.getRemoteId());
|
||||
|
@ -518,22 +519,36 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
return db.insert(table_category, null, values);
|
||||
}
|
||||
|
||||
public DBNote updateNoteAndSync(SingleSignOnAccount ssoAccount, @NonNull LocalAccount localAccount, @NonNull DBNote oldNote, @Nullable String newContent, @Nullable ISyncCallback callback) {
|
||||
return updateNoteAndSync(ssoAccount, localAccount, oldNote, newContent, null, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single Note with a new content.
|
||||
* The title is derived from the new content automatically, and modified date as well as DBStatus are updated, too -- if the content differs to the state in the database.
|
||||
*
|
||||
* @param oldNote Note to be changed
|
||||
* @param newContent New content. If this is <code>null</code>, then <code>oldNote</code> is saved again (useful for undoing changes).
|
||||
* @param newTitle New title. If this is <code>null</code>, then either the old title is reused (in case the note has been synced before) or a title is generated (in case it is a new note)
|
||||
* @param callback When the synchronization is finished, this callback will be invoked (optional).
|
||||
* @return changed note if differs from database, otherwise the old note.
|
||||
*/
|
||||
public DBNote updateNoteAndSync(SingleSignOnAccount ssoAccount, long accountId, @NonNull DBNote oldNote, @Nullable String newContent, @Nullable ISyncCallback callback) {
|
||||
//debugPrintFullDB();
|
||||
public DBNote updateNoteAndSync(SingleSignOnAccount ssoAccount, @NonNull LocalAccount localAccount, @NonNull DBNote oldNote, @Nullable String newContent, @Nullable String newTitle, @Nullable ISyncCallback callback) {
|
||||
DBNote newNote;
|
||||
if (newContent == null) {
|
||||
newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, accountId, oldNote.getExcerpt(), oldNote.getScrollY());
|
||||
newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY());
|
||||
} else {
|
||||
newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(newContent, getContext()), newContent, oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, accountId, NoteUtil.generateNoteExcerpt(newContent), oldNote.getScrollY());
|
||||
final String title;
|
||||
if (newTitle != null) {
|
||||
title = newTitle;
|
||||
} else {
|
||||
if (oldNote.getRemoteId() == 0 || localAccount.getPreferredApiVersion() == null || localAccount.getPreferredApiVersion().compareTo(new ApiVersion("1.0", 0, 0)) < 0) {
|
||||
title = NoteUtil.generateNonEmptyNoteTitle(newContent, getContext());
|
||||
} else {
|
||||
title = oldNote.getTitle();
|
||||
}
|
||||
}
|
||||
newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), title, newContent, oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY());
|
||||
}
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues(7);
|
||||
|
@ -545,7 +560,7 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
values.put(key_excerpt, newNote.getExcerpt());
|
||||
values.put(key_scroll_y, newNote.getScrollY());
|
||||
int rows = db.update(table_notes, values, key_id + " = ? AND (" + key_content + " != ? OR " + key_category + " != ?)", new String[]{String.valueOf(newNote.getId()), newNote.getContent(), newNote.getCategory()});
|
||||
removeEmptyCategory(accountId);
|
||||
removeEmptyCategory(localAccount.getId());
|
||||
// if data was changed, set new status and schedule sync (with callback); otherwise invoke callback directly.
|
||||
if (rows > 0) {
|
||||
notifyWidgets();
|
||||
|
@ -597,7 +612,7 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
values.put(key_favorite, remoteNote.isFavorite());
|
||||
values.put(key_category, getCategoryIdByTitle(localAccount.getId(), remoteNote.getCategory()));
|
||||
values.put(key_etag, remoteNote.getEtag());
|
||||
values.put(key_excerpt, NoteUtil.generateNoteExcerpt(remoteNote.getContent()));
|
||||
values.put(key_excerpt, generateNoteExcerpt(remoteNote.getContent(), remoteNote.getTitle()));
|
||||
String whereClause;
|
||||
String[] whereArgs;
|
||||
if (forceUnchangedDBNoteState != null) {
|
||||
|
@ -869,9 +884,9 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
Log.i(TAG, "Given API version is a valid JSON array but does not contain any valid API versions. Do not update database.");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("API version does contain a non-valid version.");
|
||||
throw new IllegalArgumentException("API version does contain a non-valid version: " + apiVersion);
|
||||
} catch (JSONException e) {
|
||||
throw new IllegalArgumentException("API version must contain be a JSON array.");
|
||||
throw new IllegalArgumentException("API version must contain be a JSON array: " + apiVersion);
|
||||
}
|
||||
} else {
|
||||
Log.v(TAG, "Given API version is null. Do not update database");
|
||||
|
|
|
@ -15,10 +15,10 @@ public class Migration_9_10 {
|
|||
*/
|
||||
public Migration_9_10(@NonNull SQLiteDatabase db) {
|
||||
db.execSQL("ALTER TABLE NOTES ADD COLUMN EXCERPT INTEGER NOT NULL DEFAULT ''");
|
||||
Cursor cursor = db.query("NOTES", new String[]{"ID", "CONTENT"}, null, null, null, null, null, null);
|
||||
Cursor cursor = db.query("NOTES", new String[]{"ID", "CONTENT", "TITLE"}, null, null, null, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("EXCERPT", NoteUtil.generateNoteExcerpt(cursor.getString(1)));
|
||||
values.put("EXCERPT", NoteUtil.generateNoteExcerpt(cursor.getString(1), cursor.getString(2)));
|
||||
db.update("NOTES", values, "ID" + " = ? ", new String[]{cursor.getString(0)});
|
||||
}
|
||||
cursor.close();
|
||||
|
|
|
@ -2,6 +2,7 @@ package it.niedermann.owncloud.notes.util;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -76,22 +77,34 @@ public class NoteUtil {
|
|||
* @return truncated string
|
||||
*/
|
||||
@NonNull
|
||||
private static String truncateString(@NonNull String str, int len) {
|
||||
private static String truncateString(@NonNull String str, @SuppressWarnings("SameParameterValue") int len) {
|
||||
return str.substring(0, Math.min(len, str.length()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an excerpt of a content String (reads second line which is not empty)
|
||||
* Generates an excerpt of a content that does <em>not</em> match the given title
|
||||
*
|
||||
* @param content String
|
||||
* @param content {@link String}
|
||||
* @param title {@link String} In case the content starts with the title, the excerpt should be generated starting from this point
|
||||
* @return excerpt String
|
||||
*/
|
||||
@NonNull
|
||||
public static String generateNoteExcerpt(@NonNull String content) {
|
||||
if (content.contains("\n"))
|
||||
return truncateString(removeMarkDown(content.replaceFirst("^.*\n", "")), 200).replace("\n", EXCERPT_LINE_SEPARATOR);
|
||||
else
|
||||
public static String generateNoteExcerpt(@NonNull String content, @Nullable String title) {
|
||||
content = removeMarkDown(content.trim());
|
||||
if(TextUtils.isEmpty(content)) {
|
||||
return "";
|
||||
}
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
final String trimmedTitle = removeMarkDown(title.trim());
|
||||
if (content.startsWith(trimmedTitle)) {
|
||||
content = content.substring(trimmedTitle.length());
|
||||
}
|
||||
}
|
||||
if (content.contains("\n")) {
|
||||
return truncateString(content.trim(), 200).replace("\n", EXCERPT_LINE_SEPARATOR);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
|
5
app/src/main/res/drawable/ic_title_grey600_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_title_grey600_24dp.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#757575" android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M5,4v3h5.5v12h3V7H19V4z"/>
|
||||
</vector>
|
14
app/src/main/res/layout/dialog_edit_title.xml
Normal file
14
app/src/main/res/layout/dialog_edit_title.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/change_note_title"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
|
@ -16,6 +16,12 @@
|
|||
android:title="@string/menu_favorite"
|
||||
android:checkable="true"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/menu_title"
|
||||
android:icon="@drawable/ic_title_grey600_24dp"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/menu_edit_title"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/menu_category"
|
||||
android:icon="@drawable/ic_folder_white_24dp"
|
||||
|
|
|
@ -192,6 +192,8 @@
|
|||
<string name="added_content">Added "%1$s"</string>
|
||||
<string name="shared_text_empty">Shared text was empty</string>
|
||||
<string name="append_to_note">Append to note</string>
|
||||
<string name="change_note_title">Change note title</string>
|
||||
<string name="menu_edit_title">Edit title</string>
|
||||
<string name="settings_branding">Branding</string>
|
||||
<string name="settings_gridview">Grid view 🆕</string>
|
||||
<string name="simple_security">Security</string>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package android.text;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class TextUtils {
|
||||
|
@ -26,4 +28,13 @@ public class TextUtils {
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the string is null or 0-length.
|
||||
* @param str the string to be examined
|
||||
* @return true if str is null or zero length
|
||||
*/
|
||||
public static boolean isEmpty(@Nullable CharSequence str) {
|
||||
return str == null || str.length() == 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,9 +63,37 @@ public class NoteUtilTest extends TestCase {
|
|||
}
|
||||
|
||||
public void testGenerateNoteExcerpt() {
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("Test"));
|
||||
assertEquals("Foo", NoteUtil.generateNoteExcerpt("Test\nFoo"));
|
||||
assertEquals("Foo Bar", NoteUtil.generateNoteExcerpt("Test\nFoo\nBar"));
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt(""));
|
||||
// title is different from content → return max. 200 characters starting with the first line which is not empty
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("Test", "Title"));
|
||||
assertEquals("Test Foo", NoteUtil.generateNoteExcerpt("Test\nFoo", "Title"));
|
||||
assertEquals("Test Foo Bar", NoteUtil.generateNoteExcerpt("Test\nFoo\nBar", "Title"));
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("", "Title"));
|
||||
|
||||
// content actually starts with title → return max. 200 characters starting with the first character after the title
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("Title", "Title"));
|
||||
assertEquals("Foo", NoteUtil.generateNoteExcerpt("Title\nFoo", "Title"));
|
||||
assertEquals("Title Bar", NoteUtil.generateNoteExcerpt("Title\nTitle\nBar", "Title"));
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("", "Title"));
|
||||
|
||||
// some empty lines between the actual contents → Should be ignored
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("\nTitle", "Title"));
|
||||
assertEquals("Foo", NoteUtil.generateNoteExcerpt("\n\n\n\nTitle\nFoo", "Title"));
|
||||
assertEquals("Title Bar", NoteUtil.generateNoteExcerpt("\nTitle\n\n\nTitle\nBar", "\n\nTitle"));
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("\n\n\n", "\nTitle"));
|
||||
|
||||
// content has markdown while titles markdown is already stripped
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("# Title", "Title"));
|
||||
assertEquals("Foo", NoteUtil.generateNoteExcerpt("Title\n- Foo", "Title"));
|
||||
assertEquals("Title Bar", NoteUtil.generateNoteExcerpt("# Title\n- Title\n- Bar", "Title"));
|
||||
|
||||
// title has markdown while contents markdown is stripped
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("Title", "# Title"));
|
||||
assertEquals("Foo", NoteUtil.generateNoteExcerpt("Title\nFoo", "- Title"));
|
||||
assertEquals("Title Bar", NoteUtil.generateNoteExcerpt("Title\nTitle\nBar", "- Title"));
|
||||
|
||||
// content and title have markdown
|
||||
assertEquals("", NoteUtil.generateNoteExcerpt("# Title", "# Title"));
|
||||
assertEquals("Foo", NoteUtil.generateNoteExcerpt("# Title\n- Foo", "- Title"));
|
||||
assertEquals("Title Bar", NoteUtil.generateNoteExcerpt("- Title\nTitle\nBar", "- Title"));
|
||||
}
|
||||
}
|
3
fastlane/metadata/android/en-US/changelogs/2016000.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/2016000.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
- 🔧 Support APIv1
|
||||
- Title will only be auto generated when creating a new note
|
||||
- Ability to change title manually
|
Loading…
Reference in a new issue