Merged in develop (pull request #27)

This commit is contained in:
tom79 2017-07-17 17:20:39 +00:00
commit 053daea4ec
51 changed files with 1339 additions and 144 deletions

View File

@ -7,8 +7,8 @@ android {
applicationId "fr.gouv.etalab.mastodon"
minSdkVersion 15
targetSdkVersion 25
versionCode 29
versionName "1.3.2"
versionCode 30
versionName "1.3.3"
}
buildTypes {
release {

Binary file not shown.

View File

@ -96,6 +96,14 @@ public class AboutActivity extends AppCompatActivity implements OnRetrieveSearcA
startActivity(browserIntent);
}
});
Button about_translation = (Button) findViewById(R.id.about_translation);
about_translation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://translate.yandex.com/"));
startActivity(browserIntent);
}
});
}

View File

@ -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;
@ -202,6 +203,11 @@ public class MainActivity extends AppCompatActivity
if( navigationView.getMenu().findItem(tagItem.get(fragmentTag)) != null)
navigationView.getMenu().findItem(tagItem.get(fragmentTag)).setChecked(true);
}
if( fragmentTag.equals("HOME_TIMELINE") || fragmentTag.equals("LOCAL_TIMELINE") || fragmentTag.equals("PUBLIC_TIMELINE") || fragmentTag.equals("SCHEDULED")){
toot.setVisibility(View.VISIBLE);
}else {
toot.setVisibility(View.GONE);
}
}
}
}
@ -563,7 +569,13 @@ 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) {
toot.setVisibility(View.VISIBLE);
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";

View File

@ -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;
@ -122,6 +129,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
private String sharedContent, sharedSubject;
private CheckBox toot_sensitive;
public long currentToId;
private long restored;
private String pattern = "^.*(@([a-zA-Z0-9_]{2,}))$";
@Override
@ -180,23 +188,6 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
final LinearLayout drawer_layout = (LinearLayout) findViewById(R.id.drawer_layout);
/*drawer_layout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int heightDiff = drawer_layout.getRootView().getHeight() - drawer_layout.getHeight();
if (heightDiff > 100) {
ViewGroup.LayoutParams params = toot_picture_container.getLayoutParams();
params.height = (int) Helper.convertDpToPixel(20, getApplicationContext());
params.width = (int) Helper.convertDpToPixel(20, getApplicationContext());
toot_picture_container.setLayoutParams(params);
} else {
ViewGroup.LayoutParams params = toot_picture_container.getLayoutParams();
params.height = (int) Helper.convertDpToPixel(100, getApplicationContext());
params.width = (int) Helper.convertDpToPixel(100, getApplicationContext());
toot_picture_container.setLayoutParams(params);
}
}
});*/
drawer_layout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
@ -217,10 +208,16 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
});
Bundle b = getIntent().getExtras();
restored = -1;
if(b != null) {
tootReply = b.getParcelable("tootReply");
sharedContent = b.getString("sharedContent", null);
sharedSubject = b.getString("sharedSubject", null);
restored = b.getLong("restored", -1);
}
if( restored != -1 ){
toot_it.setVisibility(View.GONE);
invalidateOptionsMenu();
}
if( tootReply != null) {
setTitle(R.string.toot_title_reply);
@ -463,7 +460,9 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
toot_space_left.setText(String.valueOf((maxChar - totalChar)));
}
});
if( restored != -1 ){
restoreToot(restored);
}
}
@ -521,7 +520,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{
@ -573,74 +572,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
@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();
restoreToot(id);
dialog.dismiss();
}
});
@ -650,13 +582,113 @@ 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);
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, android.content.Context.MODE_PRIVATE);
int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK);
if( theme == Helper.THEME_DARK){
changeDrawableColor(TootActivity.this, R.drawable.ic_skip_previous,R.color.dark_text);
changeDrawableColor(TootActivity.this, R.drawable.ic_skip_next,R.color.dark_text);
changeDrawableColor(TootActivity.this, R.drawable.ic_check,R.color.dark_text);
}else {
changeDrawableColor(TootActivity.this, R.drawable.ic_skip_previous,R.color.black);
changeDrawableColor(TootActivity.this, R.drawable.ic_skip_next,R.color.black);
changeDrawableColor(TootActivity.this, R.drawable.ic_check,R.color.black);
}
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 ImageButton date_time_previous = (ImageButton) dialogView.findViewById(R.id.date_time_previous);
final ImageButton date_time_next = (ImageButton) dialogView.findViewById(R.id.date_time_next);
final ImageButton date_time_set = (ImageButton) 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(), 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<Attachment> 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);
}
@ -665,6 +697,14 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_toot, menu);
if( restored != -1 ){
MenuItem itemRestore = menu.findItem(R.id.action_restore);
if( itemRestore != null)
itemRestore.setVisible(false);
MenuItem itemSchedule = menu.findItem(R.id.action_schedule);
if( itemSchedule != null)
itemSchedule.setVisible(false);
}
return true;
}
@ -798,7 +838,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 +867,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 +894,80 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
}
}
private void storeToot(){
private void restoreToot(long id){
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
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();
}
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 +984,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();
}
}

