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)

This commit is contained in:
tom79 2017-05-28 14:10:19 +02:00
parent 0ff7cbf19a
commit 5bc23d3a23
48 changed files with 487 additions and 63 deletions

View File

@ -14,16 +14,20 @@
* see <http://www.gnu.org/licenses>. */
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 {
});
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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 <http://www.gnu.org/licenses>. */
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<Void, Void, Void> {
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;
}
}

View File

@ -34,18 +34,19 @@ public class RetrieveHomeTimelineServiceAsyncTask extends AsyncTask<Void, Void,
private String since_id;
private String acct;
private OnRetrieveHomeTimelineServiceInterface listener;
private String instance;
public RetrieveHomeTimelineServiceAsyncTask(Context context, String since_id, String acct, OnRetrieveHomeTimelineServiceInterface onRetrieveHomeTimelineServiceInterface){
public RetrieveHomeTimelineServiceAsyncTask(Context context, String instance, String since_id, String acct, OnRetrieveHomeTimelineServiceInterface onRetrieveHomeTimelineServiceInterface){
this.context = context;
this.since_id = since_id;
this.listener = onRetrieveHomeTimelineServiceInterface;
this.acct = acct;
this.instance = instance;
}
@Override
protected Void doInBackground(Void... params) {
statuses = new API(context).getHomeTimelineSinceId(since_id);
statuses = new API(context, instance).getHomeTimelineSinceId(since_id);
return null;
}

View File

@ -36,7 +36,7 @@ public class RetrieveNotificationsAsyncTask extends AsyncTask<Void, Void, Void>
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<Void, Void, Void>
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;
}

View File

@ -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<Void, Void, Void> {
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();

View File

@ -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<Notification> 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;
}
}

View File

@ -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;
}

View File

@ -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<Account> 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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -76,6 +76,7 @@ public class AccountDAO {
db.insert(Sqlite.TABLE_USER_ACCOUNT, null, values);
}catch (Exception e) {
e.printStackTrace();
return false;
}
return true;

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

View File

@ -37,6 +37,17 @@
android:gravity="center"
android:orientation="vertical"
android:layout_height="wrap_content">
<EditText
android:visibility="gone"
android:id="@+id/login_instance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textWebEmailAddress"
android:hint="@string/instance"
/>
<EditText
android:id="@+id/login_uid"
android:layout_width="match_parent"

View File

@ -42,15 +42,30 @@
<LinearLayout
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/owner_container"
android:orientation="vertical">
<TextView
android:id="@+id/displayedName"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
android:orientation="horizontal">
<TextView
android:id="@+id/displayedName"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<ImageView
android:layout_marginLeft="5dp"
android:layout_marginStart="5dp"
android:id="@+id/owner_accounts"
android:layout_width="30dp"
android:src="@drawable/ic_arrow_drop_down"
android:layout_height="30dp"
tools:ignore="ContentDescription" />
</LinearLayout>
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 <http://www.gnu.org/licenses>.
-->
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:layout_gravity="center_vertical">
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_gravity="center"
android:gravity="center"
android:layout_marginRight="5dp"
android:layout_marginEnd="5dp"
android:layout_marginLeft="5dp"
android:layout_marginStart="5dp"
android:padding="5dp"
android:id="@+id/account_remove_button"
android:layout_width="wrap_content"
android:scaleType="center"
android:layout_height="wrap_content"
android:src="@drawable/ic_cancel"
tools:ignore="ContentDescription" />
</LinearLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group android:checkableBehavior="single">
</group>
</menu>

View File

@ -24,6 +24,9 @@
<string name="token">Jeton</string>
<string name="two_factor_authentification">Authentification en deux étapes ?</string>
<string name="no_result">Aucun résultat !</string>
<string name="instance">Instance</string>
<string name="toast_account_changed">Utilisation du compte %1$s</string>
<string name="add_account">Ajouter un compte</string>
<!--- Menu -->
<string name="home_menu">Accueil</string>
<string name="home_timeline">Accueil</string>
@ -39,6 +42,8 @@
<string name="notifications">Notifications</string>
<string name="optimization">Optimisation</string>
<string name="make_a_choice">Que souhaitez-vous faire ?</string>
<string name="delete_account_title">Supprimer un compte</string>
<string name="delete_account_message">Supprimer le compte %1$s de l\'application ?</string>
<!-- Status -->
<string name="no_status">Aucun pouet à afficher !</string>