Manage votes

This commit is contained in:
tom79 2019-03-24 16:38:12 +01:00
parent c0f6352604
commit 4c54271a70
16 changed files with 798 additions and 69 deletions

View File

@ -94,5 +94,6 @@ dependencies {
implementation 'com.github.mabbas007:TagsEditText:1.0.5'
implementation 'com.jaredrummler:material-spinner:1.3.1'
implementation "com.tonyodev.fetch2:fetch2:2.3.6"
implementation 'com.github.stom79:horizontalbargraph:1.3'
playstoreImplementation "io.github.kobakei:ratethisapp:$ratethisappLibraryVersion"
}

View File

@ -72,6 +72,7 @@ import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.Toast;
@ -124,6 +125,8 @@ import fr.gouv.etalab.mastodon.client.Entities.Emojis;
import fr.gouv.etalab.mastodon.client.Entities.Error;
import fr.gouv.etalab.mastodon.client.Entities.Mention;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
import fr.gouv.etalab.mastodon.client.Entities.Poll;
import fr.gouv.etalab.mastodon.client.Entities.PollOptions;
import fr.gouv.etalab.mastodon.client.Entities.Results;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.client.Entities.StoredStatus;
@ -217,6 +220,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
private String contentType;
private int max_media_count;
public static HashMap<String, Uri> filesMap;
private Poll poll;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -1050,29 +1054,152 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
case R.id.action_poll:
AlertDialog.Builder alertPoll = new AlertDialog.Builder(TootActivity.this, style);
alertPoll.setTitle(R.string.create_poll);
View view = getLayoutInflater().inflate(R.layout.popup_poll, null);
alertPoll.setView(view);
Spinner poll_choice = view.findViewById(R.id.poll_choice);
Spinner poll_duration = view.findViewById(R.id.poll_duration);
EditText choice_1 = view.findViewById(R.id.choice_1);
EditText choice_2 = view.findViewById(R.id.choice_2);
EditText choice_3 = view.findViewById(R.id.choice_3);
EditText choice_4 = view.findViewById(R.id.choice_4);
ArrayAdapter<CharSequence> pollduration = ArrayAdapter.createFromResource(TootActivity.this,
R.array.poll_duration, android.R.layout.simple_spinner_item);
alert.setView(input);
String content = tootReply.getContent();
if(tootReply.getReblog() != null)
content = tootReply.getReblog().getContent();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
input.setText(Html.fromHtml(content, Html.FROM_HTML_MODE_LEGACY));
else
//noinspection deprecation
input.setText(Html.fromHtml(content));
alert.setPositiveButton(R.string.close, new DialogInterface.OnClickListener() {
ArrayAdapter<CharSequence> pollchoice = ArrayAdapter.createFromResource(TootActivity.this,
R.array.poll_choice_type, android.R.layout.simple_spinner_item);
poll_choice.setAdapter(pollchoice);
poll_duration.setAdapter(pollduration);
poll_duration.setSelection(4);
poll_choice.setSelection(0);
if( poll != null){
int i = 1;
for(PollOptions pollOptions: poll.getOptionsList()){
switch (i){
case 1:
if( pollOptions.getTitle() != null)
choice_1.setText(pollOptions.getTitle());
break;
case 2:
if( pollOptions.getTitle() != null)
choice_2.setText(pollOptions.getTitle());
break;
case 3:
if( pollOptions.getTitle() != null)
choice_3.setText(pollOptions.getTitle());
break;
case 4:
if( pollOptions.getTitle() != null)
choice_4.setText(pollOptions.getTitle());
break;
}
i++;
}
switch (poll.getExpires_in()){
case 300:
poll_duration.setSelection(0);
break;
case 1800:
poll_duration.setSelection(1);
break;
case 3600:
poll_duration.setSelection(2);
break;
case 21600:
poll_duration.setSelection(3);
break;
case 86400:
poll_duration.setSelection(4);
break;
case 259200:
poll_duration.setSelection(5);
break;
case 604800:
poll_duration.setSelection(6);
break;
}
if( poll.isMultiple())
poll_choice.setSelection(1);
else
poll_choice.setSelection(0);
}
alertPoll.setNeutralButton(R.string.delete, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
if( poll != null)
poll = null;
dialog.dismiss();
}
});
alert.setNegativeButton(R.string.accounts, new DialogInterface.OnClickListener() {
alertPoll.setPositiveButton(R.string.done, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
new RetrieveAccountsForReplyAsyncTask(getApplicationContext(), tootReply.getReblog() != null?tootReply.getReblog():tootReply, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
dialog.dismiss();
int poll_duration_pos = poll_duration.getSelectedItemPosition();
int poll_choice_pos = poll_choice.getSelectedItemPosition();
String choice1 = choice_1.getText().toString().trim();
String choice2 = choice_2.getText().toString().trim();
String choice3 = choice_3.getText().toString().trim();
String choice4 = choice_4.getText().toString().trim();
if( choice1.isEmpty() && choice2.isEmpty()){
Toasty.error(getApplicationContext(), getString(R.string.poll_invalid_choices), Toast.LENGTH_SHORT).show();
}else{
poll = new Poll();
poll.setMultiple(poll_choice_pos != 0);
int expire = 0;
switch (poll_duration_pos){
case 0:
expire = 300;
break;
case 1:
expire = 1800;
break;
case 2:
expire = 3600;
break;
case 3:
expire = 21600;
break;
case 4:
expire = 86400;
break;
case 5:
expire = 259200;
break;
case 6:
expire = 604800;
break;
default:
expire = 864000;
}
poll.setExpires_in(expire);
List<PollOptions> pollOptions = new ArrayList<>();
PollOptions pollOption1 = new PollOptions();
pollOption1.setTitle(choice1);
pollOptions.add(pollOption1);
PollOptions pollOption2 = new PollOptions();
pollOption2.setTitle(choice2);
pollOptions.add(pollOption2);
PollOptions pollOption3 = new PollOptions();
pollOption3.setTitle(choice3);
pollOptions.add(pollOption3);
PollOptions pollOption4 = new PollOptions();
pollOption4.setTitle(choice4);
pollOptions.add(pollOption4);
poll.setOptionsList(pollOptions);
dialog.dismiss();
}
}
});
alert.show();
return true;
alertPoll.show();
return false;
case R.id.action_translate:
final CountryPicker picker = CountryPicker.newInstance(getString(R.string.which_language)); // dialog title
if( theme == Helper.THEME_LIGHT){
@ -1548,6 +1675,8 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
if( tootReply != null)
toot.setIn_reply_to_id(tootReply.getId());
toot.setContent(tootContent);
if( poll != null)
toot.setPoll(poll);
if( timestamp == null)
if( scheduledstatus == null)
new PostStatusAsyncTask(getApplicationContext(), accountReply, toot, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

View File

@ -20,6 +20,10 @@ import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.json.JSONArray;
import org.json.JSONException;
@ -31,6 +35,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.text.Format;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
@ -61,6 +66,8 @@ import fr.gouv.etalab.mastodon.client.Entities.Mention;
import fr.gouv.etalab.mastodon.client.Entities.NodeInfo;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
import fr.gouv.etalab.mastodon.client.Entities.Peertube;
import fr.gouv.etalab.mastodon.client.Entities.Poll;
import fr.gouv.etalab.mastodon.client.Entities.PollOptions;
import fr.gouv.etalab.mastodon.client.Entities.Relationship;
import fr.gouv.etalab.mastodon.client.Entities.Results;
import fr.gouv.etalab.mastodon.client.Entities.Schedule;
@ -436,6 +443,7 @@ public class API {
/**
* Returns a relationship between the authenticated account and an account
* @param accounts ArrayList<Account> accounts fetched
@ -2088,6 +2096,38 @@ public class API {
}
/**
* Public api call to submit a vote
* @param pollId
* @param choices
* @return
*/
public Poll submiteVote(String pollId, int[] choices){
JsonObject jsonObject = new JsonObject();
JsonArray jchoices = new JsonArray();
for(int choice : choices){
jchoices.add(choice);
}
jsonObject.add("choices",jchoices);
try {
HttpsConnection httpsConnection = new HttpsConnection(context);
String response = httpsConnection.postJson(getAbsoluteUrl(String.format("/polls/%s/votes", pollId)), 60, jsonObject, prefKeyOauthTokenT);
return parsePoll(context, new JSONObject(response));
} 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();
}
return null;
}
/**
* Posts a status
@ -2096,38 +2136,41 @@ public class API {
*/
public APIResponse postStatusAction(Status status){
HashMap<String, String> params = new HashMap<>();
try {
params.put("status", URLEncoder.encode(status.getContent(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
params.put("status", status.getContent());
}
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("status", status.getContent());
if( status.getContentType() != null)
params.put("content_type", status.getContentType());
jsonObject.addProperty("content_type", status.getContentType());
if( status.getIn_reply_to_id() != null)
params.put("in_reply_to_id", status.getIn_reply_to_id());
jsonObject.addProperty("in_reply_to_id", status.getIn_reply_to_id());
if( status.getMedia_attachments() != null && status.getMedia_attachments().size() > 0 ) {
StringBuilder parameters = new StringBuilder();
JsonArray mediaArray = new JsonArray();
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());
mediaArray.add(attachment.getId());
jsonObject.add("media_ids", mediaArray);
}
if( status.getScheduled_at() != null)
params.put("scheduled_at", status.getScheduled_at());
jsonObject.addProperty("scheduled_at", status.getScheduled_at());
if( status.isSensitive())
params.put("sensitive", Boolean.toString(status.isSensitive()));
jsonObject.addProperty("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());
jsonObject.addProperty("spoiler_text", status.getSpoiler_text());
if( status.getPoll() != null){
JsonObject poll = new JsonObject();
JsonArray options = new JsonArray();
for(PollOptions option: status.getPoll().getOptionsList()){
if( !option.getTitle().isEmpty())
options.add(option.getTitle());
}
params.put("visibility", status.getVisibility());
poll.add("options",options);
poll.addProperty("expires_in",status.getPoll().getExpires_in());
poll.addProperty("multiple",status.getPoll().isMultiple());
jsonObject.add("poll", poll);
}
jsonObject.addProperty("visibility", status.getVisibility());
statuses = new ArrayList<>();
try {
HttpsConnection httpsConnection = new HttpsConnection(context);
String response = httpsConnection.post(getAbsoluteUrl("/statuses"), 60, params, prefKeyOauthTokenT);
String response = httpsConnection.postJson(getAbsoluteUrl("/statuses"), 60, jsonObject, prefKeyOauthTokenT);
apiResponse.setSince_id(httpsConnection.getSince_id());
apiResponse.setMax_id(httpsConnection.getMax_id());
Status statusreturned = parseStatuses(context, new JSONObject(response));
@ -3502,6 +3545,40 @@ public class API {
return statuses;
}
/**
* Parse a poll
* @param context
* @param resobj
* @return
*/
public static Poll parsePoll(Context context, JSONObject resobj){
Poll poll = new Poll();
try {
poll.setId(resobj.getString("id"));
poll.setExpires_at(Helper.mstStringToDate(context, resobj.getString("expires_at")));
poll.setExpired(resobj.getBoolean("expired"));
poll.setMultiple(resobj.getBoolean("multiple"));
poll.setVotes_count(resobj.getInt("votes_count"));
poll.setVoted(resobj.getBoolean("voted"));
JSONArray options = resobj.getJSONArray("options");
List<PollOptions> pollOptions = new ArrayList<>();
for(int i = 0; i < options.length() ; i++){
JSONObject option = options.getJSONObject(i);
PollOptions pollOption = new PollOptions();
pollOption.setTitle(option.getString("title"));
pollOption.setVotes_count(option.getInt("votes_count"));
pollOptions.add(pollOption);
}
poll.setOptionsList(pollOptions);
} catch (JSONException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
return poll;
}
/**
* Parse json response for unique status
* @param resobj JSONObject
@ -3544,6 +3621,26 @@ public class API {
attachments.add(attachment);
}
}
if( resobj.has("poll") && !resobj.isNull("poll")){
Poll poll = new Poll();
poll.setId(resobj.getJSONObject("poll").getString("id"));
poll.setExpires_at(Helper.mstStringToDate(context, resobj.getJSONObject("poll").getString("expires_at")));
poll.setExpired(resobj.getJSONObject("poll").getBoolean("expired"));
poll.setMultiple(resobj.getJSONObject("poll").getBoolean("multiple"));
poll.setVotes_count(resobj.getJSONObject("poll").getInt("votes_count"));
poll.setVoted(resobj.getJSONObject("poll").getBoolean("voted"));
JSONArray options = resobj.getJSONObject("poll").getJSONArray("options");
List<PollOptions> pollOptions = new ArrayList<>();
for(int i = 0; i < options.length() ; i++){
JSONObject option = options.getJSONObject(i);
PollOptions pollOption = new PollOptions();
pollOption.setTitle(option.getString("title"));
pollOption.setVotes_count(option.getInt("votes_count"));
pollOptions.add(pollOption);
}
poll.setOptionsList(pollOptions);
status.setPoll(poll);
}
try {
status.setCard(parseCardResponse(resobj.getJSONObject("card")));

View File

@ -14,10 +14,10 @@
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.client.Entities;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -25,12 +25,24 @@ public class Poll implements Parcelable {
private String id;
private Date expires_at;
private int expires_in;
private boolean expired;
private boolean multiple;
private int votes_count;
private boolean voted;
private List<PollOptions> optionsList;
public Poll() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getExpires_at() {
return expires_at;
}
@ -79,28 +91,15 @@ public class Poll implements Parcelable {
this.optionsList = optionsList;
}
private class PollOptions{
private String title;
private String votes_count;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getVotes_count() {
return votes_count;
}
public void setVotes_count(String votes_count) {
this.votes_count = votes_count;
}
public int getExpires_in() {
return expires_in;
}
public void setExpires_in(int expires_in) {
this.expires_in = expires_in;
}
@Override
public int describeContents() {
return 0;
@ -110,26 +109,24 @@ public class Poll implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.id);
dest.writeLong(this.expires_at != null ? this.expires_at.getTime() : -1);
dest.writeInt(this.expires_in);
dest.writeByte(this.expired ? (byte) 1 : (byte) 0);
dest.writeByte(this.multiple ? (byte) 1 : (byte) 0);
dest.writeInt(this.votes_count);
dest.writeByte(this.voted ? (byte) 1 : (byte) 0);
dest.writeList(this.optionsList);
}
public Poll() {
dest.writeTypedList(this.optionsList);
}
protected Poll(Parcel in) {
this.id = in.readString();
long tmpExpires_at = in.readLong();
this.expires_at = tmpExpires_at == -1 ? null : new Date(tmpExpires_at);
this.expires_in = in.readInt();
this.expired = in.readByte() != 0;
this.multiple = in.readByte() != 0;
this.votes_count = in.readInt();
this.voted = in.readByte() != 0;
this.optionsList = new ArrayList<PollOptions>();
in.readList(this.optionsList, PollOptions.class.getClassLoader());
this.optionsList = in.createTypedArrayList(PollOptions.CREATOR);
}
public static final Parcelable.Creator<Poll> CREATOR = new Parcelable.Creator<Poll>() {

View File

@ -0,0 +1,72 @@
package fr.gouv.etalab.mastodon.client.Entities;
/* Copyright 2019 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 Mastalab; if not,
* see <http://www.gnu.org/licenses>. */
import android.os.Parcel;
import android.os.Parcelable;
public class PollOptions implements Parcelable {
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getVotes_count() {
return votes_count;
}
public void setVotes_count(int votes_count) {
this.votes_count = votes_count;
}
private String title;
private int votes_count;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.title);
dest.writeInt(this.votes_count);
}
public PollOptions() {
}
protected PollOptions(Parcel in) {
this.title = in.readString();
this.votes_count = in.readInt();
}
public static final Creator<PollOptions> CREATOR = new Creator<PollOptions>() {
@Override
public PollOptions createFromParcel(Parcel source) {
return new PollOptions(source);
}
@Override
public PollOptions[] newArray(int size) {
return new PollOptions[size];
}
};
}

View File

@ -132,6 +132,7 @@ public class Status implements Parcelable{
private String scheduled_at;
private String contentType;
private boolean isNotice = false;
private Poll poll = null;
@Override
public void writeToParcel(Parcel dest, int flags) {
@ -188,6 +189,7 @@ public class Status implements Parcelable{
dest.writeString(this.contentType);
dest.writeByte(this.showSpoiler ? (byte) 1 : (byte) 0);
dest.writeByte(this.isNotice ? (byte) 1 : (byte) 0);
dest.writeParcelable(this.poll, flags);
}
protected Status(Parcel in) {
@ -246,6 +248,7 @@ public class Status implements Parcelable{
this.contentType = in.readString();
this.showSpoiler = in.readByte() != 0;
this.isNotice = in.readByte() != 0;
this.poll = in.readParcelable(Poll.class.getClassLoader());
}
public static final Creator<Status> CREATOR = new Creator<Status>() {
@ -1331,4 +1334,12 @@ public class Status implements Parcelable{
public void setNotice(boolean notice) {
isNotice = notice;
}
public Poll getPoll() {
return poll;
}
public void setPoll(Poll poll) {
this.poll = poll;
}
}

View File

@ -20,8 +20,10 @@ import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.text.Html;
import android.text.SpannableString;
import android.util.Log;
import com.google.common.io.ByteStreams;
import com.google.gson.JsonObject;
import org.json.JSONObject;
@ -42,6 +44,7 @@ import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
@ -325,7 +328,7 @@ public class HttpsConnection {
postData.append(String.valueOf(param.getValue()));
}
byte[] postDataBytes = postData.toString().getBytes("UTF-8");
Log.v(Helper.TAG,"postData! " + postData);
if (proxy != null)
httpsURLConnection = (HttpsURLConnection) url.openConnection(proxy);
else
@ -426,6 +429,99 @@ public class HttpsConnection {
}
public String postJson(String urlConnection, int timeout, JsonObject jsonObject, String token) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException {
if( urlConnection.startsWith("https://")) {
URL url = new URL(urlConnection);
Log.v(Helper.TAG,"--> " +jsonObject);
byte[] postDataBytes = new byte[0];
postDataBytes = jsonObject.toString().getBytes("UTF-8");
if (proxy != null)
httpsURLConnection = (HttpsURLConnection) url.openConnection(proxy);
else
httpsURLConnection = (HttpsURLConnection) url.openConnection();
httpsURLConnection.setRequestProperty("User-Agent", Helper.USER_AGENT);
httpsURLConnection.setConnectTimeout(timeout * 1000);
httpsURLConnection.setDoOutput(true);
httpsURLConnection.setSSLSocketFactory(new TLSSocketFactory());
httpsURLConnection.setRequestProperty("Content-Type", "application/json");
httpsURLConnection.setRequestProperty("Accept", "application/json");
httpsURLConnection.setRequestMethod("POST");
if (token != null && !token.startsWith("Basic "))
httpsURLConnection.setRequestProperty("Authorization", "Bearer " + token);
else if( token != null && token.startsWith("Basic "))
httpsURLConnection.setRequestProperty("Authorization", token);
httpsURLConnection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));
httpsURLConnection.getOutputStream().write(postDataBytes);
String response;
if (httpsURLConnection.getResponseCode() >= 200 && httpsURLConnection.getResponseCode() < 400) {
getSinceMaxId();
response = converToString(httpsURLConnection.getInputStream());
} else {
String error = null;
if( httpsURLConnection.getErrorStream() != null) {
InputStream stream = httpsURLConnection.getErrorStream();
if (stream == null) {
stream = httpsURLConnection.getInputStream();
}
try (Scanner scanner = new Scanner(stream)) {
scanner.useDelimiter("\\Z");
error = scanner.next();
}catch (Exception e){e.printStackTrace();}
}
int responseCode = httpsURLConnection.getResponseCode();
throw new HttpsConnectionException(responseCode, error);
}
getSinceMaxId();
httpsURLConnection.getInputStream().close();
return response;
}else {
URL url = new URL(urlConnection);
byte[] postDataBytes = jsonObject.toString().getBytes("UTF-8");
if (proxy != null)
httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
else
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setRequestProperty("User-Agent", Helper.USER_AGENT);
httpURLConnection.setConnectTimeout(timeout * 1000);
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestMethod("POST");
if (token != null && !token.startsWith("Basic "))
httpURLConnection.setRequestProperty("Authorization", "Bearer " + token);
else if( token != null && token.startsWith("Basic "))
httpURLConnection.setRequestProperty("Authorization", token);
httpURLConnection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));
httpURLConnection.getOutputStream().write(postDataBytes);
String response;
if (httpURLConnection.getResponseCode() >= 200 && httpURLConnection.getResponseCode() < 400) {
getSinceMaxId();
response = converToString(httpURLConnection.getInputStream());
} else {
String error = null;
if( httpURLConnection.getErrorStream() != null) {
InputStream stream = httpURLConnection.getErrorStream();
if (stream == null) {
stream = httpURLConnection.getInputStream();
}
try (Scanner scanner = new Scanner(stream)) {
scanner.useDelimiter("\\Z");
error = scanner.next();
}catch (Exception e){e.printStackTrace();}
}
int responseCode = httpURLConnection.getResponseCode();
throw new HttpsConnectionException(responseCode, error);
}
getSinceMaxId();
httpURLConnection.getInputStream().close();
return response;
}
}
public String postMisskey(String urlConnection, int timeout, JSONObject paramaters, String token) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException {
URL url = new URL(urlConnection);
byte[] postDataBytes = paramaters.toString().getBytes("UTF-8");

View File

@ -24,6 +24,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
@ -46,6 +47,7 @@ import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.URLSpan;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MenuItem;
@ -55,12 +57,15 @@ import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.TimePicker;
@ -90,6 +95,8 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import br.com.felix.horizontalbargraph.HorizontalBar;
import br.com.felix.horizontalbargraph.model.BarItem;
import es.dmoral.toasty.Toasty;
import fr.gouv.etalab.mastodon.R;
import fr.gouv.etalab.mastodon.activities.BaseMainActivity;
@ -113,6 +120,8 @@ import fr.gouv.etalab.mastodon.client.Entities.Card;
import fr.gouv.etalab.mastodon.client.Entities.Emojis;
import fr.gouv.etalab.mastodon.client.Entities.Error;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
import fr.gouv.etalab.mastodon.client.Entities.Poll;
import fr.gouv.etalab.mastodon.client.Entities.PollOptions;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.client.Entities.TagTimeline;
import fr.gouv.etalab.mastodon.fragments.DisplayStatusFragment;
@ -328,6 +337,16 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct
LinearLayout status_peertube_container;
TextView status_peertube_reply, status_peertube_delete;
//Poll
LinearLayout poll_container, single_choice, multiple_choice, rated;
RadioGroup radio_group;
RadioButton r_choice_1, r_choice_2, r_choice_3, r_choice_4;
CheckBox c_choice_1, c_choice_2, c_choice_3, c_choice_4;
HorizontalBar choices;
TextView number_votes, remaining_time;
Button submit_vote;
public View getView(){
return itemView;
}
@ -416,6 +435,23 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct
status_peertube_reply = itemView.findViewById(R.id.status_peertube_reply);
status_peertube_delete = itemView.findViewById(R.id.status_peertube_delete);
poll_container = itemView.findViewById(R.id.poll_container);
single_choice = itemView.findViewById(R.id.single_choice);
multiple_choice = itemView.findViewById(R.id.multiple_choice);
rated = itemView.findViewById(R.id.rated);
radio_group = itemView.findViewById(R.id.radio_group);
r_choice_1 = itemView.findViewById(R.id.r_choice_1);
r_choice_2 = itemView.findViewById(R.id.r_choice_2);
r_choice_3 = itemView.findViewById(R.id.r_choice_3);
r_choice_4 = itemView.findViewById(R.id.r_choice_4);
c_choice_1 = itemView.findViewById(R.id.c_choice_1);
c_choice_2 = itemView.findViewById(R.id.c_choice_2);
c_choice_3 = itemView.findViewById(R.id.c_choice_3);
c_choice_4 = itemView.findViewById(R.id.c_choice_4);
choices = itemView.findViewById(R.id.choices);
number_votes = itemView.findViewById(R.id.number_votes);
remaining_time = itemView.findViewById(R.id.remaining_time);
submit_vote = itemView.findViewById(R.id.submit_vote);
}
}
@ -506,7 +542,91 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct
holder.status_reply.setText("");
//Display a preview for accounts that have replied *if enabled and only for home timeline*
if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON) {
holder.rated.setVisibility(View.GONE);
holder.multiple_choice.setVisibility(View.GONE);
holder.single_choice.setVisibility(View.GONE);
holder.submit_vote.setVisibility(View.GONE);
if( status.getPoll() != null){
Poll poll = status.getPoll();
int choiceCount = status.getPoll().getOptionsList().size();
if( poll.isVoted()){
holder.rated.setVisibility(View.VISIBLE);
List<BarItem> items = new ArrayList<>();
int greaterValue = 0;
for(PollOptions pollOption: status.getPoll().getOptionsList()){
if( pollOption.getVotes_count() > greaterValue)
greaterValue = pollOption.getVotes_count();
}
for(PollOptions pollOption: status.getPoll().getOptionsList()){
double value = ((double)(pollOption.getVotes_count()* 100) / (double)poll.getVotes_count()) ;
if( pollOption.getVotes_count() == greaterValue) {
BarItem bar = new BarItem(pollOption.getTitle(), value, "%", ContextCompat.getColor(context, R.color.mastodonC4), Color.WHITE);
bar.setRounded(true);
bar.setHeight1(30);
items.add(bar);
}else {
BarItem bar = new BarItem(pollOption.getTitle(), value, "%", ContextCompat.getColor(context, R.color.mastodonC2), Color.WHITE);
bar.setRounded(true);
bar.setHeight1(30);
items.add(bar);
}
}
holder.choices.init(context).hasAnimation(true).addAll(items).build();
}else {
if( poll.isMultiple()){
Log.v(Helper.TAG,"count: " + choiceCount);
holder.multiple_choice.setVisibility(View.VISIBLE);
holder.c_choice_3.setVisibility(View.GONE);
holder.c_choice_4.setVisibility(View.GONE);
if( choiceCount > 2)
holder.c_choice_3.setVisibility(View.VISIBLE);
if( choiceCount > 3)
holder.c_choice_4.setVisibility(View.VISIBLE);
int j = 1;
for(PollOptions pollOption: status.getPoll().getOptionsList()){
if( j == 1 )
holder.c_choice_1.setText(pollOption.getTitle());
else if( j == 2 )
holder.c_choice_2.setText(pollOption.getTitle());
else if( j == 3 )
holder.c_choice_3.setText(pollOption.getTitle());
else if( j == 4 )
holder.c_choice_4.setText(pollOption.getTitle());
j++;
}
}else {
Log.v(Helper.TAG,"count: " + choiceCount);
holder.single_choice.setVisibility(View.VISIBLE);
holder.r_choice_3.setVisibility(View.GONE);
holder.r_choice_4.setVisibility(View.GONE);
if( choiceCount > 2)
holder.r_choice_3.setVisibility(View.VISIBLE);
if( choiceCount > 3)
holder.r_choice_4.setVisibility(View.VISIBLE);
int j = 1;
for(PollOptions pollOption: status.getPoll().getOptionsList()){
if( j == 1 )
holder.r_choice_1.setText(pollOption.getTitle());
else if( j == 2 )
holder.r_choice_2.setText(pollOption.getTitle());
else if( j == 3 )
holder.r_choice_3.setText(pollOption.getTitle());
else if( j == 4 )
holder.r_choice_4.setText(pollOption.getTitle());
j++;
}
}
holder.submit_vote.setVisibility(View.VISIBLE);
}
holder.poll_container.setVisibility(View.VISIBLE);
holder.number_votes.setText(context.getResources().getQuantityString(R.plurals.number_of_vote,status.getPoll().getVotes_count(),status.getPoll().getVotes_count()));
holder.remaining_time.setText(context.getString(R.string.poll_finish_at, Helper.dateToStringPoll(poll.getExpires_at())));
}else {
holder.poll_container.setVisibility(View.GONE);
}
}
if (social == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE) {
holder.status_action_container.setVisibility(View.GONE);

View File

@ -665,6 +665,18 @@ public class Helper {
return dateFormat.format(date);
}
/**
* Convert a date in String -> format yyyy-MM-dd HH:mm:ss
* @param date Date
* @return String
*/
public static String dateToStringPoll(Date date) {
if( date == null)
return null;
SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm",Locale.getDefault());
return dateFormat.format(date);
}
/**
* Convert a date in String -> format yyyy-MM-dd HH:mm:ss
* @param date Date

View File

@ -725,7 +725,11 @@
android:id="@+id/status_peertube_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>/
</LinearLayout>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/layout_poll" />
<LinearLayout
android:layout_marginStart="60dp"
android:layout_marginLeft="60dp"

View File

@ -656,7 +656,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/layout_poll" />
<TextView
android:visibility="gone"
android:layout_marginEnd="20dp"

View File

@ -657,7 +657,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/layout_poll" />
<TextView
android:visibility="gone"
android:layout_marginEnd="20dp"

View File

@ -584,6 +584,12 @@
android:layout_height="wrap_content"
android:maxLines="1"
/>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/layout_poll" />
<TextView
android:id="@+id/status_toot_app"
android:visibility="gone"

View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 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 Mastalab; 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:layout_width="match_parent"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:id="@+id/poll_container"
android:visibility="gone"
android:orientation="vertical"
android:layout_height="match_parent"
>
<LinearLayout
android:visibility="gone"
android:id="@+id/single_choice"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioGroup
android:id="@+id/radio_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="UselessParent">
<RadioButton
android:id="@+id/r_choice_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
/>
<RadioButton
android:id="@+id/r_choice_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
/>
<RadioButton
android:id="@+id/r_choice_3"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
/>
<RadioButton
android:id="@+id/r_choice_4"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
/>
</RadioGroup>
</LinearLayout>
<LinearLayout
android:visibility="gone"
android:id="@+id/multiple_choice"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/c_choice_1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/c_choice_2"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/c_choice_3"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/c_choice_4"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:visibility="gone"
android:layout_marginTop="10dp"
android:gravity="center"
android:layout_gravity="center"
android:id="@+id/submit_vote"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:text="@string/vote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:visibility="gone"
android:id="@+id/rated"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<br.com.felix.horizontalbargraph.HorizontalBar
android:id="@+id/choices"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/number_votes"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:text=" - "
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/remaining_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/refresh_poll"
android:textColor="?colorAccent"
android:text="@string/refresh_poll"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View File

@ -58,7 +58,14 @@
android:layout_height="wrap_content"
android:orientation="horizontal">
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Spinner>
android:id="@+id/poll_choice"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Spinner
android:id="@+id/poll_duration"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@ -901,15 +901,39 @@
<string name="featured_hashtags">Featured hashtags</string>
<string name="filter_timeline_with_a_tag">Filter timeline with tags</string>
<string name="no_tags">No tags</string>
<!-- end languages -->
<string name="poll">Poll</string>
<string name="create_poll">Create a poll</string>
<string name="poll_choice_1">Choice 1</string>
<string name="poll_choice_2">Choice 2</string>
<string name="poll_choice_3">Choice 3</string>
<string name="poll_choice_4">Choice 4</string>
<!-- end languages -->
<string name="poll_invalid_choices">You need two choices at least for the poll!</string>
<string name="done">Done</string>
<string name="poll_finish_at">end at %s</string>
<string name="refresh_poll">Refresh poll</string>
<string name="vote">Vote</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>
</plurals>
<string-array name="poll_choice_type">
<item>Single choice</item>
<item>Multiple choices</item>
</string-array>
<string-array name="poll_duration">
<item>5 minutes</item>
<item>30 minutes</item>
<item>1 hour</item>
<item>6 hours</item>
<item>1 day</item>
<item>3 days</item>
<item>7 days</item>
</string-array >
<string-array name="settings_video_mode">
<item>Webview</item>