View File

@ -0,0 +1,90 @@
/* 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 <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.asynctasks;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.SystemClock;
import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import java.util.List;
import java.util.Set;
import fr.gouv.etalab.mastodon.client.Entities.StoredStatus;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveScheduledTootsInterface;
import fr.gouv.etalab.mastodon.jobs.ScheduledTootsSyncJob;
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<Void, Void, Void> {
private Context context;
private OnRetrieveScheduledTootsInterface listener;
private List<StoredStatus> 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();
//Retrieves job asked by the user
storedStatuses = new StatusStoredDAO(context, db).getAllScheduled();
//Retrieves real jobs still waiting
Set<JobRequest> jobRequests = JobManager.instance().getAllJobRequestsForTag(ScheduledTootsSyncJob.SCHEDULED_TOOT);
int[] jobIds;
if( jobRequests != null && jobRequests.size() > 0 ){
int i = 0;
jobIds = new int[jobRequests.size()];
for(JobRequest jobRequest : jobRequests){
jobIds[i] = jobRequest.getJobId();
i++;
}
}else{
jobIds = new int[]{};
}
if( storedStatuses != null && storedStatuses.size() > 0 ){
for(StoredStatus ss: storedStatuses){
if (!Helper.isJobPresent(jobIds, ss.getJobId())){
//JobId is fixed to -1 which means an error occured (it was never sent)
new StatusStoredDAO(context, db).updateJobId(ss.getId(),-1);
}
}
//Lets time to update db before dispaying
SystemClock.sleep(1000);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveScheduledToots(storedStatuses);
}
}

View File

@ -16,6 +16,7 @@ package fr.gouv.etalab.mastodon.client;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.widget.Toast;
import com.loopj.android.http.AsyncHttpResponseHandler;
@ -869,6 +870,7 @@ public class API {
public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response) {
actionCode = statusCode;
setError(statusCode, error);
error.printStackTrace();
}
});
}else{
@ -1461,7 +1463,6 @@ public class API {
}
private void post(String action, int timeout, RequestParams params, AsyncHttpResponseHandler responseHandler) {
try {
client.setConnectTimeout(timeout); //10s timeout
client.setUserAgent(USER_AGENT);

View File

@ -14,11 +14,11 @@ public class StoredStatus {
private Date creation_date;
private Date scheduled_date;
private Date sent_date;
private boolean isScheduled;
private int jobId;
private boolean isSent;
private Status status;
private String instance;
private String acct;
private String userId;
public int getId() {
return id;
@ -52,13 +52,6 @@ public class StoredStatus {
this.sent_date = sent_date;
}
public boolean isScheduled() {
return isScheduled;
}
public void setScheduled(boolean scheduled) {
isScheduled = scheduled;
}
public boolean isSent() {
return isSent;
@ -84,11 +77,19 @@ public class StoredStatus {
this.instance = instance;
}
public String getAcct() {
return acct;
public int getJobId() {
return jobId;
}
public void setAcct(String acct) {
this.acct = acct;
public void setJobId(int jobId) {
this.jobId = jobId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}

View File

@ -106,7 +106,7 @@ public class DraftsListAdapter extends BaseAdapter {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(draft.getStatus().getContent() + '\n' + draft.getCreation_date());
builder.setMessage(draft.getStatus().getContent() + '\n' + Helper.dateToString(context, draft.getCreation_date()));
builder.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.remove_draft)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {

View File

@ -0,0 +1,321 @@
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 <http://www.gnu.org/licenses>. */
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.CardView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.Toast;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import fr.gouv.etalab.mastodon.activities.MainActivity;
import fr.gouv.etalab.mastodon.activities.TootActivity;
import fr.gouv.etalab.mastodon.client.Entities.Application;
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.jobs.ApplicationJob;
import fr.gouv.etalab.mastodon.jobs.ScheduledTootsSyncJob;
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<StoredStatus> storedStatuses;
private LayoutInflater layoutInflater;
private ScheduledTootsListAdapter scheduledTootsListAdapter;
private RelativeLayout textviewNoAction;
public ScheduledTootsListAdapter(Context context, List<StoredStatus> storedStatuses, RelativeLayout textviewNoAction){
this.context = context;
this.storedStatuses = storedStatuses;
layoutInflater = LayoutInflater.from(this.context);
scheduledTootsListAdapter = this;
this.textviewNoAction = textviewNoAction;
}
@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_container = (CardView) convertView.findViewById(R.id.scheduled_toot_container);
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_failed = (TextView) convertView.findViewById(R.id.scheduled_toot_failed);
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' + Helper.dateToString(context, 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();
if( storedStatuses.size() == 0 && textviewNoAction != null && textviewNoAction.getVisibility() == View.GONE)
textviewNoAction.setVisibility(View.VISIBLE);
try {
//Cancel the job
ApplicationJob.cancelJob(storedStatus.getJobId());
}catch (Exception ignored){}
dialog.dismiss();
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
}
});
if (storedStatus.getJobId() > 0) {
holder.scheduled_toot_failed.setVisibility(View.GONE);
}else {
holder.scheduled_toot_failed.setVisibility(View.VISIBLE);
}
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_date.setText(Helper.dateToString(context, storedStatus.getScheduled_date()));
holder.scheduled_toot_date.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
LayoutInflater inflater = ((MainActivity)context).getLayoutInflater();
View dialogView = inflater.inflate(R.layout.datetime_picker, null);
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, android.content.Context.MODE_PRIVATE);
int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK);
if( theme == Helper.THEME_DARK){
changeDrawableColor(context, R.drawable.ic_skip_previous,R.color.dark_text);
changeDrawableColor(context, R.drawable.ic_skip_next,R.color.dark_text);
changeDrawableColor(context, R.drawable.ic_check,R.color.dark_text);
}else {
changeDrawableColor(context, R.drawable.ic_skip_previous,R.color.black);
changeDrawableColor(context, R.drawable.ic_skip_next,R.color.black);
changeDrawableColor(context, R.drawable.ic_check,R.color.black);
}
dialogBuilder.setView(dialogView);
final AlertDialog alertDialog = dialogBuilder.create();
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 ImageButton date_time_previous = (ImageButton) dialogView.findViewById(R.id.date_time_previous);
final ImageButton date_time_next = (ImageButton) dialogView.findViewById(R.id.date_time_next);
final ImageButton date_time_set = (ImageButton) 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(context, R.string.toot_scheduled_date, Toast.LENGTH_LONG).show();
}else {
//Schedules the toot to the new date
try {
//Removes the job
ApplicationJob.cancelJob(storedStatus.getJobId());
//Replace it by the new one
ScheduledTootsSyncJob.schedule(context, storedStatus.getId(), time);
StoredStatus storedStatusnew = new StatusStoredDAO(context, db).getStatus(storedStatus.getId());
//Date displayed is changed
storedStatus.setScheduled_date(storedStatusnew.getScheduled_date());
scheduledTootsListAdapter.notifyDataSetChanged();
//Notifiy all is ok
Toast.makeText(context,R.string.toot_scheduled, Toast.LENGTH_LONG).show();
}catch (Exception ignored){}
alertDialog.dismiss();
}
}
});
alertDialog.show();
}
});
holder.scheduled_toot_title.setText(status.getContent());
holder.scheduled_toot_container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentToot = new Intent(context, TootActivity.class);
Bundle b = new Bundle();
b.putLong("restored", storedStatus.getId());
intentToot.putExtras(b);
context.startActivity(intentToot);
}
});
return convertView;
}
private class ViewHolder {
CardView scheduled_toot_container;
TextView scheduled_toot_title;
TextView scheduled_toot_date_creation;
TextView scheduled_toot_media_count;
TextView scheduled_toot_failed;
ImageView scheduled_toot_delete;
ImageView scheduled_toot_privacy;
Button scheduled_toot_date;
}
}

