diff --git a/app/build.gradle b/app/build.gradle index 68b03d291..0d77f1708 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java index 53dc3e66d..4e5ec2be5 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java @@ -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 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 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 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 = 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); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java index 08c190a52..a60827b08 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java @@ -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 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 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 = 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 = 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"))); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Poll.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Poll.java index 44ff58279..5cff0fe1e 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Poll.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Poll.java @@ -14,10 +14,10 @@ * see . */ 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 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(); - in.readList(this.optionsList, PollOptions.class.getClassLoader()); + this.optionsList = in.createTypedArrayList(PollOptions.CREATOR); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/PollOptions.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/PollOptions.java new file mode 100644 index 000000000..ef17596d3 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/PollOptions.java @@ -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 . */ + +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 CREATOR = new Creator() { + @Override + public PollOptions createFromParcel(Parcel source) { + return new PollOptions(source); + } + + @Override + public PollOptions[] newArray(int size) { + return new PollOptions[size]; + } + }; +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java index bd118419c..ff6b4feca 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java @@ -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 CREATOR = new Creator() { @@ -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; + } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/HttpsConnection.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/HttpsConnection.java index 1d430bbc1..d24f0a966 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/HttpsConnection.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/HttpsConnection.java @@ -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"); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java index 48e4bdec1..ae810e836 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java @@ -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 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); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java index 8d496f5b1..c985285f3 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java @@ -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 diff --git a/app/src/main/res/layout/drawer_status.xml b/app/src/main/res/layout/drawer_status.xml index bbcc52ea5..50b52a61a 100644 --- a/app/src/main/res/layout/drawer_status.xml +++ b/app/src/main/res/layout/drawer_status.xml @@ -725,7 +725,11 @@ android:id="@+id/status_peertube_delete" android:layout_width="wrap_content" android:layout_height="wrap_content" /> - / + + - + - + + + + + + + + + + + + + + + + + + + + +