Merge branch 'master' into 603-sorting-method

This commit is contained in:
Peter S 2020-06-14 22:24:59 +08:00 committed by GitHub
commit 85342bbc2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 267 additions and 39 deletions

View file

@ -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

View file

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

View file

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

View file

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

View file

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

View file

@ -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!

View file

@ -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

View file

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

View file

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

View file

@ -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

View 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>

View 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>

View file

@ -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"

View file

@ -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>

View file

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

View file

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

View file

@ -0,0 +1,3 @@
- 🔧 Support APIv1
- Title will only be auto generated when creating a new note
- Ability to change title manually