View File

@ -197,7 +197,7 @@ public class StatusListAdapter extends BaseAdapter implements OnPostActionInterf
statusListAdapter.notifyDataSetChanged();
}
});
if( currentLocale != null && !status.getLanguage().trim().equals(currentLocale) && !status.getLanguage().trim().equals("null")){
if( currentLocale != null && status.getLanguage() != null && !status.getLanguage().trim().equals(currentLocale) && !status.getLanguage().trim().equals("null")){
holder.status_translate.setVisibility(View.VISIBLE);
}else {
holder.status_translate.setVisibility(View.GONE);

View File

@ -0,0 +1,115 @@
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 <http://www.gnu.org/licenses>. */
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
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 com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import java.util.List;
import java.util.Set;
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 fr.gouv.etalab.mastodon.jobs.ScheduledTootsSyncJob;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
import fr.gouv.etalab.mastodon.sqlite.StatusStoredDAO;
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<Void, Void, Void> 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);
//Removes all scheduled toots that have sent
SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
new StatusStoredDAO(context, db).removeAllSent();
return rootView;
}
@Override
public void onResume(){
super.onResume();
//Retrieves scheduled toots
asyncTask = new RetrieveScheduledTootsAsyncTask(context, DisplayScheduledTootsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@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<StoredStatus> storedStatuses) {
mainLoader.setVisibility(View.GONE);
if( storedStatuses != null && storedStatuses.size() > 0 ){
ScheduledTootsListAdapter scheduledTootsListAdapter = new ScheduledTootsListAdapter(context, storedStatuses, textviewNoAction);
lv_scheduled_toots.setAdapter(scheduledTootsListAdapter);
textviewNoAction.setVisibility(View.GONE);
}else {
textviewNoAction.setVisibility(View.VISIBLE);
}
}
}

