diff --git a/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java b/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java index c98281312..03aa63806 100644 --- a/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java +++ b/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java @@ -11,7 +11,7 @@ public class NoteTest extends TestCase { public void testMarkDownStrip() { - OwnCloudNote note = new OwnCloudNote(0, Calendar.getInstance(), "#Title", ""); + OwnCloudNote note = new OwnCloudNote(0, Calendar.getInstance(), "#Title", "", false); assertTrue("Title".equals(note.getTitle())); note.setTitle("* Aufzählung"); assertTrue("Aufzählung".equals(note.getTitle())); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index 91a604f94..e42f61d89 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -124,6 +124,14 @@ public boolean onCreateOptionsMenu(Menu menu) { return true; } + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem itemFavorite = menu.findItem(R.id.menu_favorite); + itemFavorite.setIcon(note.isFavorite() ? R.drawable.ic_star_white_24dp : R.drawable.ic_star_outline_white_24dp); + itemFavorite.setChecked(note.isFavorite()); + return super.onPrepareOptionsMenu(menu); + } + /** * Main-Menu-Handler */ @@ -146,6 +154,10 @@ public boolean onOptionsItemSelected(MenuItem item) { setResult(RESULT_FIRST_USER, data); finish(); return true; + case R.id.menu_favorite: + db.toggleFavorite(note, null); + invalidateOptionsMenu(); + return true; case R.id.menu_preview: saveData(null); Intent previewIntent = new Intent(getApplicationContext(), NoteActivity.class); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 7b1c1e702..7a60c5337 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -5,6 +5,7 @@ import android.app.SearchManager; import android.content.Intent; import android.content.SharedPreferences; +import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.view.MenuItemCompat; @@ -61,8 +62,7 @@ public void onFinish() { if (mActionMode != null) { mActionMode.finish(); } - // adapter.checkForUpdates(db.getNotes()); // FIXME deactivated, since it doesn't remove remotely deleted notes - setListView(db.getNotes()); + refreshList(); swipeRefreshLayout.setRefreshing(false); } }; @@ -82,7 +82,8 @@ protected void onCreate(Bundle savedInstanceState) { // Display Data db = new NoteSQLiteOpenHelper(this); - setListView(db.getNotes()); + initList(); + refreshList(); // Pull to Refresh swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swiperefreshlayout); @@ -144,6 +145,8 @@ protected void onResume() { checkNotificationSetting(); if (db.getNoteServerSyncHelper().isSyncPossible()) { synchronize(); + } else { + refreshList(); } super.onResume(); } @@ -162,79 +165,110 @@ public void onClick(View v) { } /** - * Allows other classes to set a List of Notes. - * - * @param noteList List<Note> + * Allows other classes to refresh the List of Notes. Starts an AsyncTask which loads the data in the background. */ - @SuppressWarnings("WeakerAccess") - public void setListView(List noteList) { - final List itemList = new ArrayList<>(); - // #12 Create Sections depending on Time - // TODO Move to ItemAdapter? - boolean todaySet, yesterdaySet, weekSet, monthSet, earlierSet; - todaySet = yesterdaySet = weekSet = monthSet = earlierSet = false; - Calendar recent = Calendar.getInstance(); - Calendar today = Calendar.getInstance(); - today.set(Calendar.HOUR_OF_DAY, 0); - today.set(Calendar.MINUTE, 0); - today.set(Calendar.SECOND, 0); - today.set(Calendar.MILLISECOND, 0); - Calendar yesterday = Calendar.getInstance(); - yesterday.set(Calendar.DAY_OF_YEAR, yesterday.get(Calendar.DAY_OF_YEAR) - 1); - yesterday.set(Calendar.HOUR_OF_DAY, 0); - yesterday.set(Calendar.MINUTE, 0); - yesterday.set(Calendar.SECOND, 0); - yesterday.set(Calendar.MILLISECOND, 0); - Calendar week = Calendar.getInstance(); - week.set(Calendar.DAY_OF_WEEK, week.getFirstDayOfWeek()); - week.set(Calendar.HOUR_OF_DAY, 0); - week.set(Calendar.MINUTE, 0); - week.set(Calendar.SECOND, 0); - week.set(Calendar.MILLISECOND, 0); - Calendar month = Calendar.getInstance(); - month.set(Calendar.DAY_OF_MONTH, 0); - month.set(Calendar.HOUR_OF_DAY, 0); - month.set(Calendar.MINUTE, 0); - month.set(Calendar.SECOND, 0); - month.set(Calendar.MILLISECOND, 0); - for (int i = 0; i < noteList.size(); i++) { - DBNote currentNote = noteList.get(i); - if (!todaySet && recent.getTimeInMillis() - currentNote.getModified().getTimeInMillis() >= 600000 && currentNote.getModified().getTimeInMillis() >= today.getTimeInMillis()) { - // < 10 minutes but after 00:00 today - //if (i > 0) { - //itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_today))); - //} - todaySet = true; - } else if (!yesterdaySet && currentNote.getModified().getTimeInMillis() < today.getTimeInMillis() && currentNote.getModified().getTimeInMillis() >= yesterday.getTimeInMillis()) { - // between today 00:00 and yesterday 00:00 - if (i > 0) { - itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_yesterday))); - } - yesterdaySet = true; - } else if (!weekSet && currentNote.getModified().getTimeInMillis() < yesterday.getTimeInMillis() && currentNote.getModified().getTimeInMillis() >= week.getTimeInMillis()) { - // between yesterday 00:00 and start of the week 00:00 - if (i > 0) { - itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_this_week))); - } - weekSet = true; - } else if (!monthSet && currentNote.getModified().getTimeInMillis() < week.getTimeInMillis() && currentNote.getModified().getTimeInMillis() >= month.getTimeInMillis()) { - // between start of the week 00:00 and start of the month 00:00 - if (i > 0) { - itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_this_month))); - } - monthSet = true; - } else if (!earlierSet && currentNote.getModified().getTimeInMillis() < month.getTimeInMillis()) { - // before start of the month 00:00 - if (i > 0) { - itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_earlier))); + public void refreshList() { + new RefreshListTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private class RefreshListTask extends AsyncTask> { + + private CharSequence query = null; + + @Override + protected void onPreExecute() { + if(searchView != null && !searchView.isIconified() && searchView.getQuery().length() != 0) { + query = searchView.getQuery(); + } + } + + @Override + protected List doInBackground(Void... voids) { + List noteList; + if (query==null) { + noteList = db.getNotes(); + } else { + noteList = db.searchNotes(query); + } + + final List itemList = new ArrayList<>(); + // #12 Create Sections depending on Time + // TODO Move to ItemAdapter? + boolean todaySet, yesterdaySet, weekSet, monthSet, earlierSet; + todaySet = yesterdaySet = weekSet = monthSet = earlierSet = false; + Calendar recent = Calendar.getInstance(); + Calendar today = Calendar.getInstance(); + today.set(Calendar.HOUR_OF_DAY, 0); + today.set(Calendar.MINUTE, 0); + today.set(Calendar.SECOND, 0); + today.set(Calendar.MILLISECOND, 0); + Calendar yesterday = Calendar.getInstance(); + yesterday.set(Calendar.DAY_OF_YEAR, yesterday.get(Calendar.DAY_OF_YEAR) - 1); + yesterday.set(Calendar.HOUR_OF_DAY, 0); + yesterday.set(Calendar.MINUTE, 0); + yesterday.set(Calendar.SECOND, 0); + yesterday.set(Calendar.MILLISECOND, 0); + Calendar week = Calendar.getInstance(); + week.set(Calendar.DAY_OF_WEEK, week.getFirstDayOfWeek()); + week.set(Calendar.HOUR_OF_DAY, 0); + week.set(Calendar.MINUTE, 0); + week.set(Calendar.SECOND, 0); + week.set(Calendar.MILLISECOND, 0); + Calendar month = Calendar.getInstance(); + month.set(Calendar.DAY_OF_MONTH, 0); + month.set(Calendar.HOUR_OF_DAY, 0); + month.set(Calendar.MINUTE, 0); + month.set(Calendar.SECOND, 0); + month.set(Calendar.MILLISECOND, 0); + for (int i = 0; i < noteList.size(); i++) { + DBNote currentNote = noteList.get(i); + if (currentNote.isFavorite()) { + // don't show as new section + } else if (!todaySet && currentNote.getModified().getTimeInMillis() >= today.getTimeInMillis()) { + // after 00:00 today + if (i > 0) { + itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_today))); + } + todaySet = true; + } else if (!yesterdaySet && currentNote.getModified().getTimeInMillis() < today.getTimeInMillis() && currentNote.getModified().getTimeInMillis() >= yesterday.getTimeInMillis()) { + // between today 00:00 and yesterday 00:00 + if (i > 0) { + itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_yesterday))); + } + yesterdaySet = true; + } else if (!weekSet && currentNote.getModified().getTimeInMillis() < yesterday.getTimeInMillis() && currentNote.getModified().getTimeInMillis() >= week.getTimeInMillis()) { + // between yesterday 00:00 and start of the week 00:00 + if (i > 0) { + itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_this_week))); + } + weekSet = true; + } else if (!monthSet && currentNote.getModified().getTimeInMillis() < week.getTimeInMillis() && currentNote.getModified().getTimeInMillis() >= month.getTimeInMillis()) { + // between start of the week 00:00 and start of the month 00:00 + if (i > 0) { + itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_this_month))); + } + monthSet = true; + } else if (!earlierSet && currentNote.getModified().getTimeInMillis() < month.getTimeInMillis()) { + // before start of the month 00:00 + if (i > 0) { + itemList.add(new SectionItem(getResources().getString(R.string.listview_updated_earlier))); + } + earlierSet = true; } - earlierSet = true; + itemList.add(currentNote); } - itemList.add(currentNote); + + return itemList; } - adapter = new ItemAdapter(itemList); - ItemAdapter.setNoteClickListener(this); + @Override + protected void onPostExecute(List items) { + adapter.setItemList(items); + } + } + + public void initList() { + adapter = new ItemAdapter(this); listView = (RecyclerView) findViewById(R.id.recycler_view); listView.setAdapter(adapter); listView.setLayoutManager(new LinearLayoutManager(this)); @@ -296,31 +330,17 @@ public boolean onQueryTextSubmit(String query) { @Override public boolean onQueryTextChange(String newText) { - search(newText.trim()); + refreshList(); return true; } }); return true; } - private void search(final String query) { - new Thread() { - @Override - public void run() { - if (query.length() > 0) { - setListView(db.searchNotes(query)); - } else { - setListView(db.getNotes()); - } - listView.scrollToPosition(0); - } - }.run(); - } - @Override protected void onNewIntent(Intent intent) { if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - search(intent.getStringExtra(SearchManager.QUERY)); + searchView.setQuery(intent.getStringExtra(SearchManager.QUERY), true); } super.onNewIntent(intent); } @@ -377,12 +397,8 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } if (resultCode == RESULT_OK) { DBNote editedNote = (DBNote) data.getExtras().getSerializable(EditNoteActivity.PARAM_NOTE); - if(oldItem instanceof DBNote && !editedNote.getModified().after(((DBNote)oldItem).getModified())) { - adapter.replace(editedNote, notePosition); - } else { - adapter.remove(oldItem); - adapter.add(editedNote); - } + adapter.replace(editedNote, notePosition); + refreshList(); } } } else if (requestCode == server_settings) { @@ -435,6 +451,15 @@ public void onNoteClick(int position, View v) { } } + @Override + public void onNoteFavoriteClick(int position, View view) { + DBNote note = (DBNote) adapter.getItem(position); + NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(view.getContext()); + db.toggleFavorite(note, syncCallBack); + adapter.notifyItemChanged(position); + refreshList(); + } + @Override public boolean onNoteLongClick(int position, View v) { boolean selected = adapter.select(position); @@ -464,8 +489,7 @@ private void synchronize() { /** * Handler for the MultiSelect Actions */ - private class MultiSelectedActionModeCallback implements - ActionMode.Callback { + private class MultiSelectedActionModeCallback implements ActionMode.Callback { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { @@ -499,7 +523,7 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { mode.finish(); // Action picked, so close the CAB //after delete selection has to be cleared searchView.setIconified(true); - setListView(db.getNotes()); + refreshList(); return true; default: return false; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java index 0b171f1d1..412c98078 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java @@ -64,11 +64,11 @@ public void onCreate(Bundle savedInstanceState) { private void setListView(List noteList) { List itemList = new ArrayList<>(); itemList.addAll(noteList); - adapter = new ItemAdapter(itemList); + adapter = new ItemAdapter(this); + adapter.setItemList(itemList); listView = (RecyclerView) findViewById(R.id.select_single_note_list_view); listView.setAdapter(adapter); listView.setLayoutManager(new LinearLayoutManager(this)); - ItemAdapter.setNoteClickListener(this); } @Override @@ -87,6 +87,10 @@ public void onNoteClick(int position, View v) { finish(); } + @Override + public void onNoteFavoriteClick(int position, View v) { + } + @Override public boolean onNoteLongClick(int position, View v) { return false; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java index fe4d06a8f..4aa320b19 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java @@ -63,11 +63,11 @@ public StackRemoteViewsFactory(Context context, Intent intent) { NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(mContext); db.getNoteServerSyncHelper().scheduleSync(false); mWidgetItems = db.getNotes(); - mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); + mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", false, DBStatus.VOID)); } public void onCreate() { - mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); + mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", false, DBStatus.VOID)); } @Override diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java index f25105b86..7ced944fb 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java @@ -16,8 +16,8 @@ public class DBNote extends OwnCloudNote implements Item, Serializable { private DBStatus status; private String excerpt = ""; - public DBNote(long id, long remoteId, Calendar modified, String title, String content, DBStatus status) { - super(remoteId, modified, title, content); + public DBNote(long id, long remoteId, Calendar modified, String title, String content, boolean favorite, DBStatus status) { + super(remoteId, modified, title, content, favorite); this.id = id; setExcerpt(content); this.status = status; @@ -60,6 +60,6 @@ public boolean isSection() { @Override public String toString() { - return "#" + getId() + "/R"+getRemoteId()+" " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ") " + getStatus(); + return "#" + getId() + "/R"+getRemoteId()+" " + (isFavorite() ? " (*) " : " ") + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ") " + getStatus(); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java index 01d7ad2ef..3242d7a32 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java @@ -16,22 +16,23 @@ public class ItemAdapter extends RecyclerView.Adapter { private static final int section_type = 0; private static final int note_type = 1; - private static NoteClickListener noteClickListener; + private final NoteClickListener noteClickListener; private List itemList = null; private List selected = null; - public ItemAdapter(List itemList) { - //super(context, android.R.layout.simple_list_item_1, itemList); - super(); - this.itemList = itemList; + + public ItemAdapter(NoteClickListener noteClickListener) { + this.itemList = new ArrayList<>(); this.selected = new ArrayList<>(); + this.noteClickListener = noteClickListener; } /** - * Sets the given NoteClickListener that should be notified on clicks - * @param noteClickListener NoteClickListener + * Updates the item list and notifies respective view to update. + * @param itemList */ - public static void setNoteClickListener(NoteClickListener noteClickListener) { - ItemAdapter.noteClickListener = noteClickListener; + public void setItemList(List itemList) { + this.itemList = itemList; + notifyDataSetChanged(); } /** @@ -62,42 +63,9 @@ public void removeAll() { notifyDataSetChanged(); } - /** - * Compares the given List of notes to the current internal holded notes and updates the list if necessairy - * @param newNotes List of more up to date notes - */ - public void checkForUpdates(List newNotes) { - for(DBNote newNote : newNotes) { - boolean foundNewNoteInOldList = false; - for(Item oldItem : itemList) { - if(!oldItem.isSection()) { - DBNote oldNote = (DBNote) oldItem; - if(newNote.getId() == oldNote.getId()) { - // Notes have the same id, check which is newer - if(newNote.getModified().after(oldNote.getModified())) { - // Replace old note with new note because new note has been edited more recently - int indexOfOldNote = itemList.indexOf(oldNote); - itemList.remove(indexOfOldNote); - itemList.add(indexOfOldNote, newNote); - this.notifyItemChanged(indexOfOldNote); - } - foundNewNoteInOldList = true; - break; - } - } - } - if(!foundNewNoteInOldList) { - // Add new note because it could not be found in the itemList - add(newNote); - } - } - //TODO check if a note has been deleted on server?? - } - // Create new views (invoked by the layout manager) @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, - int viewType) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v; if (viewType == section_type) { v = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_notes_list_section_item, parent, false); @@ -111,7 +79,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, // Replace the contents of a view (invoked by the layout manager) @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { // - get element from your dataset at this position // - replace the contents of the view with that element Item item = itemList.get(position); @@ -120,11 +88,19 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ((SectionViewHolder) holder).sectionTitle.setText(section.geTitle()); ((SectionViewHolder) holder).setPosition(position); } else { - DBNote note = (DBNote) item; - ((NoteViewHolder) holder).noteTitle.setText(note.getTitle()); - ((NoteViewHolder) holder).noteExcerpt.setText(note.getExcerpt()); - ((NoteViewHolder) holder).noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.GONE : View.VISIBLE); - ((NoteViewHolder) holder).setPosition(position); + final DBNote note = (DBNote) item; + final NoteViewHolder nvHolder = ((NoteViewHolder) holder); + nvHolder.noteTitle.setText(note.getTitle()); + nvHolder.noteExcerpt.setText(note.getExcerpt()); + nvHolder.noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.GONE : View.VISIBLE); + nvHolder.noteFavorite.setImageResource(note.isFavorite() ? R.drawable.ic_star_grey600_24dp : R.drawable.ic_star_outline_grey600_24dp); + nvHolder.noteFavorite.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + noteClickListener.onNoteFavoriteClick(position, view); + } + }); + nvHolder.setPosition(position); } } @@ -173,14 +149,16 @@ public int getItemViewType(int position) { public interface NoteClickListener { void onNoteClick(int position, View v); + void onNoteFavoriteClick(int position, View v); boolean onNoteLongClick(int position, View v); } - public static class NoteViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener, View.OnClickListener { + public class NoteViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener, View.OnClickListener { // each data item is just a string in this case public TextView noteTitle; public TextView noteExcerpt; public ImageView noteStatus; + public ImageView noteFavorite; public int position = -1; private NoteViewHolder(View v) { @@ -188,6 +166,7 @@ private NoteViewHolder(View v) { this.noteTitle = (TextView) v.findViewById(R.id.noteTitle); this.noteExcerpt = (TextView) v.findViewById(R.id.noteExcerpt); this.noteStatus = (ImageView) v.findViewById(R.id.noteStatus); + this.noteFavorite = (ImageView) v.findViewById(R.id.noteFavorite); v.setOnClickListener(this); v.setOnLongClickListener(this); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java index b9a31865c..9c228d336 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java @@ -17,13 +17,15 @@ public class OwnCloudNote implements Serializable { private String title = ""; private Calendar modified = null; private String content = ""; + private boolean favorite = false; - public OwnCloudNote(long remoteId, Calendar modified, String title, String content) { + public OwnCloudNote(long remoteId, Calendar modified, String title, String content, boolean favorite) { this.remoteId = remoteId; if (title != null) setTitle(title); setTitle(title); setContent(content); + setFavorite(favorite); this.modified = modified; } @@ -65,8 +67,16 @@ public void setContent(String content) { this.content = content; } + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } + @Override public String toString() { - return "#" + getRemoteId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ")"; + return "#" + getRemoteId() + " " + (isFavorite() ? " (*) " : " ") + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ")"; } } \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 7286534d6..50a1ae85b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -28,7 +28,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - private static final int database_version = 5; + private static final int database_version = 6; private static final String database_name = "OWNCLOUD_NOTES"; private static final String table_notes = "NOTES"; private static final String key_id = "ID"; @@ -37,7 +37,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { private static final String key_title = "TITLE"; private static final String key_modified = "MODIFIED"; private static final String key_content = "CONTENT"; - private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_content}; + private static final String key_favorite = "FAVORITE"; + private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite}; + private static final String default_order = key_favorite + " DESC, " + key_modified + " DESC"; private NoteServerSyncHelper serverSyncHelper = null; private Context context = null; @@ -65,7 +67,8 @@ public void onCreate(SQLiteDatabase db) { key_status + "' VARCHAR(50), '" + key_title + "' TEXT, '" + key_modified + "' TEXT, '" + - key_content + "' TEXT)"); + key_content + "' TEXT, '" + + key_favorite + "' INTEGER DEFAULT 0)"); // FIXME create index for status and remote_id } @@ -79,6 +82,9 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("UPDATE "+table_notes+" SET "+key_remote_id+"="+key_id+" WHERE ("+key_remote_id+" IS NULL OR "+key_remote_id+"=0) AND "+key_status+"!=?", new String[]{DBStatus.LOCAL_CREATED.getTitle()}); db.execSQL("UPDATE "+table_notes+" SET "+key_remote_id+"=0, "+key_status+"=? WHERE "+key_status+"=?", new String[]{DBStatus.LOCAL_EDITED.getTitle(), DBStatus.LOCAL_CREATED.getTitle()}); } + if(oldVersion<6) { + db.execSQL("ALTER TABLE "+table_notes+" ADD COLUMN "+key_favorite+" INTEGER DEFAULT 0"); + } } private void clearDatabase(SQLiteDatabase db) { @@ -96,7 +102,7 @@ public Context getContext() { */ @SuppressWarnings("UnusedReturnValue") public long addNoteAndSync(String content) { - DBNote note = new DBNote(0, 0, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, DBStatus.LOCAL_EDITED); + DBNote note = new DBNote(0, 0, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, false, DBStatus.LOCAL_EDITED); long id = addNote(note); getNoteServerSyncHelper().scheduleSync(true); return id; @@ -126,6 +132,7 @@ long addNote(OwnCloudNote note) { values.put(key_title, note.getTitle()); values.put(key_modified, note.getModified(NoteSQLiteOpenHelper.DATE_FORMAT)); values.put(key_content, note.getContent()); + values.put(key_favorite, note.isFavorite()); long id = db.insert(table_notes, null, values); db.close(); return id; @@ -192,11 +199,11 @@ private DBNote getNoteFromCursor(Cursor cursor) { } catch (ParseException e) { e.printStackTrace(); } - return new DBNote(cursor.getLong(0), cursor.getLong(1), modified, cursor.getString(3), cursor.getString(5), DBStatus.parse(cursor.getString(2))); + return new DBNote(cursor.getLong(0), cursor.getLong(1), modified, cursor.getString(3), cursor.getString(5), cursor.getInt(6)>0, DBStatus.parse(cursor.getString(2))); } public void debugPrintFullDB() { - List notes = getNotesCustom("", new String[]{}, key_modified + " DESC"); + List notes = getNotesCustom("", new String[]{}, default_order); Log.d(getClass().getSimpleName(), "Full Database:"); for (DBNote note : notes) { Log.d(getClass().getSimpleName(), " "+note); @@ -209,7 +216,7 @@ public void debugPrintFullDB() { * @return List<Note> */ public List getNotes() { - return getNotesCustom(key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()}, key_modified + " DESC"); + return getNotesCustom(key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()}, default_order); } /** @@ -217,8 +224,8 @@ public List getNotes() { * * @return List<Note> */ - public List searchNotes(String query) { - return getNotesCustom(key_status + " != ? AND " + key_content + " LIKE ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}, key_modified + " DESC"); + public List searchNotes(CharSequence query) { + return getNotesCustom(key_status + " != ? AND " + key_content + " LIKE ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}, default_order); } /** @@ -238,6 +245,22 @@ public List getLocalModifiedNotes() { return getNotesCustom(key_status + " != ?", new String[]{DBStatus.VOID.getTitle()}, null); } + + public void toggleFavorite(DBNote note, ICallback callback) { + note.setFavorite(!note.isFavorite()); + note.setStatus(DBStatus.LOCAL_EDITED); + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(key_status, note.getStatus().getTitle()); + values.put(key_favorite, note.isFavorite()?"1":"0"); + db.update(table_notes, values, key_id + " = ?", new String[]{String.valueOf(note.getId())}); + db.close(); + if(callback!=null) { + serverSyncHelper.addCallbackPush(callback); + } + serverSyncHelper.scheduleSync(true); + } + /** * 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. @@ -251,9 +274,9 @@ public DBNote updateNoteAndSync(DBNote oldNote, String newContent, ICallback cal debugPrintFullDB(); DBNote newNote; if(newContent==null) { - newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), DBStatus.LOCAL_EDITED); + newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.isFavorite(), DBStatus.LOCAL_EDITED); } else { - newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNoteTitle(newContent), newContent, DBStatus.LOCAL_EDITED); + newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNoteTitle(newContent), newContent, oldNote.isFavorite(), DBStatus.LOCAL_EDITED); } SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); @@ -304,19 +327,21 @@ int updateNote(long id, OwnCloudNote remoteNote, DBNote forceUnchangedDBNoteStat values.put(key_title, remoteNote.getTitle()); values.put(key_modified, remoteNote.getModified(DATE_FORMAT)); values.put(key_content, remoteNote.getContent()); + values.put(key_favorite, remoteNote.isFavorite()); String whereClause; String[] whereArgs; if(forceUnchangedDBNoteState!=null) { // used by: NoteServerSyncHelper.SyncTask.pushLocalChanges() - // update only, if not modified locally during the synchronization, + // update only, if not modified locally during the synchronization + // (i.e. all (!) user changeable columns (content, favorite) should still have the same value), // uses reference value gathered at start of synchronization - whereClause = key_id + " = ? AND " + key_content + " = ?"; - whereArgs = new String[]{String.valueOf(id), forceUnchangedDBNoteState.getContent()}; + whereClause = key_id + " = ? AND " + key_content + " = ? AND " + key_favorite + " = ?"; + whereArgs = new String[]{String.valueOf(id), forceUnchangedDBNoteState.getContent(), forceUnchangedDBNoteState.isFavorite()?"1":"0"}; } else { // used by: NoteServerSyncHelper.SyncTask.pullRemoteChanges() - // update only, if not modified locally - whereClause = key_id + " = ? AND " + key_status + " = ? AND ("+key_modified+"!=? OR "+key_content+"!=?)"; - whereArgs = new String[]{String.valueOf(id), DBStatus.VOID.getTitle(), remoteNote.getModified(DATE_FORMAT), remoteNote.getContent()}; + // update only, if not modified locally (i.e. STATUS="") and if modified remotely (i.e. any (!) column has changed) + whereClause = key_id + " = ? AND " + key_status + " = ? AND ("+key_modified+"!=? OR "+key_content+"!=? OR "+key_favorite+"!=?)"; + whereArgs = new String[]{String.valueOf(id), DBStatus.VOID.getTitle(), remoteNote.getModified(DATE_FORMAT), remoteNote.getContent(), remoteNote.isFavorite()?"1":"0"}; } int i = db.update(table_notes, values, whereClause, whereArgs); db.close(); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index 0ae7d6195..6090f9163 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -229,10 +229,8 @@ private void pushLocalChanges() { if (remoteNote == null) { Log.d(getClass().getSimpleName(), " ...Note does not exist on server -> (re)create"); remoteNote = client.createNote(note); - dbHelper.updateNote(note.getId(), remoteNote, note); - } else { - dbHelper.updateNote(note.getId(), remoteNote, note); } + dbHelper.updateNote(note.getId(), remoteNote, note); break; case LOCAL_DELETED: if(note.getRemoteId()>0) { @@ -252,8 +250,8 @@ private void pushLocalChanges() { throw new IllegalStateException("Unknown State of Note: "+note); } } catch (IOException | JSONException e) { - // FIXME make some errors visible in the UI - e.printStackTrace(); + Log.e(getClass().getSimpleName(), "Exception", e); + exceptions.add(e); } } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java index c3068bc06..613cff013 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java @@ -30,6 +30,7 @@ public class NotesClient { private static final String key_id = "id"; private static final String key_title = "title"; private static final String key_content = "content"; + private static final String key_favorite = "favorite"; private static final String key_modified = "modified"; private static final String application_json = "application/json"; private String url = ""; @@ -47,6 +48,7 @@ private OwnCloudNote getNoteFromJSON(JSONObject json) throws JSONException { String noteTitle = ""; String noteContent = ""; Calendar noteModified = null; + boolean noteFavorite = false; if (!json.isNull(key_id)) { noteId = json.getLong(key_id); } @@ -58,10 +60,12 @@ private OwnCloudNote getNoteFromJSON(JSONObject json) throws JSONException { } if (!json.isNull(key_modified)) { noteModified = GregorianCalendar.getInstance(); - noteModified - .setTimeInMillis(json.getLong(key_modified) * 1000); + noteModified.setTimeInMillis(json.getLong(key_modified) * 1000); } - return new OwnCloudNote(noteId, noteModified, noteTitle, noteContent); + if (!json.isNull(key_favorite)) { + noteFavorite = json.getBoolean(key_favorite); + } + return new OwnCloudNote(noteId, noteModified, noteTitle, noteContent, noteFavorite); } public List getNotes() throws JSONException, IOException { @@ -92,6 +96,7 @@ private OwnCloudNote putNote(OwnCloudNote note, String path, String method) thr JSONObject paramObject = new JSONObject(); paramObject.accumulate(key_content, note.getContent()); paramObject.accumulate(key_modified, note.getModified().getTimeInMillis()/1000); + paramObject.accumulate(key_favorite, note.isFavorite()); JSONObject json = new JSONObject(requestServer(path, method, paramObject)); return getNoteFromJSON(json); } diff --git a/app/src/main/res/drawable-hdpi/ic_star_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_star_grey600_24dp.png new file mode 100644 index 000000000..57be19b93 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_star_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_star_outline_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_star_outline_grey600_24dp.png new file mode 100644 index 000000000..31c32ace6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_star_outline_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_star_outline_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_star_outline_white_24dp.png new file mode 100644 index 000000000..61133a2f6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_star_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_star_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_star_white_24dp.png new file mode 100644 index 000000000..298c7bab5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_star_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_sync_alert_grey600_18dp.png b/app/src/main/res/drawable-hdpi/ic_sync_alert_grey600_18dp.png new file mode 100644 index 000000000..dc1020eed Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_sync_alert_grey600_18dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_star_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_star_grey600_24dp.png new file mode 100644 index 000000000..3e05c1154 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_star_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_star_outline_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_star_outline_grey600_24dp.png new file mode 100644 index 000000000..56326e05a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_star_outline_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_star_outline_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_star_outline_white_24dp.png new file mode 100644 index 000000000..228c84825 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_star_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_star_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_star_white_24dp.png new file mode 100644 index 000000000..0cb2edb8f Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_star_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_sync_alert_grey600_18dp.png b/app/src/main/res/drawable-mdpi/ic_sync_alert_grey600_18dp.png new file mode 100644 index 000000000..e8152118c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_sync_alert_grey600_18dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_star_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_star_grey600_24dp.png new file mode 100644 index 000000000..0b13a3581 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_star_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_star_outline_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_star_outline_grey600_24dp.png new file mode 100644 index 000000000..3e7873f3c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_star_outline_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_star_outline_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_star_outline_white_24dp.png new file mode 100644 index 000000000..d609a927d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_star_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_star_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_star_white_24dp.png new file mode 100644 index 000000000..624b2288c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_star_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_sync_alert_grey600_18dp.png b/app/src/main/res/drawable-xhdpi/ic_sync_alert_grey600_18dp.png new file mode 100644 index 000000000..351b1ace8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_sync_alert_grey600_18dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_star_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_star_grey600_24dp.png new file mode 100644 index 000000000..541bf1b1b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_star_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_star_outline_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_star_outline_grey600_24dp.png new file mode 100644 index 000000000..080a1884b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_star_outline_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_star_outline_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_star_outline_white_24dp.png new file mode 100644 index 000000000..2ecd727ec Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_star_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png new file mode 100644 index 000000000..08dc9e933 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_sync_alert_grey600_18dp.png b/app/src/main/res/drawable-xxhdpi/ic_sync_alert_grey600_18dp.png new file mode 100644 index 000000000..049e1e3de Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_sync_alert_grey600_18dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_star_grey600_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_star_grey600_24dp.png new file mode 100644 index 000000000..7852689bb Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_star_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_star_outline_grey600_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_star_outline_grey600_24dp.png new file mode 100644 index 000000000..935ea0f54 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_star_outline_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_star_outline_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_star_outline_white_24dp.png new file mode 100644 index 000000000..0b3ab785f Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_star_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png new file mode 100644 index 000000000..2c3e1b257 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_sync_alert_grey600_18dp.png b/app/src/main/res/drawable-xxxhdpi/ic_sync_alert_grey600_18dp.png new file mode 100644 index 000000000..ebaeae63c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_sync_alert_grey600_18dp.png differ diff --git a/app/src/main/res/layout/fragment_notes_list_note_item.xml b/app/src/main/res/layout/fragment_notes_list_note_item.xml index 7ec659ec3..735f65bc2 100644 --- a/app/src/main/res/layout/fragment_notes_list_note_item.xml +++ b/app/src/main/res/layout/fragment_notes_list_note_item.xml @@ -1,57 +1,73 @@ + android:layout_height="wrap_content" + android:background="@drawable/list_item_background_selector" + > - - - + android:layout_alignParentTop="true" + android:layout_alignParentStart="true" + android:layout_alignParentLeft="true" + android:layout_marginTop="16dp" + android:layout_marginStart="16dp" + android:layout_marginLeft="16dp" + android:layout_marginEnd="70dp" + android:layout_marginRight="70dp" + android:ellipsize="marquee" + android:maxLines="1" + android:textColor="@drawable/list_item_color_selector" + android:textSize="16sp" /> - + - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_notes_list_section_item.xml b/app/src/main/res/layout/fragment_notes_list_section_item.xml index 56c492454..a3a64667e 100644 --- a/app/src/main/res/layout/fragment_notes_list_section_item.xml +++ b/app/src/main/res/layout/fragment_notes_list_section_item.xml @@ -11,6 +11,7 @@ android:layout_alignParentEnd="false" android:layout_alignParentRight="true" android:layout_alignParentStart="true" + android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_alignWithParentIfMissing="true" android:background="@color/bg_highlighted" @@ -24,9 +25,9 @@ android:textSize="12sp" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_note_list_view.xml b/app/src/main/res/menu/menu_note_list_view.xml index 693c9673e..2aa6866e4 100644 --- a/app/src/main/res/menu/menu_note_list_view.xml +++ b/app/src/main/res/menu/menu_note_list_view.xml @@ -1,6 +1,12 @@ + Kopieren Bearbeiten Abbrechen + Favorit Vorschau Teilen Über diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ecbc753e1..530a9fe79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,6 +18,7 @@ Copy Edit Cancel + Favorite Preview Share About