diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2e9596059..99898a6d1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -63,6 +63,12 @@
android:name=".services.StreamingLocalTimelineService"
android:exported="false"/>
+
. */
+package fr.gouv.etalab.mastodon.activities;
+
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+import android.support.design.widget.AppBarLayout;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.TabLayout;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.Toolbar;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import fr.gouv.etalab.mastodon.R;
+import fr.gouv.etalab.mastodon.asynctasks.RetrieveFeedsAsyncTask;
+import fr.gouv.etalab.mastodon.client.Entities.Account;
+import fr.gouv.etalab.mastodon.client.Entities.Status;
+import fr.gouv.etalab.mastodon.client.HttpsConnection;
+import fr.gouv.etalab.mastodon.fragments.DisplayStatusFragment;
+import fr.gouv.etalab.mastodon.helper.Helper;
+import fr.gouv.etalab.mastodon.services.LiveNotificationService;
+import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
+import fr.gouv.etalab.mastodon.sqlite.InstancesDAO;
+import fr.gouv.etalab.mastodon.sqlite.Sqlite;
+import static fr.gouv.etalab.mastodon.helper.Helper.INSTANCE_NAME;
+import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_ACTION;
+import static fr.gouv.etalab.mastodon.helper.Helper.SEARCH_INSTANCE;
+import static fr.gouv.etalab.mastodon.helper.Helper.THEME_BLACK;
+
+
+public class InstanceFederatedActivity extends BaseActivity {
+
+ private FloatingActionButton add_new;
+ public static String currentLocale;
+ private TabLayout tabLayout;
+ private ViewPager viewPager;
+ private static BroadcastReceiver receive_data, receive_federated_data, receive_local_data;
+ private String userIdService;
+ private AppBarLayout appBar;
+ private String userId;
+ private String instance;
+ private PagerAdapter adapter;
+ boolean isLoadingInstance = false;
+ private AutoCompleteTextView instance_list;
+ private String oldSearch;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
+
+
+
+
+ final int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK);
+ switch (theme){
+ case Helper.THEME_LIGHT:
+ setTheme(R.style.AppTheme_NoActionBar);
+ break;
+ case Helper.THEME_DARK:
+ setTheme(R.style.AppThemeDark_NoActionBar);
+ break;
+ case Helper.THEME_BLACK:
+ setTheme(R.style.AppThemeBlack_NoActionBar);
+ break;
+ default:
+ setTheme(R.style.AppThemeDark_NoActionBar);
+ }
+ setContentView(R.layout.activity_federated);
+
+
+ FloatingActionButton federated_timeline_close = findViewById(R.id.federated_timeline_close);
+
+ federated_timeline_close.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+
+
+ FloatingActionButton add_new_instance = findViewById(R.id.add_new_instance);
+ add_new_instance.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(InstanceFederatedActivity.this);
+ LayoutInflater inflater = getLayoutInflater();
+ @SuppressLint("InflateParams") View dialogView = inflater.inflate(R.layout.search_instance, null);
+ dialogBuilder.setView(dialogView);
+
+ instance_list = dialogView.findViewById(R.id.search_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();
+ //Already in db
+ List s_ = new InstancesDAO(InstanceFederatedActivity.this, db).getInstanceByName(instanceName);
+ if( s_ == null)
+ s_ = new ArrayList<>();
+
+
+
+ new Thread(new Runnable(){
+ @Override
+ public void run() {
+ try {
+ String response = new HttpsConnection(InstanceFederatedActivity.this).get("https://" + instanceName + "/api/v1/timelines/public?local=true", 10, null, null);
+ runOnUiThread(new Runnable() {
+ public void run() {
+ JSONObject resobj;
+ try {
+ new InstancesDAO(InstanceFederatedActivity.this, db).insertInstance(instanceName);
+
+ resobj = new JSONObject(response);
+ Intent intent = new Intent(InstanceFederatedActivity.this, InstanceFederatedActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra(INTENT_ACTION, SEARCH_INSTANCE);
+ intent.putExtra(INSTANCE_NAME, instanceName);
+ startActivity(intent);
+ } catch (JSONException ignored) {ignored.printStackTrace();}
+ }
+ });
+ } catch (final Exception e) {
+ e.printStackTrace();
+ runOnUiThread(new Runnable() {
+ public void run() {
+ Toast.makeText(getApplicationContext(), R.string.toast_instance_unavailable,Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ }
+ }).start();
+
+
+
+ }
+ });
+ AlertDialog alertDialog = dialogBuilder.create();
+ alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialogInterface) {
+ //Hide keyboard
+ InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ assert imm != null;
+ imm.hideSoftInputFromWindow(instance_list.getWindowToken(), 0);
+ }
+ });
+ if( alertDialog.getWindow() != null )
+ alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+ alertDialog.show();
+
+ instance_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick (AdapterView> parent, View view, int position, long id) {
+ oldSearch = parent.getItemAtPosition(position).toString().trim();
+ }
+ });
+ instance_list.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @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.
+ 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(50));
+ 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(InstanceFederatedActivity.this).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("@"))
+ length++;
+ }
+ instances = new String[length];
+ int j = 0;
+ for (int i = 0; i < jsonArray.length(); i++) {
+ if( !jsonArray.getJSONObject(i).get("name").toString().contains("@")) {
+ instances[j] = jsonArray.getJSONObject(i).get("name").toString();
+ j++;
+ }
+ }
+ } else {
+ instances = new String[]{};
+ }
+ instance_list.setAdapter(null);
+ ArrayAdapter adapter =
+ new ArrayAdapter<>(InstanceFederatedActivity.this, android.R.layout.simple_list_item_1, instances);
+ instance_list.setAdapter(adapter);
+ if (instance_list.hasFocus() && !InstanceFederatedActivity.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;
+ }
+ }
+ });
+
+ }
+ });
+
+ //Test if user is still log in
+ if( ! Helper.isLoggedIn(getApplicationContext())) {
+ //It is not, the user is redirected to the login page
+ Intent myIntent = new Intent(InstanceFederatedActivity.this, LoginActivity.class);
+ startActivity(myIntent);
+ finish();
+ return;
+ }
+
+
+ SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
+ Helper.canPin = false;
+ Helper.fillMapEmoji(getApplicationContext());
+ //Here, the user is authenticated
+ appBar = findViewById(R.id.appBar);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ if( theme == THEME_BLACK)
+ toolbar.setBackgroundColor(ContextCompat.getColor(InstanceFederatedActivity.this, R.color.black));
+ setSupportActionBar(toolbar);
+ tabLayout = findViewById(R.id.tabLayout);
+
+
+ tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
+ tabLayout.setTabMode(TabLayout.MODE_FIXED);
+
+
+
+ //Display filter for notification when long pressing the tab
+ final LinearLayout tabStrip = (LinearLayout) tabLayout.getChildAt(0);
+
+
+ viewPager = findViewById(R.id.viewpager);
+
+ adapter = new PagerAdapter
+ (getSupportFragmentManager(), tabLayout.getTabCount());
+ viewPager.setAdapter(adapter);
+ viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
+
+
+
+
+ refreshInstanceTab();
+
+ //Hide the default title
+ if( getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+ getSupportActionBar().getThemedContext().setTheme(R.style.AppThemeBlack);
+ }
+ //Defines the current locale of the device in a static variable
+ currentLocale = Helper.currentLocale(getApplicationContext());
+
+
+ add_new = findViewById(R.id.add_new);
+
+
+ userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
+ instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(getApplicationContext()));
+
+ Account account = new AccountDAO(getApplicationContext(), db).getAccountByID(userId);
+ if( account == null){
+ Helper.logout(getApplicationContext());
+ Intent myIntent = new Intent(InstanceFederatedActivity.this, LoginActivity.class);
+ startActivity(myIntent);
+ finish();
+ return;
+ }
+
+
+ ImageView iconbar = toolbar.findViewById(R.id.iconbar);
+
+ Helper.loadPictureIcon(InstanceFederatedActivity.this, account.getAvatar(),iconbar);
+
+
+
+
+ if( receive_data != null)
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(receive_data);
+ receive_data = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle b = intent.getExtras();
+ Helper.EventStreaming eventStreaming = (Helper.EventStreaming) intent.getSerializableExtra("eventStreaming");
+ assert b != null;
+ userIdService = b.getString("userIdService", null);
+ if( userIdService != null && userIdService.equals(userId)) {
+
+ }
+ }
+ };
+ mamageNewIntent(getIntent());
+ // LocalBroadcastManager.getInstance(this).registerReceiver(receive_data, new IntentFilter(Helper.RECEIVE_DATA));
+ }
+
+ public void refreshInstanceTab(){
+ Helper.addInstanceTab(InstanceFederatedActivity.this, tabLayout, adapter);
+ }
+
+
+ @Override
+ public void onResume(){
+ super.onResume();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ mamageNewIntent(intent);
+ }
+
+
+ /**
+ * Manages new intents
+ * @param intent Intent - intent related to a notification in top bar
+ */
+ private void mamageNewIntent(Intent intent){
+ if( intent == null || intent.getExtras() == null )
+ return;
+ Bundle extras = intent.getExtras();
+ if( extras.containsKey(INTENT_ACTION) ){
+ if(extras.getInt(INTENT_ACTION) == SEARCH_INSTANCE){
+ String instanceName = extras.getString(INSTANCE_NAME);
+ if( instanceName != null){
+ adapter = new InstanceFederatedActivity.PagerAdapter
+ (getSupportFragmentManager(), tabLayout.getTabCount());
+ viewPager.setAdapter(adapter);
+ for(int i = 0; i < tabLayout.getTabCount() ; i++ ){
+ if( tabLayout.getTabAt(i).getText() != null && tabLayout.getTabAt(i).getText().equals(instanceName.trim())){
+ tabLayout.getTabAt(i).select();
+ break;
+ }
+
+ }
+ }
+ }
+ }
+ intent.replaceExtras(new Bundle());
+ intent.setAction("");
+ intent.setData(null);
+ intent.setFlags(0);
+ }
+
+
+ @Override
+ public void onStart(){
+ super.onStart();
+ if( receive_federated_data != null)
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(receive_federated_data);
+ receive_federated_data = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle b = intent.getExtras();
+ assert b != null;
+ userIdService = b.getString("userIdService", null);
+ if( userIdService != null && userIdService.equals(userId)) {
+ Status status = b.getParcelable("data");
+
+ }
+ }
+ };
+ LocalBroadcastManager.getInstance(this).registerReceiver(receive_federated_data, new IntentFilter(Helper.RECEIVE_FEDERATED_DATA));
+ }
+
+ @Override
+ public void onStop(){
+ super.onStop();
+ if( receive_federated_data != null)
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(receive_federated_data);
+
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ public void onDestroy(){
+ super.onDestroy();
+ if( receive_data != null)
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(receive_data);
+ }
+
+
+
+ /**
+ * Page Adapter for settings
+ */
+ public class PagerAdapter extends FragmentStatePagerAdapter {
+ int mNumOfTabs;
+
+ private PagerAdapter(FragmentManager fm, int NumOfTabs) {
+ super(fm);
+ this.mNumOfTabs = NumOfTabs;
+ }
+
+ public void removeTabPage() {
+ this.mNumOfTabs--;
+ notifyDataSetChanged();
+ }
+
+ public void addTabPage(String title) {
+ TabLayout.Tab tab = tabLayout.newTab();
+ tab.setText(title);
+ this.mNumOfTabs++;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ //Selection comes from another menu, no action to do
+ DisplayStatusFragment statusFragment;
+ Bundle bundle = new Bundle();
+ statusFragment = new DisplayStatusFragment();
+ bundle.putSerializable("type", RetrieveFeedsAsyncTask.Type.REMOTE_INSTANCE);
+ bundle.putString("remote_instance", tabLayout.getTabAt(position).getText().toString());
+ statusFragment.setArguments(bundle);
+ return statusFragment;
+
+ }
+
+
+ @Override
+ public int getCount() {
+ return mNumOfTabs;
+ }
+ }
+
+
+
+
+ @SuppressWarnings("ConstantConditions")
+ public void updateTimeLine(RetrieveFeedsAsyncTask.Type type, int value){
+ int position = tabLayout.getSelectedTabPosition();
+ View tabLocal = tabLayout.getTabAt(position).getCustomView();
+ assert tabLocal != null;
+ TextView tabCounter = tabLocal.findViewById(R.id.tab_counter);
+ tabCounter.setText(String.valueOf(value));
+ if( value > 0){
+ tabCounter.setVisibility(View.VISIBLE);
+ }else {
+ tabCounter.setVisibility(View.GONE);
+ }
+ }
+
+
+ public void startSreaming(){
+ SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
+ boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true);
+ if( liveNotifications) {
+ ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ assert manager != null;
+ for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+ if (LiveNotificationService.class.getName().equals(service.service.getClassName())) {
+ return;
+ }
+ }
+ Intent streamingIntent = new Intent(this, LiveNotificationService.class);
+ startService(streamingIntent);
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java
index dde695adb..adf90c010 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java
@@ -34,6 +34,7 @@ import android.text.util.Linkify;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
@@ -72,6 +73,7 @@ public class LoginActivity extends BaseActivity {
private EditText login_uid;
private EditText login_passwd;
boolean isLoadingInstance = false;
+ private String oldSearch;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -141,6 +143,12 @@ public class LoginActivity extends BaseActivity {
if (theme == Helper.THEME_LIGHT) {
connectionButton.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.white));
}
+ login_instance.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick (AdapterView> parent, View view, int position, long id) {
+ oldSearch = parent.getItemAtPosition(position).toString().trim();
+ }
+ });
login_instance.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -157,9 +165,10 @@ public class LoginActivity extends BaseActivity {
final String action = "/instances/search";
final HashMap parameters = new HashMap<>();
parameters.put("q", s.toString().trim());
- parameters.put("count", String.valueOf(5));
+ parameters.put("count", String.valueOf(50));
parameters.put("name", String.valueOf(true));
isLoadingInstance = true;
+ if( oldSearch == null || !oldSearch.equals(s.toString().trim()))
new Thread(new Runnable() {
@Override
public void run() {
@@ -173,9 +182,18 @@ public class LoginActivity extends BaseActivity {
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("instances");
if (jsonArray != null) {
- instances = new String[jsonArray.length()];
+ int length = 0;
for (int i = 0; i < jsonArray.length(); i++) {
- instances[i] = jsonArray.getJSONObject(i).get("name").toString();
+ if( !jsonArray.getJSONObject(i).get("name").toString().contains("@"))
+ length++;
+ }
+ instances = new String[length];
+ int j = 0;
+ for (int i = 0; i < jsonArray.length(); i++) {
+ if( !jsonArray.getJSONObject(i).get("name").toString().contains("@")) {
+ instances[j] = jsonArray.getJSONObject(i).get("name").toString();
+ j++;
+ }
}
} else {
instances = new String[]{};
@@ -186,6 +204,7 @@ public class LoginActivity extends BaseActivity {
login_instance.setAdapter(adapter);
if (login_instance.hasFocus() && !LoginActivity.this.isFinishing())
login_instance.showDropDown();
+ oldSearch = s.toString().trim();
} catch (JSONException ignored) {
isLoadingInstance = false;
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAsyncTask.java
index 072234d26..7cacba924 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAsyncTask.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAsyncTask.java
@@ -47,6 +47,7 @@ public class RetrieveFeedsAsyncTask extends AsyncTask {
private boolean showPinned = false;
private WeakReference contextReference;
private FilterToots filterToots;
+ private String instanceName;
public enum Type{
HOME,
@@ -60,7 +61,8 @@ public class RetrieveFeedsAsyncTask extends AsyncTask {
CONTEXT,
TAG,
CACHE_BOOKMARKS,
- CACHE_STATUS
+ CACHE_STATUS,
+ REMOTE_INSTANCE
}
@@ -79,6 +81,14 @@ public class RetrieveFeedsAsyncTask extends AsyncTask {
this.listener = onRetrieveFeedsInterface;
}
+ public RetrieveFeedsAsyncTask(Context context, Type action, String instanceName, String max_id, OnRetrieveFeedsInterface onRetrieveFeedsInterface){
+ this.contextReference = new WeakReference<>(context);
+ this.action = action;
+ this.max_id = max_id;
+ this.listener = onRetrieveFeedsInterface;
+ this.instanceName = instanceName;
+ }
+
public RetrieveFeedsAsyncTask(Context context, Type action, String targetedID, String max_id, boolean showMediaOnly, boolean showPinned, OnRetrieveFeedsInterface onRetrieveFeedsInterface){
this.contextReference = new WeakReference<>(context);
this.action = action;
@@ -111,6 +121,9 @@ public class RetrieveFeedsAsyncTask extends AsyncTask {
case PUBLIC:
apiResponse = api.getPublicTimeline(false, max_id);
break;
+ case REMOTE_INSTANCE:
+ apiResponse = api.getPublicTimeline(this.instanceName,false, max_id);
+ break;
case FAVOURITES:
apiResponse = api.getFavourites(max_id);
break;
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 49a5daaef..edce6d3d4 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
@@ -16,6 +16,7 @@ package fr.gouv.etalab.mastodon.client;
import android.content.Context;
import android.content.SharedPreferences;
+import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
@@ -530,6 +531,16 @@ public class API {
}
+ /**
+ * Retrieves public timeline for the account *synchronously*
+ * @param local boolean only local timeline
+ * @param max_id String id max
+ * @return APIResponse
+ */
+ public APIResponse getPublicTimeline(String instanceName, boolean local, String max_id){
+ return getPublicTimeline(local, instanceName, max_id, null, tootPerPage);
+ }
+
/**
* Retrieves public timeline for the account *synchronously*
* @param local boolean only local timeline
@@ -537,7 +548,7 @@ public class API {
* @return APIResponse
*/
public APIResponse getPublicTimeline(boolean local, String max_id){
- return getPublicTimeline(local, max_id, null, tootPerPage);
+ return getPublicTimeline(local, null, max_id, null, tootPerPage);
}
/**
@@ -547,7 +558,7 @@ public class API {
* @return APIResponse
*/
public APIResponse getPublicTimelineSinceId(boolean local, String since_id) {
- return getPublicTimeline(local, null, since_id, tootPerPage);
+ return getPublicTimeline(local, null, null, since_id, tootPerPage);
}
@@ -560,7 +571,7 @@ public class API {
* @param limit int limit - max value 40
* @return APIResponse
*/
- private APIResponse getPublicTimeline(boolean local, String max_id, String since_id, int limit){
+ private APIResponse getPublicTimeline(boolean local, String instanceName, String max_id, String since_id, int limit){
HashMap params = new HashMap<>();
if( local)
@@ -575,7 +586,12 @@ public class API {
statuses = new ArrayList<>();
try {
HttpsConnection httpsConnection = new HttpsConnection(context);
- String response = httpsConnection.get(getAbsoluteUrl("/timelines/public"), 60, params, prefKeyOauthTokenT);
+ String url;
+ if( instanceName == null)
+ url = getAbsoluteUrl("/timelines/public");
+ else
+ url = getAbsoluteUrlRemoteInstance(instanceName);
+ String response = httpsConnection.get(url, 60, params, prefKeyOauthTokenT);
apiResponse.setSince_id(httpsConnection.getSince_id());
apiResponse.setMax_id(httpsConnection.getMax_id());
statuses = parseStatuses(new JSONArray(response));
@@ -2291,4 +2307,7 @@ public class API {
}
+ private String getAbsoluteUrlRemoteInstance(String instanceName) {
+ return "https://" + instanceName + "/api/v1/timelines/public?local=true";
+ }
}
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayStatusFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayStatusFragment.java
index ebcf19d65..c8589e80e 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayStatusFragment.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayStatusFragment.java
@@ -81,6 +81,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
private String userId, instance;
private SharedPreferences sharedpreferences;
private boolean isSwipped;
+ private String remoteInstance;
public DisplayStatusFragment(){
}
@@ -101,6 +102,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
tag = bundle.getString("tag", null);
showMediaOnly = bundle.getBoolean("showMediaOnly",false);
showPinned = bundle.getBoolean("showPinned",false);
+ remoteInstance = bundle.getString("remote_instance", "");
}
isSwipped = false;
max_id = null;
@@ -151,6 +153,8 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
asyncTask = new RetrieveFeedsAsyncTask(context, type, targetedId, max_id, showMediaOnly, showPinned, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else if( type == RetrieveFeedsAsyncTask.Type.TAG)
asyncTask = new RetrieveFeedsAsyncTask(context, type, tag, targetedId, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ else if( remoteInstance != null)
+ asyncTask = new RetrieveFeedsAsyncTask(context, type, remoteInstance, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask = new RetrieveFeedsAsyncTask(context, type, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@@ -208,6 +212,8 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
asyncTask = new RetrieveFeedsAsyncTask(context, type, targetedId, max_id, showMediaOnly, showPinned, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else if (type == RetrieveFeedsAsyncTask.Type.TAG)
asyncTask = new RetrieveFeedsAsyncTask(context, type, tag, targetedId, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ else if( remoteInstance != null)
+ asyncTask = new RetrieveFeedsAsyncTask(context, type, remoteInstance, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else {
if( type == RetrieveFeedsAsyncTask.Type.HOME ){
String bookmark;
@@ -230,6 +236,8 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
asyncTask = new RetrieveFeedsAsyncTask(context, type, targetedId, max_id, showMediaOnly, showPinned, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else if (type == RetrieveFeedsAsyncTask.Type.TAG)
asyncTask = new RetrieveFeedsAsyncTask(context, type, tag, targetedId, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ else if( remoteInstance != null)
+ asyncTask = new RetrieveFeedsAsyncTask(context, type, remoteInstance, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else {
if( type == RetrieveFeedsAsyncTask.Type.HOME ){
String bookmark;
@@ -397,7 +405,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
}
- } else if (type == RetrieveFeedsAsyncTask.Type.PUBLIC || type == RetrieveFeedsAsyncTask.Type.LOCAL) {
+ } else if (type == RetrieveFeedsAsyncTask.Type.PUBLIC || type == RetrieveFeedsAsyncTask.Type.REMOTE_INSTANCE) {
status.setReplies(new ArrayList());
status.setNew(false);
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 a6359cb72..c1ada3908 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
@@ -142,6 +142,7 @@ import fr.gouv.etalab.mastodon.BuildConfig;
import fr.gouv.etalab.mastodon.R;
import fr.gouv.etalab.mastodon.activities.BaseMainActivity;
import fr.gouv.etalab.mastodon.activities.HashTagActivity;
+import fr.gouv.etalab.mastodon.activities.InstanceFederatedActivity;
import fr.gouv.etalab.mastodon.activities.LoginActivity;
import fr.gouv.etalab.mastodon.activities.MainActivity;
import fr.gouv.etalab.mastodon.activities.ShowAccountActivity;
@@ -158,6 +159,7 @@ import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.client.Entities.Tag;
import fr.gouv.etalab.mastodon.client.Entities.Version;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
+import fr.gouv.etalab.mastodon.sqlite.InstancesDAO;
import fr.gouv.etalab.mastodon.sqlite.SearchDAO;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
@@ -213,6 +215,7 @@ public class Helper {
public static final String SHOULD_CONTINUE_STREAMING_LOCAL = "should_continue_streaming_local";
public static final String SEARCH_KEYWORD = "search_keyword";
public static final String CLIP_BOARD = "clipboard";
+ public static final String INSTANCE_NAME = "instance_name";
//Notifications
public static final int NOTIFICATION_INTENT = 1;
public static final int HOME_TIMELINE_INTENT = 2;
@@ -221,6 +224,8 @@ public class Helper {
public static final int ADD_USER_INTENT = 5;
public static final int BACKUP_INTENT = 6;
public static final int SEARCH_TAG = 7;
+ public static final int SEARCH_INSTANCE = 8;
+
//Settings
public static final String SET_TOOTS_PER_PAGE = "set_toots_per_page";
public static final String SET_ACCOUNTS_PER_PAGE = "set_accounts_per_page";
@@ -2162,6 +2167,54 @@ public class Helper {
}
+
+ public static void addInstanceTab(Context context, TabLayout tableLayout, InstanceFederatedActivity.PagerAdapter pagerAdapter){
+ SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
+ new InstancesDAO(context, db).cleanDoublon();
+ List instances = new InstancesDAO(context, db).getAllInstances();
+ int allTabCount = tableLayout.getTabCount();
+ while(allTabCount > 0){
+ removeTab(tableLayout, pagerAdapter, allTabCount-1);
+ allTabCount -=1;
+ }
+ if( instances != null) {
+ for (String instance : instances) {
+ addTab(tableLayout, pagerAdapter, instance);
+ }
+ if( instances.size() > 0 ){
+ tableLayout.setTabGravity(TabLayout.GRAVITY_FILL);
+ tableLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
+ }
+ }
+ }
+
+ public static void removeSearchTab(String keyword, TabLayout tableLayout, InstanceFederatedActivity.PagerAdapter pagerAdapter){
+
+ int selection = -1;
+ for(int i = 0; i < tableLayout.getTabCount() ; i++ ){
+ if( tableLayout.getTabAt(i).getText() != null && tableLayout.getTabAt(i).getText().equals(keyword)) {
+ selection = i;
+ break;
+ }
+ }
+ if( selection != -1)
+ removeTab(tableLayout, pagerAdapter, selection);
+ }
+
+ private static void removeTab(TabLayout tableLayout, InstanceFederatedActivity.PagerAdapter pagerAdapter, int position) {
+ if (tableLayout.getTabCount() >= position) {
+ tableLayout.removeTabAt(position);
+ pagerAdapter.removeTabPage();
+ }
+ }
+
+ private static void addTab(TabLayout tableLayout, InstanceFederatedActivity.PagerAdapter pagerAdapter, String title) {
+ tableLayout.addTab(tableLayout.newTab().setText(title));
+ pagerAdapter.addTabPage(title);
+ }
+
+
+
public static void addSearchTag(Context context, TabLayout tableLayout, BaseMainActivity.PagerAdapter pagerAdapter){
SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
List searches = new SearchDAO(context, db).getAllSearch();
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/InstancesDAO.java b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/InstancesDAO.java
new file mode 100644
index 000000000..87c607bbb
--- /dev/null
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/InstancesDAO.java
@@ -0,0 +1,140 @@
+package fr.gouv.etalab.mastodon.sqlite;
+/* Copyright 2018 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.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import fr.gouv.etalab.mastodon.helper.Helper;
+
+
+/**
+ * Created by Thomas on 20/08/2018.
+ * Manage instance names in DB
+ */
+public class InstancesDAO {
+
+ private SQLiteDatabase db;
+ public Context context;
+ private String userId;
+
+ public InstancesDAO(Context context, SQLiteDatabase db) {
+ //Creation of the DB with tables
+ this.context = context;
+ this.db = db;
+ SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
+ userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
+
+ }
+
+
+ //------- INSERTIONS -------
+
+ /**
+ * Insert an instance name in database
+ * @param instanceName String
+ */
+ public void insertInstance(String instanceName) {
+ ContentValues values = new ContentValues();
+ values.put(Sqlite.COL_INSTANCE, instanceName);
+ values.put(Sqlite.COL_USER_ID, userId);
+ values.put(Sqlite.COL_DATE_CREATION, Helper.dateToString(new Date()));
+ //Inserts search
+ try{
+ db.insert(Sqlite.TABLE_INSTANCES, null, values);
+ }catch (Exception ignored) {}
+ }
+
+
+ //------- REMOVE -------
+
+ /***
+ * Remove instance by its name
+ * @return int
+ */
+ public int remove(String instanceName){
+ return db.delete(Sqlite.TABLE_INSTANCES, Sqlite.COL_INSTANCE + " = \"" + instanceName + "\" AND " + Sqlite.COL_USER_ID + " = \"" + userId+ "\"", null);
+ }
+
+ //------- REMOVE -------
+
+ /***
+ * Remove instance by its name
+ * @return int
+ */
+ public int cleanDoublon(){
+ return db.delete(Sqlite.TABLE_INSTANCES, Sqlite.COL_ID + " NOT IN (" +
+ " SELECT MIN("+Sqlite.COL_ID+")" +
+ " FROM " + Sqlite.TABLE_INSTANCES +
+ " GROUP BY "+ Sqlite.COL_INSTANCE + "," + Sqlite.COL_USER_ID +")", null);
+ }
+
+ //------- GETTERS -------
+
+ /**
+ * Returns all instances in db for a user
+ * @return instances List
+ */
+ public List getAllInstances(){
+ try {
+ Cursor c = db.query(Sqlite.TABLE_INSTANCES, null, Sqlite.COL_USER_ID + " = '" + userId+ "'", null, null, null, Sqlite.COL_INSTANCE + " ASC", null);
+ return cursorToListSearch(c);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+
+
+ /**
+ * Returns instance by its nale in db
+ * @return instance List
+ */
+ public List getInstanceByName(String keyword){
+ try {
+ Cursor c = db.query(Sqlite.TABLE_INSTANCES, null, Sqlite.COL_INSTANCE + " = \"" + keyword + "\" AND " + Sqlite.COL_USER_ID + " = \"" + userId+ "\"", null, null, null, null, null);
+ return cursorToListSearch(c);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+
+ /***
+ * Method to hydrate stored instances from database
+ * @param c Cursor
+ * @return List
+ */
+ private List cursorToListSearch(Cursor c){
+ //No element found
+ if (c.getCount() == 0)
+ return null;
+ List instances = new ArrayList<>();
+ while (c.moveToNext() ) {
+ instances.add(c.getString(c.getColumnIndex(Sqlite.COL_INSTANCE)));
+ }
+ //Close the cursor
+ c.close();
+ //Search list is returned
+ return instances;
+ }
+}
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java
index e1fc26b30..4f5d0dbd8 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java
@@ -26,7 +26,7 @@ import android.database.sqlite.SQLiteOpenHelper;
public class Sqlite extends SQLiteOpenHelper {
- public static final int DB_VERSION = 11;
+ public static final int DB_VERSION = 12;
public static final String DB_NAME = "mastodon_etalab_db";
public static SQLiteDatabase db;
private static Sqlite sInstance;
@@ -49,6 +49,8 @@ public class Sqlite extends SQLiteOpenHelper {
//Table for cached statuses
static final String TABLE_STATUSES_CACHE = "STATUSES_CACHE";
+ //Table for instance names
+ static final String TABLE_INSTANCES = "INSTANCES";
static final String COL_USER_ID = "USER_ID";
static final String COL_USERNAME = "USERNAME";
@@ -159,6 +161,11 @@ public class Sqlite extends SQLiteOpenHelper {
+ TABLE_STATUSES_CACHE + "(" + COL_INSTANCE +"," + COL_STATUS_ID + ")";
+ private final String CREATE_TABLE_INSTANCES = "CREATE TABLE " + TABLE_INSTANCES + " ("
+ + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + COL_INSTANCE + " TEXT NOT NULL, " + COL_USER_ID + " TEXT NOT NULL, " + COL_DATE_CREATION + " TEXT NOT NULL)";
+
+
public Sqlite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@@ -181,6 +188,7 @@ public class Sqlite extends SQLiteOpenHelper {
db.execSQL(CREATE_TABLE_TEMP_MUTE);
db.execSQL(CREATE_TABLE_STATUSES_CACHE);
db.execSQL(CREATE_UNIQUE_CACHE_INDEX);
+ db.execSQL(CREATE_TABLE_INSTANCES);
}
@Override
@@ -214,6 +222,8 @@ public class Sqlite extends SQLiteOpenHelper {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_STATUSES_CACHE);
db.execSQL(CREATE_TABLE_STATUSES_CACHE);
db.execSQL(CREATE_UNIQUE_CACHE_INDEX);
+ case 11:
+ db.execSQL(CREATE_TABLE_INSTANCES);
default:
break;
}
diff --git a/app/src/main/res/drawable-hdpi/ic_public_world.png b/app/src/main/res/drawable-hdpi/ic_public_world.png
new file mode 100644
index 000000000..d315c13e6
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_public_world.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_public_world.png b/app/src/main/res/drawable-ldpi/ic_public_world.png
new file mode 100644
index 000000000..12e648027
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_public_world.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_public_world.png b/app/src/main/res/drawable-mdpi/ic_public_world.png
new file mode 100644
index 000000000..1056867c2
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_public_world.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_public_world.png b/app/src/main/res/drawable-xhdpi/ic_public_world.png
new file mode 100644
index 000000000..348ca0b97
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_public_world.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_public_world.png b/app/src/main/res/drawable-xxhdpi/ic_public_world.png
new file mode 100644
index 000000000..a3ebc0a68
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_public_world.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_public_world.png b/app/src/main/res/drawable-xxxhdpi/ic_public_world.png
new file mode 100644
index 000000000..c138ca075
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_public_world.png differ
diff --git a/app/src/main/res/layout/activity_federated.xml b/app/src/main/res/layout/activity_federated.xml
new file mode 100644
index 000000000..ff7949932
--- /dev/null
+++ b/app/src/main/res/layout/activity_federated.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index ac41b9c32..cd42290b9 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -136,6 +136,16 @@
android:layout_margin="@dimen/fab_margin_floating"
app:srcCompat="@drawable/ic_action_add_new"
tools:ignore="VectorDrawableCompat" />
+
+
+
+
+
\ 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 80d10cf8d..9759a4c93 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -544,6 +544,7 @@
Support the app on Liberapay
There is an error in the regular expression!
No account yet?
+ No timelines was found on this instance!
- HTTP
- SOCKS