diff --git a/app/build.gradle b/app/build.gradle index e01a5421e..f14d689d4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,4 +33,5 @@ dependencies { compile 'com.evernote:android-job:1.1.11' compile 'com.github.chrisbanes:PhotoView:2.0.0' compile 'com.google.code.gson:gson:2.8.0' + compile 'org.jsoup:jsoup:1.10.3' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7c8f05a01..b5f8948b4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -85,6 +85,11 @@ android:launchMode="singleTask" android:noHistory="true" /> + 2 && !isLoadingInstance){ + if( s.length() > 2 && !isLoadingInstance && 1 ==2){ String action = "/instances/search"; RequestParams parameters = new RequestParams(); parameters.add("q", s.toString().trim()); @@ -220,15 +223,20 @@ public class RemoteFollowActivity extends AppCompatActivity implements OnRetriev rf_search.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + if( rf_instance.getText().toString().trim().equals("") || rf_username.getText().toString().trim().equals("")){ Toast.makeText(getApplicationContext(),R.string.toast_empty_search,Toast.LENGTH_LONG).show(); return; } rf_search.setEnabled(false); - String screen_name = rf_username.getText().toString().trim(); - String instance_name = rf_instance.getText().toString().trim(); + screen_name = rf_username.getText().toString().trim(); + instance_name = rf_instance.getText().toString().trim(); + lv_account.setVisibility(View.GONE); loader.setVisibility(View.VISIBLE); - new RetrieveSearchAccountsAsyncTask(getApplicationContext(), screen_name + "@" + instance_name, 1, RemoteFollowActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + rf_no_result.setVisibility(View.GONE); + if( screen_name.startsWith("@")) + screen_name = screen_name.substring(1); + new RetrieveRemoteAccountsAsyncTask(RemoteFollowActivity.this, screen_name, instance_name, RemoteFollowActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } }); } @@ -244,7 +252,7 @@ public class RemoteFollowActivity extends AppCompatActivity implements OnRetriev } } - +/* @Override public void onRetrieveSearchAccounts(APIResponse apiResponse) { rf_search.setEnabled(true); @@ -257,10 +265,52 @@ public class RemoteFollowActivity extends AppCompatActivity implements OnRetriev return; } final List accounts = apiResponse.getAccounts(); + Log.v(Helper.TAG,"accounts: " + accounts); if( accounts != null && accounts.size() > 0 && accounts.get(0) != null) { - AccountsListAdapter accountsListAdapter = new AccountsListAdapter(getApplicationContext(), RetrieveAccountsAsyncTask.Type.FOLLOWERS, null, accounts); - lv_account.setAdapter(accountsListAdapter); - lv_account.setVisibility(View.VISIBLE); + List selectedAccount = new ArrayList<>(); + for(Account account: accounts){ + if(account.getAcct().contains("@" + instance_name) || (account.getUsername().equals(account.getAcct()) && account.getUsername().equals(screen_name))) + selectedAccount.add(account); + } + if( selectedAccount.size() > 0) { + AccountsListAdapter accountsListAdapter = new AccountsListAdapter(RemoteFollowActivity.this, RetrieveAccountsAsyncTask.Type.FOLLOWERS, null, selectedAccount); + lv_account.setAdapter(accountsListAdapter); + lv_account.setVisibility(View.VISIBLE); + }else { + rf_no_result.setVisibility(View.VISIBLE); + } + }else if( firstSearch){ + firstSearch = false; + new RetrieveSearchAccountsAsyncTask(RemoteFollowActivity.this, screen_name, 50, RemoteFollowActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }else { + rf_no_result.setVisibility(View.VISIBLE); } + }*/ + + + @Override + public void onRetrieveRemoteAccount(boolean error, String name, String avatar, String bio, int statusCount, int followingCount, int followersCount) { + if( error){ + rf_no_result.setVisibility(View.VISIBLE); + Toast.makeText(getApplicationContext(), R.string.toast_error,Toast.LENGTH_LONG).show(); + return; + } + loader.setVisibility(View.GONE); + Account account = new Account(); + account.setInstance(instance_name); + account.setAcct(screen_name + "@" + instance_name); + account.setAvatar(avatar); + account.setDisplay_name(name); + account.setStatuses_count(statusCount); + account.setFollowers_count(followersCount); + account.setFollowing_count(followingCount); + account.setUsername(name); + account.setNote(bio); + List selectedAccount = new ArrayList<>(); + selectedAccount.add(account); + AccountSearchWebAdapter accountSearchWebAdapter = new AccountSearchWebAdapter(RemoteFollowActivity.this, selectedAccount); + lv_account.setAdapter(accountSearchWebAdapter); + lv_account.setVisibility(View.VISIBLE); + } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveRemoteAccountsAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveRemoteAccountsAsyncTask.java new file mode 100644 index 000000000..9cc037c97 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveRemoteAccountsAsyncTask.java @@ -0,0 +1,79 @@ +/* Copyright 2017 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 . */ +package fr.gouv.etalab.mastodon.asynctasks; + +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; +import java.io.IOException; +import fr.gouv.etalab.mastodon.helper.Helper; +import fr.gouv.etalab.mastodon.interfaces.OnRetrieveRemoteAccountInterface; + + +/** + * Created by Thomas on 22/07/2017. + * Retrieves a remote account via its webpage + */ + +public class RetrieveRemoteAccountsAsyncTask extends AsyncTask { + + private Context context; + private OnRetrieveRemoteAccountInterface listener; + private String url; + private String avatar, name, bio; + private int statusCount, followingCount, followersCount; + private boolean error = false; + private String instance; + + public RetrieveRemoteAccountsAsyncTask(Context context, String username, String instance, OnRetrieveRemoteAccountInterface onRetrieveRemoteAccountInterface){ + this.context = context; + this.url = "https://" + instance + "/@" + username; + this.listener = onRetrieveRemoteAccountInterface; + this.instance = instance; + } + + + + @Override + protected Void doInBackground(Void... params) { + String userAgent = "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36"; + try { + Document document = Jsoup.connect(url).userAgent(userAgent).get(); + Elements avatarElement = document.getElementsByClass("avatar"); + avatar = avatarElement.get(0).getElementsByClass("u-photo").get(0).attr("src"); + avatar = "https://" + instance + avatar; + Elements nameElement = document.getElementsByClass("name"); + name = nameElement.get(0).html(); + Elements bioElement = document.getElementsByClass("bio"); + bio = bioElement.get(0).html();; + Elements countElement = document.getElementsByClass("counter-number"); + statusCount = Integer.parseInt(countElement.get(0).html()); + followingCount = Integer.parseInt(countElement.get(1).html()); + followersCount = Integer.parseInt(countElement.get(2).html()); + } catch (IOException e) { + error = true; + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + listener.onRetrieveRemoteAccount(error, name, avatar, bio, statusCount, followingCount, followersCount); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveSearchAccountsAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveSearchAccountsAsyncTask.java index a695440ad..e5cdeceba 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveSearchAccountsAsyncTask.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveSearchAccountsAsyncTask.java @@ -32,27 +32,18 @@ public class RetrieveSearchAccountsAsyncTask extends AsyncTask private String query; private APIResponse apiResponse; private OnRetrieveSearcAccountshInterface listener; - private int limit; public RetrieveSearchAccountsAsyncTask(Context context, String query, OnRetrieveSearcAccountshInterface onRetrieveSearcAccountshInterface){ this.context = context; this.query = query; this.listener = onRetrieveSearcAccountshInterface; - this.limit = 10; - } - - public RetrieveSearchAccountsAsyncTask(Context context, String query, int limit, OnRetrieveSearcAccountshInterface onRetrieveSearcAccountshInterface){ - this.context = context; - this.query = query; - this.listener = onRetrieveSearcAccountshInterface; - this.limit = limit; } @Override protected Void doInBackground(Void... params) { API api = new API(context); - apiResponse = api.searchAccounts(query, limit); + apiResponse = api.searchAccounts(query, 10); return null; } 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 f9c8a198b..904246867 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 @@ -92,7 +92,8 @@ public class API { UNSTATUS, AUTHORIZE, REJECT, - REPORT + REPORT, + REMOTE_FOLLOW } public API(Context context) { diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountSearchWebAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountSearchWebAdapter.java new file mode 100644 index 000000000..74ea48428 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountSearchWebAdapter.java @@ -0,0 +1,176 @@ +package fr.gouv.etalab.mastodon.drawers; +/* Copyright 2017 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.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Build; +import android.support.design.widget.FloatingActionButton; +import android.text.Html; +import android.text.util.Linkify; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; + +import java.io.File; +import java.util.List; + +import fr.gouv.etalab.mastodon.asynctasks.PostActionAsyncTask; +import fr.gouv.etalab.mastodon.client.API; +import fr.gouv.etalab.mastodon.client.Entities.Account; +import fr.gouv.etalab.mastodon.client.Entities.Error; +import fr.gouv.etalab.mastodon.client.PatchBaseImageDownloader; +import fr.gouv.etalab.mastodon.helper.Helper; +import fr.gouv.etalab.mastodon.interfaces.OnPostActionInterface; +import mastodon.etalab.gouv.fr.mastodon.R; + + +/** + * Created by Thomas on 22/08/2017. + * Adapter for accounts from web + */ +public class AccountSearchWebAdapter extends BaseAdapter implements OnPostActionInterface { + + private List accounts; + private LayoutInflater layoutInflater; + private Context context; + private ViewHolder holder; + + public AccountSearchWebAdapter(Context context, List accounts){ + this.context = context; + this.accounts = accounts; + layoutInflater = LayoutInflater.from(context); + } + + + + @Override + public int getCount() { + return accounts.size(); + } + + @Override + public Object getItem(int position) { + return accounts.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + + ImageLoader imageLoader = ImageLoader.getInstance(); + File cacheDir = new File(context.getCacheDir(), context.getString(R.string.app_name)); + ImageLoaderConfiguration configImg = new ImageLoaderConfiguration.Builder(context) + .imageDownloader(new PatchBaseImageDownloader(context)) + .threadPoolSize(5) + .threadPriority(Thread.MIN_PRIORITY + 3) + .denyCacheImageMultipleSizesInMemory() + .diskCache(new UnlimitedDiskCache(cacheDir)) + .build(); + if( !imageLoader.isInited()) + imageLoader.init(configImg); + DisplayImageOptions options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false) + .cacheOnDisk(true).resetViewBeforeLoading(true).build(); + final Account account = accounts.get(position); + + if (convertView == null) { + convertView = layoutInflater.inflate(R.layout.drawer_account_search_html, parent, false); + holder = new ViewHolder(); + holder.account_pp = (ImageView) convertView.findViewById(R.id.account_pp); + holder.account_dn = (TextView) convertView.findViewById(R.id.account_dn); + + holder.account_ds = (TextView) convertView.findViewById(R.id.account_ds); + holder.account_sc = (TextView) convertView.findViewById(R.id.account_sc); + holder.account_fgc = (TextView) convertView.findViewById(R.id.account_fgc); + holder.account_frc = (TextView) convertView.findViewById(R.id.account_frc); + holder.account_follow = (FloatingActionButton) convertView.findViewById(R.id.account_follow); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + + + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + holder.account_ds.setText(Html.fromHtml(account.getNote(), Html.FROM_HTML_MODE_LEGACY)); + holder.account_dn.setText(Html.fromHtml(Helper.shortnameToUnicode(account.getDisplay_name(), true), Html.FROM_HTML_MODE_LEGACY)); + }else { + //noinspection deprecation + holder.account_ds.setText(Html.fromHtml(account.getNote())); + holder.account_dn.setText(Html.fromHtml(Helper.shortnameToUnicode(account.getDisplay_name(), true))); + } + holder.account_ds.setAutoLinkMask(Linkify.WEB_URLS); + holder.account_sc.setText(String.valueOf(account.getStatuses_count())); + holder.account_fgc.setText(String.valueOf(account.getFollowing_count())); + holder.account_frc.setText(String.valueOf(account.getFollowers_count())); + //Profile picture + imageLoader.displayImage(account.getAvatar(), holder.account_pp, options); + + holder.account_follow.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + holder.account_follow.setEnabled(false); + new PostActionAsyncTask(context, API.StatusAction.REMOTE_FOLLOW, null, AccountSearchWebAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + return convertView; + } + + @Override + public void onPostAction(int statusCode, API.StatusAction statusAction, String userId, Error error) { + if( error != null){ + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean show_error_messages = sharedpreferences.getBoolean(Helper.SET_SHOW_ERROR_MESSAGES, true); + if( show_error_messages) + Toast.makeText(context, error.getError(),Toast.LENGTH_LONG).show(); + holder.account_follow.setEnabled(true); + return; + } + holder.account_follow.setVisibility(View.GONE); + Toast.makeText(context, R.string.toast_follow, Toast.LENGTH_LONG).show(); + } + + + private class ViewHolder { + ImageView account_pp; + TextView account_dn; + TextView account_ds; + TextView account_sc; + TextView account_fgc; + TextView account_frc; + FloatingActionButton account_follow; + } + + + +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveRemoteAccountInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveRemoteAccountInterface.java new file mode 100644 index 000000000..202a03616 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveRemoteAccountInterface.java @@ -0,0 +1,24 @@ +/* Copyright 2017 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 . */ +package fr.gouv.etalab.mastodon.interfaces; + + +/** + * Created by Thomas on 22/08/2017. + * Interface for retrieving a remote account + */ +public interface OnRetrieveRemoteAccountInterface { + void onRetrieveRemoteAccount(boolean error, String name, String avatar, String bio, int statusCount, int followingCount, int followersCount); +} diff --git a/app/src/main/res/layout/activity_remote_follow.xml b/app/src/main/res/layout/activity_remote_follow.xml index 3f9277930..deff21837 100644 --- a/app/src/main/res/layout/activity_remote_follow.xml +++ b/app/src/main/res/layout/activity_remote_follow.xml @@ -73,6 +73,19 @@ android:scrollbars="none" android:divider="@null" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file