From c327bd893824ebdfee3392d004b2d62accbf45d8 Mon Sep 17 00:00:00 2001 From: tom79 Date: Sun, 16 Jul 2017 17:09:35 +0200 Subject: [PATCH] Schedule toots --- .../mastodon/activities/MainActivity.java | 8 +- .../mastodon/activities/TootActivity.java | 118 +++++++++++- .../RetrieveScheduledTootsAsyncTask.java | 58 ++++++ .../drawers/ScheduledTootsListAdapter.java | 175 ++++++++++++++++++ .../DisplayScheduledTootsFragment.java | 95 ++++++++++ .../gouv/etalab/mastodon/helper/Helper.java | 3 + .../OnRetrieveScheduledTootsInterface.java | 26 +++ .../etalab/mastodon/jobs/ApplicationJob.java | 6 + .../mastodon/jobs/ScheduledTootsSyncJob.java | 86 +++++++++ .../mastodon/sqlite/StatusStoredDAO.java | 70 +++++-- .../res/drawable-hdpi/ic_schedule_black.png | Bin 0 -> 459 bytes .../res/drawable-ldpi/ic_schedule_black.png | Bin 0 -> 289 bytes .../res/drawable-mdpi/ic_schedule_black.png | Bin 0 -> 308 bytes .../res/drawable-xhdpi/ic_schedule_black.png | Bin 0 -> 578 bytes .../res/drawable-xxhdpi/ic_schedule_black.png | Bin 0 -> 851 bytes .../drawable-xxxhdpi/ic_schedule_black.png | Bin 0 -> 1129 bytes app/src/main/res/layout/datetime_picker.xml | 74 ++++++++ app/src/main/res/layout/drawer_draft.xml | 1 - .../main/res/layout/drawer_scheduled_toot.xml | 91 +++++++++ .../res/layout/fragment_scheduled_toots.xml | 77 ++++++++ .../main/res/menu/activity_main_drawer.xml | 4 + app/src/main/res/menu/main_toot.xml | 4 +- app/src/main/res/values-fr/strings.xml | 12 ++ app/src/main/res/values/strings.xml | 14 ++ 24 files changed, 895 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveScheduledTootsAsyncTask.java create mode 100644 app/src/main/java/fr/gouv/etalab/mastodon/drawers/ScheduledTootsListAdapter.java create mode 100644 app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayScheduledTootsFragment.java create mode 100644 app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveScheduledTootsInterface.java create mode 100644 app/src/main/java/fr/gouv/etalab/mastodon/jobs/ScheduledTootsSyncJob.java create mode 100644 app/src/main/res/drawable-hdpi/ic_schedule_black.png create mode 100644 app/src/main/res/drawable-ldpi/ic_schedule_black.png create mode 100644 app/src/main/res/drawable-mdpi/ic_schedule_black.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_schedule_black.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_schedule_black.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_schedule_black.png create mode 100644 app/src/main/res/layout/datetime_picker.xml create mode 100644 app/src/main/res/layout/drawer_scheduled_toot.xml create mode 100644 app/src/main/res/layout/fragment_scheduled_toots.xml diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java index 426b12b20..18aab9efc 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java @@ -58,6 +58,7 @@ import fr.gouv.etalab.mastodon.client.PatchBaseImageDownloader; import fr.gouv.etalab.mastodon.fragments.DisplayAccountsFragment; import fr.gouv.etalab.mastodon.fragments.DisplayFollowRequestSentFragment; import fr.gouv.etalab.mastodon.fragments.DisplayNotificationsFragment; +import fr.gouv.etalab.mastodon.fragments.DisplayScheduledTootsFragment; import fr.gouv.etalab.mastodon.helper.Helper; import fr.gouv.etalab.mastodon.interfaces.OnUpdateAccountInfoInterface; import fr.gouv.etalab.mastodon.sqlite.Sqlite; @@ -563,7 +564,12 @@ public class MainActivity extends AppCompatActivity fragmentTag = "MUTED"; fragmentManager.beginTransaction() .replace(R.id.main_app_container, accountsFragment, fragmentTag).addToBackStack(fragmentTag).commit(); - }else if( id == R.id.nav_notification){ + }else if (id == R.id.nav_scheduled) { + DisplayScheduledTootsFragment displayScheduledTootsFragment = new DisplayScheduledTootsFragment(); + fragmentTag = "SCHEDULED"; + fragmentManager.beginTransaction() + .replace(R.id.main_app_container, displayScheduledTootsFragment, fragmentTag).addToBackStack(fragmentTag).commit(); + } else if( id == R.id.nav_notification){ toot.setVisibility(View.GONE); DisplayNotificationsFragment notificationsFragment = new DisplayNotificationsFragment(); fragmentTag = "NOTIFICATIONS"; 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 2771997da..c814ba01a 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 @@ -35,6 +35,7 @@ import android.text.Editable; import android.text.Html; import android.text.TextWatcher; import android.util.Log; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -44,6 +45,7 @@ import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; +import android.widget.DatePicker; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; @@ -51,6 +53,7 @@ import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; +import android.widget.TimePicker; import android.widget.Toast; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -61,6 +64,9 @@ import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; @@ -83,6 +89,7 @@ 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.jobs.ScheduledTootsSyncJob; import fr.gouv.etalab.mastodon.sqlite.AccountDAO; import fr.gouv.etalab.mastodon.sqlite.StatusStoredDAO; import fr.gouv.etalab.mastodon.sqlite.Sqlite; @@ -521,7 +528,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc } return true; case R.id.action_store: - storeToot(); + storeToot(true); return true; case R.id.action_restore: try{ @@ -650,13 +657,102 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc } return true; - /*case R.id.action_schedule: + 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; } + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(TootActivity.this); + LayoutInflater inflater = this.getLayoutInflater(); + View dialogView = inflater.inflate(R.layout.datetime_picker, null); + dialogBuilder.setView(dialogView); + final AlertDialog alertDialog = dialogBuilder.create(); - return true;*/ + final DatePicker datePicker = (DatePicker) dialogView.findViewById(R.id.date_picker); + final TimePicker timePicker = (TimePicker) dialogView.findViewById(R.id.time_picker); + Button date_time_cancel = (Button) dialogView.findViewById(R.id.date_time_cancel); + final Button date_time_previous = (Button) dialogView.findViewById(R.id.date_time_previous); + final Button date_time_next = (Button) dialogView.findViewById(R.id.date_time_next); + final Button date_time_set = (Button) dialogView.findViewById(R.id.date_time_set); + + //Buttons management + date_time_cancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + alertDialog.dismiss(); + } + }); + date_time_next.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + datePicker.setVisibility(View.GONE); + timePicker.setVisibility(View.VISIBLE); + date_time_previous.setVisibility(View.VISIBLE); + date_time_next.setVisibility(View.GONE); + date_time_set.setVisibility(View.VISIBLE); + } + }); + date_time_previous.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + datePicker.setVisibility(View.VISIBLE); + timePicker.setVisibility(View.GONE); + date_time_previous.setVisibility(View.GONE); + date_time_next.setVisibility(View.VISIBLE); + date_time_set.setVisibility(View.GONE); + } + }); + date_time_set.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int hour, minute; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + hour = timePicker.getHour(); + minute = timePicker.getMinute(); + }else { + //noinspection deprecation + hour = timePicker.getCurrentHour(); + //noinspection deprecation + minute = timePicker.getCurrentMinute(); + } + Calendar calendar = new GregorianCalendar(datePicker.getYear(), + datePicker.getMonth(), + datePicker.getDayOfMonth(), + hour, + minute); + long time = calendar.getTimeInMillis(); + if( (time - new Date().getTime()) < 60000 ){ + Toast.makeText(getApplicationContext(), R.string.toot_scheduled_date, Toast.LENGTH_LONG).show(); + }else { + //Store the toot as draft first + storeToot(false); + //Schedules the toot + ScheduledTootsSyncJob.schedule(getApplicationContext(), true, currentToId, time); + //Clear content + toot_content.setText(""); + toot_cw_content.setText(""); + if( attachments != null) { + for (Attachment attachment : attachments) { + View namebar = findViewById(Integer.parseInt(attachment.getId())); + if (namebar != null && namebar.getParent() != null) + ((ViewGroup) namebar.getParent()).removeView(namebar); + } + List tmp_attachment = new ArrayList<>(); + tmp_attachment.addAll(attachments); + attachments.removeAll(tmp_attachment); + tmp_attachment.clear(); + } + isSensitive = false; + toot_sensitive.setVisibility(View.GONE); + currentToId = -1; + Toast.makeText(TootActivity.this,R.string.toot_scheduled, Toast.LENGTH_LONG).show(); + alertDialog.dismiss(); + } + } + }); + + alertDialog.show(); + return true; default: return super.onOptionsItemSelected(item); } @@ -798,7 +894,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); boolean storeToot = sharedpreferences.getBoolean(Helper.SET_AUTO_STORE, true); if( storeToot) - storeToot(); + storeToot(true); } @Override @@ -827,6 +923,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc } isSensitive = false; toot_sensitive.setVisibility(View.GONE); + currentToId = -1; Toast.makeText(TootActivity.this,R.string.toot_sent, Toast.LENGTH_LONG).show(); }else { Toast.makeText(TootActivity.this,R.string.toast_error, Toast.LENGTH_LONG).show(); @@ -853,7 +950,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc } } - private void storeToot(){ + private void storeToot(boolean message){ //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; @@ -870,20 +967,21 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc 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); + currentToId = new StatusStoredDAO(TootActivity.this, db).insertStatus(toot); }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); + new StatusStoredDAO(TootActivity.this, db).insertStatus(toot); } } - - Toast.makeText(getApplicationContext(), R.string.toast_toot_saved, Toast.LENGTH_LONG).show(); + if( message ) + 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(); + if( message) + Toast.makeText(getApplicationContext(), R.string.toast_error, Toast.LENGTH_LONG).show(); } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveScheduledTootsAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveScheduledTootsAsyncTask.java new file mode 100644 index 000000000..fe1892a37 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveScheduledTootsAsyncTask.java @@ -0,0 +1,58 @@ +/* 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 . */ +package fr.gouv.etalab.mastodon.asynctasks; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; + +import java.util.List; +import fr.gouv.etalab.mastodon.client.Entities.StoredStatus; +import fr.gouv.etalab.mastodon.interfaces.OnRetrieveScheduledTootsInterface; +import fr.gouv.etalab.mastodon.sqlite.Sqlite; +import fr.gouv.etalab.mastodon.sqlite.StatusStoredDAO; + + +/** + * Created by Thomas on 16/07/2017. + * Retrieves scheduled toots for an account + */ + +public class RetrieveScheduledTootsAsyncTask extends AsyncTask { + + private Context context; + private OnRetrieveScheduledTootsInterface listener; + private List storedStatuses; + + + public RetrieveScheduledTootsAsyncTask(Context context, OnRetrieveScheduledTootsInterface onRetrieveScheduledTootsInterface){ + this.context = context; + this.listener = onRetrieveScheduledTootsInterface; + + } + + @Override + protected Void doInBackground(Void... params) { + SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + storedStatuses = new StatusStoredDAO(context, db).getAllScheduled(); + return null; + } + + @Override + protected void onPostExecute(Void result) { + listener.onRetrieveScheduledToots(storedStatuses); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/ScheduledTootsListAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/ScheduledTootsListAdapter.java new file mode 100644 index 000000000..b5984f576 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/ScheduledTootsListAdapter.java @@ -0,0 +1,175 @@ +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.Button; +import android.widget.ImageView; +import android.widget.TextView; +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; +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 16/07/2017. + * Adapter for scheduled toots + */ +public class ScheduledTootsListAdapter extends BaseAdapter { + + private Context context; + private List storedStatuses; + private LayoutInflater layoutInflater; + private ScheduledTootsListAdapter scheduledTootsListAdapter; + + + public ScheduledTootsListAdapter(Context context, List storedStatuses){ + this.context = context; + this.storedStatuses = storedStatuses; + layoutInflater = LayoutInflater.from(this.context); + scheduledTootsListAdapter = 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 storedStatus = storedStatuses.get(position); + final ViewHolder holder; + if (convertView == null) { + convertView = layoutInflater.inflate(R.layout.drawer_scheduled_toot, parent, false); + holder = new ViewHolder(); + holder.scheduled_toot_title = (TextView) convertView.findViewById(R.id.scheduled_toot_title); + holder.scheduled_toot_date_creation = (TextView) convertView.findViewById(R.id.scheduled_toot_date_creation); + holder.scheduled_toot_media_count = (TextView) convertView.findViewById(R.id.scheduled_toot_media_count); + holder.scheduled_toot_delete = (ImageView) convertView.findViewById(R.id.scheduled_toot_delete); + holder.scheduled_toot_privacy = (ImageView) convertView.findViewById(R.id.scheduled_toot_privacy); + holder.scheduled_toot_date = (Button) convertView.findViewById(R.id.scheduled_toot_date); + 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); + changeDrawableColor(context, R.drawable.ic_action_globe,R.color.dark_text); + changeDrawableColor(context, R.drawable.ic_action_lock_open,R.color.dark_text); + changeDrawableColor(context, R.drawable.ic_action_lock_closed,R.color.dark_text); + changeDrawableColor(context, R.drawable.ic_local_post_office,R.color.dark_text); + }else { + changeDrawableColor(context, R.drawable.ic_cancel,R.color.black); + changeDrawableColor(context, R.drawable.ic_action_globe,R.color.black); + changeDrawableColor(context, R.drawable.ic_action_lock_open,R.color.black); + changeDrawableColor(context, R.drawable.ic_action_lock_closed,R.color.black); + changeDrawableColor(context, R.drawable.ic_local_post_office,R.color.black); + } + + final Status status = storedStatus.getStatus(); + + switch (status.getVisibility()) { + case "public": + holder.scheduled_toot_privacy.setImageResource(R.drawable.ic_action_globe); + break; + case "unlisted": + holder.scheduled_toot_privacy.setImageResource(R.drawable.ic_action_lock_open); + break; + case "private": + holder.scheduled_toot_privacy.setImageResource(R.drawable.ic_action_lock_closed); + break; + case "direct": + holder.scheduled_toot_privacy.setImageResource(R.drawable.ic_local_post_office); + break; + } + final SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + + //Delete scheduled toot + holder.scheduled_toot_delete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setMessage(status.getContent() + '\n' + storedStatus.getCreation_date()); + builder.setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(R.string.remove_scheduled) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + new StatusStoredDAO(context, db).remove(storedStatus.getId()); + storedStatuses.remove(storedStatus); + scheduledTootsListAdapter.notifyDataSetChanged(); + dialog.dismiss(); + } + }) + .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + + }) + .show(); + } + }); + holder.scheduled_toot_media_count.setText(context.getString(R.string.media_count, status.getMedia_attachments().size())); + holder.scheduled_toot_date_creation.setText(Helper.dateToString(context, storedStatus.getCreation_date())); + holder.scheduled_toot_title.setText(status.getContent()); + + return convertView; + } + + + private class ViewHolder { + TextView scheduled_toot_title; + TextView scheduled_toot_date_creation; + TextView scheduled_toot_media_count; + ImageView scheduled_toot_delete; + ImageView scheduled_toot_privacy; + Button scheduled_toot_date; + } + +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayScheduledTootsFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayScheduledTootsFragment.java new file mode 100644 index 000000000..44fa469ff --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayScheduledTootsFragment.java @@ -0,0 +1,95 @@ +package fr.gouv.etalab.mastodon.fragments; +/* 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.Context; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; +import android.widget.RelativeLayout; +import java.util.List; +import fr.gouv.etalab.mastodon.asynctasks.RetrieveScheduledTootsAsyncTask; +import fr.gouv.etalab.mastodon.client.Entities.StoredStatus; +import fr.gouv.etalab.mastodon.drawers.ScheduledTootsListAdapter; +import fr.gouv.etalab.mastodon.interfaces.OnRetrieveScheduledTootsInterface; +import mastodon.etalab.gouv.fr.mastodon.R; + + +/** + * Created by Thomas on 16/07/2017. + * Fragment to display scheduled toots + */ +public class DisplayScheduledTootsFragment extends Fragment implements OnRetrieveScheduledTootsInterface { + + + private Context context; + private AsyncTask asyncTask; + private RelativeLayout mainLoader, textviewNoAction; + private ListView lv_scheduled_toots; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View rootView = inflater.inflate(R.layout.fragment_scheduled_toots, container, false); + context = getContext(); + + lv_scheduled_toots = (ListView) rootView.findViewById(R.id.lv_scheduled_toots); + + mainLoader = (RelativeLayout) rootView.findViewById(R.id.loader); + textviewNoAction = (RelativeLayout) rootView.findViewById(R.id.no_action); + mainLoader.setVisibility(View.VISIBLE); + + asyncTask = new RetrieveScheduledTootsAsyncTask(context, DisplayScheduledTootsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return rootView; + } + + + @Override + public void onCreate(Bundle saveInstance) + { + super.onCreate(saveInstance); + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + this.context = context; + } + + public void onDestroy() { + super.onDestroy(); + if(asyncTask != null && asyncTask.getStatus() == AsyncTask.Status.RUNNING) + asyncTask.cancel(true); + } + + + + @Override + public void onRetrieveScheduledToots(List storedStatuses) { + + mainLoader.setVisibility(View.GONE); + if( storedStatuses != null && storedStatuses.size() > 0 ){ + ScheduledTootsListAdapter scheduledTootsListAdapter = new ScheduledTootsListAdapter(context, storedStatuses); + lv_scheduled_toots.setAdapter(scheduledTootsListAdapter); + }else { + textviewNoAction.setVisibility(View.VISIBLE); + } + } +} 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 89b8a6b01..fcab27bb2 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 @@ -841,6 +841,9 @@ public class Helper { navigationView.getMenu().performIdentifierAction(R.id.nav_home, 0); SQLiteDatabase db = Sqlite.getInstance(activity, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); Account account = new AccountDAO(activity,db).getAccountByID(userID); + //Can happen when an account has been deleted and there is a click on an old notification + if( account == null) + return; //Locked account can see follow request if (account.isLocked()) { navigationView.getMenu().findItem(R.id.nav_follow_request).setVisible(true); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveScheduledTootsInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveScheduledTootsInterface.java new file mode 100644 index 000000000..123272621 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveScheduledTootsInterface.java @@ -0,0 +1,26 @@ +/* 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 . */ +package fr.gouv.etalab.mastodon.interfaces; + +import java.util.List; +import fr.gouv.etalab.mastodon.client.Entities.StoredStatus; + +/** + * Created by Thomas on 16/07/2017. + * Interface when scheduled toots have been retrieved + */ +public interface OnRetrieveScheduledTootsInterface { + void onRetrieveScheduledToots(List storedStatuses); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java index 6b4b36975..3087b476d 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java @@ -30,6 +30,8 @@ public class ApplicationJob implements JobCreator { return new NotificationsSyncJob(); case HomeTimelineSyncJob.HOME_TIMELINE: return new HomeTimelineSyncJob(); + case ScheduledTootsSyncJob.SCHEDULED_TOOT: + return new ScheduledTootsSyncJob(); default: return null; } @@ -38,4 +40,8 @@ public class ApplicationJob implements JobCreator { public static void cancelAllJob(String TAG){ JobManager.instance().cancelAllForTag(TAG); } + + private void cancelJob(int jobId) { + JobManager.instance().cancel(jobId); + } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ScheduledTootsSyncJob.java b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ScheduledTootsSyncJob.java new file mode 100644 index 000000000..9eb8c5f11 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ScheduledTootsSyncJob.java @@ -0,0 +1,86 @@ +package fr.gouv.etalab.mastodon.jobs; +/* 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.Context; +import android.database.sqlite.SQLiteDatabase; +import android.support.annotation.NonNull; +import com.evernote.android.job.Job; +import com.evernote.android.job.JobRequest; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import fr.gouv.etalab.mastodon.client.API; +import fr.gouv.etalab.mastodon.client.Entities.Status; +import fr.gouv.etalab.mastodon.client.Entities.StoredStatus; +import fr.gouv.etalab.mastodon.sqlite.Sqlite; +import fr.gouv.etalab.mastodon.sqlite.StatusStoredDAO; + + +/** + * Created by Thomas on 16/07/2017. + * Scheduled a toot a datetime + */ + +public class ScheduledTootsSyncJob extends Job { + + static final String SCHEDULED_TOOT = "job_scheduled_toot"; + + + @NonNull + @Override + protected Result onRunJob(Params params) { + //Code refresh here + int jobId = params.getId(); + SQLiteDatabase db = Sqlite.getInstance(getContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + StoredStatus storedStatus = new StatusStoredDAO(getContext(), db).getStatusScheduled(jobId); + //Retrieves the stored status + if( storedStatus != null){ + //Retrieves the linked status to toot + Status status = storedStatus.getStatus(); + if( status != null){ + int statusCode = new API(getContext()).statusAction(status); + //Toot was sent + if( statusCode == 200){ + new StatusStoredDAO(getContext(), db).updateScheduledDone(jobId, new Date()); + } + } + } + return Result.SUCCESS; + } + + + public static int schedule(Context context, boolean updateCurrent, long id, long timestampScheduling){ + + long startMs = (timestampScheduling - new Date().getTime()); + long endMs = startMs + TimeUnit.MINUTES.toMillis(5); + SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + + int jobId = new JobRequest.Builder(ScheduledTootsSyncJob.SCHEDULED_TOOT) + .setExecutionWindow(startMs, endMs) + .setPersisted(true) + .setUpdateCurrent(updateCurrent) + .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED) + .setRequirementsEnforced(false) + .build() + .schedule(); + new StatusStoredDAO(context, db).scheduleStatus(id, jobId, new Date()); + return jobId; + } + + + + +} 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 index 332b95944..ee88cf0b5 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/StatusStoredDAO.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/StatusStoredDAO.java @@ -51,7 +51,7 @@ public class StatusStoredDAO { * @param status Status * @return boolean */ - public long insertStatus(Status status, boolean isScheduled, Date scheduled_date) + public long insertStatus(Status status) { ContentValues values = new ContentValues(); String serializedStatus = Helper.statusToStringStorage(status); @@ -63,13 +63,11 @@ public class StatusStoredDAO { 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_IS_SCHEDULED, 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{ @@ -98,31 +96,49 @@ public class StatusStoredDAO { new String[]{String.valueOf(id)}); } + /** - * Update scheduled date for a Status in database - * @param scheduled_date Date + * Schedule a status in db + * @param id long + * @param jobId int + * @param date_scheduled Date * @return boolean */ - public int updateScheduledDate(long id, Date scheduled_date) { + public int scheduleStatus(long id, int jobId, Date date_scheduled ) { ContentValues values = new ContentValues(); - values.put(Sqlite.COL_DATE_SCHEDULED, Helper.dateToString(context, scheduled_date)); + values.put(Sqlite.COL_IS_SCHEDULED, jobId); + values.put(Sqlite.COL_DATE_SCHEDULED, Helper.dateToString(context, date_scheduled)); 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(int jobid, 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_IS_SCHEDULED + " = ? ", + new String[]{String.valueOf(jobid)}); + } + /** * Update date when task is done for a scheduled Status in database + * @param jobid int * @param date_sent Date * @return boolean */ - public int updateScheduledDone(long id, Date date_sent) { + public int updateScheduledDone(int jobid, 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)}); + values, Sqlite.COL_IS_SCHEDULED + " = ? ", + new String[]{String.valueOf(jobid)}); } //------- REMOVE ------- @@ -176,6 +192,22 @@ public class StatusStoredDAO { } } + + /** + * Returns all scheduled Statuses in db + * @return stored status List + */ + public List getAllScheduled(){ + 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 AND " + Sqlite.COL_SENT + " = 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 @@ -222,6 +254,18 @@ public class StatusStoredDAO { } + /** + * Returns a stored status by id of job in db + * @return stored status StoredStatus + */ + public StoredStatus getStatusScheduled(int jobid){ + try { + Cursor c = db.query(Sqlite.TABLE_STATUSES_STORED, null, Sqlite.COL_IS_SCHEDULED + " = '" + jobid + "'", null, null, null, null, null); + return cursorToStoredStatus(c); + } catch (Exception e) { + return null; + } + } /*** * Method to hydrate Stored statuses from database @@ -240,7 +284,7 @@ public class StatusStoredDAO { 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.setScheduled(c.getInt(c.getColumnIndex(Sqlite.COL_IS_SCHEDULED)) > 0 ); 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)))); @@ -267,7 +311,7 @@ public class StatusStoredDAO { 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.setScheduled(c.getInt(c.getColumnIndex(Sqlite.COL_IS_SCHEDULED)) > 0 ); 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)))); diff --git a/app/src/main/res/drawable-hdpi/ic_schedule_black.png b/app/src/main/res/drawable-hdpi/ic_schedule_black.png new file mode 100644 index 0000000000000000000000000000000000000000..6bad8f028314fe3ee31c61ccad4f48c4c60f1ba4 GIT binary patch literal 459 zcmV;+0W|)JP)_7Gd-&S_h>PnASp@ zj<;}pGjDY19Uja*@1FbaJ#*&)!!{qbaE1pIkwqHo@C>HR;1W$3%JYc~ru&hWQIo(X zatuv!<^PjZ5OX5!<3|GG@G2!Aph~=rUHwTN(j#UydvhoYF=p?nCL~uE2_k~gt~VjX zcWaLqQeN*#*F>gz{E#Hh^pd=25Q-~_tTf+u{9IO(9K!8wbQ@> zFmPhJ_tvT%Xy?T4)GSF2ZQg>Vh!y&umL&FyZHzddmZqDxedI;F5P!l#7b&T|ZHSUS z8_|U$;%!`EOqBd)+^V_l;rB2&dpe7-tQ&btFs2T-Rii3rPjBN~VSK11{!L(oV41V0 zH#eTS|Av5FfVL1r8d6wDEkt!0_k>vE?D>_EN=SJv>^S|XOH!)A*(q>y#Jm zcP#`N+OXUubqWy}d<}VcTb)w&0&a1P6;<+K*e{h>qb`DqocaI&002ovPDHLkV1o0} B%`N}{ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-ldpi/ic_schedule_black.png b/app/src/main/res/drawable-ldpi/ic_schedule_black.png new file mode 100644 index 0000000000000000000000000000000000000000..6a5ed6a03d86586013ab4e12aa17fa8137087190 GIT binary patch literal 289 zcmV++0p9+JP)C}zyU0*48&NOU}LF0YUKg2^eCc*;tAA~`GB;bRsONih`JD78n=PQKYi2)~5@SqDWG&#!q3*+8B zBusL-+7y=HFh<_zj4OJSW=|}|VW|^XL5oQa=QG9^LDF>0dohLsjG)8aW&XkW+D-=a zU;%sBvb>L-sy%ru$6#qgn8AQwFsYuK&qh}Z)3apOOGzR1$e1Hm$zJ+CSaU(RY8X(3;@Cg`r5mDmqfVB?#i?rol z?9zLdlXJd%{zK*0ti zb{VvfIwhta2aHOnAQ1*E+a_R*kzskIavY6Ox)H^+1)b!yODed)xiMcVO4>6Ex&ax1 zpR=}KK^Vp@E^;DZlKaZkqC;G8%`uV2^&2p%<3bseIH-s~MJf4g8kJW<8FWR|BYZB5 z%5ug0u87)%&$ChaDU@DUL^Z}{c3QchQhdaQ)FL6@#u5t~iRS!d*k z3XotxlJ<>Cd>oBsT#d{Bh(Q_JH=4vA!2=^w6)G&CJNlDzPm7|5`nQHo8@1mG7rY#e zW}nCjPP^)4)wwZCGc|+!wSA_7``o?%)if4cxFpvPywiJePQ%b=ZNGmYKXsBJ`+M8# QS^xk507*qoM6N<$f(7yh`~Uy| literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_schedule_black.png b/app/src/main/res/drawable-xxhdpi/ic_schedule_black.png new file mode 100644 index 0000000000000000000000000000000000000000..185332a0aa5fbace3680e74b11e85ad164215a27 GIT binary patch literal 851 zcmV-Z1FZasP)hDsTx6 z#C0e~2>!W%3F9_;@f))#{0lv3M9?c3A8OH`29nBsIE(B|p-$nQBap#EIO-l~HM*36 z41R!>ZlLz!Q;MOtXriPDB9C0_qi2xbn(2FzrQTzgF4QqhC#69vB9^ixXd{V)^dt`H zK}qE<&RGiqSK za9r+%`7Ug1jZL0_-QoBzwToD}Ct#Iq&!k=}crfS*ScvVFr4QEeV4f#n0k(HBeXxNC zeV%~ju)X@s!475urt8K(xJ%oSOE;<}NB|F6@*6H&F15A^qMG_KIwIPfH`s3~Q)QR> zelF@}g-tRSSH-?{59`t-TI`BCv0M!)?#dg5LnkP~WMVDCNPOhEx$;d`Em3X*q(cX= zLUf(Rzt^2Y+NRNUBpli){_S8}Tdfpdv!6F>wK*||e#csVFXH&WvR10*`8?L{tJaI( zPuWhDUBffNVl}Q0Bp2AmAhPTPPq5$=#n27y#0=YTY{C=iJVq_2E*0V{>)&@4Uxkk> zK2Gb3-d81{Mnxgm&~C9dOjFCL9;+EqX<4iM+3Am=Fl@lRn#Ji#fVkdO%ltUOO-p-(!!jM zr_J1&2lUR;W|WO{yk=T+FF82Nt9zbeG@{4!I_Vj%Var_HVEM5fr>TLd#}#^(QMa;W d;r4KT(_cQXjQeU7Iz#Wb>TiNYw* zw9F)Ec}uaf!o>8FL}Y4cpP@7}_nvd-oH-|axZi+W^;>>-zWKHpz)ybi(`+RtGH?=A z=)_03F@{n4*^3TTAP=c<{7)#6ID&SJV^U|0q7~VQupi7;G~u@{6n2fH5gY6SlS&Sm zG_l&S(RwgZxNQo|%(?-Wm2mdsyAqtK^$okN0W%NRG)#O!H8PQgXoMk%Rv3LsM;2<( zuOV@TxD;t2MT@86K@=fgS1y5!4|$P%Na;d>TzKuF@-ywqd?{%M1%980{0oGe-<>xe z*(Kj#$_qgOB~1~M)7;q+o%I&0OntFfbP1XsnK?^W1toVByzfMv$MH5u`ve6g2v7=+ z0F((D4JCo4Lf$F0B!QF(dC5SktQDLUUwOE{=d!ZHazG(e1OHiQ^u@k}YrnFj zOc(-sggo=0WX&=P$ibCeA7AC1jANsBOu@wW9hgyTP4GmX)B-{#!|U9+jE93%~n7ejHPGVY`qLy zw5@<*7)$T9+1w0Un5}@qxqPQdY|sbU3MiN>;5GreXgwe`mrYVIM_KDKwL?9%ejrb3 zZzT^k8}{kafOg;=2dbNb+M=+pG~9a3%y4Hg!hyPr71D|Ex?Jg~G1$T{ClD;85uDR0 zo9q~IOj~Ih!`5$NuGlU(uNQkXitAT~D@Ct$Vgo}h7NZ~<4Q zxJ!W^*YCTGswfo{)FV{k>@*76E*n=8gl0iWH{wi2R*#_R0p^`+AnrpGIw^oC4ph(2Rt`+T3Lf4smC@CW&-7Q zyig}|WmkSR%hxR9w3+&)acL-HR0r3 + + + + + +