diff --git a/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java b/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java index eed6df7a3..816c03ddd 100644 --- a/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java @@ -160,13 +160,19 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra AutoCompleteTextView instance_list = dialogView.findViewById(R.id.search_instance); //Manage download of attachments RadioGroup radioGroup = dialogView.findViewById(R.id.set_attachment_group); - + radioGroup.setOnCheckedChangeListener((group, checkedId) -> { + if( checkedId == R.id.twitter_accounts){ + instance_list.setHint(R.string.list_of_twitter_accounts); + }else { + instance_list.setHint(R.string.instance); + } + }); instance_list.setFilters(new InputFilter[]{new InputFilter.LengthFilter(60)}); dialogBuilder.setPositiveButton(R.string.validate, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - String instanceName = instance_list.getText().toString().trim(); + String instanceName = instance_list.getText().toString().trim().replace("@",""); new Thread(new Runnable() { @Override public void run() { @@ -182,7 +188,6 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra } else if (radioGroup.getCheckedRadioButtonId() == R.id.gnu_instance) { new HttpsConnection(ReorderTimelinesActivity.this, null).get("https://" + instanceName + "/api/statuses/public_timeline.json", 10, null, null); } - runOnUiThread(new Runnable() { public void run() { dialog.dismiss(); @@ -196,6 +201,8 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra new InstancesDAO(ReorderTimelinesActivity.this, db).insertInstance(instanceName, "MISSKEY"); } else if (radioGroup.getCheckedRadioButtonId() == R.id.gnu_instance) { new InstancesDAO(ReorderTimelinesActivity.this, db).insertInstance(instanceName, "GNU"); + }else if (radioGroup.getCheckedRadioButtonId() == R.id.twitter_accounts) { + new InstancesDAO(ReorderTimelinesActivity.this, db).insertInstance(instanceName, "NITTER"); } if (timelines != null && adapter != null) { List instance = new InstancesDAO(ReorderTimelinesActivity.this, db).getInstanceByName(instanceName); @@ -256,78 +263,84 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra @Override public void afterTextChanged(Editable s) { - Pattern host = Pattern.compile("([\\da-z\\.-]+\\.[a-z\\.]{2,12})"); - Matcher matcher = host.matcher(s.toString().trim()); - if (s.toString().trim().length() == 0 || !matcher.find()) { - alertDialog.getButton( - AlertDialog.BUTTON_POSITIVE).setEnabled(false); - } else { - // Something into edit text. Enable the button. + if (radioGroup.getCheckedRadioButtonId() != R.id.twitter_accounts){ + Pattern host = Pattern.compile("([\\da-z\\.-]+\\.[a-z\\.]{2,12})"); + Matcher matcher = host.matcher(s.toString().trim()); + if (s.toString().trim().length() == 0 || !matcher.find()) { + alertDialog.getButton( + AlertDialog.BUTTON_POSITIVE).setEnabled(false); + } else { + // Something into edit text. Enable the button. + alertDialog.getButton( + AlertDialog.BUTTON_POSITIVE).setEnabled(true); + } + if (s.length() > 2 && !isLoadingInstance) { + final String action = "/instances/search"; + final HashMap parameters = new HashMap<>(); + parameters.put("q", s.toString().trim()); + parameters.put("count", String.valueOf(1000)); + parameters.put("name", String.valueOf(true)); + isLoadingInstance = true; + + if (oldSearch == null || !oldSearch.equals(s.toString().trim())) + new Thread(new Runnable() { + @Override + public void run() { + try { + final String response = new HttpsConnection(ReorderTimelinesActivity.this, null).get("https://instances.social/api/1.0" + action, 30, parameters, Helper.THEKINRAR_SECRET_TOKEN); + runOnUiThread(new Runnable() { + public void run() { + isLoadingInstance = false; + String[] instances; + try { + JSONObject jsonObject = new JSONObject(response); + JSONArray jsonArray = jsonObject.getJSONArray("instances"); + if (jsonArray != null) { + int length = 0; + for (int i = 0; i < jsonArray.length(); i++) { + if (!jsonArray.getJSONObject(i).get("name").toString().contains("@") && jsonArray.getJSONObject(i).get("up").toString().equals("true")) + length++; + } + instances = new String[length]; + int j = 0; + for (int i = 0; i < jsonArray.length(); i++) { + if (!jsonArray.getJSONObject(i).get("name").toString().contains("@") && jsonArray.getJSONObject(i).get("up").toString().equals("true")) { + instances[j] = jsonArray.getJSONObject(i).get("name").toString(); + j++; + } + } + } else { + instances = new String[]{}; + } + instance_list.setAdapter(null); + ArrayAdapter adapter = + new ArrayAdapter<>(ReorderTimelinesActivity.this, android.R.layout.simple_list_item_1, instances); + instance_list.setAdapter(adapter); + if (instance_list.hasFocus() && !ReorderTimelinesActivity.this.isFinishing()) + instance_list.showDropDown(); + oldSearch = s.toString().trim(); + + } catch (JSONException ignored) { + isLoadingInstance = false; + } + } + }); + + } catch (HttpsConnection.HttpsConnectionException e) { + isLoadingInstance = false; + } catch (Exception e) { + isLoadingInstance = false; + } + } + }).start(); + else + isLoadingInstance = false; + } + }else { alertDialog.getButton( AlertDialog.BUTTON_POSITIVE).setEnabled(true); } - if (s.length() > 2 && !isLoadingInstance) { - final String action = "/instances/search"; - final HashMap parameters = new HashMap<>(); - parameters.put("q", s.toString().trim()); - parameters.put("count", String.valueOf(1000)); - parameters.put("name", String.valueOf(true)); - isLoadingInstance = true; - if (oldSearch == null || !oldSearch.equals(s.toString().trim())) - new Thread(new Runnable() { - @Override - public void run() { - try { - final String response = new HttpsConnection(ReorderTimelinesActivity.this, null).get("https://instances.social/api/1.0" + action, 30, parameters, Helper.THEKINRAR_SECRET_TOKEN); - runOnUiThread(new Runnable() { - public void run() { - isLoadingInstance = false; - String[] instances; - try { - JSONObject jsonObject = new JSONObject(response); - JSONArray jsonArray = jsonObject.getJSONArray("instances"); - if (jsonArray != null) { - int length = 0; - for (int i = 0; i < jsonArray.length(); i++) { - if (!jsonArray.getJSONObject(i).get("name").toString().contains("@") && jsonArray.getJSONObject(i).get("up").toString().equals("true")) - length++; - } - instances = new String[length]; - int j = 0; - for (int i = 0; i < jsonArray.length(); i++) { - if (!jsonArray.getJSONObject(i).get("name").toString().contains("@") && jsonArray.getJSONObject(i).get("up").toString().equals("true")) { - instances[j] = jsonArray.getJSONObject(i).get("name").toString(); - j++; - } - } - } else { - instances = new String[]{}; - } - instance_list.setAdapter(null); - ArrayAdapter adapter = - new ArrayAdapter<>(ReorderTimelinesActivity.this, android.R.layout.simple_list_item_1, instances); - instance_list.setAdapter(adapter); - if (instance_list.hasFocus() && !ReorderTimelinesActivity.this.isFinishing()) - instance_list.showDropDown(); - oldSearch = s.toString().trim(); - - } catch (JSONException ignored) { - isLoadingInstance = false; - } - } - }); - - } catch (HttpsConnection.HttpsConnectionException e) { - isLoadingInstance = false; - } catch (Exception e) { - isLoadingInstance = false; - } - } - }).start(); - else - isLoadingInstance = false; - } } }); } diff --git a/app/src/main/java/app/fedilab/android/asynctasks/RetrieveFeedsAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/RetrieveFeedsAsyncTask.java index 72658d5b5..4ce798c55 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/RetrieveFeedsAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/RetrieveFeedsAsyncTask.java @@ -249,7 +249,15 @@ public class RetrieveFeedsAsyncTask extends AsyncTask { status.setType(action); } } - } else if (remoteInstanceObj != null && remoteInstanceObj.size() > 0 && remoteInstanceObj.get(0).getType().equals("PIXELFED")) { + } else if (remoteInstanceObj != null && remoteInstanceObj.size() > 0 && remoteInstanceObj.get(0).getType().equals("NITTER")) { + apiResponse = api.getNitter(this.instanceName, max_id); + List statusesTemp = apiResponse.getStatuses(); + if (statusesTemp != null) { + for (app.fedilab.android.client.Entities.Status status : statusesTemp) { + status.setType(action); + } + } + }else if (remoteInstanceObj != null && remoteInstanceObj.size() > 0 && remoteInstanceObj.get(0).getType().equals("PIXELFED")) { apiResponse = api.getPixelfedTimeline(instanceName, max_id); } else if (remoteInstanceObj != null && remoteInstanceObj.size() > 0 && remoteInstanceObj.get(0).getType().equals("GNU")) { apiResponse = api.getGNUTimeline(instanceName, max_id); diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 1707b01ef..60b5d4567 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -20,6 +20,7 @@ import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; + import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.google.gson.JsonArray; @@ -28,15 +29,21 @@ import com.google.gson.JsonObject; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.text.DateFormat; import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -45,6 +52,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import app.fedilab.android.R; import app.fedilab.android.activities.MainActivity; @@ -760,6 +769,159 @@ public class API { return status; } + + /** + * Parse xml response for Nitter + * + * @param xml String + * @return List + */ + + private List parseNitter(String xml) { + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String nitterHost = sharedpreferences.getString(Helper.SET_NITTER_HOST, Helper.DEFAULT_NITTER_HOST).toLowerCase(); + + List statuses = new ArrayList<>(); + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + + xpp.setInput( new StringReader( xml ) ); + int eventType = xpp.getEventType(); + Account account = null; + Status status = null; + HashMap mappedProfile = new HashMap<>(); + while (eventType != XmlPullParser.END_DOCUMENT) { + + if(eventType == XmlPullParser.START_TAG) { + if( xpp.getName().compareTo("item") == 0 ){ + status = new Status(); + status.setReplies_count(0); + status.setFavourites_count(0); + status.setReblogs_count(0); + status.setFavourited(false); + status.setReblogged(false); + status.setEmojiFound(true); + status.setPollEmojiFound(true); + status.setEmojiTranslateFound(true); + status.setMedia_attachments(new ArrayList<>()); + account = new Account(); + }else if( xpp.getName().compareTo("creator") == 0 ){ + eventType = xpp.next(); + if(eventType == XmlPullParser.TEXT) { + if( account != null ){ + account.setAcct(xpp.getText().replace("@","")+"@" + nitterHost); + account.setDisplay_name(xpp.getText().replace("@","")); + account.setUsername(xpp.getText().replace("@","")); + account.setId("https://" + nitterHost + "/" + xpp.getText()); + account.setUuid("https://" + nitterHost + "/" + xpp.getText()); + account.setUrl("https://" + nitterHost + "/" + xpp.getText()); + if( !mappedProfile.containsKey(xpp.getText()) ){ + HttpsConnection httpsConnection = new HttpsConnection(context, nitterHost); + try { + String response = httpsConnection.get("https://" + nitterHost + "/" + xpp.getText() + "/rss", 10, null, null); + XmlPullParserFactory factory2 = XmlPullParserFactory.newInstance(); + factory2.setNamespaceAware(true); + XmlPullParser xpp2 = factory2.newPullParser(); + + xpp2.setInput( new StringReader( response ) ); + int eventType2 = xpp2.getEventType(); + while (eventType2 != XmlPullParser.END_DOCUMENT) { + if (eventType2 == XmlPullParser.START_TAG) { + if (xpp2.getName().compareTo("url") == 0) { + eventType2 = xpp2.next(); + if(eventType2 == XmlPullParser.TEXT ) { + mappedProfile.put(xpp.getText(), xpp2.getText()); + } + break; + } + } + eventType2 = xpp2.next(); + } + } catch (NoSuchAlgorithmException | KeyManagementException | HttpsConnection.HttpsConnectionException e) { + e.printStackTrace(); + } + } + account.setAvatar(mappedProfile.get(xpp.getText())); + } + } + } else if( xpp.getName().compareTo("pubDate") == 0 ){ + eventType = xpp.next(); + if(eventType == XmlPullParser.TEXT && status != null) { + if( xpp.getText() != null ) { + try { + DateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"); + Date date = formatter.parse(xpp.getText()); + status.setCreated_at(date); + } catch (ParseException e) { + status.setCreated_at(new Date()); + } + } + } + }else if( xpp.getName().compareTo("description") == 0 ){ + eventType = xpp.next(); + if(eventType == XmlPullParser.TEXT && status != null) { + if( xpp.getText() != null ) { + String description = xpp.getText(); + Pattern imgPattern = Pattern.compile("]*src=\"([^\"]+)\"[^>]*>"); + Matcher matcher = imgPattern.matcher(description); + List imgs = new ArrayList<>(); + int i = 1; + ArrayList attachments = new ArrayList<>(); + while (matcher.find()) { + description = description.replaceAll(Pattern.quote(matcher.group()), ""); + imgs.add("[media_" + i + "]|" + matcher.group(1)); + Attachment attachment = new Attachment(); + attachment.setType("image"); + attachment.setDescription(""); + attachment.setUrl(matcher.group(1)); + attachment.setPreview_url(matcher.group(1)); + attachment.setId(matcher.group(1)); + attachments.add(attachment); + } + status.setMedia_attachments(attachments); + status.setContent(context, description); + } + } + }else if( xpp.getName().compareTo("guid") == 0 ){ + eventType = xpp.next(); + if(eventType == XmlPullParser.TEXT && status != null) { + if( xpp.getText() != null ) { + status.setUri(xpp.getText()); + Pattern idPattern = Pattern.compile("([0-9])+"); + Matcher matcher = idPattern.matcher(xpp.getText()); + while (matcher.find()) { + status.setId(matcher.group(0)); + } + } + } + }else if( xpp.getName().compareTo("link") == 0 ){ + eventType = xpp.next(); + if(eventType == XmlPullParser.TEXT && status != null) { + if( xpp.getText() != null ) { + status.setUrl(xpp.getText()); + } + } + } + } else if(eventType == XmlPullParser.END_TAG) { + if( xpp.getName().compareTo("item") == 0 ){ + if (status != null) { + status.setAccount(account); + statuses.add(status); + } + account = null; + status = null; + } + } + eventType = xpp.next(); + } + } catch (XmlPullParserException | IOException e) { + e.printStackTrace(); + } + return statuses; + } + /** * Parse json response for several notes (Misskey) * @@ -3237,8 +3399,51 @@ public class API { return apiResponse; } + /** - * Retrieves Peertube videos from an instance *synchronously* + * Retrieves Nitter timeline from accounts *synchronously* + * + * @return APIResponse + */ + public APIResponse getNitter(String instance, String max_id) { + + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String nitterHost = sharedpreferences.getString(Helper.SET_NITTER_HOST, Helper.DEFAULT_NITTER_HOST).toLowerCase(); + String[] usernames = instance.split(" "); + if( usernames.length == 0 ){ + Error error = new Error(); + error.setError(context.getString(R.string.toast_error)); + error.setStatusCode(404); + apiResponse.setError(error); + return apiResponse; + } + StringBuilder urlparams = new StringBuilder(); + for(String param: usernames){ + urlparams.append(param.trim()).append(","); + } + + String url = "https://" + nitterHost + "/" + urlparams + "/rss"; + if( max_id != null ){ + url += "?max_position=" + max_id; + } + try { + statuses = new ArrayList<>(); + HttpsConnection httpsConnection = new HttpsConnection(context, this.instance); + String response = httpsConnection.get(url, 30, null, null); + apiResponse.setMax_id(httpsConnection.getMax_id()); + statuses = parseNitter(response); + + } catch (HttpsConnection.HttpsConnectionException e) { + setError(e.getStatusCode(), e); + } catch (NoSuchAlgorithmException | IOException | KeyManagementException e) { + e.printStackTrace(); + } + apiResponse.setStatuses(statuses); + return apiResponse; + } + + /** + * Retrieves Misskey timeline from an instance *synchronously* * * @return APIResponse */ diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index ce89ffa80..b8ef97fc3 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -474,18 +474,20 @@ public class Status implements Parcelable { account.setInstance(instance); account.setUrl(url); String accountId = null; - for (Mention mention : mentions) { - String[] accountMentionAcct = mention.getAcct().split("@"); - //Different isntance - if (accountMentionAcct.length > 1) { - if (mention.getAcct().equals(account.getAcct() + "@" + account.getInstance())) { - accountId = mention.getId(); - break; - } - } else { - if (mention.getAcct().equals(account.getAcct())) { - accountId = mention.getId(); - break; + if( mentions != null) { + for (Mention mention : mentions) { + String[] accountMentionAcct = mention.getAcct().split("@"); + //Different isntance + if (accountMentionAcct.length > 1) { + if (mention.getAcct().equals(account.getAcct() + "@" + account.getInstance())) { + accountId = mention.getId(); + break; + } + } else { + if (mention.getAcct().equals(account.getAcct())) { + accountId = mention.getId(); + break; + } } } } diff --git a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java index 6eb75ab6d..e03ace1e4 100644 --- a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java +++ b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java @@ -170,13 +170,18 @@ public class HttpsConnection { } } StringBuilder postData = new StringBuilder(); - for (Map.Entry param : params.entrySet()) { - if (postData.length() != 0) postData.append('&'); - postData.append(param.getKey()); - postData.append('='); - postData.append(param.getValue()); + URL url; + if( params.size() > 0 ) { + for (Map.Entry param : params.entrySet()) { + if (postData.length() != 0) postData.append('&'); + postData.append(param.getKey()); + postData.append('='); + postData.append(param.getValue()); + } + url = new URL(urlConnection + "?" + postData); + }else{ + url = new URL(urlConnection); } - URL url = new URL(urlConnection + "?" + postData); if (Build.VERSION.SDK_INT >= 21) { OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(timeout, TimeUnit.SECONDS).cache(new Cache(context.getCacheDir(), cacheSize)); @@ -331,6 +336,7 @@ public class HttpsConnection { throw new HttpsConnectionException(code, error); } } + } else { URL url = new URL(urlConnection); if (proxy != null) @@ -1443,6 +1449,12 @@ public class HttpsConnection { } } + }else if (entry.toString().startsWith("Min-Id") || entry.toString().startsWith("min-id")) { + Pattern patternMaxId = Pattern.compile("min-id=\\[([0-9a-zA-Z]{1,}).*\\]"); + Matcher matcherMaxId = patternMaxId.matcher(entry.toString()); + if (matcherMaxId.find()) { + max_id = matcherMaxId.group(1); + } } } } @@ -1454,6 +1466,7 @@ public class HttpsConnection { if (httpsURLConnection == null) return; Map> map = httpsURLConnection.getHeaderFields(); + for (Map.Entry> entry : map.entrySet()) { if (entry.toString().startsWith("Link") || entry.toString().startsWith("link")) { Pattern patternMaxId = Pattern.compile("max_id=([0-9a-zA-Z]{1,}).*"); @@ -1469,6 +1482,12 @@ public class HttpsConnection { } } + }else if (entry.toString().startsWith("Min-Id") || entry.toString().startsWith("min-id")) { + Pattern patternMaxId = Pattern.compile("min-id=\\[([0-9a-zA-Z]{1,}).*\\]"); + Matcher matcherMaxId = patternMaxId.matcher(entry.toString()); + if (matcherMaxId.find()) { + max_id = matcherMaxId.group(1); + } } } } else { diff --git a/app/src/main/java/app/fedilab/android/drawers/ReorderTabAdapter.java b/app/src/main/java/app/fedilab/android/drawers/ReorderTabAdapter.java index 37beb46c4..145e0aad6 100644 --- a/app/src/main/java/app/fedilab/android/drawers/ReorderTabAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/ReorderTabAdapter.java @@ -129,6 +129,9 @@ public class ReorderTabAdapter extends RecyclerView.Adapter lstHolders; private List emojisPicker; private Status statusForQuickReply; + private String instanceType; private Runnable updateAnimatedEmoji = new Runnable() { @Override @@ -266,6 +267,21 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct currentToId = -1; } + public StatusListAdapter(String instanceType, RetrieveFeedsAsyncTask.Type type, String targetedId, boolean isOnWifi, List statuses) { + super(); + this.statuses = statuses; + this.isOnWifi = isOnWifi; + statusListAdapter = this; + this.type = type; + this.targetedId = targetedId; + redraft = false; + lstHolders = new ArrayList<>(); + toot_content = null; + toot_cw_content = null; + tootReply = null; + currentToId = -1; + this.instanceType = instanceType; + } public StatusListAdapter(TagTimeline tagTimeline, String targetedId, boolean isOnWifi, List statuses) { super(); @@ -283,6 +299,23 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct currentToId = -1; } + public StatusListAdapter(String instanceType, TagTimeline tagTimeline, String targetedId, boolean isOnWifi, List statuses) { + super(); + this.statuses = statuses; + this.isOnWifi = isOnWifi; + statusListAdapter = this; + this.type = RetrieveFeedsAsyncTask.Type.TAG; + this.targetedId = targetedId; + redraft = false; + this.tagTimeline = tagTimeline; + this.instanceType = instanceType; + lstHolders = new ArrayList<>(); + toot_content = null; + toot_cw_content = null; + tootReply = null; + currentToId = -1; + } + public StatusListAdapter(int position, String targetedId, boolean isOnWifi, List statuses) { this.statuses = statuses; this.isOnWifi = isOnWifi; @@ -616,15 +649,20 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct show_boosts = ((ShowAccountActivity) context).showBoosts(); show_replies = ((ShowAccountActivity) context).showReplies(); } - if (type != RetrieveFeedsAsyncTask.Type.REMOTE_INSTANCE && type != RetrieveFeedsAsyncTask.Type.NEWS && !Helper.filterToots(statuses.get(position), type, context instanceof ShowAccountActivity, show_boosts, show_replies)) + if (type != RetrieveFeedsAsyncTask.Type.REMOTE_INSTANCE && type != RetrieveFeedsAsyncTask.Type.NEWS && !Helper.filterToots(statuses.get(position), type, context instanceof ShowAccountActivity, show_boosts, show_replies)) { return HIDDEN_STATUS; + } if (statuses.get(position).isFocused() && type == RetrieveFeedsAsyncTask.Type.CONTEXT && statuses.get(position).getViewType() != CONSOLE_STATUS) return FOCUSED_STATUS; else { if (social == UpdateAccountInfoAsyncTask.SOCIAL.PIXELFED && type == RetrieveFeedsAsyncTask.Type.CONTEXT) { return COMPACT_STATUS; } else { - return statuses.get(position).getViewType(); + if( instanceType == null || instanceType.compareTo("NITTER") != 0 ) { + return statuses.get(position).getViewType(); + }else{ + return COMPACT_STATUS; + } } } } @@ -653,6 +691,7 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); final String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); context = viewHolder.itemView.getContext(); + if (viewHolder.getItemViewType() != HIDDEN_STATUS) { final ViewHolder holder = (ViewHolder) viewHolder; synchronized (lock) { @@ -662,9 +701,10 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct holder.startUpdateTimer(); final Status status = statuses.get(i); + + if (status == null) return; - //TODO:It sounds that sometimes this value is null - need deeper investigation if (status.getVisibility() == null) { status.setVisibility("public"); @@ -1301,6 +1341,38 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct status.setImageFound(true); Status.makeImage(context, this, status); } + if (instanceType != null && instanceType.compareTo("NITTER") == 0) { + holder.status_action_container.setVisibility(View.GONE); + if( holder.status_action_container_twitter != null){ + holder.status_action_container_twitter.setVisibility(View.VISIBLE); + holder.status_action_container_twitter.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.shared_via)); + String url = status.getUrl(); + String extra_text; + + extra_text = (status.getReblog() != null) ? status.getReblog().getAccount().getAcct() : status.getAccount().getAcct(); + if (extra_text.split("@").length == 1) + extra_text = "@" + extra_text + "@" + Helper.getLiveInstance(context); + else + extra_text = "@" + extra_text; + extra_text += " " + Helper.shortnameToUnicode(":link:", true) + " " + url + "\r\n-\n"; + final String contentToot; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + contentToot = Html.fromHtml((status.getReblog() != null) ? status.getReblog().getContent() : status.getContent(), Html.FROM_HTML_MODE_LEGACY).toString(); + else + //noinspection deprecation + contentToot = Html.fromHtml((status.getReblog() != null) ? status.getReblog().getContent() : status.getContent()).toString(); + extra_text += contentToot; + sendIntent.putExtra(Intent.EXTRA_TEXT, extra_text); + sendIntent.setType("text/plain"); + context.startActivity(Intent.createChooser(sendIntent, context.getString(R.string.share_with))); + } + }); + } + } holder.status_content.setOnTouchListener(new View.OnTouchListener() { @Override @@ -1328,7 +1400,7 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct }); //Click on a conversation - if ((getItemViewType(viewHolder.getAdapterPosition()) == DISPLAYED_STATUS || getItemViewType(viewHolder.getAdapterPosition()) == COMPACT_STATUS || getItemViewType(viewHolder.getAdapterPosition()) == CONSOLE_STATUS)) { + if (( instanceType == null || instanceType.compareTo("NITTER") != 0) && (getItemViewType(viewHolder.getAdapterPosition()) == DISPLAYED_STATUS || getItemViewType(viewHolder.getAdapterPosition()) == COMPACT_STATUS || getItemViewType(viewHolder.getAdapterPosition()) == CONSOLE_STATUS)) { holder.status_spoiler.setOnClickListener(new View.OnClickListener() { @Override @@ -3152,7 +3224,7 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct } }); - } else { + } else if( instanceType == null || instanceType.compareTo("NITTER") != 0){ holder.status_account_profile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -3225,6 +3297,7 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct } } + } private void loadAttachments(final Status status, final ViewHolder holder, boolean blur) { @@ -4220,6 +4293,7 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct ConstraintLayout main_container; TextView yandex_translate; ConstraintLayout status_action_container; + ConstraintLayout status_action_container_twitter; Button fetch_more; ImageView new_element; LinearLayout status_spoiler_mention_container; @@ -4325,6 +4399,7 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct yandex_translate = itemView.findViewById(R.id.yandex_translate); new_element = itemView.findViewById(R.id.new_element); status_action_container = itemView.findViewById(R.id.status_action_container); + status_action_container_twitter = itemView.findViewById(R.id.status_action_container_twitter); status_spoiler_mention_container = itemView.findViewById(R.id.status_spoiler_mention_container); status_mention_spoiler = itemView.findViewById(R.id.status_mention_spoiler); status_cardview = itemView.findViewById(R.id.status_cardview); diff --git a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java index 079ef4f0e..c008b0a42 100644 --- a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java +++ b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java @@ -206,7 +206,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn lastReadToot = sharedpreferences.getString(Helper.LAST_READ_TOOT_ID + userId + instance, null); lastReadTootDate = Helper.stringToDate(context, sharedpreferences.getString(Helper.LAST_READ_TOOT_DATE + userId + instance, null)); } - if (instanceType == null || instanceType.equals("MASTODON") || instanceType.equals("MISSKEY") || instanceType.equals("GNU")) { + if (instanceType == null || instanceType.equals("MASTODON") || instanceType.equals("MISSKEY") || instanceType.equals("GNU") || instanceType.equals("NITTER")) { if (type == RetrieveFeedsAsyncTask.Type.TAG && tag != null) { BaseMainActivity.displayPeertube = null; List tagTimelines = new SearchDAO(context, db).getTimelineInfo(tag); @@ -217,7 +217,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn } } else { BaseMainActivity.displayPeertube = null; - statusListAdapter = new StatusListAdapter(type, targetedId, isOnWifi, this.statuses); + statusListAdapter = new StatusListAdapter(instanceType, type, targetedId, isOnWifi, this.statuses); lv_status.setAdapter(statusListAdapter); } } else if (instanceType.equals("PEERTUBE")) { @@ -580,7 +580,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn } //Let's deal with statuses if (statuses != null && statuses.size() > 0) { - if (statusListAdapter != null && (instanceType.equals("MASTODON") || instanceType.equals("MISSKEY") || instanceType.equals("GNU"))) { + if (statusListAdapter != null && (instanceType.equals("MASTODON") || instanceType.equals("NITTER") || instanceType.equals("MISSKEY") || instanceType.equals("GNU"))) { this.statuses.addAll(statuses); statusListAdapter.notifyItemRangeInserted(previousPosition, statuses.size()); } else if (artListAdapter != null && instanceType.equals("ART")) { @@ -863,6 +863,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn switch (instanceType) { case "MASTODON": case "MISSKEY": + case "NITTER": case "GNU": statusListAdapter.notifyItemRangeChanged(0, this.statuses.size()); break; @@ -892,7 +893,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn Status status = it.next(); for (Status status1 : statuses) { if (status.getConversationId() != null && status.getConversationId().equals(status1.getConversationId())) { - if (instanceType.equals("MASTODON") || instanceType.equals("MISSKEY") || instanceType.equals("GNU")) + if (instanceType.equals("MASTODON") || instanceType.equals("MISSKEY") || instanceType.equals("NITTER")|| instanceType.equals("GNU")) statusListAdapter.notifyItemRemoved(position); else if (instanceType.equals("PIXELFED")) pixelfedListAdapter.notifyItemRemoved(position); @@ -931,7 +932,8 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn } } } - if (statusListAdapter != null && (instanceType.equals("MASTODON") || instanceType.equals("MISSKEY") || instanceType.equals("GNU"))) + + if (statusListAdapter != null && (instanceType.equals("MASTODON") || instanceType.equals("MISSKEY") || instanceType.equals("NITTER") || instanceType.equals("GNU"))) statusListAdapter.notifyItemRangeInserted(0, inserted); else if (pixelfedListAdapter != null && instanceType.equals("PIXELFED")) pixelfedListAdapter.notifyItemRangeInserted(0, inserted); @@ -1158,6 +1160,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn switch (instanceType) { case "MASTODON": case "MISSKEY": + case "NITTER": case "GNU": statusListAdapter.notifyItemRangeRemoved(0, lenght); break; diff --git a/app/src/main/res/drawable/nitter.xml b/app/src/main/res/drawable/nitter.xml new file mode 100644 index 000000000..abe938838 --- /dev/null +++ b/app/src/main/res/drawable/nitter.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/layout/drawer_status_compact.xml b/app/src/main/res/layout/drawer_status_compact.xml index 1af4a9fdf..d53fb9402 100644 --- a/app/src/main/res/layout/drawer_status_compact.xml +++ b/app/src/main/res/layout/drawer_status_compact.xml @@ -842,6 +842,24 @@ android:textStyle="italic" android:visibility="gone" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4b8964c7d..2e240d345 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1205,4 +1205,6 @@ Trends Trending now %d people talking + Twitter accounts + Twitter usernames space separated \ No newline at end of file