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 000000000..6bad8f028 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_schedule_black.png differ 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 000000000..6a5ed6a03 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_schedule_black.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_schedule_black.png b/app/src/main/res/drawable-mdpi/ic_schedule_black.png new file mode 100644 index 000000000..f43719251 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_schedule_black.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_schedule_black.png b/app/src/main/res/drawable-xhdpi/ic_schedule_black.png new file mode 100644 index 000000000..7d812d2e6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_schedule_black.png differ 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 000000000..185332a0a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_schedule_black.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_schedule_black.png b/app/src/main/res/drawable-xxxhdpi/ic_schedule_black.png new file mode 100644 index 000000000..88c0a33e8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_schedule_black.png differ diff --git a/app/src/main/res/layout/datetime_picker.xml b/app/src/main/res/layout/datetime_picker.xml new file mode 100644 index 000000000..c2df21595 --- /dev/null +++ b/app/src/main/res/layout/datetime_picker.xml @@ -0,0 +1,74 @@ + + + + + + +