diff --git a/app/build.gradle b/app/build.gradle
index 5ab820972..989e687b8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,9 +7,8 @@ android {
applicationId "fr.gouv.etalab.mastodon"
minSdkVersion 15
targetSdkVersion 25
- versionCode 5
- versionName "1.0.5"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ versionCode 7
+ versionName "1.1.0"
}
buildTypes {
release {
@@ -21,17 +20,11 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
- androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
- exclude group: 'com.android.support', module: 'support-annotations'
- })
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:support-v4:25.3.1'
- compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4'
compile 'com.loopj.android:android-async-http:1.4.9'
compile 'com.google.code.gson:gson:2.8.0'
- compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
- compile 'com.evernote:android-job:1.1.9'
- testCompile 'junit:junit:4.12'
+ compile 'com.evernote:android-job:1.1.10'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8cc184ac3..a6b2329f3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -42,10 +42,6 @@
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar">
-
. */
package fr.gouv.etalab.mastodon.activities;
+import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.graphics.Color;
-import android.os.Build;
+import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
-import android.text.Html;
-import android.text.method.LinkMovementMethod;
import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
import android.widget.TextView;
import mastodon.etalab.gouv.fr.mastodon.R;
@@ -48,24 +48,34 @@ public class AboutActivity extends AppCompatActivity {
about_version.setText(getResources().getString(R.string.about_vesrion, version));
} catch (PackageManager.NameNotFoundException ignored) {}
- TextView about_developer = (TextView) findViewById(R.id.about_developer);
- TextView about_license = (TextView) findViewById(R.id.about_license);
- TextView about_code = (TextView) findViewById(R.id.about_code);
- about_developer.setMovementMethod(LinkMovementMethod.getInstance());
- about_license.setMovementMethod(LinkMovementMethod.getInstance());
- about_code.setMovementMethod(LinkMovementMethod.getInstance());
- about_developer.setLinkTextColor(Color.BLUE);
- about_license.setLinkTextColor(Color.BLUE);
- about_code.setLinkTextColor(Color.BLUE);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- about_developer.setText(Html.fromHtml(getString(R.string.about_developer), Html.FROM_HTML_MODE_COMPACT));
- about_license.setText(Html.fromHtml(getString(R.string.about_license), Html.FROM_HTML_MODE_COMPACT));
- about_code.setText(Html.fromHtml(getString(R.string.about_code), Html.FROM_HTML_MODE_COMPACT));
- }else {
- about_developer.setText(Html.fromHtml(getString(R.string.about_developer)));
- about_license.setText(Html.fromHtml(getString(R.string.about_license)));
- about_code.setText(Html.fromHtml(getString(R.string.about_code)));
- }
+ Button about_developer = (Button) findViewById(R.id.about_developer);
+ Button about_code = (Button) findViewById(R.id.about_code);
+ Button about_license = (Button) findViewById(R.id.about_license);
+
+ about_code.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://bitbucket.org/tom79/mastodon_etalab"));
+ startActivity(browserIntent);
+ }
+ });
+ about_developer.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(AboutActivity.this, ShowAccountActivity.class);
+ Bundle b = new Bundle();
+ b.putString("accountId", "2416");
+ intent.putExtras(b);
+ startActivity(intent);
+ }
+ });
+ about_license.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.gnu.org/licenses/quick-guide-gplv3.fr.html"));
+ startActivity(browserIntent);
+ }
+ });
}
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 77e82b560..30715b650 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
@@ -15,26 +15,37 @@
package fr.gouv.etalab.mastodon.activities;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
+import android.widget.EditText;
import android.widget.Toast;
+import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
+import com.loopj.android.http.RequestParams;
import org.json.JSONException;
import org.json.JSONObject;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
import java.util.HashMap;
import cz.msebera.android.httpclient.Header;
+import fr.gouv.etalab.mastodon.asynctasks.UpdateAccountInfoAsyncTask;
+import fr.gouv.etalab.mastodon.client.MastalabSSLSocketFactory;
import fr.gouv.etalab.mastodon.client.OauthClient;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
+import static fr.gouv.etalab.mastodon.helper.Helper.USER_AGENT;
+
/**
* Created by Thomas on 23/04/2017.
@@ -49,14 +60,23 @@ public class LoginActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
- final Button connectionButton = (Button) findViewById(R.id.login_button);
- final Intent webviewIntent = new Intent(this, WebviewActivity.class);
-
-
+ Button connectionButton = (Button) findViewById(R.id.login_button);
connectionButton.setEnabled(false);
+ }
+
+ @Override
+ protected void onResume(){
+ super.onResume();
+ Button connectionButton = (Button) findViewById(R.id.login_button);
+ if( !connectionButton.isEnabled())
+ retrievesClientId();
+ }
+
+ private void retrievesClientId(){
+ final Button connectionButton = (Button) findViewById(R.id.login_button);
String action = "/api/v1/apps";
- HashMap parameters = new HashMap<>();
+ final HashMap parameters = new HashMap<>();
parameters.put(Helper.CLIENT_NAME, Helper.OAUTH_REDIRECT_HOST);
parameters.put(Helper.REDIRECT_URIS,"https://" + Helper.INSTANCE + Helper.REDIRECT_CONTENT);
parameters.put(Helper.SCOPES, Helper.OAUTH_SCOPES);
@@ -95,14 +115,54 @@ public class LoginActivity extends AppCompatActivity {
connectionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- startActivity(webviewIntent);
- finish();
+ connectionButton.setEnabled(false);
+ AsyncHttpClient client = new AsyncHttpClient();
+ RequestParams requestParams = new RequestParams();
+ SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
+ requestParams.add(Helper.CLIENT_ID, sharedpreferences.getString(Helper.CLIENT_ID, null));
+ requestParams.add(Helper.CLIENT_SECRET, sharedpreferences.getString(Helper.CLIENT_SECRET, null));
+ requestParams.add("grant_type", "password");
+ EditText login_uid = (EditText) findViewById(R.id.login_uid);
+ EditText login_passwd = (EditText) findViewById(R.id.login_passwd);
+ requestParams.add("username",login_uid.getText().toString().trim());
+ requestParams.add("password",login_passwd.getText().toString().trim());
+ client.setUserAgent(USER_AGENT);
+ try {
+ client.setSSLSocketFactory(new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore()));
+ client.post("https://" + Helper.INSTANCE + "/oauth/token", requestParams, new AsyncHttpResponseHandler() {
+ @Override
+ public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
+ String response = new String(responseBody);
+ JSONObject resobj;
+ try {
+ resobj = new JSONObject(response);
+ String token = resobj.get("access_token").toString();
+ SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedpreferences.edit();
+ 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);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
+ connectionButton.setEnabled(true);
+ Toast.makeText(getApplicationContext(),R.string.toast_error_login,Toast.LENGTH_LONG).show();
+ }
+ });
+
+ } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException | KeyStoreException e) {
+ e.printStackTrace();
+ }
+
}
});
-
}
-
}
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 3a79eee0a..fee1fc9cc 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
@@ -50,6 +50,7 @@ import java.util.HashMap;
import fr.gouv.etalab.mastodon.asynctasks.UpdateAccountInfoByIDAsyncTask;
import fr.gouv.etalab.mastodon.client.Entities.Account;
+import fr.gouv.etalab.mastodon.client.PatchBaseImageDownloader;
import fr.gouv.etalab.mastodon.fragments.DisplayAccountsFragment;
import fr.gouv.etalab.mastodon.fragments.DisplayNotificationsFragment;
import fr.gouv.etalab.mastodon.helper.Helper;
@@ -62,8 +63,9 @@ import fr.gouv.etalab.mastodon.fragments.TabLayoutSettingsFragment;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
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.INTENT_NOTIFICATION;
+import static fr.gouv.etalab.mastodon.helper.Helper.NOTIFICATION_INTENT;
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, OnUpdateAccountInfoInterface {
@@ -119,6 +121,7 @@ public class MainActivity extends AppCompatActivity
File cacheDir = new File(getCacheDir(), getString(R.string.app_name));
ImageLoaderConfiguration configImg = new ImageLoaderConfiguration.Builder(this)
.threadPoolSize(5)
+ .imageDownloader(new PatchBaseImageDownloader(getApplicationContext()))
.threadPriority(Thread.MIN_PRIORITY + 3)
.denyCacheImageMultipleSizesInMemory()
.diskCache(new UnlimitedDiskCache(cacheDir))
@@ -138,10 +141,14 @@ public class MainActivity extends AppCompatActivity
boolean menuWasSelected = false;
if( getIntent() != null && getIntent().getExtras() != null ){
Bundle extras = getIntent().getExtras();
- if (extras.getInt(INTENT_ACTION) == INTENT_NOTIFICATION){
+ if (extras.getInt(INTENT_ACTION) == NOTIFICATION_INTENT){
navigationView.setCheckedItem(R.id.nav_notification);
navigationView.getMenu().performIdentifierAction(R.id.nav_notification, 0);
menuWasSelected = true;
+ }else if( extras.getInt(INTENT_ACTION) == HOME_TIMELINE_INTENT){
+ navigationView.setCheckedItem(R.id.nav_home);
+ navigationView.getMenu().performIdentifierAction(R.id.nav_home, 0);
+ menuWasSelected = true;
}
}
if (savedInstanceState == null && !menuWasSelected) {
@@ -190,10 +197,13 @@ public class MainActivity extends AppCompatActivity
return;
Bundle extras = intent.getExtras();
if( extras.containsKey(INTENT_ACTION) ){
- if (extras.getInt(INTENT_ACTION) == INTENT_NOTIFICATION){
- final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+ final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+ if (extras.getInt(INTENT_ACTION) == NOTIFICATION_INTENT){
navigationView.setCheckedItem(R.id.nav_notification);
navigationView.getMenu().performIdentifierAction(R.id.nav_notification, 0);
+ }else if( extras.getInt(INTENT_ACTION) == HOME_TIMELINE_INTENT){
+ navigationView.setCheckedItem(R.id.nav_home);
+ navigationView.getMenu().performIdentifierAction(R.id.nav_home, 0);
}
}
intent.replaceExtras(new Bundle());
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainApplication.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainApplication.java
index 3ed2bfe00..f35014176 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainApplication.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainApplication.java
@@ -18,10 +18,12 @@ import android.app.Application;
import com.evernote.android.job.JobManager;
import fr.gouv.etalab.mastodon.jobs.ApplicationJob;
+import fr.gouv.etalab.mastodon.jobs.HomeTimelineSyncJob;
import fr.gouv.etalab.mastodon.jobs.NotificationsSyncJob;
/**
* Created by Thomas on 29/04/2017.
+ * Main application, jobs are launched here.
*/
public class MainApplication extends Application{
@@ -32,6 +34,7 @@ public class MainApplication extends Application{
super.onCreate();
JobManager.create(this).addJobCreator(new ApplicationJob());
JobManager.instance().getConfig().setVerbose(false);
- NotificationsSyncJob.schedule(getApplicationContext(), false);
+ NotificationsSyncJob.schedule(false);
+ HomeTimelineSyncJob.schedule(false);
}
}
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowAccountActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowAccountActivity.java
index bd3998e29..ceacccd0b 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowAccountActivity.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowAccountActivity.java
@@ -14,21 +14,28 @@
* see . */
package fr.gouv.etalab.mastodon.activities;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
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.LocalBroadcastManager;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
+import android.text.Html;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -77,8 +84,9 @@ public class ShowAccountActivity extends AppCompatActivity implements OnPostActi
private ViewPager mPager;
private String accountId;
private TabLayout tabLayout;
-
-
+ private BroadcastReceiver hide_header;
+ private TextView account_note;
+ private String userId;
public enum action{
FOLLOW,
@@ -94,6 +102,9 @@ public class ShowAccountActivity extends AppCompatActivity implements OnPostActi
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_account);
+
+
+
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
imageLoader = ImageLoader.getInstance();
statuses = new ArrayList<>();
@@ -109,7 +120,7 @@ public class ShowAccountActivity extends AppCompatActivity implements OnPostActi
accountId = b.getString("accountId");
new RetrieveRelationshipAsyncTask(getApplicationContext(), accountId,ShowAccountActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new RetrieveAccountAsyncTask(getApplicationContext(),accountId, ShowAccountActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
+ userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
if( accountId != null && accountId.equals(userId)){
account_follow.setVisibility(View.GONE);
}
@@ -164,6 +175,30 @@ public class ShowAccountActivity extends AppCompatActivity implements OnPostActi
}
});
+
+
+ account_note = (TextView) findViewById(R.id.account_note);
+ //Register LocalBroadcast to receive selected accounts after search
+ hide_header = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean hide = intent.getBooleanExtra("hide", false);
+ if( hide){
+ account_follow.setVisibility(View.GONE);
+ account_note.setVisibility(View.GONE);
+ tabLayout.setVisibility(View.GONE);
+ }else {
+ account_follow.setVisibility(View.VISIBLE);
+ if( accountId != null && accountId.equals(userId)){
+ account_follow.setVisibility(View.GONE);
+ }
+ tabLayout.setVisibility(View.VISIBLE);
+ account_note.setVisibility(View.VISIBLE);
+ }
+ }
+ };
+ LocalBroadcastManager.getInstance(this).registerReceiver(hide_header, new IntentFilter(Helper.HEADER_ACCOUNT));
+
//Follow button
account_follow.setOnClickListener(new View.OnClickListener() {
@Override
@@ -215,9 +250,14 @@ public class ShowAccountActivity extends AppCompatActivity implements OnPostActi
account_ac.setVisibility(View.GONE);
else
account_ac.setText(account.getAcct());
- tabLayout.getTabAt(0).setText(getString(R.string.status) + "\n" + String.valueOf(account.getStatuses_count()));
- tabLayout.getTabAt(1).setText(getString(R.string.following) + "\n" + String.valueOf(account.getFollowing_count()));
- tabLayout.getTabAt(2).setText(getString(R.string.followers) + "\n" + String.valueOf(account.getFollowers_count()));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+ account_note.setText(Html.fromHtml(account.getNote(), Html.FROM_HTML_MODE_COMPACT));
+ else
+ //noinspection deprecation
+ account_note.setText(Html.fromHtml(account.getNote()));
+ tabLayout.getTabAt(0).setText(getString(R.string.status_cnt, account.getStatuses_count()));
+ tabLayout.getTabAt(1).setText(getString(R.string.following_cnt, account.getFollowing_count()));
+ tabLayout.getTabAt(2).setText(getString(R.string.followers_cnt, account.getFollowers_count()));
imageLoader.displayImage(account.getAvatar(), account_pp, options);
}
}
@@ -232,6 +272,12 @@ public class ShowAccountActivity extends AppCompatActivity implements OnPostActi
}
}
+ @Override
+ public void onDestroy(){
+ super.onDestroy();
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(hide_header);
+ }
+
@Override
public void onRetrieveRelationship(Relationship relationship) {
if( relationship.isBlocking()){
@@ -280,18 +326,21 @@ public class ShowAccountActivity extends AppCompatActivity implements OnPostActi
DisplayStatusFragment displayStatusFragment = new DisplayStatusFragment();
bundle.putSerializable("type", RetrieveFeedsAsyncTask.Type.USER);
bundle.putString("targetedId", accountId);
+ bundle.putBoolean("hideHeader",true);
displayStatusFragment.setArguments(bundle);
return displayStatusFragment;
case 1:
DisplayAccountsFragment displayAccountsFragment = new DisplayAccountsFragment();
bundle.putSerializable("type", RetrieveAccountsAsyncTask.Type.FOLLOWING);
bundle.putString("targetedId", accountId);
+ bundle.putBoolean("hideHeader",true);
displayAccountsFragment.setArguments(bundle);
return displayAccountsFragment;
case 2:
displayAccountsFragment = new DisplayAccountsFragment();
bundle.putSerializable("type", RetrieveAccountsAsyncTask.Type.FOLLOWERS);
bundle.putString("targetedId", accountId);
+ bundle.putBoolean("hideHeader",true);
displayAccountsFragment.setArguments(bundle);
return displayAccountsFragment;
}
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
deleted file mode 100644
index 6f5afcb85..000000000
--- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/* 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.activities;
-
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.v7.app.AlertDialog;
-import android.support.v7.app.AppCompatActivity;
-import android.webkit.CookieManager;
-import android.webkit.CookieSyncManager;
-import android.webkit.WebChromeClient;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.ProgressBar;
-
-import com.loopj.android.http.AsyncHttpResponseHandler;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.HashMap;
-
-import cz.msebera.android.httpclient.Header;
-import mastodon.etalab.gouv.fr.mastodon.R;
-import fr.gouv.etalab.mastodon.asynctasks.UpdateAccountInfoAsyncTask;
-import fr.gouv.etalab.mastodon.client.OauthClient;
-import fr.gouv.etalab.mastodon.helper.Helper;
-
-/**
- * Created by Thomas on 24/04/2017.
- * Webview to connect accounts
- */
-public class WebviewActivity extends AppCompatActivity {
-
-
- private Activity activity;
- private WebView webView;
- private Context context;
- private AlertDialog alert;
- private String clientId, clientSecret;
-
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_webview);
- this.activity = this;
- this.context = this;
- this.context = this.getApplicationContext();
- SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
- clientId = sharedpreferences.getString(Helper.CLIENT_ID, null);
- clientSecret = sharedpreferences.getString(Helper.CLIENT_SECRET, null);
-
- webView = (WebView) findViewById(R.id.webviewConnect);
- clearCookies(getApplicationContext());
- final ProgressBar pbar = (ProgressBar) findViewById(R.id.progress_bar);
-
- webView.setWebChromeClient(new WebChromeClient() {
- @Override
- public void onProgressChanged(WebView view, int progress) {
- if (progress < 100 && pbar.getVisibility() == ProgressBar.GONE) {
- pbar.setVisibility(ProgressBar.VISIBLE);
- }
- pbar.setProgress(progress);
- if (progress == 100) {
- pbar.setVisibility(ProgressBar.GONE);
- }
- }
- });
- webView.setWebViewClient(new WebViewClient() {
- @SuppressWarnings("deprecation")
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url){
- super.shouldOverrideUrlLoading(view,url);
- if( url.contains(Helper.REDIRECT_CONTENT)){
- String val[] = url.split("code=");
- String code = val[1];
-
- String action = "/oauth/token";
- HashMap parameters = new HashMap<>();
- parameters.put(Helper.CLIENT_ID, clientId);
- parameters.put(Helper.CLIENT_SECRET, clientSecret);
- parameters.put(Helper.REDIRECT_URI,"https://" + Helper.INSTANCE + Helper.REDIRECT_CONTENT);
- parameters.put("grant_type", "authorization_code");
- parameters.put("code",code);
- new OauthClient().post(action, parameters, new AsyncHttpResponseHandler() {
- @Override
- public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
- String response = new String(responseBody);
- JSONObject resobj;
- try {
- resobj = new JSONObject(response);
- String token = resobj.get("access_token").toString();
- SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedpreferences.edit();
- editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token);
- editor.apply();
- //Update the account with the token;
- new UpdateAccountInfoAsyncTask(WebviewActivity.this, true, token).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } catch (JSONException e) {
- e.printStackTrace();
- }
-
- }
-
- @Override
- public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
- error.printStackTrace();
- }
- });
-
-
- return true;
- }
- return false;
- }
- });
- webView.loadUrl(redirectUserToAuthorizeAndLogin());
- }
-
-
- @Override
- public void onBackPressed() {
- if (webView != null && webView.canGoBack()) {
- webView.goBack();
- } else {
- super.onBackPressed();
- }
- }
-
-
-
- private String redirectUserToAuthorizeAndLogin() {
-
- String queryString = Helper.CLIENT_ID + "="+ clientId;
- queryString += "&" + Helper.REDIRECT_URI + "="+ Uri.encode("https://" + Helper.INSTANCE + "/redirect_mastodon_api");
- queryString += "&" + Helper.RESPONSE_TYPE +"=code";
- queryString += "&" + Helper.SCOPE +"=" + Helper.OAUTH_SCOPES;
- /*try {
- queryString = URLEncoder.encode(queryString, "utf-8");
- } catch (UnsupportedEncodingException ignored) {}*/
- return "https://" + Helper.INSTANCE + Helper.EP_AUTHORIZE + "?" + queryString;
- }
-
-
- private String getOauthRedirectUri() {
- return Helper.OAUTH_SCHEME + "://" + Helper.OAUTH_REDIRECT_HOST + "/";
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (alert != null) {
- alert.dismiss();
- alert = null;
- }
- }
-
- @SuppressWarnings("deprecation")
- public static void clearCookies(Context context)
- {
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
- CookieManager.getInstance().removeAllCookies(null);
- CookieManager.getInstance().flush();
- } else
- {
- CookieSyncManager cookieSyncMngr=CookieSyncManager.createInstance(context);
- cookieSyncMngr.startSync();
- CookieManager cookieManager=CookieManager.getInstance();
- cookieManager.removeAllCookie();
- cookieManager.removeSessionCookie();
- cookieSyncMngr.stopSync();
- cookieSyncMngr.sync();
- }
- }
-}
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 0e6565bc9..10fae3b05 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
@@ -16,10 +16,12 @@ package fr.gouv.etalab.mastodon.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
+import android.util.Log;
import java.util.List;
import fr.gouv.etalab.mastodon.client.API;
+import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveFeedsInterface;
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
new file mode 100644
index 000000000..1abc000f3
--- /dev/null
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveHomeTimelineServiceAsyncTask.java
@@ -0,0 +1,57 @@
+/* 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.content.Context;
+import android.os.AsyncTask;
+
+import java.util.List;
+
+import fr.gouv.etalab.mastodon.client.API;
+import fr.gouv.etalab.mastodon.interfaces.OnRetrieveHomeTimelineServiceInterface;
+
+/**
+ * Created by Thomas on 20/05/2017.
+ * Retrieves home timeline for the authenticated user - used in the service
+ */
+
+public class RetrieveHomeTimelineServiceAsyncTask extends AsyncTask {
+
+ private Context context;
+ private List statuses;
+ private String since_id;
+ private String acct;
+ private OnRetrieveHomeTimelineServiceInterface listener;
+
+
+ public RetrieveHomeTimelineServiceAsyncTask(Context context, String since_id, String acct, OnRetrieveHomeTimelineServiceInterface onRetrieveHomeTimelineServiceInterface){
+ this.context = context;
+ this.since_id = since_id;
+ this.listener = onRetrieveHomeTimelineServiceInterface;
+ this.acct = acct;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ statuses = new API(context).getHomeTimelineSinceId(since_id);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ listener.onRetrieveHomeTimelineService(statuses, acct);
+ }
+
+}
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 a39442654..84ea7437f 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,13 +20,11 @@ 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;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.helper.Helper;
-import fr.gouv.etalab.mastodon.interfaces.OnUpdateAccountInfoInterface;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
@@ -39,22 +37,11 @@ public class UpdateAccountInfoAsyncTask extends AsyncTask {
private Context context;
private String token;
- private boolean fromWebview;
- private boolean error;
- private OnUpdateAccountInfoInterface listener;
- public UpdateAccountInfoAsyncTask(Context context, String token, OnUpdateAccountInfoInterface onUpdateAccountInfoInterface){
+
+ public UpdateAccountInfoAsyncTask(Context context, String token){
this.context = context;
this.token = token;
- this.fromWebview = false;
- this.error = false;
- this.listener = onUpdateAccountInfoInterface;
- }
-
- public UpdateAccountInfoAsyncTask(Context context, boolean fromWebview, String token){
- this.context = context;
- this.token = token;
- this.fromWebview = fromWebview;
}
@@ -78,8 +65,6 @@ public class UpdateAccountInfoAsyncTask extends AsyncTask {
else {
if( account.getUsername() != null && account.getCreated_at() != null)
new AccountDAO(context, db).insertAccount(account);
- else //Here the user credential in db doesn't match the remote one (it will be disconnected)
- error = true;
}
return null;
}
@@ -87,14 +72,10 @@ public class UpdateAccountInfoAsyncTask extends AsyncTask {
@Override
protected void onPostExecute(Void result) {
- if( fromWebview){
- Intent mainActivity = new Intent(context, MainActivity.class);
- mainActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(mainActivity);
- ((Activity) context).finish();
- }else{
- listener.onUpdateAccountInfo(error);
- }
+ Intent mainActivity = new Intent(context, MainActivity.class);
+ mainActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(mainActivity);
+ ((Activity) context).finish();
}
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 c49e7e1a9..b1f403286 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,7 +16,6 @@ package fr.gouv.etalab.mastodon.client;
import android.content.Context;
import android.content.SharedPreferences;
-import android.util.Log;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.JsonHttpResponseHandler;
@@ -29,6 +28,10 @@ import org.json.JSONObject;
import java.io.InputStream;
import java.lang.*;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
import java.util.ArrayList;
import java.util.List;
@@ -42,6 +45,8 @@ import fr.gouv.etalab.mastodon.client.Entities.Notification;
import fr.gouv.etalab.mastodon.client.Entities.Relationship;
import fr.gouv.etalab.mastodon.client.Entities.Status;
+import static fr.gouv.etalab.mastodon.helper.Helper.USER_AGENT;
+
/**
* Created by Thomas on 23/04/2017.
@@ -66,7 +71,6 @@ public class API {
private List notifications;
private int tootPerPage, accountPerPage, notificationPerPage;
private int actionCode;
- private String userId;
public enum StatusAction{
FAVOURITE,
@@ -90,7 +94,6 @@ 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);
- userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
}
/***
@@ -296,13 +299,7 @@ public class API {
return statusContext;
}
- /**
- * Retrieves home timeline for the account *synchronously*
- * @return List
- */
- public List getHomeTimeline() {
- return getHomeTimeline(null, null, tootPerPage);
- }
+
/**
* Retrieves home timeline for the account *synchronously*
@@ -313,6 +310,14 @@ public class API {
return getHomeTimeline(max_id, null, tootPerPage);
}
+ /**
+ * Retrieves home timeline for the account since an Id value *synchronously*
+ * @return List
+ */
+ public List getHomeTimelineSinceId(String since_id) {
+ return getHomeTimeline(null, since_id, tootPerPage);
+ }
+
/**
* Retrieves home timeline for the account *synchronously*
* @param max_id String id max
@@ -331,8 +336,7 @@ public class API {
limit = 40;
params.put("limit",String.valueOf(limit));
statuses = new ArrayList<>();
-
- get(String.format("/accounts/%s/statuses",userId), params, new JsonHttpResponseHandler() {
+ get("/timelines/home", params, new JsonHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
@@ -351,14 +355,6 @@ public class API {
return statuses;
}
- /**
- * Retrieves public timeline for the account *synchronously*
- * @param local boolean only local timeline
- * @return List
- */
- public List getPublicTimeline(boolean local){
- return getPublicTimeline(local, null, null, tootPerPage);
- }
/**
* Retrieves public timeline for the account *synchronously*
@@ -669,10 +665,8 @@ public class API {
if( status.getIn_reply_to_id() != null)
params.put("in_reply_to_id", status.getIn_reply_to_id());
if( status.getMedia_attachments() != null && status.getMedia_attachments().size() > 0 ) {
- int i = 0;
for(Attachment attachment: status.getMedia_attachments()) {
params.add("media_ids[]", attachment.getId());
- i++;
}
}
if( status.isSensitive())
@@ -703,7 +697,7 @@ public class API {
}
});
}else{
- delete(action, params, new JsonHttpResponseHandler() {
+ delete(action, null, new JsonHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
@@ -1162,25 +1156,43 @@ public class API {
private void get(String action, RequestParams params, AsyncHttpResponseHandler responseHandler) {
- client.setTimeout(5000);
+ client.setTimeout(10000);
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
client.addHeader("Authorization", "Bearer "+prefKeyOauthTokenT);
- client.get(getAbsoluteUrl(action), params, responseHandler);
+ try {
+ client.setUserAgent(USER_AGENT);
+ client.setSSLSocketFactory(new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore()));
+ client.get(getAbsoluteUrl(action), params, responseHandler);
+ } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) {
+ e.printStackTrace();
+ }
}
private void post(String action, RequestParams params, AsyncHttpResponseHandler responseHandler) {
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
client.addHeader("Authorization", "Bearer "+prefKeyOauthTokenT);
- client.post(getAbsoluteUrl(action), params, responseHandler);
+ try {
+ client.setUserAgent(USER_AGENT);
+ client.setSSLSocketFactory(new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore()));
+ client.post(getAbsoluteUrl(action), params, responseHandler);
+ } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) {
+ e.printStackTrace();
+ }
}
private void delete(String action, RequestParams params, AsyncHttpResponseHandler responseHandler){
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
client.addHeader("Authorization", "Bearer "+prefKeyOauthTokenT);
- client.delete(getAbsoluteUrl(action), params, responseHandler);
+ try {
+ client.setUserAgent(USER_AGENT);
+ client.setSSLSocketFactory(new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore()));
+ client.delete(getAbsoluteUrl(action), params, responseHandler);
+ } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) {
+ e.printStackTrace();
+ }
}
private String getAbsoluteUrl(String action) {
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/MastalabSSLSocketFactory.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/MastalabSSLSocketFactory.java
new file mode 100644
index 000000000..1789a491f
--- /dev/null
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/MastalabSSLSocketFactory.java
@@ -0,0 +1,74 @@
+package fr.gouv.etalab.mastodon.client;
+
+/**
+ * Created by Thomas on 20/05/2017.
+ * Custom MySSLSocketFactory
+ */
+
+
+import java.io.IOException;
+import java.net.Socket;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+
+/**
+ * This file is introduced to fix HTTPS Post bug on API < ICS see
+ * https://code.google.com/p/android/issues/detail?id=13117#c14
Warning! This omits SSL
+ * certificate validation on every device, use with caution
+ */
+public class MastalabSSLSocketFactory extends com.loopj.android.http.MySSLSocketFactory {
+
+ private final SSLContext sslContext = SSLContext.getInstance("TLS");
+ /**
+ * Creates a new SSL Socket Factory with the given KeyStore.
+ *
+ * @param truststore A KeyStore to create the SSL Socket Factory in context of
+ * @throws NoSuchAlgorithmException NoSuchAlgorithmException
+ * @throws KeyManagementException KeyManagementException
+ * @throws KeyStoreException KeyStoreException
+ * @throws UnrecoverableKeyException UnrecoverableKeyException
+ */
+ public MastalabSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(truststore);
+ X509TrustManager tm = new X509TrustManager() {
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ };
+ sslContext.init(null, new TrustManager[]{tm}, null);
+ }
+
+ @Override
+ public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
+ return enableTLSOnSocket(sslContext.getSocketFactory().createSocket(socket, host, port, autoClose));
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return enableTLSOnSocket(sslContext.getSocketFactory().createSocket());
+ }
+
+ private Socket enableTLSOnSocket(Socket socket) {
+ if(socket != null && (socket instanceof SSLSocket)) {
+ ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
+ }
+ return socket;
+ }
+}
\ No newline at end of file
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 fceddcb44..262c1a0a2 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
@@ -14,16 +14,24 @@
* see . */
package fr.gouv.etalab.mastodon.client;
+import android.os.Build;
+
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import fr.gouv.etalab.mastodon.helper.Helper;
+import static fr.gouv.etalab.mastodon.helper.Helper.USER_AGENT;
+
/**
* Created by Thomas on 23/04/2017.
* Client to call urls
@@ -37,13 +45,27 @@ public class OauthClient {
public void get(String action, HashMap paramaters, AsyncHttpResponseHandler responseHandler) {
client.setTimeout(5000);
+ client.setUserAgent(USER_AGENT);
RequestParams params = hashToRequestParams(paramaters);
+ try {
+ client.setSSLSocketFactory(new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore()));
+ client.post(getAbsoluteUrl(action), params, responseHandler);
+ } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) {
+ e.printStackTrace();
+ }
client.get(getAbsoluteUrl(action), params, responseHandler);
}
public void post(String action, HashMap paramaters, AsyncHttpResponseHandler responseHandler) {
RequestParams params = hashToRequestParams(paramaters);
- client.post(getAbsoluteUrl(action), params, responseHandler);
+ try {
+ client.setConnectTimeout(30000); //30s timeout
+ client.setUserAgent(USER_AGENT);
+ client.setSSLSocketFactory(new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore()));
+ client.post(getAbsoluteUrl(action), params, responseHandler);
+ } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) {
+ e.printStackTrace();
+ }
}
private String getAbsoluteUrl(String action) {
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/PatchBaseImageDownloader.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/PatchBaseImageDownloader.java
new file mode 100644
index 000000000..a80527c1d
--- /dev/null
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/PatchBaseImageDownloader.java
@@ -0,0 +1,127 @@
+package fr.gouv.etalab.mastodon.client;
+
+import android.content.Context;
+
+import com.nostra13.universalimageloader.core.assist.ContentLengthInputStream;
+import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * Created by Thomas on 21/05/2017.
+ * Patch for universal image loader to support TLS 1.1+
+ */
+
+public class PatchBaseImageDownloader extends BaseImageDownloader {
+
+ private SSLSocketFactory sf;
+
+ public PatchBaseImageDownloader(Context context) {
+ super(context);
+
+ initSSLSocketFactory();
+ }
+
+ private void initSSLSocketFactory() {
+ sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
+ try {
+ sf = new MySSLSocketFactory();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
+ HttpURLConnection conn = createConnection(imageUri, extra);
+ if (conn instanceof HttpsURLConnection) {
+ ((HttpsURLConnection) conn).setSSLSocketFactory(sf);
+ }
+ return new ContentLengthInputStream(new BufferedInputStream(conn.getInputStream(), BUFFER_SIZE), conn.getContentLength());
+ }
+
+ private static class MySSLSocketFactory extends SSLSocketFactory {
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+
+ MySSLSocketFactory() throws Exception {
+ super();
+ TrustManager tm = new X509TrustManager() {
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ };
+
+ sslContext.init(null, new TrustManager[]{tm}, null);
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return new String[0];
+ }
+
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return enableTLSOnSocket(sslContext.getSocketFactory().createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
+ return enableTLSOnSocket(sslContext.getSocketFactory().createSocket(host, port, localHost, localPort));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return enableTLSOnSocket(sslContext.getSocketFactory().createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+ return enableTLSOnSocket(sslContext.getSocketFactory().createSocket(address, port, localAddress, localPort));
+ }
+
+ @Override
+ public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
+ return enableTLSOnSocket(sslContext.getSocketFactory().createSocket(socket, host, port, autoClose));
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return enableTLSOnSocket(sslContext.getSocketFactory().createSocket());
+ }
+
+ private Socket enableTLSOnSocket(Socket socket) {
+ if(socket != null && (socket instanceof SSLSocket)) {
+ ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
+ }
+ return socket;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/NotificationsListAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/NotificationsListAdapter.java
index 476492543..b45a3346b 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/NotificationsListAdapter.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/NotificationsListAdapter.java
@@ -171,19 +171,16 @@ public class NotificationsListAdapter extends BaseAdapter {
//Adds attachment -> disabled, to enable them uncomment the line below
//loadAttachments(status, holder);
holder.notification_status_container.setVisibility(View.VISIBLE);
-
- if( !status.getIn_reply_to_account_id().equals("null") || !status.getIn_reply_to_id().equals("null") ) {
- holder.notification_status_content.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(context, ShowConversationActivity.class);
- Bundle b = new Bundle();
- b.putString("statusId", status.getId()); //Your id
- intent.putExtras(b); //Put your id to your next Intent
- context.startActivity(intent);
- }
- });
- }
+ holder.notification_status_content.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(context, ShowConversationActivity.class);
+ Bundle b = new Bundle();
+ b.putString("statusId", status.getId());
+ intent.putExtras(b);
+ context.startActivity(intent);
+ }
+ });
switch (status.getVisibility()){
case "public":
holder.status_privacy.setImageResource(R.drawable.ic_action_globe);
@@ -216,8 +213,8 @@ public class NotificationsListAdapter extends BaseAdapter {
public void onClick(View v) {
Intent intent = new Intent(context, TootActivity.class);
Bundle b = new Bundle();
- b.putString("inReplyTo", notification.getStatus().getId()); //Your id
- intent.putExtras(b); //Put your id to your next Intent
+ b.putString("inReplyTo", notification.getStatus().getId());
+ intent.putExtras(b);
context.startActivity(intent);
}
});
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java
index 91341fab8..a9003785d 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java
@@ -332,7 +332,10 @@ public class StatusListAdapter extends BaseAdapter implements OnPostActionInterf
public void onClick(View v) {
Intent intent = new Intent(context, ShowAccountActivity.class);
Bundle b = new Bundle();
- b.putString("accountId", status.getAccount().getId());
+ if( status.getReblog() == null)
+ b.putString("accountId", status.getAccount().getId());
+ else
+ b.putString("accountId", status.getReblog().getAccount().getId());
intent.putExtras(b);
context.startActivity(intent);
}
@@ -406,7 +409,7 @@ public class StatusListAdapter extends BaseAdapter implements OnPostActionInterf
String url = attachment.getPreview_url();
if( url == null || url.trim().equals(""))
url = attachment.getUrl();
- if( url.trim().equals("https://mastodon.etalab.gouv.fr/files/small/missing.png"))
+ if( url.trim().contains("missing.png"))
continue;
imageLoader.displayImage(url, imageView, options);
imageView.setOnClickListener(new View.OnClickListener() {
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayAccountsFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayAccountsFragment.java
index 2a0d3a0e4..77297226a 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayAccountsFragment.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayAccountsFragment.java
@@ -14,10 +14,12 @@ package fr.gouv.etalab.mastodon.fragments;
* You should have received a copy of the GNU General Public License along with Thomas Schneider; if not,
* see . */
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
@@ -58,6 +60,7 @@ public class DisplayAccountsFragment extends Fragment implements OnRetrieveAccou
private SwipeRefreshLayout swipeRefreshLayout;
private int accountPerPage;
private String targetedId;
+ private boolean hideHeader = false;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -69,6 +72,7 @@ public class DisplayAccountsFragment extends Fragment implements OnRetrieveAccou
if (bundle != null) {
type = (RetrieveAccountsAsyncTask.Type) bundle.get("type");
targetedId = bundle.getString("targetedId", null);
+ hideHeader = bundle.getBoolean("hideHeader", false);
}
max_id = null;
firstLoad = true;
@@ -78,7 +82,7 @@ public class DisplayAccountsFragment extends Fragment implements OnRetrieveAccou
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipeContainer);
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
accountPerPage = sharedpreferences.getInt(Helper.SET_ACCOUNTS_PER_PAGE, 40);
- ListView lv_accounts = (ListView) rootView.findViewById(R.id.lv_accounts);
+ final ListView lv_accounts = (ListView) rootView.findViewById(R.id.lv_accounts);
mainLoader = (RelativeLayout) rootView.findViewById(R.id.loader);
nextElementLoader = (RelativeLayout) rootView.findViewById(R.id.loading_next_accounts);
@@ -109,6 +113,35 @@ public class DisplayAccountsFragment extends Fragment implements OnRetrieveAccou
}
});
+ //Hide account header when scrolling for ShowAccountActivity
+ if( hideHeader ) {
+ lv_accounts.setOnScrollListener(new AbsListView.OnScrollListener() {
+ int lastFirstVisibleItem = 0;
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ if (view.getId() == lv_accounts.getId() && totalItemCount > visibleItemCount) {
+ final int currentFirstVisibleItem = lv_accounts.getFirstVisiblePosition();
+
+ if (currentFirstVisibleItem > lastFirstVisibleItem) {
+ Intent intent = new Intent(Helper.HEADER_ACCOUNT);
+ intent.putExtra("hide", true);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ } else if (currentFirstVisibleItem < lastFirstVisibleItem) {
+ Intent intent = new Intent(Helper.HEADER_ACCOUNT);
+ intent.putExtra("hide", false);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ }
+ lastFirstVisibleItem = currentFirstVisibleItem;
+ }
+ }
+ });
+ }
+
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
@@ -157,7 +190,6 @@ public class DisplayAccountsFragment extends Fragment implements OnRetrieveAccou
}
-
@Override
public void onRetrieveAccounts(List accounts) {
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 5f277dfe9..ded4620ad 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
@@ -14,10 +14,12 @@ package fr.gouv.etalab.mastodon.fragments;
* You should have received a copy of the GNU General Public License along with Thomas Schneider; if not,
* see . */
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
@@ -25,7 +27,7 @@ import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.RelativeLayout;
-import android.widget.TextView;
+
import java.util.ArrayList;
import java.util.List;
@@ -46,7 +48,6 @@ import fr.gouv.etalab.mastodon.interfaces.OnRetrieveFeedsInterface;
public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsInterface {
- private TextView noAction;
private boolean flag_loading;
private Context context;
private AsyncTask asyncTask;
@@ -57,9 +58,9 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
private RelativeLayout mainLoader, nextElementLoader, textviewNoAction;
private boolean firstLoad;
private SwipeRefreshLayout swipeRefreshLayout;
- private int tootPerPage;
private String targetedId;
private String tag;
+ private boolean hideHeader = false;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -72,6 +73,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
type = (RetrieveFeedsAsyncTask.Type) bundle.get("type");
targetedId = bundle.getString("targetedId", null);
tag = bundle.getString("tag", null);
+ hideHeader = bundle.getBoolean("hideHeader", false);
}
max_id = null;
flag_loading = true;
@@ -80,10 +82,9 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
boolean isOnWifi = Helper.isOnWIFI(context);
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipeContainer);
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
- tootPerPage = sharedpreferences.getInt(Helper.SET_TOOTS_PER_PAGE, 40);
int behaviorWithAttachments = sharedpreferences.getInt(Helper.SET_ATTACHMENT_ACTION, Helper.ATTACHMENT_ALWAYS);
- ListView lv_status = (ListView) rootView.findViewById(R.id.lv_status);
+ final ListView lv_status = (ListView) rootView.findViewById(R.id.lv_status);
mainLoader = (RelativeLayout) rootView.findViewById(R.id.loader);
nextElementLoader = (RelativeLayout) rootView.findViewById(R.id.loading_next_status);
@@ -117,6 +118,35 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
}
});
+ //Hide account header when scrolling for ShowAccountActivity
+ if( hideHeader ) {
+ lv_status.setOnScrollListener(new AbsListView.OnScrollListener() {
+ int lastFirstVisibleItem = 0;
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ if (view.getId() == lv_status.getId() && totalItemCount > visibleItemCount) {
+ final int currentFirstVisibleItem = lv_status.getFirstVisiblePosition();
+
+ if (currentFirstVisibleItem > lastFirstVisibleItem) {
+ Intent intent = new Intent(Helper.HEADER_ACCOUNT);
+ intent.putExtra("hide", true);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ } else if (currentFirstVisibleItem < lastFirstVisibleItem) {
+ Intent intent = new Intent(Helper.HEADER_ACCOUNT);
+ intent.putExtra("hide", false);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ }
+ lastFirstVisibleItem = currentFirstVisibleItem;
+ }
+ }
+ });
+ }
+
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
@@ -193,9 +223,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
}
swipeRefreshLayout.setRefreshing(false);
firstLoad = false;
- if( statuses != null && statuses.size() < tootPerPage )
- flag_loading = true;
- else
- flag_loading = false;
+ flag_loading = false;
+
}
}
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsNotificationsFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsNotificationsFragment.java
index 9d44598cf..c46601da2 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsNotificationsFragment.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsNotificationsFragment.java
@@ -51,6 +51,8 @@ public class SettingsNotificationsFragment extends Fragment {
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
boolean notif_validation = sharedpreferences.getBoolean(Helper.SET_NOTIF_VALIDATION, true);
boolean notif_wifi = sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false);
+ boolean notif_silent = sharedpreferences.getBoolean(Helper.SET_NOTIF_SILENT, false);
+ boolean notif_hometimeline = sharedpreferences.getBoolean(Helper.SET_NOTIF_HOMETIMELINE, true);
final CheckBox set_notif_follow = (CheckBox) rootView.findViewById(R.id.set_notif_follow);
final CheckBox set_notif_follow_add = (CheckBox) rootView.findViewById(R.id.set_notif_follow_add);
@@ -58,7 +60,9 @@ public class SettingsNotificationsFragment extends Fragment {
final CheckBox set_notif_follow_mention = (CheckBox) rootView.findViewById(R.id.set_notif_follow_mention);
final CheckBox set_notif_follow_share = (CheckBox) rootView.findViewById(R.id.set_notif_follow_share);
final CheckBox set_share_validation = (CheckBox) rootView.findViewById(R.id.set_share_validation);
+ final CheckBox set_notif_hometimeline = (CheckBox) rootView.findViewById(R.id.set_notif_hometimeline);
final SwitchCompat switchCompatWIFI = (SwitchCompat) rootView.findViewById(R.id.set_wifi_only);
+ final SwitchCompat switchCompatSilent = (SwitchCompat) rootView.findViewById(R.id.set_silence);
set_notif_follow.setChecked(notif_follow);
set_notif_follow_add.setChecked(notif_add);
@@ -66,9 +70,18 @@ public class SettingsNotificationsFragment extends Fragment {
set_notif_follow_mention.setChecked(notif_mention);
set_notif_follow_share.setChecked(notif_share);
set_share_validation.setChecked(notif_validation);
+ set_notif_hometimeline.setChecked(notif_hometimeline);
switchCompatWIFI.setChecked(notif_wifi);
+ switchCompatSilent.setChecked(notif_silent);
-
+ set_notif_hometimeline.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ SharedPreferences.Editor editor = sharedpreferences.edit();
+ editor.putBoolean(Helper.SET_NOTIF_HOMETIMELINE, set_notif_hometimeline.isChecked());
+ editor.apply();
+ }
+ });
set_notif_follow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -128,7 +141,15 @@ public class SettingsNotificationsFragment extends Fragment {
}
});
-
+ switchCompatSilent.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ // Save the state here
+ SharedPreferences.Editor editor = sharedpreferences.edit();
+ editor.putBoolean(Helper.SET_NOTIF_SILENT, isChecked);
+ editor.apply();
+ }
+ });
return rootView;
}
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 32bdc16e5..3c102960f 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
@@ -19,17 +19,25 @@ package fr.gouv.etalab.mastodon.helper;
import android.app.AlertDialog;
import android.app.DownloadManager;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.media.RingtoneManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
import android.view.WindowManager;
import android.widget.Toast;
+import com.loopj.android.http.BuildConfig;
+
import java.io.File;
import java.net.InetAddress;
import java.text.ParseException;
@@ -38,9 +46,12 @@ import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
+import fr.gouv.etalab.mastodon.activities.MainActivity;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.client.API;
+import static android.app.Notification.DEFAULT_SOUND;
+import static android.app.Notification.DEFAULT_VIBRATE;
import static android.content.Context.DOWNLOAD_SERVICE;
@@ -77,10 +88,10 @@ public class Helper {
public static final String SCOPES = "scopes";
public static final String WEBSITE = "website";
public static final String LAST_NOTIFICATION_MAX_ID = "last_notification_max_id";
-
+ public static final String LAST_HOMETIMELINE_MAX_ID = "last_hometimeline_max_id";
//Notifications
- public static final String NOTIFICATION_TYPE = "notification_type";
public static final int NOTIFICATION_INTENT = 1;
+ public static final int HOME_TIMELINE_INTENT = 2;
//Settings
public static final String SET_TOOTS_PER_PAGE = "set_toots_per_page";
@@ -99,6 +110,7 @@ public class Helper {
public static final String SET_NOTIF_SHARE = "set_notif_follow_share";
public static final String SET_NOTIF_VALIDATION = "set_share_validation";
public static final String SET_WIFI_ONLY = "set_wifi_only";
+ public static final String SET_NOTIF_HOMETIMELINE = "set_notif_hometimeline";
public static final String SET_NOTIF_SILENT = "set_notif_silent";
//End points
@@ -107,16 +119,17 @@ public class Helper {
//Refresh job
public static final int MINUTES_BETWEEN_NOTIFICATIONS_REFRESH = 15;
+ public static final int MINUTES_BETWEEN_HOME_TIMELINE = 30;
//Intent
public static final String INTENT_ACTION = "intent_action";
- public static final int INTENT_NOTIFICATION = 1;
//Receiver
public static final String SEARCH_VALIDATE_ACCOUNT = "search_validate_account";
+ public static final String HEADER_ACCOUNT = "header_account";
-
-
+ //User agent
+ public static final String USER_AGENT = "Mastalab/"+ BuildConfig.VERSION_NAME + " Android/"+ Build.VERSION.RELEASE;
/***
* Check if the user is connected to Internet
@@ -364,4 +377,40 @@ public class Helper {
alert.show();
}
+
+ /**
+ * Sends notification with intent
+ * @param context Context
+ * @param intentAction int intent action
+ * @param notificationId int id of the notification
+ * @param icon Bitmap profile picture
+ * @param title String title of the notification
+ * @param message String message for the notification
+ */
+ public static void notify_user(Context context, int intentAction, int notificationId, Bitmap icon, String title, String message ) {
+ final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
+ // prepare intent which is triggered if the user click on the notification
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
+ final Intent intent = new Intent(context, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK );
+ intent.putExtra(INTENT_ACTION, intentAction);
+ PendingIntent pIntent = PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_ONE_SHOT);
+ RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ // build notification
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
+ .setSmallIcon(R.drawable.notification_icon)
+ .setTicker(message)
+ .setWhen(System.currentTimeMillis())
+ .setAutoCancel(true)
+ .setContentIntent(pIntent)
+ .setContentText(message);
+ if( sharedpreferences.getBoolean(Helper.SET_NOTIF_SILENT,false) ) {
+ notificationBuilder.setDefaults(DEFAULT_VIBRATE);
+ }else {
+ notificationBuilder.setDefaults(DEFAULT_SOUND);
+ }
+ notificationBuilder.setContentTitle(title);
+ notificationBuilder.setLargeIcon(icon);
+ notificationManager.notify(notificationId, notificationBuilder.build());
+ }
}
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveHomeTimelineServiceInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveHomeTimelineServiceInterface.java
new file mode 100644
index 000000000..4ac7f3fe2
--- /dev/null
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveHomeTimelineServiceInterface.java
@@ -0,0 +1,27 @@
+/* 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.interfaces;
+
+
+import java.util.List;
+import fr.gouv.etalab.mastodon.client.Entities.Status;
+
+/**
+ * Created by Thomas on 20/05/2017.
+ * Interface when home timeline toots have been retrieved
+ */
+public interface OnRetrieveHomeTimelineServiceInterface {
+ void onRetrieveHomeTimelineService(List statuses, String acct);
+}
diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java
index 84c301cc3..31be81763 100644
--- a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java
@@ -28,6 +28,8 @@ public class ApplicationJob implements JobCreator {
switch (tag) {
case NotificationsSyncJob.NOTIFICATION_REFRESH:
return new NotificationsSyncJob();
+ case HomeTimelineSyncJob.HOME_TIMELINE:
+ return new HomeTimelineSyncJob();
default:
return null;
}
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
new file mode 100644
index 000000000..41ed06f85
--- /dev/null
+++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/HomeTimelineSyncJob.java
@@ -0,0 +1,164 @@
+package fr.gouv.etalab.mastodon.jobs;
+/* 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 . */
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+import android.support.annotation.NonNull;
+
+import com.evernote.android.job.Job;
+import com.evernote.android.job.JobManager;
+import com.evernote.android.job.JobRequest;
+import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import fr.gouv.etalab.mastodon.asynctasks.RetrieveHomeTimelineServiceAsyncTask;
+import fr.gouv.etalab.mastodon.client.Entities.Account;
+import fr.gouv.etalab.mastodon.client.Entities.Status;
+import fr.gouv.etalab.mastodon.client.PatchBaseImageDownloader;
+import fr.gouv.etalab.mastodon.helper.Helper;
+import fr.gouv.etalab.mastodon.interfaces.OnRetrieveHomeTimelineServiceInterface;
+import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
+import fr.gouv.etalab.mastodon.sqlite.Sqlite;
+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.notify_user;
+
+
+/**
+ * Created by Thomas on 20/05/2017.
+ * Notifications for home timeline job
+ */
+
+public class HomeTimelineSyncJob extends Job implements OnRetrieveHomeTimelineServiceInterface{
+
+ static final String HOME_TIMELINE = "home_timeline";
+ private int notificationId;
+
+ @NonNull
+ @Override
+ protected Result onRunJob(Params params) {
+ callAsynchronousTask();
+ return Result.SUCCESS;
+ }
+
+
+ public static int schedule(boolean updateCurrent){
+
+ Set jobRequests = JobManager.instance().getAllJobRequestsForTag(HOME_TIMELINE);
+ if (!jobRequests.isEmpty() && !updateCurrent) {
+ return jobRequests.iterator().next().getJobId();
+ }
+ return new JobRequest.Builder(HomeTimelineSyncJob.HOME_TIMELINE)
+ .setPeriodic(TimeUnit.MINUTES.toMillis(Helper.MINUTES_BETWEEN_HOME_TIMELINE), TimeUnit.MINUTES.toMillis(5))
+ .setPersisted(true)
+ .setUpdateCurrent(updateCurrent)
+ .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
+ .setRequirementsEnforced(false)
+ .build()
+ .schedule();
+ }
+
+
+ /**
+ * Task in background starts here.
+ */
+ private void callAsynchronousTask() {
+ final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
+ boolean notif_hometimeline = sharedpreferences.getBoolean(Helper.SET_NOTIF_HOMETIMELINE, true);
+ //User disagree with home timeline refresh
+ if( !notif_hometimeline)
+ return; //Nothing is done
+ SQLiteDatabase db = Sqlite.getInstance(getContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
+ //If an Internet connection and user agrees with notification refresh
+ //If WIFI only and on WIFI OR user defined any connections to use the service.
+ if(!sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false) || Helper.isOnWIFI(getContext())) {
+ List accounts = new AccountDAO(getContext(),db).getAllAccount();
+ //It means there is no user in DB.
+ if( accounts == null )
+ return;
+ //Retrieve users in db that owner has.
+ 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);
+
+ }
+ }
+ }
+
+
+ @Override
+ public void onRetrieveHomeTimelineService(List statuses, String acct) {
+ if( statuses == null || statuses.size() == 0)
+ return;
+ Bitmap icon_notification = null;
+ final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
+
+ String max_id = sharedpreferences.getString(Helper.LAST_HOMETIMELINE_MAX_ID + acct, null);
+ //No previous notifications in cache, so no notification will be sent
+ if( max_id != null ){
+ String message;
+ String title = null;
+ for(Status status: statuses){
+ //The notification associated to max_id is discarded as it is supposed to have already been sent
+ if( status.getId().equals(max_id))
+ continue;
+ String notificationUrl = status.getAccount().getAvatar();
+ if( notificationUrl != null && icon_notification == null){
+ try {
+ ImageLoader imageLoaderNoty = ImageLoader.getInstance();
+ File cacheDir = new File(getContext().getCacheDir(), getContext().getString(R.string.app_name));
+ ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getContext())
+ .imageDownloader(new PatchBaseImageDownloader(getContext()))
+ .threadPoolSize(5)
+ .threadPriority(Thread.MIN_PRIORITY + 3)
+ .denyCacheImageMultipleSizesInMemory()
+ .diskCache(new UnlimitedDiskCache(cacheDir))
+ .build();
+ imageLoaderNoty.init(config);
+ icon_notification = imageLoaderNoty.loadImageSync(notificationUrl);
+ title = getContext().getResources().getString(R.string.notif_pouet, status.getAccount().getDisplay_name());
+ }catch (Exception e){
+ icon_notification = BitmapFactory.decodeResource(getContext().getResources(),
+ R.drawable.mastodonlogo);
+ }
+ }
+ }
+ if(statuses.size() > 0 )
+ message = getContext().getResources().getQuantityString(R.plurals.other_notif_hometimeline, statuses.size(), statuses.size());
+ else
+ message = "";
+ notify_user(getContext(), HOME_TIMELINE_INTENT, notificationId, icon_notification,title,message);
+ }
+ SharedPreferences.Editor editor = sharedpreferences.edit();
+ editor.putString(Helper.LAST_HOMETIMELINE_MAX_ID + acct, statuses.get(0).getId());
+ editor.apply();
+ }
+
+
+
+}
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 35e2df73d..84510a7e1 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
@@ -13,18 +13,15 @@ package fr.gouv.etalab.mastodon.jobs;
*
* You should have received a copy of the GNU General Public License along with Thomas Schneider; if not,
* see . */
-import android.app.PendingIntent;
+
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.media.RingtoneManager;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
+
import com.evernote.android.job.Job;
import com.evernote.android.job.JobManager;
@@ -39,7 +36,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-import fr.gouv.etalab.mastodon.activities.MainActivity;
+import fr.gouv.etalab.mastodon.client.PatchBaseImageDownloader;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveNotificationsAsyncTask;
@@ -49,8 +46,8 @@ import fr.gouv.etalab.mastodon.interfaces.OnRetrieveNotificationsInterface;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
-import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_ACTION;
-import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_NOTIFICATION;
+import static fr.gouv.etalab.mastodon.helper.Helper.NOTIFICATION_INTENT;
+import static fr.gouv.etalab.mastodon.helper.Helper.notify_user;
/**
@@ -60,8 +57,7 @@ import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_NOTIFICATION;
public class NotificationsSyncJob extends Job implements OnRetrieveNotificationsInterface{
- public static final String NOTIFICATION_REFRESH = "job_notification";
- private int jobId;
+ static final String NOTIFICATION_REFRESH = "job_notification";
private int notificationId;
@NonNull
@@ -73,7 +69,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
}
- public static int schedule(Context context, boolean updateCurrent){
+ public static int schedule(boolean updateCurrent){
Set jobRequests = JobManager.instance().getAllJobRequestsForTag(NOTIFICATION_REFRESH);
if (!jobRequests.isEmpty() && !updateCurrent) {
@@ -99,6 +95,15 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
SQLiteDatabase db = Sqlite.getInstance(getContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
//If an Internet connection and user agrees with notification refresh
final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
+ //Check which notifications the user wants to see
+ boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
+ boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
+ boolean notif_ask = sharedpreferences.getBoolean(Helper.SET_NOTIF_ASK, true);
+ boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
+ boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
+ //User disagree with all notifications
+ if( !notif_follow && !notif_add && !notif_ask && !notif_mention && !notif_share)
+ return; //Nothing is done
//If WIFI only and on WIFI OR user defined any connections to use the service.
if(!sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false) || Helper.isOnWIFI(getContext())) {
List accounts = new AccountDAO(getContext(),db).getAllAccount();
@@ -124,7 +129,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
- boolean notif_ask = sharedpreferences.getBoolean(Helper.SET_NOTIF_ASK, true);
+ //boolean notif_ask = sharedpreferences.getBoolean(Helper.SET_NOTIF_ASK, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + acct, null);
@@ -137,7 +142,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
int newShare = 0;
String notificationUrl = null;
String title = null;
- String message = null;
+ String message;
for(Notification notification: notifications){
//The notification associated to max_id is discarded as it is supposed to have already been sent
if( notification.getId().equals(max_id))
@@ -186,6 +191,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
ImageLoader imageLoaderNoty = ImageLoader.getInstance();
File cacheDir = new File(getContext().getCacheDir(), getContext().getString(R.string.app_name));
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getContext())
+ .imageDownloader(new PatchBaseImageDownloader(getContext()))
.threadPoolSize(5)
.threadPriority(Thread.MIN_PRIORITY + 3)
.denyCacheImageMultipleSizesInMemory()
@@ -195,7 +201,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
icon_notification = imageLoaderNoty.loadImageSync(notificationUrl);
}catch (Exception e){
icon_notification = BitmapFactory.decodeResource(getContext().getResources(),
- R.drawable.mastodon_logo);
+ R.drawable.mastodonlogo);
}
}
}
@@ -207,7 +213,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
message = getContext().getResources().getQuantityString(R.plurals.other_notifications, other, other);
else
message = "";
- notify_user(icon_notification,title,message);
+ notify_user(getContext(), NOTIFICATION_INTENT, notificationId, icon_notification,title,message);
}
}
SharedPreferences.Editor editor = sharedpreferences.edit();
@@ -216,36 +222,4 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
}
- /**
- * Sends notification with intent
- * @param icon Bitmap profile picture
- * @param title String title of the notification
- * @param message String message for the notification
- */
- private void notify_user(Bitmap icon, String title, String message ) {
- final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
- // prepare intent which is triggered if the user click on the notification
- NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getContext());
- final Intent intent = new Intent(getContext(), MainActivity.class);
- intent.putExtra(Helper.NOTIFICATION_TYPE, Helper.NOTIFICATION_INTENT);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK );
- intent.putExtra(INTENT_ACTION, INTENT_NOTIFICATION);
- PendingIntent pIntent = PendingIntent.getActivity(getContext(), notificationId, intent, PendingIntent.FLAG_ONE_SHOT);
-
- RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
- // build notification
- NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getContext())
- .setSmallIcon(R.drawable.notification_icon)
- .setTicker(message)
- .setWhen(System.currentTimeMillis())
- .setAutoCancel(true)
- .setContentIntent(pIntent)
- .setContentText(message);
- if( !sharedpreferences.getBoolean(Helper.SET_NOTIF_SILENT,false) ) {
- notificationBuilder.setDefaults(-1);
- }
- notificationBuilder.setContentTitle(title);
- notificationBuilder.setLargeIcon(icon);
- notificationManager.notify(notificationId, notificationBuilder.build());
- }
}
diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml
index 3b4b972b7..1204da0ab 100644
--- a/app/src/main/res/drawable/background_splash.xml
+++ b/app/src/main/res/drawable/background_splash.xml
@@ -23,6 +23,6 @@
-
+ android:src="@drawable/mastodonlogo"/>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/mastodon_logo.png b/app/src/main/res/drawable/mastodon_logo.png
deleted file mode 100644
index f0df29927..000000000
Binary files a/app/src/main/res/drawable/mastodon_logo.png and /dev/null differ
diff --git a/app/src/main/res/drawable/mastodonlogo.png b/app/src/main/res/drawable/mastodonlogo.png
new file mode 100644
index 000000000..3a2574151
Binary files /dev/null and b/app/src/main/res/drawable/mastodonlogo.png differ
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
index f94e777b9..6990bbf42 100644
--- a/app/src/main/res/layout/activity_about.xml
+++ b/app/src/main/res/layout/activity_about.xml
@@ -20,6 +20,7 @@
android:layout_height="match_parent"
android:orientation="vertical"
>
+
+
-
+
-
+
+
+
+
+
+
-
+
+
+
+
+
+
+ android:layout_height="wrap_content">
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index 97fd84b21..e8f35ad13 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -24,25 +24,41 @@
android:orientation="vertical"
>
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_settings_notifications.xml b/app/src/main/res/layout/fragment_settings_notifications.xml
index fdadadddd..4872e10e3 100644
--- a/app/src/main/res/layout/fragment_settings_notifications.xml
+++ b/app/src/main/res/layout/fragment_settings_notifications.xml
@@ -69,7 +69,20 @@
android:layout_height="wrap_content" />
-
+
+
+
+
-
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9b2980938..7d1257a0a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -17,6 +17,8 @@
Annuler
Télécharger
Télécharger %1$s
+ Mot de passe
+ Email
Accueil
Accueil
@@ -119,15 +121,20 @@
Version %1$s
- Développeur : @tschneider@mastodon.etalab.gouv.fr
- Licence : GNU GPL V3
- Code source : bitbucket
+ Développeur :
+ \@tschneider
+ Licence :
+ GNU GPL V3
+ Code source :
+ bitbucket
Conversation
Aucun compte à afficher
-
+ Pouets \n %d
+ Abonnements \n %d
+ Abonnés \n %d
Aucune notification à afficher
@@ -135,10 +142,15 @@
a partagé votre pouet
a ajouté votre pouet à ses favoris
vous a suivi
+ Nouveau pouet de %1$s
- et %d autre notification
- et %d autres notifications
+
+ - et un autre pouet à découvrir
+ - et %d autres pouets à découvrir
+
Pouets
Abonnements
@@ -160,6 +172,7 @@
Le pouet a été supprimé !
Oups ! Une erreur s\'est produite !
Une erreur s\'est produite en chargeant le compte !
+ Impossible de vous connecter !
Optimisation du chargement
Nombre de pouets par chargement
@@ -183,6 +196,8 @@
Notifier en WIFI seulement
Utiliser le vibreur
+ Actualités
+ Notifier lors de nouveaux pouets sur la page d\'accueil
Suivre
Se désabonner
diff --git a/build.gradle b/build.gradle
index b78a0b86c..d0aa70430 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.1'
+ classpath 'com.android.tools.build:gradle:2.3.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files