From 5bc23d3a23da139378c7fa0878be135333032eab Mon Sep 17 00:00:00 2001 From: tom79 Date: Sun, 28 May 2017 14:10:19 +0200 Subject: [PATCH] Allows to add new accounts even from other instances but the entry point remains an account from the main instance (ie: mastodon.etalab.gouv.fr) --- .../mastodon/activities/LoginActivity.java | 64 ++++- .../mastodon/activities/MainActivity.java | 41 +--- .../mastodon/activities/WebviewActivity.java | 13 +- .../asynctasks/RemoveAccountAsyncTask.java | 47 ++++ .../RetrieveHomeTimelineServiceAsyncTask.java | 7 +- .../RetrieveNotificationsAsyncTask.java | 15 +- .../UpdateAccountInfoAsyncTask.java | 10 +- .../fr/gouv/etalab/mastodon/client/API.java | 22 +- .../etalab/mastodon/client/OauthClient.java | 10 +- .../gouv/etalab/mastodon/helper/Helper.java | 227 ++++++++++++++++++ .../mastodon/jobs/HomeTimelineSyncJob.java | 2 +- .../mastodon/jobs/NotificationsSyncJob.java | 2 +- .../etalab/mastodon/sqlite/AccountDAO.java | 1 + .../res/drawable-hdpi/ic_arrow_drop_down.png | Bin 0 -> 125 bytes .../res/drawable-hdpi/ic_arrow_drop_up.png | Bin 0 -> 130 bytes app/src/main/res/drawable-hdpi/ic_cancel.png | Bin 0 -> 435 bytes app/src/main/res/drawable-hdpi/ic_person.png | Bin 0 -> 291 bytes .../main/res/drawable-hdpi/ic_person_add.png | Bin 0 -> 313 bytes .../res/drawable-ldpi/ic_arrow_drop_down.png | Bin 0 -> 172 bytes .../res/drawable-ldpi/ic_arrow_drop_up.png | Bin 0 -> 165 bytes app/src/main/res/drawable-ldpi/ic_cancel.png | Bin 0 -> 309 bytes app/src/main/res/drawable-ldpi/ic_person.png | Bin 0 -> 258 bytes .../main/res/drawable-ldpi/ic_person_add.png | Bin 0 -> 289 bytes .../res/drawable-mdpi/ic_arrow_drop_down.png | Bin 0 -> 95 bytes .../res/drawable-mdpi/ic_arrow_drop_up.png | Bin 0 -> 98 bytes app/src/main/res/drawable-mdpi/ic_cancel.png | Bin 0 -> 312 bytes app/src/main/res/drawable-mdpi/ic_person.png | Bin 0 -> 199 bytes .../main/res/drawable-mdpi/ic_person_add.png | Bin 0 -> 211 bytes .../res/drawable-xhdpi/ic_arrow_drop_down.png | Bin 0 -> 120 bytes .../res/drawable-xhdpi/ic_arrow_drop_up.png | Bin 0 -> 130 bytes app/src/main/res/drawable-xhdpi/ic_cancel.png | Bin 0 -> 572 bytes app/src/main/res/drawable-xhdpi/ic_person.png | Bin 0 -> 330 bytes .../main/res/drawable-xhdpi/ic_person_add.png | Bin 0 -> 356 bytes .../drawable-xxhdpi/ic_arrow_drop_down.png | Bin 0 -> 152 bytes .../res/drawable-xxhdpi/ic_arrow_drop_up.png | Bin 0 -> 159 bytes .../main/res/drawable-xxhdpi/ic_cancel.png | Bin 0 -> 813 bytes .../main/res/drawable-xxhdpi/ic_person.png | Bin 0 -> 481 bytes .../res/drawable-xxhdpi/ic_person_add.png | Bin 0 -> 515 bytes .../drawable-xxxhdpi/ic_arrow_drop_down.png | Bin 0 -> 185 bytes .../res/drawable-xxxhdpi/ic_arrow_drop_up.png | Bin 0 -> 991 bytes .../main/res/drawable-xxxhdpi/ic_cancel.png | Bin 0 -> 1094 bytes .../main/res/drawable-xxxhdpi/ic_person.png | Bin 0 -> 651 bytes .../res/drawable-xxxhdpi/ic_person_add.png | Bin 0 -> 688 bytes app/src/main/res/layout/activity_login.xml | 11 + app/src/main/res/layout/nav_header_main.xml | 25 +- app/src/main/res/layout/update_account.xml | 39 +++ app/src/main/res/menu/menu_accounts.xml | 9 + app/src/main/res/values/strings.xml | 5 + 48 files changed, 487 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RemoveAccountAsyncTask.java create mode 100644 app/src/main/res/drawable-hdpi/ic_arrow_drop_down.png create mode 100644 app/src/main/res/drawable-hdpi/ic_arrow_drop_up.png create mode 100644 app/src/main/res/drawable-hdpi/ic_cancel.png create mode 100644 app/src/main/res/drawable-hdpi/ic_person.png create mode 100644 app/src/main/res/drawable-hdpi/ic_person_add.png create mode 100644 app/src/main/res/drawable-ldpi/ic_arrow_drop_down.png create mode 100644 app/src/main/res/drawable-ldpi/ic_arrow_drop_up.png create mode 100644 app/src/main/res/drawable-ldpi/ic_cancel.png create mode 100644 app/src/main/res/drawable-ldpi/ic_person.png create mode 100644 app/src/main/res/drawable-ldpi/ic_person_add.png create mode 100644 app/src/main/res/drawable-mdpi/ic_arrow_drop_down.png create mode 100644 app/src/main/res/drawable-mdpi/ic_arrow_drop_up.png create mode 100644 app/src/main/res/drawable-mdpi/ic_cancel.png create mode 100644 app/src/main/res/drawable-mdpi/ic_person.png create mode 100644 app/src/main/res/drawable-mdpi/ic_person_add.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_arrow_drop_down.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_arrow_drop_up.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_cancel.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_person.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_person_add.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_arrow_drop_down.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_arrow_drop_up.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_cancel.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_person.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_person_add.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_arrow_drop_down.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_arrow_drop_up.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_cancel.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_person.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_person_add.png create mode 100644 app/src/main/res/layout/update_account.xml create mode 100644 app/src/main/res/menu/menu_accounts.xml 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 080db6d86..27fc0ddb2 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 @@ -14,16 +14,20 @@ * see . */ package fr.gouv.etalab.mastodon.activities; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Paint; import android.os.AsyncTask; import android.os.Bundle; +import android.support.design.widget.NavigationView; import android.support.v7.app.AppCompatActivity; +import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; @@ -60,13 +64,17 @@ public class LoginActivity extends AppCompatActivity { private String client_secret; private TextView login_two_step; private static boolean client_id_for_webview = false; + private String instance; + private boolean addAccount = false; + private EditText login_instance; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); - Button connectionButton = (Button) findViewById(R.id.login_button); + final Button connectionButton = (Button) findViewById(R.id.login_button); + login_instance = (EditText) findViewById(R.id.login_instance); connectionButton.setEnabled(false); login_two_step = (TextView) findViewById(R.id.login_two_step); login_two_step.setVisibility(View.GONE); @@ -78,28 +86,61 @@ public class LoginActivity extends AppCompatActivity { retrievesClientId(); } }); + + Bundle b = getIntent().getExtras(); + if(b != null) + addAccount = b.getBoolean("addAccount", false); + + if( addAccount ) + login_instance.setVisibility(View.VISIBLE); + + if( addAccount) { + login_instance.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + connectionButton.setEnabled(false); + login_two_step.setVisibility(View.INVISIBLE); + if (!hasFocus) { + retrievesClientId(); + } + } + }); + } } @Override protected void onResume(){ super.onResume(); Button connectionButton = (Button) findViewById(R.id.login_button); - if( client_id_for_webview || !connectionButton.isEnabled()) { - connectionButton.setEnabled(false); - client_id_for_webview = false; - retrievesClientId(); + if( !addAccount ) { + if (client_id_for_webview || !connectionButton.isEnabled()) { + connectionButton.setEnabled(false); + client_id_for_webview = false; + retrievesClientId(); + } + }else { + if (login_instance.getText() != null && login_instance.getText().toString().length() > 0 && client_id_for_webview) { + connectionButton.setEnabled(false); + client_id_for_webview = false; + retrievesClientId(); + } } } private void retrievesClientId(){ final Button connectionButton = (Button) findViewById(R.id.login_button); + if( login_instance.getText() != null && login_instance.getText().length() > 0 ) + instance = login_instance.getText().toString().trim(); + else + instance = Helper.INSTANCE; + String action = "/api/v1/apps"; RequestParams parameters = new RequestParams(); parameters.add(Helper.CLIENT_NAME, Helper.OAUTH_REDIRECT_HOST); parameters.add(Helper.REDIRECT_URIS, client_id_for_webview?Helper.REDIRECT_CONTENT_WEB:Helper.REDIRECT_CONTENT); parameters.add(Helper.SCOPES, Helper.OAUTH_SCOPES); - parameters.add(Helper.WEBSITE,"https://" + Helper.INSTANCE); - new OauthClient().post(action, parameters, new AsyncHttpResponseHandler() { + parameters.add(Helper.WEBSITE,"https://" + Helper.getLiveInstance(getApplicationContext())); + new OauthClient(instance).post(action, parameters, new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { String response = new String(responseBody); @@ -120,6 +161,7 @@ public class LoginActivity extends AppCompatActivity { login_two_step.setVisibility(View.VISIBLE); if( client_id_for_webview){ Intent i = new Intent(LoginActivity.this, WebviewActivity.class); + i.putExtra("instance", instance); startActivity(i); } } catch (JSONException e) { @@ -158,7 +200,7 @@ public class LoginActivity extends AppCompatActivity { client.setUserAgent(USER_AGENT); try { client.setSSLSocketFactory(new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore())); - client.post("https://" + Helper.INSTANCE + "/oauth/token", requestParams, new AsyncHttpResponseHandler() { + client.post("https://" + instance+ "/oauth/token", requestParams, new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { String response = new String(responseBody); @@ -171,7 +213,7 @@ public class LoginActivity extends AppCompatActivity { editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token); editor.apply(); //Update the account with the token; - new UpdateAccountInfoAsyncTask(LoginActivity.this, token).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new UpdateAccountInfoAsyncTask(LoginActivity.this, token, instance).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } catch (JSONException e) { e.printStackTrace(); } @@ -180,6 +222,7 @@ public class LoginActivity extends AppCompatActivity { @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { connectionButton.setEnabled(true); + error.printStackTrace(); Toast.makeText(getApplicationContext(),R.string.toast_error_login,Toast.LENGTH_LONG).show(); } }); @@ -192,4 +235,7 @@ public class LoginActivity extends AppCompatActivity { }); } + + + } \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java index 25c56f7e6..b7e7f99a3 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java @@ -35,10 +35,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; -import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -67,6 +64,8 @@ import mastodon.etalab.gouv.fr.mastodon.R; import static fr.gouv.etalab.mastodon.helper.Helper.HOME_TIMELINE_INTENT; import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_ACTION; import static fr.gouv.etalab.mastodon.helper.Helper.NOTIFICATION_INTENT; +import static fr.gouv.etalab.mastodon.helper.Helper.menuAccounts; +import static fr.gouv.etalab.mastodon.helper.Helper.updateHeaderAccountInfo; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, OnUpdateAccountInfoInterface { @@ -137,7 +136,16 @@ public class MainActivity extends AppCompatActivity SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); String prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); Account account = new AccountDAO(getApplicationContext(), db).getAccountByToken(prefKeyOauthTokenT); - updateHeaderAccountInfo(account); + updateHeaderAccountInfo(MainActivity.this, account, headerLayout, imageLoader, options); + + LinearLayout owner_container = (LinearLayout) headerLayout.findViewById(R.id.owner_container); + owner_container.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + menuAccounts(MainActivity.this); + } + }); + boolean menuWasSelected = false; if( getIntent() != null && getIntent().getExtras() != null ){ Bundle extras = getIntent().getExtras(); @@ -436,29 +444,6 @@ public class MainActivity extends AppCompatActivity return true; } - private void updateHeaderAccountInfo(Account account){ - ImageView profilePicture = (ImageView) headerLayout.findViewById(R.id.profilePicture); - TextView username = (TextView) headerLayout.findViewById(R.id.username); - TextView displayedName = (TextView) headerLayout.findViewById(R.id.displayedName); - TextView ownerStatus = (TextView) headerLayout.findViewById(R.id.owner_status); - TextView ownerFollowing = (TextView) headerLayout.findViewById(R.id.owner_following); - TextView ownerFollowers = (TextView) headerLayout.findViewById(R.id.owner_followers); - //Something wrong happened with the account recorded in db (ie: bad token) - if( account == null ) { - Helper.logout(getApplicationContext()); - Intent myIntent = new Intent(MainActivity.this, LoginActivity.class); - Toast.makeText(getApplicationContext(),R.string.toast_error, Toast.LENGTH_LONG).show(); - startActivity(myIntent); - finish(); //User is logged out to get a new token - }else { - ownerStatus.setText(String.valueOf(account.getStatuses_count())); - ownerFollowers.setText(String.valueOf(account.getFollowers_count())); - ownerFollowing.setText(String.valueOf(account.getFollowing_count())); - username.setText(String.format("@%s",account.getUsername())); - displayedName.setText(account.getDisplay_name()); - imageLoader.displayImage(account.getAvatar(), profilePicture, options); - } - } private void populateTitleWithTag(String tag, String title, int index){ if( tag == null) @@ -488,7 +473,7 @@ public class MainActivity extends AppCompatActivity String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); SQLiteDatabase db = Sqlite.getInstance(MainActivity.this, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); Account account = new AccountDAO(getApplicationContext(), db).getAccountByID(userId); - updateHeaderAccountInfo(account); + updateHeaderAccountInfo(MainActivity.this, account, headerLayout, imageLoader, options); } } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java index 952c923cb..fb8549a27 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java @@ -53,11 +53,18 @@ public class WebviewActivity extends AppCompatActivity { private WebView webView; private AlertDialog alert; private String clientId, clientSecret; + private String instance; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); + Bundle b = getIntent().getExtras(); + if(b != null) + instance = b.getString("instance"); + if( instance == null) + finish(); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); clientId = sharedpreferences.getString(Helper.CLIENT_ID, null); clientSecret = sharedpreferences.getString(Helper.CLIENT_SECRET, null); @@ -95,7 +102,7 @@ public class WebviewActivity extends AppCompatActivity { parameters.add(Helper.REDIRECT_URI,Helper.REDIRECT_CONTENT_WEB); parameters.add("grant_type", "authorization_code"); parameters.add("code",code); - new OauthClient().post(action, parameters, new AsyncHttpResponseHandler() { + new OauthClient(instance).post(action, parameters, new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { String response = new String(responseBody); @@ -108,7 +115,7 @@ public class WebviewActivity extends AppCompatActivity { editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token); editor.apply(); //Update the account with the token; - new UpdateAccountInfoAsyncTask(WebviewActivity.this, token).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new UpdateAccountInfoAsyncTask(WebviewActivity.this, token, instance).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } catch (JSONException e) { e.printStackTrace(); } @@ -149,7 +156,7 @@ public class WebviewActivity extends AppCompatActivity { queryString += "&" + Helper.REDIRECT_URI + "="+ Uri.encode(Helper.REDIRECT_CONTENT_WEB); queryString += "&" + Helper.RESPONSE_TYPE +"=code"; queryString += "&" + Helper.SCOPE +"=" + Helper.OAUTH_SCOPES; - return "https://" + Helper.INSTANCE + Helper.EP_AUTHORIZE + "?" + queryString; + return "https://" + instance + Helper.EP_AUTHORIZE + "?" + queryString; } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RemoveAccountAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RemoveAccountAsyncTask.java new file mode 100644 index 000000000..03d68173b --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RemoveAccountAsyncTask.java @@ -0,0 +1,47 @@ +/* Copyright 2017 Thomas Schneider + * + * This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr + * + * 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. + * + * Mastodon Etalab 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 Thomas Schneider; if not, + * see . */ +package fr.gouv.etalab.mastodon.asynctasks; + +import android.app.Activity; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; +import fr.gouv.etalab.mastodon.client.Entities.Account; +import fr.gouv.etalab.mastodon.sqlite.AccountDAO; +import fr.gouv.etalab.mastodon.sqlite.Sqlite; + +/** + * Created by Thomas on 28/05/2017. + * Remove an account in db + */ + +public class RemoveAccountAsyncTask extends AsyncTask { + + private Activity activity; + private Account account; + + public RemoveAccountAsyncTask(Activity activity, Account account){ + this.activity = activity; + this.account = account; + } + + @Override + protected Void doInBackground(Void... params) { + SQLiteDatabase db = Sqlite.getInstance(activity, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + new AccountDAO(activity, db).removeUser(account); + return null; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveHomeTimelineServiceAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveHomeTimelineServiceAsyncTask.java index 1abc000f3..89a6c6b7c 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveHomeTimelineServiceAsyncTask.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveHomeTimelineServiceAsyncTask.java @@ -34,18 +34,19 @@ public class RetrieveHomeTimelineServiceAsyncTask extends AsyncTask private String max_id; private String acct; private OnRetrieveNotificationsInterface listener; - + private String instance; public RetrieveNotificationsAsyncTask(Context context, String max_id, String acct, OnRetrieveNotificationsInterface onRetrieveNotificationsInterface){ this.context = context; @@ -45,12 +45,21 @@ public class RetrieveNotificationsAsyncTask extends AsyncTask this.acct = acct; } + + public RetrieveNotificationsAsyncTask(Context context, String instance, String max_id, String acct, OnRetrieveNotificationsInterface onRetrieveNotificationsInterface){ + this.context = context; + this.max_id = max_id; + this.listener = onRetrieveNotificationsInterface; + this.acct = acct; + this.instance = instance; + } + @Override protected Void doInBackground(Void... params) { if( acct == null) - notifications = new API(context).getNotifications(max_id); + notifications = new API(context, instance).getNotifications(max_id); else - notifications = new API(context).getNotificationsSince(max_id); + notifications = new API(context, instance).getNotificationsSince(max_id); return null; } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java index b482cffbf..84057d894 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.os.AsyncTask; +import android.util.Log; import fr.gouv.etalab.mastodon.activities.MainActivity; import fr.gouv.etalab.mastodon.client.API; @@ -37,23 +38,24 @@ public class UpdateAccountInfoAsyncTask extends AsyncTask { private Context context; private String token; + private String instance; - public UpdateAccountInfoAsyncTask(Context context, String token){ + public UpdateAccountInfoAsyncTask(Context context, String token, String instance){ this.context = context; this.token = token; - + this.instance = instance; } @Override protected Void doInBackground(Void... params) { - Account account = new API(context).verifyCredentials(); + Account account = new API(context, instance).verifyCredentials(); SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); if( token == null) { token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); } account.setToken(token); //TODO: remove this static value to allow other instances - account.setInstance(Helper.INSTANCE); + account.setInstance(instance); SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); boolean userExists = new AccountDAO(context, db).userExist(account); SharedPreferences.Editor editor = sharedpreferences.edit(); 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 fd074721b..10b68f107 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 @@ -59,7 +59,7 @@ import static fr.gouv.etalab.mastodon.helper.Helper.USER_AGENT; public class API { - private static final String BASE_URL = "https://" + Helper.INSTANCE + "/api/v1"; + private SyncHttpClient client = new SyncHttpClient(); @@ -74,6 +74,7 @@ public class API { private List notifications; private int tootPerPage, accountPerPage, notificationPerPage; private int actionCode; + private String instance; public enum StatusAction{ FAVOURITE, @@ -97,8 +98,22 @@ public class API { tootPerPage = sharedpreferences.getInt(Helper.SET_TOOTS_PER_PAGE, 40); accountPerPage = sharedpreferences.getInt(Helper.SET_ACCOUNTS_PER_PAGE, 40); notificationPerPage = sharedpreferences.getInt(Helper.SET_NOTIFICATIONS_PER_PAGE, 40); + this.instance = Helper.getLiveInstance(context); } + public API(Context context, String instance) { + this.context = context; + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + tootPerPage = sharedpreferences.getInt(Helper.SET_TOOTS_PER_PAGE, 40); + accountPerPage = sharedpreferences.getInt(Helper.SET_ACCOUNTS_PER_PAGE, 40); + notificationPerPage = sharedpreferences.getInt(Helper.SET_NOTIFICATIONS_PER_PAGE, 40); + if( instance != null) + this.instance = instance; + else + this.instance = Helper.getLiveInstance(context); + } + + /*** * Verifiy credential of the authenticated user *synchronously* * @return Account @@ -1215,6 +1230,7 @@ public class API { client.addHeader("Authorization", "Bearer "+prefKeyOauthTokenT); client.setSSLSocketFactory(new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore())); client.get(getAbsoluteUrl(action), params, responseHandler); + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) { Toast.makeText(context, R.string.toast_error,Toast.LENGTH_LONG).show(); e.printStackTrace(); @@ -1252,10 +1268,10 @@ public class API { } } + private String getAbsoluteUrl(String action) { - return BASE_URL + action; + return "https://" + this.instance + "/api/v1" + action; } - } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/OauthClient.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/OauthClient.java index 77275279e..9b5f66c38 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/OauthClient.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/OauthClient.java @@ -35,9 +35,13 @@ import static fr.gouv.etalab.mastodon.helper.Helper.USER_AGENT; public class OauthClient { - private static final String BASE_URL = "https://" + Helper.INSTANCE; - private static AsyncHttpClient client = new AsyncHttpClient(); + private String instance; + + + public OauthClient(String instance){ + this.instance = instance; + } public void get(String action, RequestParams params, AsyncHttpResponseHandler responseHandler) { try { @@ -63,7 +67,7 @@ public class OauthClient { } private String getAbsoluteUrl(String action) { - return BASE_URL + action; + return "https://" + instance + action; } 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 d157c8252..08ed3caab 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 @@ -17,6 +17,7 @@ package fr.gouv.etalab.mastodon.helper; +import android.app.Activity; import android.app.AlertDialog; import android.app.DownloadManager; import android.app.PendingIntent; @@ -25,30 +26,56 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; +import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.media.RingtoneManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; +import android.os.AsyncTask; import android.os.Build; +import android.os.Bundle; import android.os.Environment; +import android.support.design.widget.NavigationView; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.util.DisplayMetrics; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import com.loopj.android.http.BuildConfig; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer; +import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import java.io.File; import java.net.InetAddress; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Locale; import java.util.TimeZone; +import fr.gouv.etalab.mastodon.activities.LoginActivity; import fr.gouv.etalab.mastodon.activities.MainActivity; +import fr.gouv.etalab.mastodon.asynctasks.RemoveAccountAsyncTask; +import fr.gouv.etalab.mastodon.client.Entities.Account; +import fr.gouv.etalab.mastodon.sqlite.AccountDAO; +import fr.gouv.etalab.mastodon.sqlite.Sqlite; import mastodon.etalab.gouv.fr.mastodon.R; import fr.gouv.etalab.mastodon.client.API; @@ -134,6 +161,9 @@ public class Helper { //User agent public static final String USER_AGENT = "Mastalab/"+ BuildConfig.VERSION_NAME + " Android/"+ Build.VERSION.RELEASE; + + public static boolean menuAccountsOpened = false; + /*** * Check if the user is connected to Internet * @return boolean @@ -417,10 +447,207 @@ public class Helper { notificationManager.notify(notificationId, notificationBuilder.build()); } + /** + * Returns the instance of the authenticated user + * @param context Context + * @return String domain instance + */ + public static String getLiveInstance(Context context){ + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + if( userId == null) //User not authenticated + return Helper.INSTANCE; + Account account = new AccountDAO(context, db).getAccountByID(userId); + if( account != null){ + return account.getInstance().trim(); + } //User not in db + else return Helper.INSTANCE; + } + + /** + * Converts dp to pixel + * @param dp float - the value in dp to convert + * @param context Context + * @return float - the converted value in pixel + */ public static float convertDpToPixel(float dp, Context context){ Resources resources = context.getResources(); DisplayMetrics metrics = resources.getDisplayMetrics(); return dp * ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); } + + + /** + * Toggle for the menu (ie: main menu or accounts menu) + * @param activity Activity + */ + public static void menuAccounts(final Activity activity){ + + final NavigationView navigationView = (NavigationView) activity.findViewById(R.id.nav_view); + SharedPreferences mSharedPreferences = activity.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String currrentUserId = mSharedPreferences.getString(Helper.PREF_KEY_ID, null); + final ImageView arrow = (ImageView) navigationView.getHeaderView(0).findViewById(R.id.owner_accounts); + if( currrentUserId == null) + return; + + if( !menuAccountsOpened ){ + + arrow.setImageResource(R.drawable.ic_arrow_drop_up); + + navigationView.getMenu().clear(); + navigationView.inflateMenu(R.menu.menu_accounts); + + SQLiteDatabase db = Sqlite.getInstance(activity, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + + final List accounts = new AccountDAO(activity, db).getAllAccount(); + navigationView.setItemIconTintList(null); + for(final Account account: accounts) { + if( !currrentUserId.equals(account.getId()) ) { + final MenuItem item = navigationView.getMenu().add("@" + account.getAcct() + "@" + account.getInstance()); + ImageLoader imageLoader; + DisplayImageOptions options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false) + .cacheOnDisk(true).resetViewBeforeLoading(true).build(); + imageLoader = ImageLoader.getInstance(); + final ImageView imageView = new ImageView(activity); + item.setIcon(R.drawable.ic_person); + imageLoader.displayImage(account.getAvatar(), imageView, options, new ImageLoadingListener() { + @Override + public void onLoadingStarted(String s, View view) { + } + + @Override + public void onLoadingFailed(String s, View view, FailReason failReason) { + } + + @Override + public void onLoadingComplete(String s, View view, Bitmap bitmap) { + item.setIcon(new BitmapDrawable(activity.getResources(), bitmap)); + } + + @Override + public void onLoadingCancelled(String s, View view) { + } + }); + + item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + if( ! activity.isFinishing() ) { + menuAccountsOpened = false; + String userId = account.getId(); + Toast.makeText(activity, activity.getString(R.string.toast_account_changed, "@" + account.getAcct() + "@" + account.getInstance()), Toast.LENGTH_LONG).show(); + changeUser(activity, userId); + arrow.setImageResource(R.drawable.ic_arrow_drop_down); + navigationView.getMenu().clear(); + navigationView.inflateMenu(R.menu.activity_main_drawer); + navigationView.setCheckedItem(R.id.nav_home); + navigationView.getMenu().performIdentifierAction(R.id.nav_home, 0); + return true; + } + return false; + } + }); + item.setActionView(R.layout.update_account); + ImageView deleteButton = (ImageView) item.getActionView().findViewById(R.id.account_remove_button); + deleteButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new AlertDialog.Builder(activity) + .setTitle(activity.getString(R.string.delete_account_title)) + .setMessage(activity.getString(R.string.delete_account_message, "@" + account.getAcct() + "@" + account.getInstance())) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + new RemoveAccountAsyncTask(activity, account).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + item.setVisible(false); + } + }) + .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // do nothing + } + }) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + } + }); + + } + } + MenuItem addItem = navigationView.getMenu().add(R.string.add_account); + addItem.setIcon(R.drawable.ic_person_add); + addItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(activity, LoginActivity.class); + intent.putExtra("addAccount", true); + activity.startActivity(intent); + return true; + } + }); + }else{ + arrow.setImageResource(R.drawable.ic_arrow_drop_down); + navigationView.getMenu().clear(); + navigationView.inflateMenu(R.menu.activity_main_drawer); + + } + menuAccountsOpened = !menuAccountsOpened; + + } + + /** + * Changes the user in shared preferences + * @param activity Activity + * @param userID String - the new user id + */ + private static void changeUser(Activity activity, String userID) { + + SQLiteDatabase db = Sqlite.getInstance(activity, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + Account account = new AccountDAO(activity,db).getAccountByID(userID); + + SharedPreferences sharedpreferences = activity.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, account.getToken()); + editor.putString(Helper.PREF_KEY_ID, account.getId()); + editor.apply(); + ImageLoader imageLoader; + DisplayImageOptions options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false) + .cacheOnDisk(true).resetViewBeforeLoading(true).build(); + imageLoader = ImageLoader.getInstance(); + NavigationView navigationView = (NavigationView) activity.findViewById(R.id.nav_view); + updateHeaderAccountInfo(activity, account, navigationView, imageLoader, options); + } + + + /** + * Update the header with the new selected account + * @param activity Activity + * @param account Account - new account in use + * @param headerLayout View - the menu header + * @param imageLoader ImageLoader - instance of ImageLoader + * @param options DisplayImageOptions - current configuration of ImageLoader + */ + public static void updateHeaderAccountInfo(Activity activity, Account account, View headerLayout, ImageLoader imageLoader, DisplayImageOptions options){ + ImageView profilePicture = (ImageView) headerLayout.findViewById(R.id.profilePicture); + TextView username = (TextView) headerLayout.findViewById(R.id.username); + TextView displayedName = (TextView) headerLayout.findViewById(R.id.displayedName); + TextView ownerStatus = (TextView) headerLayout.findViewById(R.id.owner_status); + TextView ownerFollowing = (TextView) headerLayout.findViewById(R.id.owner_following); + TextView ownerFollowers = (TextView) headerLayout.findViewById(R.id.owner_followers); + if( account == null ) { + Helper.logout(activity); + Intent myIntent = new Intent(activity, LoginActivity.class); + Toast.makeText(activity,R.string.toast_error, Toast.LENGTH_LONG).show(); + activity.startActivity(myIntent); + activity.finish(); //User is logged out to get a new token + }else { + ownerStatus.setText(String.valueOf(account.getStatuses_count())); + ownerFollowers.setText(String.valueOf(account.getFollowers_count())); + ownerFollowing.setText(String.valueOf(account.getFollowing_count())); + username.setText(String.format("@%s",account.getUsername())); + displayedName.setText(account.getDisplay_name()); + imageLoader.displayImage(account.getAvatar(), profilePicture, options); + } + } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/HomeTimelineSyncJob.java b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/HomeTimelineSyncJob.java index 743371932..0e914fa42 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/HomeTimelineSyncJob.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/HomeTimelineSyncJob.java @@ -104,7 +104,7 @@ public class HomeTimelineSyncJob extends Job implements OnRetrieveHomeTimelineSe for (Account account: accounts) { String since_id = sharedpreferences.getString(Helper.LAST_HOMETIMELINE_MAX_ID + account.getAcct(), null); notificationId = (int) Math.round(Double.parseDouble(account.getId())/1000); - new RetrieveHomeTimelineServiceAsyncTask(getContext(), since_id, account.getAcct(), HomeTimelineSyncJob.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new RetrieveHomeTimelineServiceAsyncTask(getContext(), account.getInstance(), since_id, account.getAcct(), HomeTimelineSyncJob.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java index 84510a7e1..0f747bdc1 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java @@ -114,7 +114,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications for (Account account: accounts) { String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getAcct(), null); notificationId = (int) Math.round(Double.parseDouble(account.getId())/1000); - new RetrieveNotificationsAsyncTask(getContext(), max_id, account.getAcct(), NotificationsSyncJob.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new RetrieveNotificationsAsyncTask(getContext(), account.getInstance(), max_id, account.getAcct(), NotificationsSyncJob.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/AccountDAO.java b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/AccountDAO.java index 3a75885c4..a5b7a6c09 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/AccountDAO.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/AccountDAO.java @@ -76,6 +76,7 @@ public class AccountDAO { db.insert(Sqlite.TABLE_USER_ACCOUNT, null, values); }catch (Exception e) { + e.printStackTrace(); return false; } return true; diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_drop_down.png b/app/src/main/res/drawable-hdpi/ic_arrow_drop_down.png new file mode 100644 index 0000000000000000000000000000000000000000..43666f2024bff68bce255ee961b9225364780115 GIT binary patch literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;i>HfYNX4zUmv{0q7;vySdM*6F z>cx{e5z{949uSV)&g!#GsmIVvUZhQ<*)PCSZONxIlUEsvho;M1P3F6rZniY0Y2*7S YhyV5Gi@#P&1I=UbboFyt=akR{0BXJ|JOBUy literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_drop_up.png b/app/src/main/res/drawable-hdpi/ic_arrow_drop_up.png new file mode 100644 index 0000000000000000000000000000000000000000..4198b76690558406c30deaee1c9acd3bd9000027 GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;r>Bc!NX4zU7dHwrC~!Cjx+VT! zdB^22lggaA&z3)pb8abqx3w(kpc{XKj!yo_3&EB62Z literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_cancel.png b/app/src/main/res/drawable-hdpi/ic_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..e25273a149ffa7dfdc9eaec108fa7977e8d27613 GIT binary patch literal 435 zcmV;k0ZjghP)tCWA9JRqh~d%O)66aU92BU6VtsJQ)A9SYfWG! zYHY#OSaMLQN$zPMW{k)j8wYKjG*Nm?jr~7?{_NLQ7ED40rLGLYPA!yn;E dYWZEj_yKMMTh}0$(s=*?002ovPDHLkV1oCP!1w?F literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_person.png b/app/src/main/res/drawable-hdpi/ic_person.png new file mode 100644 index 0000000000000000000000000000000000000000..ae93a3f058a7b5aa2ab68fcf63dc807c9cca57d9 GIT binary patch literal 291 zcmV+;0o?wHP)3gE0;84Z8Jc#mIlC+p{=8pw!IVqE8gZsdStnM{_jmL_Fy{Hw?czoH5W=7G(UUW8! zoHvThUmM$tzKx=qX8h}bW=UMrq@47|-=a@#w-Yp&a^k|ArlyQho=J)_T?Xu=%QwMc zE2j=6xp{h0RYtPP+z_q|sr)y=nDqG&s(9p=Iv-)0qAXO`f;k0~Cc}@F#$!)9DC%2l pJbFRVQ6)0$Cs#=%Qi!621ujeiANVRD{#$74QhgsK5o}C?@P#8BPETLW9xD1#p48h7f`-NXj=0la`tO z%(yTieV^5I+9&PI+kql~Pn8Z+HvBN7DW_QhkkF1 zW@;3T?L`}-DELQ_`O`+}fx&;2O^~_RmYI>!5x2W6_vHTi|8dM8#Wm`9g_uuHd}6zR zt#ZM!QU~UJO$HsjLdx1}9HbiWMY0}Bk!Ir6XItRS;vjKknF*VuhFOY8fH(ugLQVhP UywyqOK>V8)4TeYC zVs@ALGPGzK`8Yc}|GT<6e6@qb5w+U_3%sxW{r&yDHb?Uf6=}9B3S3uquw)#_R`|zs zbiHGef@RX-)~^o^HeYjiKZUu_R!WckWaAZqTnT~VhX>Xy&aB+Q!0_aNfA4`wQ~iMU OGI+ZBxvXaZ++gZiv5JuEXgP@)Yh6eCXLjo_dpm8v7VWQ@;A;OHl(vykykx_|3wZ;( zxe-Yy<{A-G$<;L?a%i<QS6RmE&s{6=Bde+ zH*j-Y`72jg!^nO&l}E^+vfq__)B!=YE2!jzkJ9zhb)et22OdXcqs;>NTmS$707*qo IM6N<$f~Lf8t^fc4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-ldpi/ic_person_add.png b/app/src/main/res/drawable-ldpi/ic_person_add.png new file mode 100644 index 0000000000000000000000000000000000000000..d92cb44127aa0de9e6e81b1a70ea7389738307b3 GIT binary patch literal 289 zcmV++0p9+JP)rJeDgJ@r`W6MI^G|&&YYGHK-&cG-$gGzMagW=B1nS0L_ zJ@1bZf|!J3Mdly}CCQm{hI&=6L|(juH<9Z#2wWlsiM3p*i6}0e2V0Q>K~k+clhD~F z^r3({C|KDNY&)&pKxS*rCKS`yjb^r7&s;{ET2m2dtp4)78&qol`;+07Iu6K>z>% literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_drop_up.png b/app/src/main/res/drawable-mdpi/ic_arrow_drop_up.png new file mode 100644 index 0000000000000000000000000000000000000000..54989ad3bd9b28941e81f2c6c8f0d575279d6b10 GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1O-~ockcwN$2@E6N!j=Cs`1AW33KD7Rq7=f^6mBc?1A1`n>g4|gzEhRo)AZr&y zWp&Ul5CJ|ccX1^*BRB-OwAQbCdm~sn*ILhBtsm>&+-hx)Zq%hqvkT z%$6|e2>v(mp}k0*g4=1a9Yq3nJR%NLoDs?AqJ>YDJi1&m*`e12N9nI;|b zWPWk=yu*%p7EiaZvGJ7h?3S2k@Q3ZtaUN^QEXgm0+S^hs8ikw;D&`!Nn*B+mZGXhn x<_Gd^96r3~3$@q}rq7W2C)_Y4TVfXj!?cS{=Y=e7+JP=(@O1TaS?83{1OP&hM(zLr literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_person_add.png b/app/src/main/res/drawable-mdpi/ic_person_add.png new file mode 100644 index 0000000000000000000000000000000000000000..397df9747a09c47deeaddfa8a7785b491310081b GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iS)MMAAr-fh6C}<#bSH8j4sX-v znJr<`5&UoBLwk`r1-H{;JBkGEbog?5Ffs01l<~tS;;?v-X^zvy4RXPnMl6iH3&Ios zl=Eb@m^N(5VX=B@e4)hj&BLZwDoPBlQalo`B+3kJ5`XZv^h=f*T`_v`_*%y40;a>^ z3Keq>O3nVH(Y8NgYV!m6HVz-&^MzXM2h(rVHt`3oOVnUu$hbUj^W4chF9Ti4;OXk; Jvd$@?2>^Y%O#uJ^ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_drop_down.png b/app/src/main/res/drawable-xhdpi/ic_arrow_drop_down.png new file mode 100644 index 0000000000000000000000000000000000000000..9af517d9ceec04f73d5d43d4acb6059e9e30feac GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tdrud~kcwMxuUzB=N*oDrKmV+( z^&bn@*=UC^S7xese%h0o|6tmIw@l>$J0c$(>)jo=!@Ij$e8+Nkehtr0ik}@P)rVViPln;}d`%$z*; z zou1dXSRXZ{T=4sNv%<&bAs-IDh)XeiY&9gL_+{LO#t-d|{waETH~dIK@D>-&_|ZaH zx7LqnyJp9Hk1WtbQZGr!<~(T0f9Dni_muv4l^Eae=II8PpPH{Lr?!Kv}bq%>e95i1%^G z1=?C)`7I&A`?%o(9fNOQ1)vhVj|VQ`8N+8HA;J52&rU5#rU+J#Aa6WY-nq?GlfwWCxtG8}|)v zISIWYn>$k1gS4XHOloZzf;uyP4ea;OPkym=7K7BsWuLH`MvH2N)7RDEQqA-f`z&4h zK0Ut$baZs&Q$QCRoCzBya=hgU^x!?=lFs0(pT5-Jm~bh2Z|i+bp1Jn|aXHqaD-d|- zs0%hi!Af0l779*ZAh3l3OI=_J1ubT-P@0$VaJ;p@OBb z`jG|7^u`CtU@l#LXNI(FeF^)I>wR`8%6u10+VeA%uhw93YTfqEV|>$doT-N!e6@~p~)z2eB#U?9M9(5ALd z&e6u>Lico@h_6j*yKfo-6){+xJ+uDO)G6!2y{~)y}Fb4fB_GKVgI|c zvP-y|_OR)!R8ZgXE{Ykb=25R*)!I3+>!${*zMK%O_;SkRxg`@P&n%fddFq!5lP7+e zBs}+vqPo$`WWSJ0{c}2&%(q-US*<8c&G%WzEj^%qhh4l!r=Is?Hmnl@@)$f_{an^L HB{Ts54MsbE literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_cancel.png b/app/src/main/res/drawable-xxhdpi/ic_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..aeb96f0c9c58551405c09f766d3d4ce0979f32ff GIT binary patch literal 813 zcmV+|1JeA7P)L9lL)tG8f>e{ocXvjsfGJL5b`n1{oUL%=L<-bDA7aJDDv3EJ{q`06CJW9y=!0( z8 z#Ce13SUAF3D7k@2V<$Gut!*O}7iR)hXE?%+F&+nJ3g4aL2>WUu3?@9_^pM8we5dLK zN7#vtFYS24>AAP@L61CJ$t( z*2<9X;s`BC_w1??$r}3@f_tkA1CaW0c#m7*foclRC@7FjaUx1kjGib;cNLO3PJ}i_ zkgL`DIoH}sj)b5Ub;O5xxS>qJ@ zDuKX7VBE0A2{nro!_+ZvjiXnb7$?_Jjq-!bSvE**Jm#G7EC!Ap(kWjtuExTV7bo&v z->N1;@*;mhGXVC3FET~`q^j?bw=73@+MuF#8Wz#9+wpW}j^C@f! z$KG(h;E~Ow`YFD7!8ym6?JPgz0qILJ2&NRF$gB7~#wxV=DrQ&oFL(~YhFYDCg)u|P} rB7io%UeKpq`gV@*BoZY`^e^=jHKlI8Gr0}`00000NkvXXu0mjfVgZ0k literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_person.png b/app/src/main/res/drawable-xxhdpi/ic_person.png new file mode 100644 index 0000000000000000000000000000000000000000..c1e75b4d13a7279425d0bbd26fd902314b467cae GIT binary patch literal 481 zcmV<70UrK|P)PmqKgQk%!T4G zommV;W!mQ3%ZzBCU?X@L^vL1B%JvCDIKWR*b~p0muNc}Iow zf+?=^KCT3ca!y1CzjF4tXT})KJG~bxiU1O#dB^_d%gSInxIYhz28=xI$8Bnjw!s9~ zlqiu%xHE!9Y~ctwlu@NEvP*5!Rhjc>V?_L>jA9el+LVtiVZ*;gT*uu&7_YZj8yqu<3tuo^&oMCwCqi$4<8v!m>X$Q* zN8dSaA28PsXPnmDG1ME3>4SESaoq0cgGxq0SNfo5qo5~!kYl(45|Dr-5{X12`5$=$ Xv9Fkxq+J7(00000NkvXXu0mjfHz?YW literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_person_add.png b/app/src/main/res/drawable-xxhdpi/ic_person_add.png new file mode 100644 index 0000000000000000000000000000000000000000..d122edbb1604b9f7d14bf817664091eae9d9d427 GIT binary patch literal 515 zcmV+e0{s1nP))NiR(xsr=QcJrKK{R&J z(hyIO(AG#bU8oELOK>kU}0cG|5$3B@yi%%rr8v;e4}= z3q*Q>vxE8%7|$QrGL4h?6UOs_*{|aWc+*_&)P;a7Oql_?6vA3oR4@=`!I`hjXvKo$K!O(~$p00LF}Eo5>@oz)MNlma@^4-}PXj4k!ULmUV+ z?}!4OtGr&s-HOn>BeLrq6J~%wc27>YYfYsnIJ!v&ld0M|xaI%=002ovPDHLk FV1ky~?~MQe literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_arrow_drop_down.png b/app/src/main/res/drawable-xxxhdpi/ic_arrow_drop_down.png new file mode 100644 index 0000000000000000000000000000000000000000..452e5021691e4dc4f13ae7d6359216cd807bc215 GIT binary patch literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcg8a!PbLn>~)z3Rx`G8!u292#CG8`Cs!c=gWUwFaJ%vR4aC=cFyHm<)y!ae82BD z$o+S^rSo&ViqG8rGYl&KIUV})(_TpGS-oe*%>NTEnEY2;nETJ&>G2gTe~DWM4fMkq+2 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_arrow_drop_up.png b/app/src/main/res/drawable-xxxhdpi/ic_arrow_drop_up.png new file mode 100644 index 0000000000000000000000000000000000000000..45ea95ed6b848784f5ebd0f0aefcba2355bde618 GIT binary patch literal 991 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wF4!X83KGlT!GyGU;t!{g3%BdX(8Zo zYCAj7&rBsje!&b4v$g#sfV>`07srr_TW_!4=4wz7V7S=!@Be&drCph-@9bQP?`0n< zk!ACoq~dug-0?AU^6THl%%5L-^61R>k4lJ@o5Xbc(`2C=|Ko(1j=y!Tc(BKolP_kw z{ldmeAC(LO?Dn;=W<73P;86QVKxl__J`2mcx|-V1^i<+WSy g6#^}or1JMV%l35)rMA|AhM<`7boFyt=akR{00AvpP5=M^ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_cancel.png b/app/src/main/res/drawable-xxxhdpi/ic_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb6fbb478344bee98375fbf3d3c48728e263dda GIT binary patch literal 1094 zcmV-M1iAZ(P)J$EM3dCw-0m^^R1bLO0TZh%060tE^bh))mDhCxhW z6%oX6fh!Rg;!y-Em=w>np*laoOVNdKL}3$R3uEX+Nxq;Lh;MZc=OpyhNMIJN;;(&G zIr_0Hjh-4i=tG%5@Fnxhl}UtLaO4)JR82?JF(7@kv{o`@5F@uZOisKdi08hxbFr|QLAoX*?0Ww8cDh0 zwDTmCD%BEV!F+;dar0NfvJesrgjQ@T^b7Ba_%Fn3D-$;7QGrrk{Z_tN{6WTt zf(7euBiTT*aD-Ah1N~C?*OtQy_;d@sGWd51#luZ}84Ffg8&9#YEwh8sM!;y!xbVUX z_>P(hpBSPWm?O0vb3@R38*(`A4 z+Zb>+Fr`@XX0}kqif?1g-N34H)t=b`C%%m(_qUH|G#(aVSfPV&W7FM0OsjWtv!I7> z-#csVOjI-D^IE8+Tx46!_#w!)X6}?0KUG3IEtuybF)V9bN9~fpEyn58*NogXceT*-AoojirdLq z5Ay90&h|7Y7Ha7h8VRGODDKEi2>M9JlNuE3%$v)vku)iLBR8Qb!?BbF^$`;dKPG?9 zbclFJOn1n4%2fmXt77cs&6stUou}&X{=Uvht#s9UY~dT8ai86- z!?uTurxpfO?a}3I@vZ&zj5KK+e}pIVWLFQd08_i`dh$b*T0=F`yk5~DTdD4`!tbb8 zoT@6uI~=jV$3(kQ1YcB&Ud`kh6}_&{KT0eRebo%kq|xJ;Mw8r5gV_buArjzM5_$x~ zX!mQ;xEFpc7{C~ou!%z?a48}o9&KU~qhhUxXdC2#0tE^bC=gA*0rEe|G$oYx-~a#s M07*qoM6N<$g1s0HVgLXD literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_person.png b/app/src/main/res/drawable-xxxhdpi/ic_person.png new file mode 100644 index 0000000000000000000000000000000000000000..940aa754a277a917821b4214dfebe47b3312479f GIT binary patch literal 651 zcmV;60(AX}P)Fiw5h_f*1=zO(P& z{&fUZSK*P*;<;H!ipD}duzgQCJrn0er~s~(GFlI1-;<-keoupC%a48{X+OYZX#1Wf z><4g+0<72%P%sLxVL!mRQGk;D0IpGh3IiB(7WWd0{UBbA`!t$03NUUzKvH_$u=ao8 zv4-7{6Xyc*$b_6qD;Uty5#&Mzc(11;OGtwl)c!WQG_i3_vLf%>HLas$fP}?bjO(j`X_qJK&FUm103sdfgS-sFmc5SLAg=K8@>G zbqOJakj8Y%CM}D%_=GRmmj$ehOId;c;1maPWAiC6N=Ehw7T_!_zS znvU@X51MIz)S%w|kBhNpbz950YP$VvEk*wjJk^A%zq@ZvMQi`CrS?4?p)V@?-Lk4y zEvOjPf4n|eu^-u!{O2tjy0>+OLGu9f*6e#)G!L+2H9*lkz;~+wewzoVS{4H^00S@p l1K15f2qA7tw0Bq*v{sur=bbrc;CY^#VPFr>Z|8l_$LsA!p)e)bH0!~>Vsr`nK+M(0775NXwfKsVT8L5sprnR31K%*N1IqSAQ z0nf`*QlIa&0UE3Guq;xm(%5xzO|Ir-)6jubWxkMkA_5SA00ba_bAWc)q?ParOIX2Y zS-=i(EF=BKE;i-PGG1d;Hht~>0o=kf%;00plUXR+xmo!{(LF#L22n=Ucw17%B!+Os z4Zr}V5*}hM@(!l)ILmIiCXc_O68)?{;JJ*Py4vspU)94m{RJ;^HO=@#7WE##FP@|m zz92KlN$T+%p3(miyqH4OUw&>b>c-#7G=Ai5+}4WUC9CSG16Q^F_7_5hR4;zypg2Uv3oP_+m6?iAqA{fn?HUZfF#00bZa0n!7YD2k#eilQhg@A?Bl Woa$%KF*Be50000 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 53a6e9d1d..06bfd9422 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -37,6 +37,17 @@ android:gravity="center" android:orientation="vertical" android:layout_height="wrap_content"> + + + + - + android:orientation="horizontal"> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_accounts.xml b/app/src/main/res/menu/menu_accounts.xml new file mode 100644 index 000000000..891ed2c9a --- /dev/null +++ b/app/src/main/res/menu/menu_accounts.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8166e0151..04dac5c29 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,6 +24,9 @@ Jeton Authentification en deux étapes ? Aucun résultat ! + Instance + Utilisation du compte %1$s + Ajouter un compte Accueil Accueil @@ -39,6 +42,8 @@ Notifications Optimisation Que souhaitez-vous faire ? + Supprimer un compte + Supprimer le compte %1$s de l\'application ? Aucun pouet à afficher !