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 000000000..8d3257cbb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_restore.png differ 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 000000000..94e5d7d6d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_schedule.png differ 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 000000000..b671bcc15 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_restore.png differ 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 000000000..2a6e7ee36 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_schedule.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_restore.png b/app/src/main/res/drawable-mdpi/ic_restore.png new file mode 100644 index 000000000..b33a842eb Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_restore.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_schedule.png b/app/src/main/res/drawable-mdpi/ic_schedule.png new file mode 100644 index 000000000..3d2bf02b4 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_schedule.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_restore.png b/app/src/main/res/drawable-xhdpi/ic_restore.png new file mode 100644 index 000000000..e64a34918 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_restore.png differ 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 000000000..1b5458a77 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_schedule.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_restore.png b/app/src/main/res/drawable-xxhdpi/ic_restore.png new file mode 100644 index 000000000..2cf7c0458 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_restore.png differ 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 000000000..293905e7a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_schedule.png differ 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 000000000..ea203ff3b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_restore.png differ 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 000000000..4c4940682 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_schedule.png differ 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