diff --git a/app/build.gradle b/app/build.gradle index 0183b0169..1cbbc992b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { defaultConfig { minSdk 21 targetSdk 31 - versionCode 423 - versionName "3.6.2" + versionCode 424 + versionName "3.6.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } flavorDimensions "default" @@ -95,6 +95,8 @@ dependencies { implementation "com.github.bumptech.glide:glide:4.12.0" implementation "com.github.bumptech.glide:okhttp3-integration:4.12.0" + implementation "org.jsoup:jsoup:1.15.1" + implementation 'com.github.mergehez:ArgPlayer:v3.1' implementation project(path: ':mytransl') implementation project(path: ':ratethisapp') diff --git a/app/src/main/assets/release_notes/notes.json b/app/src/main/assets/release_notes/notes.json index 4eccb6258..633185ba7 100644 --- a/app/src/main/assets/release_notes/notes.json +++ b/app/src/main/assets/release_notes/notes.json @@ -1,4 +1,9 @@ [ + { + "version": "3.6.3", + "code": "424", + "note": "Fixed:\n- Issue with messages/notifications not correctly displayed\n- Friendica: issues with mentions and tags (open browser)\n- Improve sharing behaviour\n" + }, { "version": "3.6.2", "code": "423", diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 8f4f77bfa..08ff2d3c1 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -79,6 +79,10 @@ import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayout; import com.jaredrummler.cyanea.Cyanea; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -852,111 +856,114 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt boolean fetchSharedMedia = sharedpreferences.getBoolean(getString(R.string.SET_RETRIEVE_METADATA_IF_URL_FROM_EXTERAL), true); boolean fetchShareContent = sharedpreferences.getBoolean(getString(R.string.SET_SHARE_DETAILS), true); if (url[0] != null && count == 1 && (fetchShareContent || fetchSharedMedia)) { - if (!url[0].trim().equalsIgnoreCase(sharedText.trim())) { - Bundle b = new Bundle(); - b.putString(Helper.ARG_SHARE_TITLE, sharedSubject); - b.putString(Helper.ARG_SHARE_DESCRIPTION, sharedText); - CrossActionHelper.doCrossShare(BaseMainActivity.this, b); - } else { - new Thread(() -> { - if (url[0].startsWith("www.")) - url[0] = "http://" + url[0]; - Matcher matcherPattern = Patterns.WEB_URL.matcher(url[0]); - String potentialUrl = null; - while (matcherPattern.find()) { - int matchStart = matcherPattern.start(1); - int matchEnd = matcherPattern.end(); - if (matchStart < matchEnd && url[0].length() >= matchEnd) - potentialUrl = url[0].substring(matchStart, matchEnd); - } - // If we actually have a URL then make use of it. - if (potentialUrl != null && potentialUrl.length() > 0) { - Pattern titlePattern = Pattern.compile("]*property=[\"']og:title[\"'] [^>]*content=[\"']([^'^\"]+?)[\"'][^>]*>"); - Pattern descriptionPattern = Pattern.compile("]*property=[\"']og:description[\"'] [^>]*content=[\"']([^'^\"]+?)[\"'][^>]*>"); - Pattern imagePattern = Pattern.compile("]*property=[\"']og:image[\"'] [^>]*content=[\"']([^'^\"]+?)[\"'][^>]*>"); + String originalUrl = url[0]; + new Thread(() -> { + if (!url[0].matches("^https?://.*")) url[0] = "http://" + url[0]; + Matcher matcherPattern = Patterns.WEB_URL.matcher(url[0]); + String potentialUrl = null; + while (matcherPattern.find()) { + int matchStart = matcherPattern.start(1); + int matchEnd = matcherPattern.end(); + if (matchStart < matchEnd && url[0].length() >= matchEnd) + potentialUrl = url[0].substring(matchStart, matchEnd); + } + // If we actually have a URL then make use of it. + if (potentialUrl != null && potentialUrl.length() > 0) { - try { - OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(10, TimeUnit.SECONDS) - .proxy(Helper.getProxy(getApplication().getApplicationContext())) - .readTimeout(10, TimeUnit.SECONDS).build(); - Request request = new Request.Builder() - .url(potentialUrl) - .build(); - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull IOException e) { - e.printStackTrace(); + + try { + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .proxy(Helper.getProxy(getApplication().getApplicationContext())) + .readTimeout(10, TimeUnit.SECONDS).build(); + Request request = new Request.Builder() + .url(potentialUrl) + .build(); + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + e.printStackTrace(); + runOnUiThread(() -> Toasty.warning(BaseMainActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show()); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull final Response response) { + if (response.isSuccessful()) { + try { + String data = response.body().string(); + Document html = Jsoup.parse(data); + + Element titleEl = html.selectFirst("meta[property='og:title']"); + Element descriptionEl = html.selectFirst("meta[property='og:description']"); + Element imageUrlEl = html.selectFirst("meta[property='og:image']"); + + String title = ""; + String description = ""; + + if(titleEl != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + title = Html.fromHtml(titleEl.attr("content"), Html.FROM_HTML_MODE_LEGACY).toString(); + } else { + title = Html.fromHtml(titleEl.attr("content")).toString(); + } + } + + if(descriptionEl != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + description = Html.fromHtml(descriptionEl.attr("content"), Html.FROM_HTML_MODE_LEGACY).toString(); + } else { + description = Html.fromHtml(descriptionEl.attr("content")).toString(); + } + } + + String imageUrl = ""; + if(imageUrlEl != null) { + imageUrl = imageUrlEl.attr("content"); + } + + StringBuilder titleBuilder = new StringBuilder(); + + if(!originalUrl.trim().equalsIgnoreCase(sharedText.trim())) { + // If the shared text is not just the URL, add it to the top + String toAppend = sharedText.replaceAll("\\s*" + Pattern.quote(originalUrl) + "\\s*", ""); + titleBuilder.append(toAppend); + } + + if (title.length() > 0) { + // OG title fetched from source + if(titleBuilder.length() > 0) titleBuilder.append("\n\n"); + titleBuilder.append(title); + } + + String finalImage = imageUrl; + String finalTitle = titleBuilder.toString(); + String finalDescription = description; + + runOnUiThread(() -> { + Bundle b = new Bundle(); + b.putString(Helper.ARG_SHARE_URL, url[0]); + b.putString(Helper.ARG_SHARE_URL_MEDIA, finalImage); + b.putString(Helper.ARG_SHARE_TITLE, finalTitle); + b.putString(Helper.ARG_SHARE_DESCRIPTION, finalDescription); + b.putString(Helper.ARG_SHARE_SUBJECT, sharedSubject); + b.putString(Helper.ARG_SHARE_CONTENT, sharedText); + CrossActionHelper.doCrossShare(BaseMainActivity.this, b); + }); + } catch (Exception e) { + e.printStackTrace(); + } + } else { runOnUiThread(() -> Toasty.warning(BaseMainActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show()); } - - @Override - public void onResponse(@NonNull Call call, @NonNull final Response response) { - if (response.isSuccessful()) { - try { - String data = response.body().string(); - Matcher matcherTitle; - matcherTitle = titlePattern.matcher(data); - Matcher matcherDescription = descriptionPattern.matcher(data); - Matcher matcherImage = imagePattern.matcher(data); - String titleEncoded = null; - String descriptionEncoded = null; - if (fetchShareContent) { - while (matcherTitle.find()) - titleEncoded = matcherTitle.group(1); - while (matcherDescription.find()) - descriptionEncoded = matcherDescription.group(1); - } - String image = null; - if (fetchSharedMedia) { - while (matcherImage.find()) - image = matcherImage.group(1); - } - String title = null; - String description = null; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (titleEncoded != null) - title = Html.fromHtml(titleEncoded, Html.FROM_HTML_MODE_LEGACY).toString(); - if (descriptionEncoded != null) - description = Html.fromHtml(descriptionEncoded, Html.FROM_HTML_MODE_LEGACY).toString(); - } else { - if (titleEncoded != null) - title = Html.fromHtml(titleEncoded).toString(); - if (descriptionEncoded != null) - description = Html.fromHtml(descriptionEncoded).toString(); - } - String finalImage = image; - String finalTitle = title; - String finalDescription = description; - - - runOnUiThread(() -> { - Bundle b = new Bundle(); - b.putString(Helper.ARG_SHARE_URL, url[0]); - b.putString(Helper.ARG_SHARE_URL_MEDIA, finalImage); - b.putString(Helper.ARG_SHARE_TITLE, finalTitle); - b.putString(Helper.ARG_SHARE_DESCRIPTION, finalDescription); - b.putString(Helper.ARG_SHARE_SUBJECT, sharedSubject); - b.putString(Helper.ARG_SHARE_CONTENT, sharedText); - CrossActionHelper.doCrossShare(BaseMainActivity.this, b); - }); - } catch (Exception e) { - e.printStackTrace(); - } - } else { - runOnUiThread(() -> Toasty.warning(BaseMainActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show()); - } - } - }); - } catch (IndexOutOfBoundsException e) { - Toasty.warning(BaseMainActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); - } - + } + }); + } catch (IndexOutOfBoundsException e) { + Toasty.warning(BaseMainActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); } - }).start(); - } + } + }).start(); } else { Bundle b = new Bundle(); b.putString(Helper.ARG_SHARE_TITLE, sharedSubject); diff --git a/app/src/main/java/app/fedilab/android/activities/InstanceProfileActivity.java b/app/src/main/java/app/fedilab/android/activities/InstanceProfileActivity.java index 20c92bbaa..934127de1 100644 --- a/app/src/main/java/app/fedilab/android/activities/InstanceProfileActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/InstanceProfileActivity.java @@ -15,34 +15,21 @@ package app.fedilab.android.activities; * see . */ -import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY; - -import android.os.Build; import android.os.Bundle; -import android.text.Html; import android.text.SpannableString; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; -import android.widget.TextView; import android.widget.Toast; import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.LinearLayoutManager; -import java.util.ArrayList; -import java.util.List; - -import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; -import app.fedilab.android.client.entities.api.Account; import app.fedilab.android.databinding.ActivityInstanceProfileBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; -import app.fedilab.android.ui.drawer.AccountAdapter; import app.fedilab.android.viewmodel.mastodon.NodeInfoVM; -import app.fedilab.android.viewmodel.mastodon.SearchVM; import es.dmoral.toasty.Toasty; public class InstanceProfileActivity extends BaseActivity { @@ -75,40 +62,13 @@ public class InstanceProfileActivity extends BaseActivity { finish(); return; } - binding.name.setText(nodeInfo.metadata != null ? nodeInfo.metadata.nodeName : instance); + binding.name.setText(instance); SpannableString descriptionSpan; - if (nodeInfo.metadata != null && nodeInfo.metadata.nodeDescription != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - descriptionSpan = new SpannableString(Html.fromHtml(nodeInfo.metadata.nodeDescription, FROM_HTML_MODE_LEGACY)); - else - descriptionSpan = new SpannableString(Html.fromHtml(nodeInfo.metadata.nodeDescription)); - binding.description.setText(descriptionSpan, TextView.BufferType.SPANNABLE); - } binding.userCount.setText(Helper.withSuffix((nodeInfo.usage.users.total))); binding.statusCount.setText(Helper.withSuffix(((nodeInfo.usage.localPosts)))); String softwareStr = nodeInfo.software.name + " - "; binding.software.setText(softwareStr); binding.version.setText(nodeInfo.software.version); - if (nodeInfo.metadata != null && nodeInfo.metadata.staffAccounts != null && nodeInfo.metadata.staffAccounts.size() > 0) { - SearchVM searchVM = new ViewModelProvider(InstanceProfileActivity.this).get(SearchVM.class); - List accounts = new ArrayList<>(); - for (String accountURL : nodeInfo.metadata.staffAccounts) { - searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountURL, null, "accounts", false, true, false, 0, null, null, 1) - .observe(InstanceProfileActivity.this, results -> { - if (results.accounts != null && results.accounts.size() > 0) { - accounts.add(results.accounts.get(0)); - } - if (accounts.size() == nodeInfo.metadata.staffAccounts.size()) { - AccountAdapter accountsListAdapter = new AccountAdapter(accounts); - binding.lvAccounts.setAdapter(accountsListAdapter); - final LinearLayoutManager mLayoutManager; - mLayoutManager = new LinearLayoutManager(InstanceProfileActivity.this); - binding.lvAccounts.setLayoutManager(mLayoutManager); - } - }); - - } - } binding.instanceContainer.setVisibility(View.VISIBLE); binding.loader.setVisibility(View.GONE); }); diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Tag.java b/app/src/main/java/app/fedilab/android/client/entities/api/Tag.java index eedf564f1..7c29dc451 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Tag.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Tag.java @@ -27,4 +27,16 @@ public class Tag implements Serializable { public String url; @SerializedName("history") public List history; + + + public int getWeight() { + int weight = 0; + for (History h : history) { + try { + weight += Integer.parseInt(h.accounts); + } catch (Exception ignored) { + } + } + return weight; + } } diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/WellKnownNodeinfo.java b/app/src/main/java/app/fedilab/android/client/entities/app/WellKnownNodeinfo.java index 7c183132b..7c376cf6a 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/WellKnownNodeinfo.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/WellKnownNodeinfo.java @@ -37,8 +37,6 @@ public class WellKnownNodeinfo { public Software software; @SerializedName("usage") public Usage usage; - @SerializedName("metadata") - public Metadata metadata; @SerializedName("openRegistrations") public boolean openRegistrations; diff --git a/app/src/main/java/app/fedilab/android/interfaces/InstancesSocialService.java b/app/src/main/java/app/fedilab/android/interfaces/InstancesSocialService.java index eed29bf86..aba3888a1 100644 --- a/app/src/main/java/app/fedilab/android/interfaces/InstancesSocialService.java +++ b/app/src/main/java/app/fedilab/android/interfaces/InstancesSocialService.java @@ -23,7 +23,7 @@ import retrofit2.http.Query; public interface InstancesSocialService { - @GET("instances/search?name=true") + @GET("instances/search?name=true&count=50") Call getInstances(@Header("Authorization") String token, @Query("q") String search); } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java b/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java index 5c1fdf061..c193b1aab 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java @@ -238,21 +238,23 @@ public class FragmentLoginMain extends Fragment { } private void retrievesClientId(String instance) { + String oldInstance = instance; if (!instance.startsWith("http://") && !instance.startsWith("https://")) { instance = "https://" + instance; } - String host = instance; + String host; try { URL url = new URL(instance); host = url.getHost(); } catch (MalformedURLException e) { + host = oldInstance; e.printStackTrace(); } try { currentInstanceLogin = URLEncoder.encode(host, "utf-8"); } catch (UnsupportedEncodingException e) { - Toasty.error(requireActivity(), getString(R.string.client_error), Toast.LENGTH_LONG).show(); + currentInstanceLogin = host; } String scopes = ((LoginActivity) requireActivity()).requestedAdmin() ? Helper.OAUTH_SCOPES_ADMIN : Helper.OAUTH_SCOPES; AppsVM appsVM = new ViewModelProvider(requireActivity()).get(AppsVM.class); diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTag.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTag.java index e287dd40e..fc252f8d3 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTag.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTag.java @@ -26,6 +26,8 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import app.fedilab.android.BaseMainActivity; @@ -89,9 +91,7 @@ public class FragmentMastodonTag extends Fragment { } else if (timelineType == Timeline.TimeLineEnum.TREND_TAG) { TimelinesVM timelinesVM = new ViewModelProvider(FragmentMastodonTag.this).get(TimelinesVM.class); timelinesVM.getTagsTrends(BaseMainActivity.currentToken, BaseMainActivity.currentInstance) - .observe(getViewLifecycleOwner(), tags -> { - initializeTagCommonView(tags); - }); + .observe(getViewLifecycleOwner(), this::initializeTagCommonView); } } @@ -120,10 +120,23 @@ public class FragmentMastodonTag extends Fragment { binding.noAction.setVisibility(View.VISIBLE); binding.noActionText.setText(R.string.no_tags); return; - } else { - binding.recyclerView.setVisibility(View.VISIBLE); - binding.noAction.setVisibility(View.GONE); } + Collections.sort(tags, (obj1, obj2) -> Integer.compare(obj2.getWeight(), obj1.getWeight())); + boolean isInCollection = false; + for (Tag tag : tags) { + if (tag.name.compareToIgnoreCase(search) == 0) { + isInCollection = true; + break; + } + } + if (!isInCollection) { + Tag tag = new Tag(); + tag.name = search; + tag.history = new ArrayList<>(); + tags.add(0, tag); + } + binding.recyclerView.setVisibility(View.VISIBLE); + binding.noAction.setVisibility(View.GONE); tagAdapter = new TagAdapter(tags); LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); binding.recyclerView.setLayoutManager(mLayoutManager); diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/InstanceSocialVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/InstanceSocialVM.java index ecbffc630..1fac3f555 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/InstanceSocialVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/InstanceSocialVM.java @@ -22,6 +22,7 @@ import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.MutableLiveData; +import java.util.ArrayList; import java.util.concurrent.TimeUnit; import app.fedilab.android.client.entities.app.InstanceSocial; @@ -73,7 +74,17 @@ public class InstanceSocialVM extends AndroidViewModel { Response response = instanceSocialCall.execute(); if (response.isSuccessful() && response.body() != null) { Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> instanceSocialMutableLiveData.setValue(response.body()); + InstanceSocial instanceSocial = response.body(); + InstanceSocial filtered = new InstanceSocial(); + filtered.instances = new ArrayList<>(); + if (instanceSocial != null && instanceSocial.instances != null) { + for (InstanceSocial.Instance instance : instanceSocial.instances) { + if (instance.up) { + filtered.instances.add(instance); + } + } + } + Runnable myRunnable = () -> instanceSocialMutableLiveData.setValue(filtered); mainHandler.post(myRunnable); } } catch (Exception e) { diff --git a/app/src/main/res/layout/drawer_admin_account.xml b/app/src/main/res/layout/drawer_admin_account.xml index a5da662eb..c06a9ad4a 100644 --- a/app/src/main/res/layout/drawer_admin_account.xml +++ b/app/src/main/res/layout/drawer_admin_account.xml @@ -1,16 +1,18 @@ + android:layout_marginHorizontal="6dp" + android:layout_marginTop="6dp" + android:backgroundTint="@color/cyanea_primary_dark_reference" + app:cardElevation="0dp">