First step scheduling

This commit is contained in:
stom79 2019-01-19 19:41:55 +01:00
parent 5ed01ddb48
commit 0411b878c2
8 changed files with 307 additions and 65 deletions

View File

@ -499,35 +499,7 @@ public class TootActivity extends BaseActivity implements OnRetrieveSearcAccount
toot_it.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toot_it.setEnabled(false);
if(toot_content.getText().toString().trim().length() == 0 && attachments.size() == 0){
Toasty.error(getApplicationContext(),getString(R.string.toot_select_image_error),Toast.LENGTH_LONG).show();
toot_it.setEnabled(true);
return;
}
boolean split_toot = sharedpreferences.getBoolean(Helper.SET_AUTOMATICALLY_SPLIT_TOOTS, false);
int split_toot_size = sharedpreferences.getInt(Helper.SET_AUTOMATICALLY_SPLIT_TOOTS_SIZE, Helper.SPLIT_TOOT_SIZE);
String tootContent;
if( toot_cw_content.getText() != null && toot_cw_content.getText().toString().trim().length() > 0 )
split_toot_size -= toot_cw_content.getText().toString().trim().length();
if( !split_toot || (toot_content.getText().toString().trim().length() < split_toot_size)){
tootContent = toot_content.getText().toString().trim();
}else{
splitToot = Helper.splitToots(toot_content.getText().toString().trim(), split_toot_size);
tootContent = splitToot.get(0);
stepSpliToot = 1;
}
Status toot = new Status();
toot.setSensitive(isSensitive);
toot.setMedia_attachments(attachments);
if( toot_cw_content.getText().toString().trim().length() > 0)
toot.setSpoiler_text(toot_cw_content.getText().toString().trim());
toot.setVisibility(visibility);
if( tootReply != null)
toot.setIn_reply_to_id(tootReply.getId());
toot.setContent(tootContent);
new PostStatusAsyncTask(getApplicationContext(), accountReply, toot, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
sendToot(null);
}
});
@ -1259,29 +1231,33 @@ public class TootActivity extends BaseActivity implements OnRetrieveSearcAccount
if( (time - new Date().getTime()) < 60000 ){
Toasty.warning(getApplicationContext(), getString(R.string.toot_scheduled_date), Toast.LENGTH_LONG).show();
}else {
//Store the toot as draft first
storeToot(false, false);
//Schedules the toot
ScheduledTootsSyncJob.schedule(getApplicationContext(), currentToId, time);
//Clear content
toot_content.setText("");
toot_cw_content.setText("");
toot_space_left.setText("0");
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();
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, android.content.Context.MODE_PRIVATE);
String instanceVersion = sharedpreferences.getString(Helper.INSTANCE_VERSION + userId + instance, null);
Version currentVersion = new Version(instanceVersion);
Version minVersion = new Version("2.7");
if (currentVersion.compareTo(minVersion) == 1 || currentVersion.equals(minVersion)) {
AlertDialog.Builder builderSingle = new AlertDialog.Builder(TootActivity.this, style);
builderSingle.setTitle(getString(R.string.choose_schedule));
builderSingle.setNegativeButton(R.string.device_schedule, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
deviceSchedule(time);
dialog.dismiss();
}
});
builderSingle.setPositiveButton(R.string.server_schedule, new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int which) {
serverSchedule(time);
}
});
builderSingle.show();
} else {
deviceSchedule(time);
}
isSensitive = false;
toot_sensitive.setVisibility(View.GONE);
currentToId = -1;
Toasty.info(TootActivity.this,getString(R.string.toot_scheduled), Toast.LENGTH_LONG).show();
alertDialog.dismiss();
}
}
@ -1293,6 +1269,80 @@ public class TootActivity extends BaseActivity implements OnRetrieveSearcAccount
}
}
private void sendToot(String timestamp){
toot_it.setEnabled(false);
if(toot_content.getText().toString().trim().length() == 0 && attachments.size() == 0){
Toasty.error(getApplicationContext(),getString(R.string.toot_select_image_error),Toast.LENGTH_LONG).show();
toot_it.setEnabled(true);
return;
}
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, android.content.Context.MODE_PRIVATE);
boolean split_toot = sharedpreferences.getBoolean(Helper.SET_AUTOMATICALLY_SPLIT_TOOTS, false);
int split_toot_size = sharedpreferences.getInt(Helper.SET_AUTOMATICALLY_SPLIT_TOOTS_SIZE, Helper.SPLIT_TOOT_SIZE);
String tootContent;
if( toot_cw_content.getText() != null && toot_cw_content.getText().toString().trim().length() > 0 )
split_toot_size -= toot_cw_content.getText().toString().trim().length();
if( !split_toot || (toot_content.getText().toString().trim().length() < split_toot_size)){
tootContent = toot_content.getText().toString().trim();
}else{
splitToot = Helper.splitToots(toot_content.getText().toString().trim(), split_toot_size);
tootContent = splitToot.get(0);
stepSpliToot = 1;
}
Status toot = new Status();
toot.setSensitive(isSensitive);
toot.setMedia_attachments(attachments);
if( toot_cw_content.getText().toString().trim().length() > 0)
toot.setSpoiler_text(toot_cw_content.getText().toString().trim());
toot.setVisibility(visibility);
if( tootReply != null)
toot.setIn_reply_to_id(tootReply.getId());
toot.setContent(tootContent);
if( timestamp != null ){
toot.setScheduled_at(timestamp);
}
new PostStatusAsyncTask(getApplicationContext(), accountReply, toot, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void serverSchedule(long time){
sendToot(String.valueOf(time));
resetForNextToot();
}
private void deviceSchedule(long time){
//Store the toot as draft first
storeToot(false, false);
//Schedules the toot
ScheduledTootsSyncJob.schedule(getApplicationContext(), currentToId, time);
resetForNextToot();
}
private void resetForNextToot(){
//Clear content
toot_content.setText("");
toot_cw_content.setText("");
toot_space_left.setText("0");
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;
Toasty.info(TootActivity.this,getString(R.string.toot_scheduled), Toast.LENGTH_LONG).show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_toot, menu);

View File

@ -90,7 +90,8 @@ public class RetrieveFeedsAsyncTask extends AsyncTask<Void, Void, Void> {
PF_HOME,
PF_LOCAL,
PF_DISCOVER,
PF_NOTIFICATION
PF_NOTIFICATION,
SCHEDULED_TOOTS
}
@ -168,6 +169,9 @@ public class RetrieveFeedsAsyncTask extends AsyncTask<Void, Void, Void> {
case PUBLIC:
apiResponse = api.getPublicTimeline(false, max_id);
break;
case SCHEDULED_TOOTS:
apiResponse = api.scheduledAction("GET", null, max_id);
break;
case DIRECT:
apiResponse = api.getDirectTimeline(max_id);
break;

View File

@ -58,6 +58,7 @@ import fr.gouv.etalab.mastodon.client.Entities.Peertube;
import fr.gouv.etalab.mastodon.client.Entities.Relationship;
import fr.gouv.etalab.mastodon.client.Entities.Results;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.client.Entities.StoredStatus;
import fr.gouv.etalab.mastodon.client.Entities.Tag;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
@ -131,15 +132,19 @@ public class API {
tootPerPage = sharedpreferences.getInt(Helper.SET_TOOTS_PER_PAGE, 40);
accountPerPage = sharedpreferences.getInt(Helper.SET_ACCOUNTS_PER_PAGE, 40);
notificationPerPage = sharedpreferences.getInt(Helper.SET_NOTIFICATIONS_PER_PAGE, 15);
this.prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
if( Helper.getLiveInstance(context) != null)
this.instance = Helper.getLiveInstance(context);
else {
SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
Account account = new AccountDAO(context, db).getAccountByID(userId);
Account account = new AccountDAO(context, db).getAccountByToken(this.prefKeyOauthTokenT);
if( account == null) {
APIError = new Error();
APIError.setError(context.getString(R.string.toast_error));
return;
}
this.instance = account.getInstance().trim();
}
this.prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
apiResponse = new APIResponse();
APIError = null;
}
@ -1775,6 +1780,8 @@ public class API {
} catch (UnsupportedEncodingException e) {
params.put("status", status.getContent());
}
if( status.getScheduled_at() != null)
params.put("scheduled_at", status.getScheduled_at());
if( status.getIn_reply_to_id() != null)
params.put("in_reply_to_id", status.getIn_reply_to_id());
if( status.getMedia_attachments() != null && status.getMedia_attachments().size() > 0 ) {
@ -1841,6 +1848,111 @@ public class API {
}
/**
* scheduled action for a status
* @param status Status object related to the status
* @return APIResponse
*/
public APIResponse scheduledAction(String call, Status status, String max_id){
HashMap<String, String> params = new HashMap<>();
if( call.equals("PUT")){
try {
params.put("status", URLEncoder.encode(status.getContent(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
params.put("status", status.getContent());
}
if( status.getIn_reply_to_id() != null)
params.put("in_reply_to_id", status.getIn_reply_to_id());
if( status.getMedia_attachments() != null && status.getMedia_attachments().size() > 0 ) {
StringBuilder parameters = new StringBuilder();
for(Attachment attachment: status.getMedia_attachments())
parameters.append("media_ids[]=").append(attachment.getId()).append("&");
parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(12));
params.put("media_ids[]", parameters.toString());
}
if( status.isSensitive())
params.put("sensitive", Boolean.toString(status.isSensitive()));
if( status.getSpoiler_text() != null)
try {
params.put("spoiler_text", URLEncoder.encode(status.getSpoiler_text(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
params.put("spoiler_text", status.getSpoiler_text());
}
params.put("visibility", status.getVisibility());
}else if(call.equals("GET")){
if( max_id != null )
params.put("max_id", max_id);
}
List<StoredStatus> storedStatus = new ArrayList<>();
try {
HttpsConnection httpsConnection = new HttpsConnection(context);
String response = null;
int responseCode;
if( call.equals("GET"))
response = httpsConnection.get(getAbsoluteUrl("/scheduled_statuses/"), 60, null, prefKeyOauthTokenT);
else if( call.equals("PUT"))
response = httpsConnection.get(getAbsoluteUrl(String.format("/scheduled_statuses/%s", status.getId())), 60, params, prefKeyOauthTokenT);
else if( call.equals("DELETE"))
responseCode = httpsConnection.delete(getAbsoluteUrl(String.format("/scheduled_statuses/%s", status.getId())), 60, params, prefKeyOauthTokenT);
if(call.equals("GET")) {
apiResponse.setSince_id(httpsConnection.getSince_id());
apiResponse.setMax_id(httpsConnection.getMax_id());
}
if (response != null && call.equals("PUT")) {
Status statusreturned = parseStatuses(context, new JSONObject(response));
StoredStatus st = new StoredStatus();
st.setCreation_date(status.getCreated_at());
st.setId(-1);
st.setJobId(-1);
st.setScheduled_date(new Date(Long.parseLong(status.getScheduled_at())));
st.setStatusReply(null);
st.setSent_date(null);
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
st.setUserId(userId);
st.setStatus(statusreturned);
storedStatus.add(st);
}else if (response != null && call.equals("GET")) {
List<Status> statusreturned = parseStatuses(context, new JSONArray(response));
for(Status status1: statusreturned){
StoredStatus st = new StoredStatus();
st.setCreation_date(status.getCreated_at());
st.setId(-1);
st.setJobId(-1);
st.setScheduled_date(new Date(Long.parseLong(status.getScheduled_at())));
st.setStatusReply(null);
st.setSent_date(null);
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
st.setUserId(userId);
st.setStatus(status1);
storedStatus.add(st);
}
storedStatus.addAll(storedStatus);
}
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
apiResponse.setStoredStatuses(storedStatus);
return apiResponse;
}
/**
* Posts a status
* @param status Status object related to the status
@ -2030,6 +2142,7 @@ public class API {
/**
* Changes media description
* @param mediaId String

View File

@ -13,10 +13,23 @@ package fr.gouv.etalab.mastodon.client;
*
* You should have received a copy of the GNU General Public License along with Mastalab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.Context;
import java.util.List;
import fr.gouv.etalab.mastodon.client.Entities.*;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.client.Entities.Conversation;
import fr.gouv.etalab.mastodon.client.Entities.Emojis;
import fr.gouv.etalab.mastodon.client.Entities.Error;
import fr.gouv.etalab.mastodon.client.Entities.Filters;
import fr.gouv.etalab.mastodon.client.Entities.HowToVideo;
import fr.gouv.etalab.mastodon.client.Entities.Instance;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
import fr.gouv.etalab.mastodon.client.Entities.Peertube;
import fr.gouv.etalab.mastodon.client.Entities.Relationship;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.client.Entities.StoredStatus;
/**
* Created by Thomas on 03/06/2017.
@ -40,7 +53,7 @@ public class APIResponse {
private fr.gouv.etalab.mastodon.client.Entities.Error error = null;
private String since_id, max_id;
private Instance instance;
private List<StoredStatus> storedStatuses;
public List<Account> getAccounts() {
return accounts;
}
@ -168,4 +181,12 @@ public class APIResponse {
public void setConversations(List<Conversation> conversations) {
this.conversations = conversations;
}
public List<StoredStatus> getStoredStatuses() {
return storedStatuses;
}
public void setStoredStatuses(List<StoredStatus> storedStatuses) {
this.storedStatuses = storedStatuses;
}
}

View File

@ -125,7 +125,7 @@ public class Status implements Parcelable{
private String webviewURL = null;
private boolean isBoostAnimated = false, isFavAnimated = false;
private String scheduled_at;
@Override
public void writeToParcel(Parcel dest, int flags) {
@ -178,6 +178,7 @@ public class Status implements Parcelable{
dest.writeString(this.webviewURL);
dest.writeByte(this.isBoostAnimated ? (byte) 1 : (byte) 0);
dest.writeByte(this.isFavAnimated ? (byte) 1 : (byte) 0);
dest.writeString(this.scheduled_at);
}
protected Status(Parcel in) {
@ -232,6 +233,7 @@ public class Status implements Parcelable{
this.webviewURL = in.readString();
this.isBoostAnimated = in.readByte() != 0;
this.isFavAnimated = in.readByte() != 0;
this.scheduled_at = in.readString();
}
public static final Creator<Status> CREATOR = new Creator<Status>() {
@ -1236,4 +1238,11 @@ public class Status implements Parcelable{
}
public String getScheduled_at() {
return scheduled_at;
}
public void setScheduled_at(String scheduled_at) {
this.scheduled_at = scheduled_at;
}
}

View File

@ -37,14 +37,20 @@ import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import es.dmoral.toasty.Toasty;
import fr.gouv.etalab.mastodon.R;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveFeedsAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveScheduledTootsAsyncTask;
import fr.gouv.etalab.mastodon.client.APIResponse;
import fr.gouv.etalab.mastodon.client.Entities.StoredStatus;
import fr.gouv.etalab.mastodon.drawers.ScheduledTootsListAdapter;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveFeedsInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveScheduledTootsInterface;
import fr.gouv.etalab.mastodon.sqlite.BoostScheduleDAO;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
@ -57,7 +63,7 @@ import static fr.gouv.etalab.mastodon.helper.Helper.changeDrawableColor;
* Created by Thomas on 16/07/2017.
* Fragment to display scheduled toots
*/
public class DisplayScheduledTootsFragment extends Fragment implements OnRetrieveScheduledTootsInterface {
public class DisplayScheduledTootsFragment extends Fragment implements OnRetrieveScheduledTootsInterface, OnRetrieveFeedsInterface {
private Context context;
@ -66,10 +72,14 @@ public class DisplayScheduledTootsFragment extends Fragment implements OnRetriev
private ListView lv_scheduled_toots;
private TextView warning_battery_message;
private typeOfSchedule type;
private List<StoredStatus> storedStatuses;
private boolean firstCall;
private ScheduledTootsListAdapter scheduledTootsListAdapter;
public enum typeOfSchedule{
TOOT,
BOOST
BOOST,
SERVER
}
@Override
@ -83,21 +93,44 @@ public class DisplayScheduledTootsFragment extends Fragment implements OnRetriev
if( type == null)
type = typeOfSchedule.TOOT;
lv_scheduled_toots = rootView.findViewById(R.id.lv_scheduled_toots);
firstCall = true;
mainLoader = rootView.findViewById(R.id.loader);
warning_battery_message = rootView.findViewById(R.id.warning_battery_message);
textviewNoAction = rootView.findViewById(R.id.no_action);
mainLoader.setVisibility(View.VISIBLE);
storedStatuses = new ArrayList<>();
//Removes all scheduled toots that have sent
SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
if( type == typeOfSchedule.TOOT)
new StatusStoredDAO(context, db).removeAllSent();
else if( type == typeOfSchedule.BOOST)
new BoostScheduleDAO(context, db).removeAllSent();
else if( type == typeOfSchedule.SERVER)
asyncTask = new RetrieveFeedsAsyncTask(context, RetrieveFeedsAsyncTask.Type.SCHEDULED_TOOTS, null, DisplayScheduledTootsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
scheduledTootsListAdapter = new ScheduledTootsListAdapter(context, type, storedStatuses, textviewNoAction);
lv_scheduled_toots.setAdapter(scheduledTootsListAdapter);
return rootView;
}
@Override
public void onRetrieveFeeds(APIResponse apiResponse) {
if( apiResponse.getError() != null && apiResponse.getError().getStatusCode() != 404 ){
Toasty.error(context, apiResponse.getError().getError(), Toast.LENGTH_LONG).show();
return;
}
mainLoader.setVisibility(View.GONE);
if(apiResponse.getStoredStatuses() != null && apiResponse.getStoredStatuses().size() > 0 ){
storedStatuses.addAll(apiResponse.getStoredStatuses());
textviewNoAction.setVisibility(View.GONE);
lv_scheduled_toots.setVisibility(View.VISIBLE);
scheduledTootsListAdapter.notifyDataSetChanged();
}else if( firstCall){
textviewNoAction.setVisibility(View.VISIBLE);
lv_scheduled_toots.setVisibility(View.GONE);
}
firstCall = false;
}
@Override
public void onResume(){
@ -180,7 +213,7 @@ public class DisplayScheduledTootsFragment extends Fragment implements OnRetriev
mainLoader.setVisibility(View.GONE);
if( storedStatuses != null && storedStatuses.size() > 0 ){
ScheduledTootsListAdapter scheduledTootsListAdapter = new ScheduledTootsListAdapter(context, type, storedStatuses, textviewNoAction);
scheduledTootsListAdapter = new ScheduledTootsListAdapter(context, type, storedStatuses, textviewNoAction);
lv_scheduled_toots.setAdapter(scheduledTootsListAdapter);
textviewNoAction.setVisibility(View.GONE);
}else {

View File

@ -43,7 +43,8 @@ public class TabLayoutScheduleFragment extends Fragment {
View inflatedView = inflater.inflate(R.layout.tablayout_toots, container, false);
TabLayout tabLayout = inflatedView.findViewById(R.id.tabLayout);
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.toots)));
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.toots_server)));
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.toots_client)));
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.reblog)));
final ViewPager viewPager = inflatedView.findViewById(R.id.viewpager);
viewPager.setAdapter(new PagerAdapter
@ -87,10 +88,16 @@ public class TabLayoutScheduleFragment extends Fragment {
case 0:
DisplayScheduledTootsFragment displayScheduledTootsFragment = new DisplayScheduledTootsFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("type", DisplayScheduledTootsFragment.typeOfSchedule.TOOT);
bundle.putSerializable("type", DisplayScheduledTootsFragment.typeOfSchedule.SERVER);
displayScheduledTootsFragment.setArguments(bundle);
return displayScheduledTootsFragment;
case 1:
displayScheduledTootsFragment = new DisplayScheduledTootsFragment();
bundle = new Bundle();
bundle.putSerializable("type", DisplayScheduledTootsFragment.typeOfSchedule.TOOT);
displayScheduledTootsFragment.setArguments(bundle);
return displayScheduledTootsFragment;
case 2:
displayScheduledTootsFragment = new DisplayScheduledTootsFragment();
bundle = new Bundle();
bundle.putSerializable("type", DisplayScheduledTootsFragment.typeOfSchedule.BOOST);

View File

@ -828,6 +828,11 @@
<string name="leave_a_comment">Leave a comment</string>
<string name="share">Share</string>
<string name="my_pictures">My pictures</string>
<string name="choose_schedule">Choose a schedule mode</string>
<string name="device_schedule">From device</string>
<string name="server_schedule">From server</string>
<string name="toots_server">Toots (Server)</string>
<string name="toots_client">Toots (Device)</string>
<!-- end languages -->