From 60b9e574ce994e1ba612292cad5cfed784568535 Mon Sep 17 00:00:00 2001 From: tom79 Date: Sat, 15 Jul 2017 14:59:09 +0200 Subject: [PATCH] Toots saved as drafts --- app/build.gradle | 1 + .../mastodon/activities/MediaActivity.java | 2 - .../mastodon/activities/TootActivity.java | 189 +++++++++++- .../client/Entities/StoredStatus.java | 94 ++++++ .../mastodon/drawers/DraftsListAdapter.java | 142 +++++++++ .../mastodon/fragments/SettingsFragment.java | 13 + .../gouv/etalab/mastodon/helper/Helper.java | 13 + .../gouv/etalab/mastodon/sqlite/Sqlite.java | 24 +- .../mastodon/sqlite/StatusStoredDAO.java | 281 ++++++++++++++++++ app/src/main/res/drawable-hdpi/ic_restore.png | Bin 0 -> 571 bytes .../main/res/drawable-hdpi/ic_schedule.png | Bin 0 -> 576 bytes app/src/main/res/drawable-ldpi/ic_restore.png | Bin 0 -> 494 bytes .../main/res/drawable-ldpi/ic_schedule.png | Bin 0 -> 465 bytes app/src/main/res/drawable-mdpi/ic_restore.png | Bin 0 -> 352 bytes .../main/res/drawable-mdpi/ic_schedule.png | Bin 0 -> 384 bytes .../main/res/drawable-xhdpi/ic_restore.png | Bin 0 -> 706 bytes .../main/res/drawable-xhdpi/ic_schedule.png | Bin 0 -> 772 bytes .../main/res/drawable-xxhdpi/ic_restore.png | Bin 0 -> 1039 bytes .../main/res/drawable-xxhdpi/ic_schedule.png | Bin 0 -> 1135 bytes .../main/res/drawable-xxxhdpi/ic_restore.png | Bin 0 -> 1415 bytes .../main/res/drawable-xxxhdpi/ic_schedule.png | Bin 0 -> 1527 bytes app/src/main/res/layout/drawer_draft.xml | 60 ++++ app/src/main/res/layout/fragment_settings.xml | 7 +- app/src/main/res/menu/main_toot.xml | 17 +- app/src/main/res/values-fr/strings.xml | 9 +- app/src/main/res/values/strings.xml | 10 +- 26 files changed, 852 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/StoredStatus.java create mode 100644 app/src/main/java/fr/gouv/etalab/mastodon/drawers/DraftsListAdapter.java create mode 100644 app/src/main/java/fr/gouv/etalab/mastodon/sqlite/StatusStoredDAO.java create mode 100644 app/src/main/res/drawable-hdpi/ic_restore.png create mode 100644 app/src/main/res/drawable-hdpi/ic_schedule.png create mode 100644 app/src/main/res/drawable-ldpi/ic_restore.png create mode 100644 app/src/main/res/drawable-ldpi/ic_schedule.png create mode 100644 app/src/main/res/drawable-mdpi/ic_restore.png create mode 100644 app/src/main/res/drawable-mdpi/ic_schedule.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_restore.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_schedule.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_restore.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_schedule.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_restore.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_schedule.png create mode 100644 app/src/main/res/layout/drawer_draft.xml diff --git a/app/build.gradle b/app/build.gradle index 30a5cef1a..06b143bad 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,4 +32,5 @@ dependencies { compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' compile 'com.evernote:android-job:1.1.11' compile 'com.github.chrisbanes:PhotoView:2.0.0' + compile 'com.google.code.gson:gson:2.8.0' } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MediaActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MediaActivity.java index 0010af654..50aaa5cb0 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MediaActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MediaActivity.java @@ -43,8 +43,6 @@ import android.widget.Toast; import android.widget.VideoView; import com.github.chrisbanes.photoview.OnMatrixChangedListener; -import com.github.chrisbanes.photoview.OnPhotoTapListener; -import com.github.chrisbanes.photoview.OnScaleChangedListener; import com.github.chrisbanes.photoview.PhotoView; import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.FileAsyncHttpResponseHandler; diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java index 762ef9b73..2771997da 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java @@ -34,6 +34,7 @@ import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.text.Html; import android.text.TextWatcher; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -75,12 +76,15 @@ import fr.gouv.etalab.mastodon.client.Entities.Attachment; import fr.gouv.etalab.mastodon.client.Entities.Error; import fr.gouv.etalab.mastodon.client.Entities.Mention; import fr.gouv.etalab.mastodon.client.Entities.Status; +import fr.gouv.etalab.mastodon.client.Entities.StoredStatus; import fr.gouv.etalab.mastodon.drawers.AccountsSearchAdapter; +import fr.gouv.etalab.mastodon.drawers.DraftsListAdapter; import fr.gouv.etalab.mastodon.helper.Helper; import fr.gouv.etalab.mastodon.interfaces.OnPostActionInterface; import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAttachmentInterface; import fr.gouv.etalab.mastodon.interfaces.OnRetrieveSearcAccountshInterface; import fr.gouv.etalab.mastodon.sqlite.AccountDAO; +import fr.gouv.etalab.mastodon.sqlite.StatusStoredDAO; import fr.gouv.etalab.mastodon.sqlite.Sqlite; import mastodon.etalab.gouv.fr.mastodon.R; @@ -117,6 +121,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc private Status tootReply = null; private String sharedContent, sharedSubject; private CheckBox toot_sensitive; + public long currentToId; private String pattern = "^.*(@([a-zA-Z0-9_]{2,}))$"; @Override @@ -133,7 +138,8 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc if( getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true); - + //By default the toot is not restored so the id -1 is defined + currentToId = -1; imageLoader = ImageLoader.getInstance(); options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false) .cacheOnDisk(true).resetViewBeforeLoading(true).build(); @@ -494,6 +500,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc @Override public boolean onOptionsItemSelected(MenuItem item) { + final SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); switch (item.getItemId()) { case android.R.id.home: finish(); @@ -513,6 +520,143 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc Toast.LENGTH_SHORT).show(); } return true; + case R.id.action_store: + storeToot(); + return true; + case R.id.action_restore: + try{ + final List drafts = new StatusStoredDAO(TootActivity.this, db).getAllDrafts(); + if( drafts == null || drafts.size() == 0){ + Toast.makeText(getApplicationContext(), R.string.no_draft, Toast.LENGTH_LONG).show(); + return true; + } + AlertDialog.Builder builderSingle = new AlertDialog.Builder(TootActivity.this); + builderSingle.setTitle(getString(R.string.choose_toot)); + final DraftsListAdapter draftsListAdapter = new DraftsListAdapter(TootActivity.this, drafts); + final int[] ids = new int[drafts.size()]; + int i = 0; + for(StoredStatus draft: drafts){ + ids[i] = draft.getId(); + i++; + } + builderSingle.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + builderSingle.setPositiveButton(R.string.delete_all, new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, int which) { + AlertDialog.Builder builder = new AlertDialog.Builder(TootActivity.this); + builder.setTitle(R.string.delete_all); + builder.setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogConfirm, int which) { + new StatusStoredDAO(getApplicationContext(), db).removeAllDrafts(); + dialogConfirm.dismiss(); + dialog.dismiss(); + } + }) + .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogConfirm, int which) { + dialogConfirm.dismiss(); + } + }) + .show(); + + } + }); + builderSingle.setAdapter(draftsListAdapter, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int id = ids[which]; + StoredStatus draft = new StatusStoredDAO(TootActivity.this, db).getStatus(id); + Status status = draft.getStatus(); + //Retrieves attachments + attachments = status.getMedia_attachments(); + toot_picture_container.removeAllViews(); + loading_picture.setVisibility(View.GONE); + if( attachments != null && attachments.size() > 0){ + toot_picture_container.setVisibility(View.VISIBLE); + int i = 0 ; + for(Attachment attachment: attachments){ + String url = attachment.getPreview_url(); + if( url == null || url.trim().equals("")) + url = attachment.getUrl(); + final ImageView imageView = new ImageView(getApplicationContext()); + imageView.setId(Integer.parseInt(attachment.getId())); + imageLoader.displayImage(url, imageView, options); + LinearLayout.LayoutParams imParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); + imParams.setMargins(20, 5, 20, 5); + imParams.height = (int) Helper.convertDpToPixel(100, getApplicationContext()); + imageView.setAdjustViewBounds(true); + imageView.setScaleType(ImageView.ScaleType.FIT_XY); + toot_picture_container.addView(imageView, i, imParams); + imageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showRemove(imageView.getId()); + } + }); + if( attachments.size() < 4) + toot_picture.setEnabled(true); + toot_sensitive.setVisibility(View.VISIBLE); + i++; + } + }else { + toot_picture_container.setVisibility(View.GONE); + } + //Sensitive content + toot_sensitive.setChecked(status.isSensitive()); + if( status.getSpoiler_text() != null && status.getSpoiler_text().length() > 0 ){ + toot_cw_content.setText(status.getSpoiler_text()); + toot_cw_content.setVisibility(View.VISIBLE); + }else { + toot_cw_content.setText(""); + toot_cw_content.setVisibility(View.GONE); + } + String content = status.getContent(); + toot_content.setText(content); + toot_content.setSelection(toot_content.getText().length()); + switch (status.getVisibility()){ + case "public": + visibility = "public"; + toot_visibility.setImageResource(R.drawable.ic_action_globe); + break; + case "unlisted": + visibility = "unlisted"; + toot_visibility.setImageResource(R.drawable.ic_action_lock_open); + break; + case "private": + visibility = "private"; + toot_visibility.setImageResource(R.drawable.ic_action_lock_closed); + break; + case "direct": + visibility = "direct"; + toot_visibility.setImageResource(R.drawable.ic_local_post_office); + break; + } + //The current id is set to the draft + currentToId = draft.getId(); + dialog.dismiss(); + } + }); + builderSingle.show(); + }catch (Exception e){ + Toast.makeText(getApplicationContext(), R.string.toast_error, Toast.LENGTH_LONG).show(); + } + return true; + + /*case R.id.action_schedule: + if(toot_content.getText().toString().trim().length() == 0 ){ + Toast.makeText(getApplicationContext(),R.string.toot_error_no_content, Toast.LENGTH_LONG).show(); + return true; + } + + return true;*/ default: return super.onOptionsItemSelected(item); } @@ -648,6 +792,15 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc dialog.show(); } + @Override + public void onPause(){ + super.onPause(); + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean storeToot = sharedpreferences.getBoolean(Helper.SET_AUTO_STORE, true); + if( storeToot) + storeToot(); + } + @Override public void onPostAction(int statusCode, API.StatusAction statusAction, String userId, Error error) { if( error != null){ @@ -700,4 +853,38 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc } } + private void storeToot(){ + //Nothing to store here.... + if(toot_content.getText().toString().trim().length() == 0 && (attachments == null || attachments.size() <1) && toot_cw_content.getText().toString().trim().length() == 0) + return; + + Status toot = new Status(); + toot.setSensitive(isSensitive); + toot.setMedia_attachments(attachments); + if( toot_cw_content.getText().toString().trim().length() > 0) + toot.setSpoiler_text(toot_cw_content.getText().toString().trim()); + toot.setVisibility(visibility); + toot.setContent(toot_content.getText().toString().trim()); + if( tootReply != null) + toot.setIn_reply_to_id(tootReply.getId()); + SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + try{ + if( currentToId == -1 ) { + currentToId = new StatusStoredDAO(TootActivity.this, db).insertStatus(toot, false, null); + + }else{ + StoredStatus storedStatus = new StatusStoredDAO(TootActivity.this, db).getStatus(currentToId); + if( storedStatus != null ){ + new StatusStoredDAO(TootActivity.this, db).updateStatus(currentToId, toot); + }else { //Might have been deleted, so it needs insertion + new StatusStoredDAO(TootActivity.this, db).insertStatus(toot, false, null); + } + } + + Toast.makeText(getApplicationContext(), R.string.toast_toot_saved, Toast.LENGTH_LONG).show(); + }catch (Exception e){ + Toast.makeText(getApplicationContext(), R.string.toast_error, Toast.LENGTH_LONG).show(); + } + } + } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/StoredStatus.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/StoredStatus.java new file mode 100644 index 000000000..c2924db9b --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/StoredStatus.java @@ -0,0 +1,94 @@ +package fr.gouv.etalab.mastodon.client.Entities; + +import java.util.Date; + + +/** + * Created by Thomas on 15/07/2017. + * Manage Stored status + */ + +public class StoredStatus { + + private int id; + private Date creation_date; + private Date scheduled_date; + private Date sent_date; + private boolean isScheduled; + private boolean isSent; + private Status status; + private String instance; + private String acct; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Date getCreation_date() { + return creation_date; + } + + public void setCreation_date(Date creation_date) { + this.creation_date = creation_date; + } + + public Date getScheduled_date() { + return scheduled_date; + } + + public void setScheduled_date(Date scheduled_date) { + this.scheduled_date = scheduled_date; + } + + public Date getSent_date() { + return sent_date; + } + + public void setSent_date(Date sent_date) { + this.sent_date = sent_date; + } + + public boolean isScheduled() { + return isScheduled; + } + + public void setScheduled(boolean scheduled) { + isScheduled = scheduled; + } + + public boolean isSent() { + return isSent; + } + + public void setSent(boolean sent) { + isSent = sent; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public String getInstance() { + return instance; + } + + public void setInstance(String instance) { + this.instance = instance; + } + + public String getAcct() { + return acct; + } + + public void setAcct(String acct) { + this.acct = acct; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/DraftsListAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/DraftsListAdapter.java new file mode 100644 index 000000000..cc2eca763 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/DraftsListAdapter.java @@ -0,0 +1,142 @@ +package fr.gouv.etalab.mastodon.drawers; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of Mastalab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Mastalab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Thomas Schneider; if not, + * see . */ + + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.List; + +import fr.gouv.etalab.mastodon.client.Entities.StoredStatus; +import fr.gouv.etalab.mastodon.helper.Helper; +import fr.gouv.etalab.mastodon.sqlite.Sqlite; +import fr.gouv.etalab.mastodon.sqlite.StatusStoredDAO; +import mastodon.etalab.gouv.fr.mastodon.R; + +import static fr.gouv.etalab.mastodon.helper.Helper.changeDrawableColor; + + +/** + * Created by Thomas on 15/07/2017. + * Adapter for toot drafts + */ +public class DraftsListAdapter extends BaseAdapter { + + private List storedStatuses; + private LayoutInflater layoutInflater; + private Context context; + private DraftsListAdapter draftsListAdapter; + + public DraftsListAdapter(Context context, List storedStatuses){ + this.storedStatuses = storedStatuses; + this.context = context; + layoutInflater = LayoutInflater.from(context); + draftsListAdapter = this; + } + + @Override + public int getCount() { + return storedStatuses.size(); + } + + @Override + public Object getItem(int position) { + return storedStatuses.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + + final StoredStatus draft = storedStatuses.get(position); + final ViewHolder holder; + if (convertView == null) { + convertView = layoutInflater.inflate(R.layout.drawer_draft, parent, false); + holder = new ViewHolder(); + holder.draft_title = (TextView) convertView.findViewById(R.id.draft_title); + holder.draft_date = (TextView) convertView.findViewById(R.id.draft_date); + holder.draft_delete = (ImageView) convertView.findViewById(R.id.draft_delete); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); + if( theme == Helper.THEME_DARK){ + changeDrawableColor(context, R.drawable.ic_cancel,R.color.dark_text); + }else { + changeDrawableColor(context, R.drawable.ic_cancel,R.color.black); + } + final SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + if(draft.getStatus() != null && draft.getStatus().getContent() != null ) { + if (draft.getStatus().getContent().length() > 20) + holder.draft_title.setText(draft.getStatus().getContent().substring(0, 20)); + else + holder.draft_title.setText(draft.getStatus().getContent()); + } + holder.draft_date.setText(Helper.dateToString(context, draft.getCreation_date())); + holder.draft_delete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setMessage(draft.getStatus().getContent() + '\n' + draft.getCreation_date()); + builder.setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(R.string.remove_draft) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + new StatusStoredDAO(context, db).remove(draft.getId()); + storedStatuses.remove(draft); + draftsListAdapter.notifyDataSetChanged(); + dialog.dismiss(); + } + }) + .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + + }) + .show(); + } + }); + return convertView; + } + + + private class ViewHolder { + TextView draft_title; + TextView draft_date; + ImageView draft_delete; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsFragment.java index 4a62686e6..62356da80 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsFragment.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsFragment.java @@ -62,6 +62,8 @@ public class SettingsFragment extends Fragment { boolean show_reply = sharedpreferences.getBoolean(Helper.SET_SHOW_REPLY, false); + boolean auto_store = sharedpreferences.getBoolean(Helper.SET_AUTO_STORE, true); + final CheckBox set_show_reply = (CheckBox) rootView.findViewById(R.id.set_show_reply); set_show_reply.setChecked(show_reply); @@ -74,6 +76,17 @@ public class SettingsFragment extends Fragment { } }); + final CheckBox set_auto_store = (CheckBox) rootView.findViewById(R.id.set_auto_store); + set_auto_store.setChecked(auto_store); + set_auto_store.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putBoolean(Helper.SET_AUTO_STORE, set_auto_store.isChecked()); + editor.apply(); + } + }); + boolean show_error_messages = sharedpreferences.getBoolean(Helper.SET_SHOW_ERROR_MESSAGES, true); final CheckBox set_show_error_messages = (CheckBox) rootView.findViewById(R.id.set_show_error_messages); set_show_error_messages.setChecked(show_error_messages); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java index ff3119b14..89b8a6b01 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java @@ -67,6 +67,7 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import com.google.gson.Gson; import com.loopj.android.http.BuildConfig; import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -108,6 +109,7 @@ import fr.gouv.etalab.mastodon.activities.WebviewActivity; import fr.gouv.etalab.mastodon.asynctasks.RemoveAccountAsyncTask; import fr.gouv.etalab.mastodon.client.Entities.Account; import fr.gouv.etalab.mastodon.client.Entities.Mention; +import fr.gouv.etalab.mastodon.client.Entities.Status; import fr.gouv.etalab.mastodon.client.Entities.Tag; import fr.gouv.etalab.mastodon.client.PatchBaseImageDownloader; import fr.gouv.etalab.mastodon.sqlite.AccountDAO; @@ -168,6 +170,7 @@ public class Helper { public static final String SET_THEME = "set_theme"; public static final String SET_TIME_FROM = "set_time_from"; public static final String SET_TIME_TO = "set_time_to"; + public static final String SET_AUTO_STORE = "set_auto_store"; public static final int ATTACHMENT_ALWAYS = 1; public static final int ATTACHMENT_WIFI = 2; public static final int ATTACHMENT_ASK = 3; @@ -1218,4 +1221,14 @@ public class Helper { return true; } } + + public static String statusToStringStorage(Status status){ + Gson gson = new Gson(); + return gson.toJson(status); + } + + public static Status restoreStatusFromString(String serializedStatus){ + Gson gson = new Gson(); + return gson.fromJson(serializedStatus, Status.class); + } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java index 6e2781942..21f5fc8d8 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java @@ -26,7 +26,7 @@ import android.database.sqlite.SQLiteOpenHelper; @SuppressWarnings("WeakerAccess") public class Sqlite extends SQLiteOpenHelper { - public static final int DB_VERSION = 1; + public static final int DB_VERSION = 2; public static final String DB_NAME = "mastodon_etalab_db"; public static SQLiteDatabase db; private static Sqlite sInstance; @@ -36,7 +36,8 @@ public class Sqlite extends SQLiteOpenHelper { */ //Table of owned accounts static final String TABLE_USER_ACCOUNT = "USER_ACCOUNT"; - + //Table of stored status + static final String TABLE_STATUSES_STORED = "STATUSES_STORED"; public static final String COL_USER_ID = "USER_ID"; @@ -69,6 +70,21 @@ public class Sqlite extends SQLiteOpenHelper { + COL_INSTANCE + " TEXT NOT NULL, " + COL_OAUTHTOKEN + " TEXT NOT NULL, " + COL_CREATED_AT + " TEXT NOT NULL)"; + public static final String COL_ID = "ID"; + public static final String COL_STATUS_SERIALIZED = "STATUS_SERIALIZED"; + public static final String COL_DATE_CREATION = "DATE_CREATION"; + public static final String COL_IS_SCHEDULED = "IS_SCHEDULED"; + public static final String COL_DATE_SCHEDULED = "DATE_SCHEDULED"; + public static final String COL_SENT = "SENT"; + public static final String COL_DATE_SENT = "DATE_SENT"; + + private static final String CREATE_TABLE_STATUSES_STORED = "CREATE TABLE " + TABLE_STATUSES_STORED + " (" + + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COL_USER_ID + " TEXT NOT NULL, " + COL_INSTANCE + " TEXT NOT NULL, " + + COL_STATUS_SERIALIZED + " TEXT NOT NULL, " + COL_DATE_CREATION + " TEXT NOT NULL, " + + COL_IS_SCHEDULED + " INTEGER NOT NULL, " + COL_DATE_SCHEDULED + " TEXT, " + + COL_SENT + " INTEGER NOT NULL, " + COL_DATE_SENT + " TEXT)"; + public Sqlite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); @@ -86,12 +102,14 @@ public class Sqlite extends SQLiteOpenHelper { @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_USER_ACCOUNT); - + db.execSQL(CREATE_TABLE_STATUSES_STORED); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { switch (oldVersion) { + case 1: + db.execSQL(CREATE_TABLE_STATUSES_STORED); default: break; } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/StatusStoredDAO.java b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/StatusStoredDAO.java new file mode 100644 index 000000000..332b95944 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/StatusStoredDAO.java @@ -0,0 +1,281 @@ +package fr.gouv.etalab.mastodon.sqlite; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of Mastalab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Mastalab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Thomas Schneider; if not, + * see . */ + +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import fr.gouv.etalab.mastodon.client.Entities.Status; +import fr.gouv.etalab.mastodon.client.Entities.StoredStatus; +import fr.gouv.etalab.mastodon.helper.Helper; + + +/** + * Created by Thomas on 15/07/2017. + * Manage Status storage in DB + */ +public class StatusStoredDAO { + + private SQLiteDatabase db; + public Context context; + + + public StatusStoredDAO(Context context, SQLiteDatabase db) { + //Creation of the DB with tables + this.context = context; + this.db = db; + } + + + //------- INSERTIONS ------- + + /** + * Insert a status in database + * @param status Status + * @return boolean + */ + public long insertStatus(Status status, boolean isScheduled, Date scheduled_date) + { + ContentValues values = new ContentValues(); + String serializedStatus = Helper.statusToStringStorage(status); + + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = Helper.getLiveInstance(context); + if( userId == null || instance == null) + return -1; + values.put(Sqlite.COL_STATUS_SERIALIZED, serializedStatus); + values.put(Sqlite.COL_DATE_CREATION, Helper.dateToString(context, new Date())); + values.put(Sqlite.COL_IS_SCHEDULED, isScheduled?1:0); + values.put(Sqlite.COL_INSTANCE, instance); + values.put(Sqlite.COL_USER_ID, userId); + values.put(Sqlite.COL_SENT, 0); + + if( isScheduled && scheduled_date != null) + values.put(Sqlite.COL_DATE_SCHEDULED, Helper.dateToString(context, scheduled_date)); + //Inserts stored status + long last_id; + try{ + last_id = db.insert(Sqlite.TABLE_STATUSES_STORED, null, values); + }catch (Exception e) { + last_id = -1; + } + return last_id; + } + + //------- UPDATES ------- + + /** + * Update a Status in database + * @param status Status + * @return boolean + */ + public int updateStatus(long id, Status status ) { + ContentValues values = new ContentValues(); + + String serializedStatus = Helper.statusToStringStorage(status); + values.put(Sqlite.COL_STATUS_SERIALIZED, serializedStatus); + values.put(Sqlite.COL_DATE_CREATION, Helper.dateToString(context, new Date())); + return db.update(Sqlite.TABLE_STATUSES_STORED, + values, Sqlite.COL_ID + " = ? ", + new String[]{String.valueOf(id)}); + } + + /** + * Update scheduled date for a Status in database + * @param scheduled_date Date + * @return boolean + */ + public int updateScheduledDate(long id, Date scheduled_date) { + ContentValues values = new ContentValues(); + values.put(Sqlite.COL_DATE_SCHEDULED, Helper.dateToString(context, scheduled_date)); + return db.update(Sqlite.TABLE_STATUSES_STORED, + values, Sqlite.COL_ID + " = ? ", + new String[]{String.valueOf(id)}); + } + + /** + * Update date when task is done for a scheduled Status in database + * @param date_sent Date + * @return boolean + */ + public int updateScheduledDone(long id, Date date_sent) { + ContentValues values = new ContentValues(); + values.put(Sqlite.COL_DATE_SENT, Helper.dateToString(context, date_sent)); + values.put(Sqlite.COL_SENT, 1); + return db.update(Sqlite.TABLE_STATUSES_STORED, + values, Sqlite.COL_ID + " = ? ", + new String[]{String.valueOf(id)}); + } + + //------- REMOVE ------- + + /*** + * Remove stored status by id + * @return int + */ + public int remove(long id){ + return db.delete(Sqlite.TABLE_STATUSES_STORED, Sqlite.COL_ID + " = \"" + id + "\"", null); + } + + public int removeAllDrafts(){ + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = Helper.getLiveInstance(context); + return db.delete(Sqlite.TABLE_STATUSES_STORED, Sqlite.COL_IS_SCHEDULED + " = \"0\" AND " + Sqlite.COL_USER_ID + " = '" + userId+ "' AND " + Sqlite.COL_INSTANCE + " = '" + instance+ "'", null); + } + + //------- GETTERS ------- + + /** + * Returns all stored Statuses in db + * @return stored status List + */ + public List getAllStatus(){ + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = Helper.getLiveInstance(context); + try { + Cursor c = db.query(Sqlite.TABLE_STATUSES_STORED, null, Sqlite.COL_USER_ID + " = '" + userId+ "' AND " + Sqlite.COL_INSTANCE + " = '" + instance+ "'", null, null, null, Sqlite.COL_DATE_CREATION + " DESC", null); + return cursorToListStatuses(c); + } catch (Exception e) { + return null; + } + } + + /** + * Returns all stored Statuses in db + * @return stored status List + */ + public List getAllDrafts(){ + try { + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = Helper.getLiveInstance(context); + Cursor c = db.query(Sqlite.TABLE_STATUSES_STORED, null, Sqlite.COL_USER_ID + " = '" + userId+ "' AND " + Sqlite.COL_INSTANCE + " = '" + instance+ "' AND " + Sqlite.COL_IS_SCHEDULED + " = 0", null, null, null, Sqlite.COL_DATE_CREATION + " DESC", null); + return cursorToListStatuses(c); + } catch (Exception e) { + return null; + } + } + + /** + * Returns all not sent Statuses in db + * @return stored status List + */ + public List getAllNotSent(){ + try { + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = Helper.getLiveInstance(context); + Cursor c = db.query(Sqlite.TABLE_STATUSES_STORED, null, Sqlite.COL_USER_ID + " = '" + userId+ "' AND " + Sqlite.COL_INSTANCE + " = '" + instance+ "' AND " +Sqlite.COL_IS_SCHEDULED + " = 1 AND " + Sqlite.COL_SENT + " = 0", null, null, null, Sqlite.COL_DATE_CREATION + " DESC", null); + return cursorToListStatuses(c); + } catch (Exception e) { + return null; + } + } + + /** + * Returns all sent Statuses in db + * @return stored status List + */ + public List getAllSent(){ + try { + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = Helper.getLiveInstance(context); + Cursor c = db.query(Sqlite.TABLE_STATUSES_STORED, null, Sqlite.COL_USER_ID + " = '" + userId+ "' AND " + Sqlite.COL_INSTANCE + " = '" + instance+ "' AND " +Sqlite.COL_IS_SCHEDULED + " = 1 AND " + Sqlite.COL_SENT + " = 1", null, null, null, Sqlite.COL_DATE_CREATION + " DESC", null); + return cursorToListStatuses(c); + } catch (Exception e) { + return null; + } + } + + /** + * Returns a stored status by id in db + * @return stored status StoredStatus + */ + public StoredStatus getStatus(long id){ + try { + Cursor c = db.query(Sqlite.TABLE_STATUSES_STORED, null, Sqlite.COL_ID + " = '" + id + "'", null, null, null, null, null); + return cursorToStoredStatus(c); + } catch (Exception e) { + return null; + } + } + + + + /*** + * Method to hydrate Stored statuses from database + * @param c Cursor + * @return StoredStatus + */ + private StoredStatus cursorToStoredStatus(Cursor c){ + //No element found + if (c.getCount() == 0) + return null; + //Take the first element + c.moveToFirst(); + //New user + StoredStatus storedStatus = new StoredStatus(); + storedStatus.setId(c.getInt(c.getColumnIndex(Sqlite.COL_ID))); + Status status = Helper.restoreStatusFromString(c.getString(c.getColumnIndex(Sqlite.COL_STATUS_SERIALIZED))); + storedStatus.setStatus(status); + storedStatus.setSent(c.getInt(c.getColumnIndex(Sqlite.COL_SENT)) == 1); + storedStatus.setScheduled(c.getInt(c.getColumnIndex(Sqlite.COL_IS_SCHEDULED)) == 1); + storedStatus.setCreation_date(Helper.stringToDate(context, c.getString(c.getColumnIndex(Sqlite.COL_DATE_CREATION)))); + storedStatus.setScheduled_date(Helper.stringToDate(context, c.getString(c.getColumnIndex(Sqlite.COL_DATE_SCHEDULED)))); + storedStatus.setSent_date(Helper.stringToDate(context, c.getString(c.getColumnIndex(Sqlite.COL_DATE_SENT)))); + //Close the cursor + c.close(); + //Stored status is returned + return storedStatus; + } + + /*** + * Method to hydrate stored statuses from database + * @param c Cursor + * @return List + */ + private List cursorToListStatuses(Cursor c){ + //No element found + if (c.getCount() == 0) + return null; + List storedStatuses = new ArrayList<>(); + while (c.moveToNext() ) { + //Restore the status + StoredStatus storedStatus = new StoredStatus(); + storedStatus.setId(c.getInt(c.getColumnIndex(Sqlite.COL_ID))); + Status status = Helper.restoreStatusFromString(c.getString(c.getColumnIndex(Sqlite.COL_STATUS_SERIALIZED))); + storedStatus.setStatus(status); + storedStatus.setSent(c.getInt(c.getColumnIndex(Sqlite.COL_SENT)) == 1); + storedStatus.setScheduled(c.getInt(c.getColumnIndex(Sqlite.COL_IS_SCHEDULED)) == 1); + storedStatus.setCreation_date(Helper.stringToDate(context, c.getString(c.getColumnIndex(Sqlite.COL_DATE_CREATION)))); + storedStatus.setScheduled_date(Helper.stringToDate(context, c.getString(c.getColumnIndex(Sqlite.COL_DATE_SCHEDULED)))); + storedStatus.setSent_date(Helper.stringToDate(context, c.getString(c.getColumnIndex(Sqlite.COL_DATE_SENT)))); + storedStatuses.add(storedStatus); + } + //Close the cursor + c.close(); + //Statuses list is returned + return storedStatuses; + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_restore.png b/app/src/main/res/drawable-hdpi/ic_restore.png new file mode 100644 index 0000000000000000000000000000000000000000..8d3257cbb295f4741c8583c70498b411e0a4d08b GIT binary patch literal 571 zcmV-B0>u4^P)MjchB~h313C6UkMUW1?HB?0@t&RlPBe8B9&6BZe3j{+ z@DdpbP6nE=B)teK%t1vs;KU1b8ch2Ye#rHDUfzwd}F;@1sY&$JY^y(kiZ1%A5@@4wwi5% zT3P?70!7$z+XR)cey9S)FNM=4$SE(31}a5ZaiezVi;18ixO5r4dCvk?rPhO8{KRXe z=Rg;Eu?VBk3bZQsZ<$JKW`dqYQ(2MTAvG#t{f~m$nL4Gjvet?>_oZUNdia(xGjIpF{R002ov JPDHLkV1kQn{B8gM literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_schedule.png b/app/src/main/res/drawable-hdpi/ic_schedule.png new file mode 100644 index 0000000000000000000000000000000000000000..94e5d7d6da0b5c356c489f14a897db40aeb9c4b5 GIT binary patch literal 576 zcmV-G0>AxBz$K>8fH++;ic0!7jcdoZid;XLT@lq zqR_}G!j`a-Cdz;iX0j$=UYH4erPB6kJa|3EF{H4!CGBbjF(J9}T#96XBZXazTjr0d z?f1lvUL`~;M0mgSmAggJAqBSr3t3XXRyVxL;FKo7AVi)Sj#+YpXACxk5^Cg_6E|pA zEFMaziepl4@Pol8qmlo0Wp|X?$fw ztWVNzvIk>s6sa<52K{TB!ics~K4N(^7RxxL(09DidvT0KG%M}*AM6_;1-@)zoTsS( O0000IQiL literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-ldpi/ic_restore.png b/app/src/main/res/drawable-ldpi/ic_restore.png new file mode 100644 index 0000000000000000000000000000000000000000..b671bcc155e12ae850503a2bc0ee96d1dd1e9eec GIT binary patch literal 494 zcmV3VP17b?O>QU=s)$a)VADy`fshcZLHrF?YGP|5ECykaFjS&{0BKN*u+?Wn zjGb9;{a#L!`<(Zj_naEWe|(l@1wa#sfG`MwhMyStlF4LysZ^S+ zR4SX`5bT2`VncA@FHH~y5s#B}W!v^6zc=6r6q%R7J^VVl4lffwqG`l;B%U*#NTpIS zDr|;}GLI8qfq!6}m#W>iV?ZVlNd?D#9N&g*XFJa#V{;(lzL-8S&eQ@hFQv$x(bVAvuY-46+P6iTNEy z1H?`3JY&S984K@$YPD+Ox=Y~M{`g)PUla<3w3w-FNC&g%x|s)DjZ`p>=m7Dnw4CuI zF}ZvOh9=^EumM+un-Nai+LW{PT?Dtl7%dNqNv)e&w??E9@NF3HxE{7jib%^5OHzy6 zE5~9!Oj_aQRr@n_?bpp%X$#@0+M<3T6L71Z7gB#`h|(ln${9%cE>C35^NDyo+C10; ki^K-M^=SX}Z{#=M8%?!-)FvY9{{R3007*qoM6N<$g4Sf*R{#J2 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-ldpi/ic_schedule.png b/app/src/main/res/drawable-ldpi/ic_schedule.png new file mode 100644 index 0000000000000000000000000000000000000000..2a6e7ee365c42bdfa17c99c583f9d33e24cd342b GIT binary patch literal 465 zcmV;?0WSWDP)oHB!ux)!MilY5Ej?aM`U;vyU*WnsDOCOMGARl7W zQ<5YTV21byjG03mfMb!nOymUQFJOudL;QA;ZxM`<^SK6mXsuST!!MD@8#Z)hp$=>V z>x`Mhngh-|@X0S*uqnc$zN8C6g9M5oz5`Ar#seGRxZ(!fI~QVM<$sthCf-ft#=r&Q zjchYybAr8lb0Kzw#iVaq$XKS(3vihVDPJd7&FE)^*xnh7nyXzZbcMCeTyt9L=wKD0 zU7Y-klgD@;XX{ZVRBBBd+Q{Ex{T1(f3dovQu*Qj1vHh~iMQ#h6BX%|n!|PtJ*GBwM zD7Lx~sHINY{+!JJEc45Y9Kk?2Zej5E8k{WMV#O6n>!?MQFYC(*$^=;sh+@B=HXD^Tin}Mpd=eOBeKm1XhtA$9!^Y z=7Cn*b`K#<$H!b{OZO6Q0`?X^jyH;T+>0%)DOyg+#D}cA;%hoH=&Lr_PzMFI8kk~+ yY-c^*=YH;ZcbvPYpc)tYbz(;JXfNFA-;Hm8MJX%yRNC190000RyrVh$S|IF-%M3SFY>bk*l2YRBH?wacA0+9k_H{?|HD1^?)+$`rcf`N7=&=QwABJ ei$m4)a>pO6nN3_KaQn>w00007T;6HubRK(p~=f&}KZWO{0PrMstlNW8dJm)-8_dS#?c^Zx7HJV)jd3@oe>zLGRX9sR!Lkd;1EufLIB$8!x83VZ=D{T7;JM92> zc*$deB_%==p0NE58^CeNeI0C4xlTg#B>^%V{bh@%kKZB+)F=5|s7%lE*6vfxhN4<` zfVcaM84#V?Nu?V-z{3=uZUdl$^S<2~P~daXrUzUSCUY7mCV+U=4)9Ow3E!>B?2anip zN*XVTu_0X+`Bv^j?W4N+M<#frQ8z2;6Bz}d`&H3hfCv?v2vfqEbrb?m(H$;r}S*ePanI#?$X&6HC&)IsfbX&rNP;N{kF0avJD%+apYQ_bn6Q(LMym@l`w zPYO{3HHNlw?eE~&W>G(NoJq1&nK`Z>AhkG$7aG?Ah3S->_h|s3?xjzt8GN8Cbsekt oM*B03!MNsZACOY3fd2sS8;TK_HN@AgSO5S307*qoM6N<$g5O<2LjV8( literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_schedule.png b/app/src/main/res/drawable-xhdpi/ic_schedule.png new file mode 100644 index 0000000000000000000000000000000000000000..1b5458a776e9ada2d2093c4b7359da556d17dd58 GIT binary patch literal 772 zcmV+f1N;1mP)6s~EK)@I=&4-_8@w_*{bwO#oK#En96p->vyotqMEb)(>`b)_P;sM`jk78EJ? zC#2T8(8l5$wGF{WZQ{buQ+n>?#yfLo=H87$Paq`s`{p}y&zy5+rm1Po{sTiZx^V`R zc!3gr(Wiu2Jirk4BNASGC$8WVRQ4X1ur0WF2DfRxI;&v}$sqE_QHH54qQ_1=hC2-c zOPD|w9Y`WhpA`MdB2Q1!rLl^kL4$d@SdAXJ429i9!E)5j%%v zmka${xPtQvyN%ws9mRT}bmU`#`V?u4e47^Idi;rHG5fS-N;_|{fX!;!Ehhbt2WJ&j z2Fh8B8|tcc%4$%-@Nq!sNgiEQR5wONVnzcwh>0*=l)({vWVlx;;p>3#EGxXjIxpDG z;5T)-XdNU_VUVr|7P07GvB{5kR3zZExeJniCdM7nk;etKh<3x^%!@JC z_4FvF1bvxK3ceXdnKLY64?L2!580u1%5|hI(pH{O?7;mZF<0!N(czchV!YvKUZK5q zfNDKM?YM=$yXap*R4wO@(d-+Rd(rp;@&$*3+;Nh0si&7oSog-nORK>B#=M?2}Gqp zM-D2{j7J#67-BTW=)Z@!j#Jn~ziM8G2ERi`9@_8)PBMyCZ1;xc4<>|i3_w)+QHI3% z1S-ZSc$Hxso*$qLbi%JZz*^NnMfe68nZN;cKsuT%UowRb)S(cW2-8YQq`#TigL;~e zDa+@tI4^QhoJF`}Vf;BtkZh}yf@64Lp>>O_NdoB>I9>bv74i(wxHCWm27bwH9vmPVWS1?V(6VA?8>mcB6sMCP6#P?yd z6i*Ft$p=ue!C9%$Bu1LSaYbH%Lio%r<@HiHGh-vSyg!=HIA`^8?t3a6pZzBh7o^ji z%~CHwI(MRXxomV%pm_8!R^J;Vl9Os4_bqN=P!veRF3#>YaiBKtkkFI?X*An&(jp3^ zQ!+XyB}#$H7|SEP)9jUUxD!2DDNrh=8SB|%K$VP2hrmm`0BKYd*z8_=m1ahzPB~D7 zv3^Yqi2a5_Evdmm+B1#+uj+e z)@5DPQWD3?eXNoXjP^(u1s3Z{+~ag6$zGC6rtW%lSR-8!PE&W_EvGp^r8WOVmremy literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_schedule.png b/app/src/main/res/drawable-xxhdpi/ic_schedule.png new file mode 100644 index 0000000000000000000000000000000000000000..293905e7acc45b47b5e5421b18520249541ed9e0 GIT binary patch literal 1135 zcmV-#1d#iQP)N zER&$iTZx$!CZ~BF<{eItk=giFSG7}LIwy=fmKQ+W5 z9|v&>9e4{b{P5Aw9<-whMaY5sUrI^A9z4Vd%*=-tY)6vwVpgIN-yv%wXux9U#N^O~ zs8}nO*e@m(*PvsUk!+`&E%*#w`-Bblh#8Ly219&6HMSuavygyj`Xtb|JQSb?eFh$9 z5Und`677~i8$cN{O#L!x@WCLScM+!}CmGKJDBsgtnIPrurj6el;Qs`P>Tl;oKy)d# z7V;2_5(=84q&T&;BTm*!%u#xcA=b_q@@Porcyw~yaygDm zp}x>NOI{FR=W;=yMMlnY?iq?DJS+$zbScNbD73?EE-Jfi7Zgi5@UJz5f=}hjuL7wd zED0ifAy>jB$%6+ZbCy*>ZZ7SbKQtaN&@#yZED9ozv&<;>&F7ch0~wA9%3?{ zUNPmDG`)2}k?3be$}t8WU|`kG2_m1X%t-rO3EU|`C+!!cH)WvBoNDis=`HG? z_60>U?^bqEv)evTTF_d&=0tTO->z+c(Q4^2*25jaFemCJW=aRf3c1u#W3hyvK_XZi z#xdix$+Z#tj7f7DSf4^)u|}A@9&9v7t{)lHDznmw-T0YG4ftVv724+60jO-r@`IqQ z8tv{Mj_GLNB>8b%K%8U#+iXs@lYtJZAMRo`x)`QXp$zm&R~1O+iu~;weQFF2{hqvc zh~@sXi+g*aPK$7hf%o!M3X^w3JXSy)ci?v;G|5tK{!6qw`fTC%6CD2n8HzUUm8RQz zygZM)0ce(U;+yGLTE~<;^F(f`k7A*$;}uR)olVCvQ9?MVl6OtP5a;T#MG$}9?X`3@ zgH6H#oJaFWFDl^peZ5tRa)F>8@fvHVL0~&=og^Af0!b(4D(-;-@?~Kf-CFs&XgQ-ab<)!}MOZD&HL{$#PTE zRD>$D;|+xYA9W7{=*B}-U@ul+d_>s77PkM?egHa&_9%Z(&ldmy002ovPDHLkV1f?o B3yA;# literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_restore.png b/app/src/main/res/drawable-xxxhdpi/ic_restore.png new file mode 100644 index 0000000000000000000000000000000000000000..ea203ff3b33931e2585fb8907b1110024cdf0b04 GIT binary patch literal 1415 zcmV;21$g?2P)W?BuL;GD8~J0#=AI#Q|LlJGPL?|2`6v>d(ebh6e4i30=Ql?uo++A2J)c~ zpJF4XjU@slSchZK!w|m48l*;_z-@RFS7D0Z@Cqi39)VlXfjRt|G_yw-; z6CRG2Kq)?eYqZi6jfub<+A$wEiCJ+Fcmh2+%^1W{Y)1p?P=yM5yNl>&75%mrZ!GqTVNg4N}FIv6XgO-TAhlTN)vnd=Y4r zsO-TO3TC>}ZY9dS5(@P`2s|lydG=wd@p+ntPb4qW64wOg2#;<*Jr&z{@~kj_x`umP z5-7!4fs*r>>m(hShYJGfCvwLHJr$y`b;drU{BKnYH>lMWfu%z8Cp%5XrwA!5azP*^ z$a&5?-2MNbLMNx^tUS$A7raeybmuxZKROHiao%K0po07Xi$*Wz53goU*A-c0R~4BS z_Lm;*iZ-+pFlfiSiV9%xnH5sx%mNe?G3!4)qd1I z#GzyJ1Hm1?QjX)%jfPIm5AZmC7TotaW-AeBV(x2G@j{_P^P_{^{_|dego{{%aazgTI@LH3IyFC9kK(jI&NsMMddDO#3e8s!WVtE4X4J4M z#3sQPkfP5+c=3`1l{d)bH zIP_rt@EVWitl5f>m2?l`E|a2uq?p$ows0%){@;hRAJu}tmT|))Uzs+xLNDe=s}{%b zs#04ESZ&@~3_Y1Yyv7M-Tp?-(*HQP z1y0PLFL-%|Yp|3r0_m(z9_t-=9?|&I@A=vQF3gW`NV{j39kx1zY2yGE`x-e*3)J;e z#4gj8R7OcdpV0gaR{0t^yH7eXcbUwYtdY)z(s|zuUp~5g0;{-OJ^LfzUaZLDUMEqt z)kcNA3mcp48-L0?Mwe=+TM4HlvaJMn#vnaAkpE>^X}#&66d z7v}@VbsCU%t}lf>6enF`2)p!#@$JLUBH@gc8P4Fr_@~#SX2}(6=@ja5So2I|@Fkui4`r;?eLjCTo~N{;H?Qf}DSdbjciQUnRhAS` zUDZg5a65iPC(#+Cpw)v5G Vh$6ShW$yq0002ovPDHLkV1f`~lTH8t literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_schedule.png b/app/src/main/res/drawable-xxxhdpi/ic_schedule.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4940682fd9c291a3b46e910043d61d94442d3b GIT binary patch literal 1527 zcmVoY+F#p8%3Kg8fZd4%k zc2PZv&ioTPWnE0*W30gJXqCSUbr^yl3}8LdqftH`Z{Z?rV1({QO1R3;!Wx{1O`O4V z;i;cXF(wEc!Tn*V|1z#1D2(HUV3kiri-I$>q5C4rh#CFW_#d$7vP_-mC!PcihS5WReU3k8^W^r!k zO(Hfi<&hdtDQI)lo1|MHSrn-Oiv?|d_9pp5Ajyu@fQ5oK!`>ufTb&ZA0jc6QYu+Sc zE0Gkb0ZC$8^*=Bm02jhxvur-yA8Wf%0GD`r9QK~cSKB6+IDjzv-sGBuFcn=Fo!NM$9cSj zSw_WNt$sN@rW0=f8%skT}2-21{amyoO~qD>$jv%OV?XJ$B4H|}6hWMW*< zqD7s32|J;B$1-L((-R6Q40uYN`E0g-IGQ-%85)s?&*dkJEH>KhS4(|vGcjO?69~we zB%7r8kjZ~dcewp0OFNG_ApwuGvF-)!0Y4hq8T5p+tV)O1$7F9b>4nZRHU~m6;1H8L zs2o6NFnUpvHERM_zg8l((v;azNdZ*6Rg}s|mqebM_?p*9ChQkT zt9YOn%Pgg>J{SJ;Y@JZP%dYwqn*0gDd@35`Tf9Sfz@~>5xvtxfViPiqcxFGlS~ z5DIKxvit;Z$XvuWXuP86WtX#?A?(?cI#jrgTR%ti@X*(l5#m(YSI zwI&S#J5&V~1*eo$@4`-$M&vXK$*u%-*ozMQLcTqLNnEGjzvCx-K?ZKba-{)zj2JOu d#E7AYe*kmWNR4izo)G{5002ovPDHLkV1n?B(j@=@ literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/drawer_draft.xml b/app/src/main/res/layout/drawer_draft.xml new file mode 100644 index 000000000..7c099460c --- /dev/null +++ b/app/src/main/res/layout/drawer_draft.xml @@ -0,0 +1,60 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 6e8f6abf8..b231779a3 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -53,7 +53,12 @@ android:text="@string/set_display_reply" android:layout_height="wrap_content" /> - + + app:showAsAction="ifRoom" /> + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 2601bdbd5..41a8f0206 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -28,6 +28,7 @@ Tags Jeton Sauvegarder + Restaurer Authentification en deux étapes ? Autre instance que mastodon.etalab.gouv.fr ? Aucun résultat ! @@ -41,6 +42,8 @@ Microphone Veuillez dire quelque chose Désolé ! Votre appareil ne supporte pas la commande vocale ! + Tout effacer + Programmer Accueil Fil public local @@ -149,7 +152,9 @@ N\'afficher que pour vos abonné(e)s N\'afficher que pour les personnes mentionnées - + Aucun brouillon ! + Choisissez un pouet + Supprimer le brouillon ? Aucune description ! @@ -215,6 +220,7 @@ Aucune action ne peut être réalisée Le média a été enregistré ! Une erreur est survenue lors de la traduction ! + Brouillon enregistré ! Optimisation du chargement Nombre de pouets par chargement @@ -229,6 +235,7 @@ Charger les images sensibles Afficher le message précédent lors d\'une réponse Destination : + Enregistrer les brouillons automatiquement Gestion des notifications Notifier lorsque quelqu’un me suit diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1b34884aa..03cb96e3e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ Tags Token Save + Restore Two-step authentication? Other instance than mastodon.etalab.gouv.fr? No results! @@ -42,7 +43,9 @@ Microphone Please, say something Sorry! Your device does not support the voice input! + Delete all + Schedule Home Local timeline @@ -152,6 +155,9 @@ Post to mentioned users only + No draft! + Choose a toot + Remove draft? No description available! @@ -218,6 +224,8 @@ No action can be taken The media has been saved! An error occurred while translating! + Draft saved! + Optimisation of loading Number of toots per load @@ -232,7 +240,7 @@ Sensitive content Display previous message in responses Path: - + Save drafts automatically Manage notifications Notify when someone follows you Notify when someone requests to follow you