View File

@ -168,7 +168,7 @@ public class SettingsFragment extends Fragment {
}else {
set_cookies_container.setVisibility(View.GONE);
}
final String targeted_folder = sharedpreferences.getString(Helper.SET_FOLDER_RECORD, Environment.DIRECTORY_DOWNLOADS);
final String targeted_folder = sharedpreferences.getString(Helper.SET_FOLDER_RECORD, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath());
set_folder = (TextView) rootView.findViewById(R.id.set_folder);
set_folder.setText(targeted_folder);
@ -217,7 +217,7 @@ public class SettingsFragment extends Fragment {
DocumentsContract.getTreeDocumentId(treeUri));
String path = getPath(context, docUri);
if( path == null )
path = Environment.DIRECTORY_DOWNLOADS;
path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.SET_FOLDER_RECORD, path);

View File

@ -79,6 +79,7 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -573,18 +574,22 @@ public class Helper {
public static void manageMoveFileDownload(final Context context, final String preview_url, final String url, Bitmap bitmap, File fileVideo){
final String fileName = URLUtil.guessFileName(url, null, null);final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String myDir = sharedpreferences.getString(Helper.SET_FOLDER_RECORD, Environment.DIRECTORY_DOWNLOADS);
String myDir = sharedpreferences.getString(Helper.SET_FOLDER_RECORD, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath());
try {
File file;
if( bitmap != null) {
File filebmp = new File (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
FileOutputStream out = new FileOutputStream(filebmp);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
file = new File(myDir, fileName);
copy(filebmp, file);
file.createNewFile();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
byte[] bitmapdata = bos.toByteArray();
FileOutputStream fos = new FileOutputStream(file);
fos.write(bitmapdata);
fos.flush();
fos.close();
}else{
File fileVideoTargeded = new File(myDir, fileName);
copy(fileVideo, fileVideoTargeded);
@ -594,7 +599,6 @@ public class Helper {
final int notificationIdTmp = r.nextInt(10000);
// prepare intent which is triggered if the
// notification is selected
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
final Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
Uri uri = Uri.parse("file://" + file.getAbsolutePath());
@ -841,6 +845,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);
@ -1222,13 +1229,36 @@ public class Helper {
}
}
/**
* Serialized a Status class
* @param status Status to serialize
* @return String serialized Status
*/
public static String statusToStringStorage(Status status){
Gson gson = new Gson();
return gson.toJson(status);
}
/**
* Unserialized a Status
* @param serializedStatus String serialized status
* @return Status
*/
public static Status restoreStatusFromString(String serializedStatus){
Gson gson = new Gson();
return gson.fromJson(serializedStatus, Status.class);
}
/**
* Check if a job id is in array of ids
* @param jobIds int[]
* @param id int id to check
* @return boolean
*/
public static boolean isJobPresent(int[] jobIds, int id){
for(int x:jobIds) {
if (x == id) {return true;}
}
return false;
}
}

View File

@ -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 <http://www.gnu.org/licenses>. */
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<StoredStatus> storedStatuses);
}

View File

@ -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);
}
public static void cancelJob(int jobId) {
JobManager.instance().cancel(jobId);
}
}

View File

@ -0,0 +1,95 @@
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 <http://www.gnu.org/licenses>. */
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import android.util.Log;
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.Account;
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.AccountDAO;
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 {
public 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();
//Retrieves the stored status
StoredStatus storedStatus = new StatusStoredDAO(getContext(), db).getStatusScheduled(jobId);
if( storedStatus != null){
String userId = storedStatus.getUserId();
String instance = storedStatus.getInstance();
if( instance != null && userId != null){
Account account = new AccountDAO(getContext(), db).getAccountByUserIDInstance(userId, instance);
if( account != null){
//Retrieves the linked status to toot
Status status = storedStatus.getStatus();
if( status != null){
int statusCode = new API(getContext(), account.getInstance(), account.getToken()).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, 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(false)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequirementsEnforced(false)
.build()
.schedule();
new StatusStoredDAO(context, db).scheduleStatus(id, jobId, new Date(timestampScheduling));
return jobId;
}
}

View File

@ -133,6 +133,21 @@ public class AccountDAO {
}
}
/**
* Returns an Account by id and instance
* @param accountId String
* @param instance String
* @return Account
*/
public Account getAccountByUserIDInstance(String accountId, String instance){
try {
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_USER_ID + " = '" + accountId + "' AND " + Sqlite.COL_INSTANCE + "= '"+ instance +"'", null, null, null, null, "1");
return cursorToUser(c);
} catch (Exception e) {
return null;
}
}
/**
* Returns all Account in db
* @return Account List<Account>

View File

@ -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{
@ -99,30 +97,61 @@ public class StatusStoredDAO {
}
/**
* Update scheduled date for a Status in database
* @param scheduled_date Date
* @return boolean
* Update a Status in database
* @param id long
* @param jobId int
* @return int
*/
public int updateScheduledDate(long id, Date scheduled_date) {
public int updateJobId(long id, int jobId) {
ContentValues values = new ContentValues();
values.put(Sqlite.COL_DATE_SCHEDULED, Helper.dateToString(context, scheduled_date));
values.put(Sqlite.COL_IS_SCHEDULED, jobId);
return db.update(Sqlite.TABLE_STATUSES_STORED,
values, Sqlite.COL_ID + " = ? ",
new String[]{String.valueOf(id)});
}
/**
* Schedule a status in db
* @param id long
* @param jobId int
* @param date_scheduled Date
* @return boolean
*/
public int scheduleStatus(long id, int jobId, Date date_scheduled ) {
ContentValues values = new ContentValues();
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 -------
@ -142,6 +171,10 @@ public class StatusStoredDAO {
return db.delete(Sqlite.TABLE_STATUSES_STORED, Sqlite.COL_IS_SCHEDULED + " = \"0\" AND " + Sqlite.COL_USER_ID + " = '" + userId+ "' AND " + Sqlite.COL_INSTANCE + " = '" + instance+ "'", null);
}
public int removeAllSent(){
return db.delete(Sqlite.TABLE_STATUSES_STORED, Sqlite.COL_IS_SCHEDULED + " != 0 AND " + Sqlite.COL_SENT + " = 1", null);
}
//------- GETTERS -------
/**
@ -176,6 +209,22 @@ public class StatusStoredDAO {
}
}
/**
* Returns all scheduled Statuses in db
* @return stored status List<StoredStatus>
*/
public List<StoredStatus> 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_SCHEDULED + " ASC", null);
return cursorToListStatuses(c);
} catch (Exception e) {
return null;
}
}
/**
* Returns all not sent Statuses in db
* @return stored status List<StoredStatus>
@ -185,7 +234,7 @@ public class StatusStoredDAO {
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);
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;
@ -201,7 +250,7 @@ public class StatusStoredDAO {
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);
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 + " = 1", null, null, null, Sqlite.COL_DATE_CREATION + " DESC", null);
return cursorToListStatuses(c);
} catch (Exception e) {
return null;
@ -222,6 +271,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,10 +301,12 @@ 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.setJobId(c.getInt(c.getColumnIndex(Sqlite.COL_IS_SCHEDULED)));
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))));
storedStatus.setUserId(c.getString(c.getColumnIndex(Sqlite.COL_USER_ID)));
storedStatus.setInstance(c.getString(c.getColumnIndex(Sqlite.COL_INSTANCE)));
//Close the cursor
c.close();
//Stored status is returned
@ -267,10 +330,12 @@ 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.setJobId(c.getInt(c.getColumnIndex(Sqlite.COL_IS_SCHEDULED)) );
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))));
storedStatus.setUserId(c.getString(c.getColumnIndex(Sqlite.COL_USER_ID)));
storedStatus.setInstance(c.getString(c.getColumnIndex(Sqlite.COL_INSTANCE)));
storedStatuses.add(storedStatus);
}
//Close the cursor

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 <http://www.gnu.org/licenses>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<DatePicker
android:id="@+id/date_picker"
android:layout_width="match_parent"
android:calendarViewShown="true"
android:gravity="center"
android:spinnersShown="false"
android:layout_height="wrap_content" />
<TimePicker
android:visibility="gone"
android:gravity="center"
android:id="@+id/time_picker"
android:layout_weight="4"
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/date_time_cancel"
android:layout_width="wrap_content"
android:text="@string/cancel"
android:layout_height="40dp"
tools:ignore="ButtonStyle" />
<ImageButton
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:visibility="gone"
android:id="@+id/date_time_previous"
android:layout_width="40dp"
android:src="@drawable/ic_skip_previous"
android:layout_height="40dp"
tools:ignore="ButtonStyle" />
<ImageButton
android:id="@+id/date_time_next"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:src="@drawable/ic_skip_next"
android:layout_width="40dp"
android:layout_height="40dp"
tools:ignore="ButtonStyle" />
<ImageButton
android:visibility="gone"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:id="@+id/date_time_set"
android:src="@drawable/ic_check"
android:layout_width="40dp"
android:layout_height="40dp"
tools:ignore="ButtonStyle" />
</LinearLayout>
</LinearLayout>

View File

@ -56,5 +56,4 @@
android:layout_height="40dp"
android:padding="10dp"
tools:ignore="ContentDescription" />
</LinearLayout>

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 <http://www.gnu.org/licenses>.
-->
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/tools"
android:layout_marginTop="10dp"
style="?attr/cardStyle"
card_view:cardPreventCornerOverlap="true"
android:id="@+id/scheduled_toot_container"
app:cardUseCompatPadding="true">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:padding="5dp"
android:id="@+id/account_container"
android:orientation="vertical">
<TextView
android:id="@+id/scheduled_toot_title"
android:textStyle="bold"
android:maxLines="3"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/scheduled_toot_privacy"
android:layout_width="25dp"
android:layout_height="25dp"
tools:ignore="ContentDescription" />
<TextView
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:id="@+id/scheduled_toot_media_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/scheduled_toot_date_creation"
android:textSize="12sp"
android:textStyle="italic"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:gravity="end"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:focusableInTouchMode="false"
android:id="@+id/scheduled_toot_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:visibility="gone"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:id="@+id/scheduled_toot_failed"
android:textColor="@color/red_1"
android:text="@string/failed"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/scheduled_toot_delete"
android:src="@drawable/ic_cancel"
android:layout_gravity="center"
android:gravity="center"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="10dp"
tools:ignore="ContentDescription" />
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 <http://www.gnu.org/licenses>.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/fab_margin"
android:paddingRight="@dimen/fab_margin"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Listview Scheduled toots -->
<ListView
android:id="@+id/lv_scheduled_toots"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:divider="@null"
>
</ListView>
<RelativeLayout
android:id="@+id/no_action"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/no_action_text"
android:padding="10dp"
android:layout_centerInParent="true"
android:gravity="center"
android:textSize="25sp"
android:layout_gravity="center"
android:textStyle="italic|bold"
android:typeface="serif"
android:text="@string/no_scheduled_toots"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:padding="10dp"
android:layout_below="@+id/no_action_text"
android:layout_marginTop="10dp"
android:gravity="center"
android:textStyle="italic"
android:layout_gravity="center"
android:typeface="serif"
android:text="@string/no_scheduled_toots_indications"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
<!-- Main Loader -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/loader"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
</RelativeLayout>
</RelativeLayout>

View File

@ -30,6 +30,10 @@
android:id="@+id/nav_notification"
android:icon="@drawable/ic_notifications"
android:title="@string/notifications" />
<item
android:id="@+id/nav_scheduled"
android:icon="@drawable/ic_schedule_black"
android:title="@string/scheduled_toots" />
<item
android:id="@+id/nav_follow_request"
android:icon="@drawable/ic_group_add"

View File

@ -16,9 +16,9 @@
android:title="@string/restore"
android:icon="@drawable/ic_restore"
app:showAsAction="never" />
<!--<item
<item
android:id="@+id/action_schedule"
android:title="@string/schedule"
android:icon="@drawable/ic_schedule"
app:showAsAction="never" />-->
app:showAsAction="never" />
</menu>

View File

@ -44,6 +44,8 @@
<string name="speech_not_supported">Désolé ! Votre appareil ne supporte pas la commande vocale !</string>
<string name="delete_all">Tout effacer</string>
<string name="schedule">Programmer</string>
<string name="next">Suivant</string>
<string name="previous">Précédent</string>
<!--- Menu -->
<string name="home_menu">Accueil</string>
<string name="local_menu">Fil public local</string>
@ -65,6 +67,8 @@
<string name="choose_file">Veuillez sélectionner un fichier</string>
<string name="choose_file_error">Aucun explorateur de fichiers trouvé !</string>
<string name="click_to_change">Cliquer sur le chemin pour changer</string>
<string name="failed">Erreur !</string>
<string name="scheduled_toots">Pouets programmés</string>
<!-- Status -->
<string name="no_status">Aucun pouet à afficher !</string>
<string name="fav_added">Pouet ajouté aux favoris !</string>
@ -179,6 +183,14 @@
<string name="authorize">Autoriser</string>
<string name="reject">Rejeter</string>
<!-- Scheduled toots -->
<string name="no_scheduled_toots">Aucun pouet programmé à afficher !</string>
<string name="no_scheduled_toots_indications">Ecrivez un pouet, puis choisissez <b>Programmer</b> dans le menu du haut.</string>
<string name="remove_scheduled">Supprimer le pouet programmé ?</string>
<string name="media_count">Média(s): %d</string>
<string name="toot_scheduled">Le pouet a été programmé !</string>
<string name="toot_scheduled_date">La date doit être supérieure à l\'heure actuelle !</string>
<!-- Notifications -->
<string name="no_notifications">Aucune notification à afficher</string>
<string name="notif_mention">a mentionné votre pouet</string>

View File

@ -46,6 +46,9 @@
<string name="delete_all">Delete all</string>
<string name="schedule">Schedule</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<!--- Menu -->
<string name="home_menu">Home</string>
<string name="local_menu">Local timeline</string>
@ -67,6 +70,8 @@
<string name="choose_file">Please select a file</string>
<string name="choose_file_error">No file explorer found!</string>
<string name="click_to_change">Click on the path to change it</string>
<string name="failed">Failed!</string>
<string name="scheduled_toots">Scheduled toots</string>
<!-- Status -->
<string name="no_status">No toot to display</string>
<string name="fav_added">The toot was added to favourites</string>
@ -183,6 +188,15 @@
<string name="authorize">Authorize</string>
<string name="reject">Reject</string>
<!-- Scheduled toots -->
<string name="no_scheduled_toots">No scheduled toot to display!</string>
<string name="no_scheduled_toots_indications">Write a toot and then choose <b>Schedule</b> from the top menu.</string>
<string name="remove_scheduled">Delete scheduled toot?</string>
<string name="media_count">Media: %d</string>
<string name="toot_scheduled">The toot has been scheduled!</string>
<string name="toot_scheduled_date">The scheduled date must be greater than the current hour!</string>
<!-- Notifications -->
<string name="no_notifications">No notification to display</string>
<string name="notif_mention">mentioned your status</string>