Notifications service for new toots in home timeline + improves about page + cleans some libs

This commit is contained in:
tom79 2017-05-20 15:15:02 +02:00
parent 4c3b64e744
commit 3d23b66d81
16 changed files with 477 additions and 119 deletions

View File

@ -9,7 +9,6 @@ android {
targetSdkVersion 25 targetSdkVersion 25
versionCode 5 versionCode 5
versionName "1.0.5" versionName "1.0.5"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
@ -21,17 +20,11 @@ android {
dependencies { dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) 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:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1' compile 'com.android.support:design:25.3.1'
compile 'com.android.support:support-v4: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.loopj.android:android-async-http:1.4.9'
compile 'com.google.code.gson:gson:2.8.0' 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.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'com.evernote:android-job:1.1.9' compile 'com.evernote:android-job:1.1.10'
testCompile 'junit:junit:4.12'
} }

View File

@ -66,7 +66,6 @@
<activity android:name="fr.gouv.etalab.mastodon.activities.AboutActivity" <activity android:name="fr.gouv.etalab.mastodon.activities.AboutActivity"
android:windowSoftInputMode="stateAlwaysHidden" android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:noHistory="true"
android:label="@string/app_name" android:label="@string/app_name"
/> />
<activity android:name="fr.gouv.etalab.mastodon.activities.TootActivity" <activity android:name="fr.gouv.etalab.mastodon.activities.TootActivity"

View File

@ -14,15 +14,19 @@
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.activities; package fr.gouv.etalab.mastodon.activities;
import android.content.Intent;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.text.Html; import android.text.Html;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import mastodon.etalab.gouv.fr.mastodon.R; import mastodon.etalab.gouv.fr.mastodon.R;
@ -48,24 +52,34 @@ public class AboutActivity extends AppCompatActivity {
about_version.setText(getResources().getString(R.string.about_vesrion, version)); about_version.setText(getResources().getString(R.string.about_vesrion, version));
} catch (PackageManager.NameNotFoundException ignored) {} } catch (PackageManager.NameNotFoundException ignored) {}
TextView about_developer = (TextView) findViewById(R.id.about_developer); Button about_developer = (Button) findViewById(R.id.about_developer);
TextView about_license = (TextView) findViewById(R.id.about_license); Button about_code = (Button) findViewById(R.id.about_code);
TextView about_code = (TextView) findViewById(R.id.about_code); Button about_license = (Button) findViewById(R.id.about_license);
about_developer.setMovementMethod(LinkMovementMethod.getInstance());
about_license.setMovementMethod(LinkMovementMethod.getInstance()); about_code.setOnClickListener(new View.OnClickListener() {
about_code.setMovementMethod(LinkMovementMethod.getInstance()); @Override
about_developer.setLinkTextColor(Color.BLUE); public void onClick(View v) {
about_license.setLinkTextColor(Color.BLUE); Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://bitbucket.org/tom79/mastodon_etalab"));
about_code.setLinkTextColor(Color.BLUE); startActivity(browserIntent);
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_developer.setOnClickListener(new View.OnClickListener() {
about_code.setText(Html.fromHtml(getString(R.string.about_code), Html.FROM_HTML_MODE_COMPACT)); @Override
}else { public void onClick(View v) {
about_developer.setText(Html.fromHtml(getString(R.string.about_developer))); Intent intent = new Intent(AboutActivity.this, ShowAccountActivity.class);
about_license.setText(Html.fromHtml(getString(R.string.about_license))); Bundle b = new Bundle();
about_code.setText(Html.fromHtml(getString(R.string.about_code))); 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);
}
});
} }

View File

@ -62,8 +62,9 @@ import fr.gouv.etalab.mastodon.fragments.TabLayoutSettingsFragment;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO; import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
import mastodon.etalab.gouv.fr.mastodon.R; 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_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 public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, OnUpdateAccountInfoInterface { implements NavigationView.OnNavigationItemSelectedListener, OnUpdateAccountInfoInterface {
@ -138,10 +139,14 @@ public class MainActivity extends AppCompatActivity
boolean menuWasSelected = false; boolean menuWasSelected = false;
if( getIntent() != null && getIntent().getExtras() != null ){ if( getIntent() != null && getIntent().getExtras() != null ){
Bundle extras = getIntent().getExtras(); 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.setCheckedItem(R.id.nav_notification);
navigationView.getMenu().performIdentifierAction(R.id.nav_notification, 0); navigationView.getMenu().performIdentifierAction(R.id.nav_notification, 0);
menuWasSelected = true; 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) { if (savedInstanceState == null && !menuWasSelected) {
@ -190,10 +195,13 @@ public class MainActivity extends AppCompatActivity
return; return;
Bundle extras = intent.getExtras(); Bundle extras = intent.getExtras();
if( extras.containsKey(INTENT_ACTION) ){ 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.setCheckedItem(R.id.nav_notification);
navigationView.getMenu().performIdentifierAction(R.id.nav_notification, 0); 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()); intent.replaceExtras(new Bundle());

View File

@ -18,10 +18,12 @@ import android.app.Application;
import com.evernote.android.job.JobManager; import com.evernote.android.job.JobManager;
import fr.gouv.etalab.mastodon.jobs.ApplicationJob; import fr.gouv.etalab.mastodon.jobs.ApplicationJob;
import fr.gouv.etalab.mastodon.jobs.HomeTimelineSyncJob;
import fr.gouv.etalab.mastodon.jobs.NotificationsSyncJob; import fr.gouv.etalab.mastodon.jobs.NotificationsSyncJob;
/** /**
* Created by Thomas on 29/04/2017. * Created by Thomas on 29/04/2017.
* Main application, jobs are launched here.
*/ */
public class MainApplication extends Application{ public class MainApplication extends Application{
@ -32,6 +34,7 @@ public class MainApplication extends Application{
super.onCreate(); super.onCreate();
JobManager.create(this).addJobCreator(new ApplicationJob()); JobManager.create(this).addJobCreator(new ApplicationJob());
JobManager.instance().getConfig().setVerbose(false); JobManager.instance().getConfig().setVerbose(false);
NotificationsSyncJob.schedule(getApplicationContext(), false); NotificationsSyncJob.schedule(false);
HomeTimelineSyncJob.schedule(false);
} }
} }

View File

@ -17,6 +17,7 @@ package fr.gouv.etalab.mastodon.activities;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.TabLayout; import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
@ -25,6 +26,7 @@ import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter; import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@ -206,7 +208,7 @@ public class ShowAccountActivity extends AppCompatActivity implements OnPostActi
TextView account_dn = (TextView) findViewById(R.id.account_dn); TextView account_dn = (TextView) findViewById(R.id.account_dn);
TextView account_un = (TextView) findViewById(R.id.account_un); TextView account_un = (TextView) findViewById(R.id.account_un);
TextView account_ac = (TextView) findViewById(R.id.account_ac); TextView account_ac = (TextView) findViewById(R.id.account_ac);
TextView account_note = (TextView) findViewById(R.id.account_note);
if( account != null){ if( account != null){
setTitle(account.getAcct()); setTitle(account.getAcct());
account_dn.setText(account.getDisplay_name()); account_dn.setText(account.getDisplay_name());
@ -215,9 +217,14 @@ public class ShowAccountActivity extends AppCompatActivity implements OnPostActi
account_ac.setVisibility(View.GONE); account_ac.setVisibility(View.GONE);
else else
account_ac.setText(account.getAcct()); account_ac.setText(account.getAcct());
tabLayout.getTabAt(0).setText(getString(R.string.status) + "\n" + String.valueOf(account.getStatuses_count())); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
tabLayout.getTabAt(1).setText(getString(R.string.following) + "\n" + String.valueOf(account.getFollowing_count())); account_note.setText(Html.fromHtml(account.getNote(), Html.FROM_HTML_MODE_COMPACT));
tabLayout.getTabAt(2).setText(getString(R.string.followers) + "\n" + String.valueOf(account.getFollowers_count())); 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); imageLoader.displayImage(account.getAvatar(), account_pp, options);
} }
} }

View File

@ -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 <http://www.gnu.org/licenses>. */
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<Void, Void, Void> {
private Context context;
private List<fr.gouv.etalab.mastodon.client.Entities.Status> 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);
}
}

View File

@ -296,13 +296,7 @@ public class API {
return statusContext; return statusContext;
} }
/**
* Retrieves home timeline for the account *synchronously*
* @return List<Status>
*/
public List<Status> getHomeTimeline() {
return getHomeTimeline(null, null, tootPerPage);
}
/** /**
* Retrieves home timeline for the account *synchronously* * Retrieves home timeline for the account *synchronously*
@ -313,6 +307,14 @@ public class API {
return getHomeTimeline(max_id, null, tootPerPage); return getHomeTimeline(max_id, null, tootPerPage);
} }
/**
* Retrieves home timeline for the account since an id *synchronously*
* @return List<Status>
*/
public List<Status> getHomeTimelineSinceId(String since_id) {
return getHomeTimeline(null, since_id, tootPerPage);
}
/** /**
* Retrieves home timeline for the account *synchronously* * Retrieves home timeline for the account *synchronously*
* @param max_id String id max * @param max_id String id max

View File

@ -19,14 +19,20 @@ package fr.gouv.etalab.mastodon.helper;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.DownloadManager; import android.app.DownloadManager;
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.RingtoneManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
@ -38,9 +44,12 @@ import java.util.Date;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import fr.gouv.etalab.mastodon.activities.MainActivity;
import mastodon.etalab.gouv.fr.mastodon.R; import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.client.API; 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; import static android.content.Context.DOWNLOAD_SERVICE;
@ -77,10 +86,10 @@ public class Helper {
public static final String SCOPES = "scopes"; public static final String SCOPES = "scopes";
public static final String WEBSITE = "website"; public static final String WEBSITE = "website";
public static final String LAST_NOTIFICATION_MAX_ID = "last_notification_max_id"; 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 //Notifications
public static final String NOTIFICATION_TYPE = "notification_type";
public static final int NOTIFICATION_INTENT = 1; public static final int NOTIFICATION_INTENT = 1;
public static final int HOME_TIMELINE_INTENT = 2;
//Settings //Settings
public static final String SET_TOOTS_PER_PAGE = "set_toots_per_page"; public static final String SET_TOOTS_PER_PAGE = "set_toots_per_page";
@ -108,10 +117,10 @@ public class Helper {
//Refresh job //Refresh job
public static final int MINUTES_BETWEEN_NOTIFICATIONS_REFRESH = 15; public static final int MINUTES_BETWEEN_NOTIFICATIONS_REFRESH = 15;
public static final int MINUTES_BETWEEN_HOME_TIMELINE = 30;
//Intent //Intent
public static final String INTENT_ACTION = "intent_action"; public static final String INTENT_ACTION = "intent_action";
public static final int INTENT_NOTIFICATION = 1;
//Receiver //Receiver
public static final String SEARCH_VALIDATE_ACCOUNT = "search_validate_account"; public static final String SEARCH_VALIDATE_ACCOUNT = "search_validate_account";
@ -365,4 +374,40 @@ public class Helper {
alert.show(); 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());
}
} }

View File

@ -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 <http://www.gnu.org/licenses>. */
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<Status> statuses, String acct);
}

View File

@ -28,6 +28,8 @@ public class ApplicationJob implements JobCreator {
switch (tag) { switch (tag) {
case NotificationsSyncJob.NOTIFICATION_REFRESH: case NotificationsSyncJob.NOTIFICATION_REFRESH:
return new NotificationsSyncJob(); return new NotificationsSyncJob();
case HomeTimelineSyncJob.HOME_TIMELINE:
return new HomeTimelineSyncJob();
default: default:
return null; return null;
} }

View File

@ -0,0 +1,162 @@
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 <http://www.gnu.org/licenses>. */
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.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<JobRequest> 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<Account> 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<Status> 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())
.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.mastodon_logo);
}
}
}
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();
}
}

View File

@ -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, * You should have received a copy of the GNU General Public License along with Thomas Schneider; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.support.annotation.NonNull; 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.Job;
import com.evernote.android.job.JobManager; import com.evernote.android.job.JobManager;
@ -39,7 +36,6 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import fr.gouv.etalab.mastodon.activities.MainActivity;
import fr.gouv.etalab.mastodon.helper.Helper; import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R; import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveNotificationsAsyncTask; import fr.gouv.etalab.mastodon.asynctasks.RetrieveNotificationsAsyncTask;
@ -49,8 +45,8 @@ import fr.gouv.etalab.mastodon.interfaces.OnRetrieveNotificationsInterface;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO; import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
import fr.gouv.etalab.mastodon.sqlite.Sqlite; 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.NOTIFICATION_INTENT;
import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_NOTIFICATION; import static fr.gouv.etalab.mastodon.helper.Helper.notify_user;
/** /**
@ -60,8 +56,7 @@ import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_NOTIFICATION;
public class NotificationsSyncJob extends Job implements OnRetrieveNotificationsInterface{ public class NotificationsSyncJob extends Job implements OnRetrieveNotificationsInterface{
public static final String NOTIFICATION_REFRESH = "job_notification"; static final String NOTIFICATION_REFRESH = "job_notification";
private int jobId;
private int notificationId; private int notificationId;
@NonNull @NonNull
@ -73,7 +68,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
} }
public static int schedule(Context context, boolean updateCurrent){ public static int schedule(boolean updateCurrent){
Set<JobRequest> jobRequests = JobManager.instance().getAllJobRequestsForTag(NOTIFICATION_REFRESH); Set<JobRequest> jobRequests = JobManager.instance().getAllJobRequestsForTag(NOTIFICATION_REFRESH);
if (!jobRequests.isEmpty() && !updateCurrent) { if (!jobRequests.isEmpty() && !updateCurrent) {
@ -99,6 +94,15 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
SQLiteDatabase db = Sqlite.getInstance(getContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); SQLiteDatabase db = Sqlite.getInstance(getContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
//If an Internet connection and user agrees with notification refresh //If an Internet connection and user agrees with notification refresh
final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); 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 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())) { if(!sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false) || Helper.isOnWIFI(getContext())) {
List<Account> accounts = new AccountDAO(getContext(),db).getAllAccount(); List<Account> accounts = new AccountDAO(getContext(),db).getAllAccount();
@ -124,7 +128,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, 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_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + acct, null); String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + acct, null);
@ -137,7 +141,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
int newShare = 0; int newShare = 0;
String notificationUrl = null; String notificationUrl = null;
String title = null; String title = null;
String message = null; String message;
for(Notification notification: notifications){ for(Notification notification: notifications){
//The notification associated to max_id is discarded as it is supposed to have already been sent //The notification associated to max_id is discarded as it is supposed to have already been sent
if( notification.getId().equals(max_id)) if( notification.getId().equals(max_id))
@ -207,7 +211,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications
message = getContext().getResources().getQuantityString(R.plurals.other_notifications, other, other); message = getContext().getResources().getQuantityString(R.plurals.other_notifications, other, other);
else else
message = ""; message = "";
notify_user(icon_notification,title,message); notify_user(getContext(), NOTIFICATION_INTENT, notificationId, icon_notification,title,message);
} }
} }
SharedPreferences.Editor editor = sharedpreferences.edit(); SharedPreferences.Editor editor = sharedpreferences.edit();
@ -216,36 +220,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());
}
} }

View File

@ -20,6 +20,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
> >
<!-- About app name -->
<TextView <TextView
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:textSize="20sp" android:textSize="20sp"
@ -28,6 +29,7 @@
android:text="@string/app_name" android:text="@string/app_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<!-- About version -->
<TextView <TextView
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:id="@+id/about_version" android:id="@+id/about_version"
@ -36,29 +38,75 @@
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView
android:id="@+id/about_developer" <!-- About developer -->
android:layout_marginTop="10dp" <LinearLayout
android:textSize="16sp" android:orientation="horizontal"
android:layout_gravity="center_horizontal" android:padding="10dp"
android:gravity="center_horizontal"
android:clickable="true"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/about_license" android:textSize="16sp"
android:layout_marginTop="10dp" android:layout_gravity="center"
android:textSize="16sp" android:gravity="center"
android:layout_gravity="center_horizontal" android:clickable="true"
android:gravity="center_horizontal" android:layout_weight="3"
android:layout_width="0dp"
android:text="@string/about_developer"
android:layout_height="wrap_content" />
<Button
android:id="@+id/about_developer"
android:text="@string/about_developer_action"
android:layout_weight="2"
android:layout_width="0dp"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:layout_height="wrap_content" />
</LinearLayout>
<!-- About license -->
<LinearLayout
android:orientation="horizontal"
android:padding="10dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/about_code" android:text="@string/about_license"
android:layout_marginTop="10dp" android:textSize="16sp"
android:textSize="16sp" android:layout_gravity="center"
android:layout_gravity="center_horizontal" android:gravity="center"
android:gravity="center_horizontal" android:layout_weight="3"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/about_license"
android:text="@string/about_license_action"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="wrap_content" />
</LinearLayout>
<!-- About license -->
<LinearLayout
android:orientation="horizontal"
android:padding="10dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content">
<TextView
android:text="@string/about_code"
android:textSize="16sp"
android:layout_gravity="center"
android:gravity="center"
android:layout_weight="3"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/about_code"
android:text="@string/about_code_action"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -99,6 +99,15 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<TextView
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:id="@+id/account_note"
android:gravity="center"
android:layout_width="match_parent"
android:maxLines="3"
android:autoLink="web"
android:layout_height="wrap_content" />
<android.support.design.widget.AppBarLayout <android.support.design.widget.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"

View File

@ -119,15 +119,20 @@
<!-- About --> <!-- About -->
<string name="about_vesrion">Version %1$s</string> <string name="about_vesrion">Version %1$s</string>
<string name="about_developer">Développeur : <a href="https://mastodon.etalab.gouv.fr/@tschneider">@tschneider@mastodon.etalab.gouv.fr</a></string> <string name="about_developer">Développeur :</string>
<string name="about_license">Licence : <a href="https://www.gnu.org/licenses/quick-guide-gplv3.fr.html">GNU GPL V3</a></string> <string name="about_developer_action">\@tschneider</string>
<string name="about_code">Code source : <a href="https://bitbucket.org/tom79/mastodon_etalab">bitbucket</a></string> <string name="about_license">Licence : </string>
<string name="about_license_action">GNU GPL V3</string>
<string name="about_code">Code source : </string>
<string name="about_code_action">bitbucket</string>
<!-- Conversation --> <!-- Conversation -->
<string name="conversation">Conversation</string> <string name="conversation">Conversation</string>
<!-- Accounts --> <!-- Accounts -->
<string name="no_accounts">Aucun compte à afficher</string> <string name="no_accounts">Aucun compte à afficher</string>
<string name="status_cnt">Pouets \n %d</string>
<string name="following_cnt">Abonnements \n %d</string>
<string name="followers_cnt">Abonnés \n %d</string>
<!-- Notifications --> <!-- Notifications -->
<string name="no_notifications">Aucune notification à afficher</string> <string name="no_notifications">Aucune notification à afficher</string>
@ -135,10 +140,15 @@
<string name="notif_reblog">a partagé votre pouet</string> <string name="notif_reblog">a partagé votre pouet</string>
<string name="notif_favourite">a ajouté votre pouet à ses favoris</string> <string name="notif_favourite">a ajouté votre pouet à ses favoris</string>
<string name="notif_follow">vous a suivi</string> <string name="notif_follow">vous a suivi</string>
<string name="notif_pouet">Nouveau pouet de %1$s</string>
<plurals name="other_notifications"> <plurals name="other_notifications">
<item quantity="one">et %d autre notification</item> <item quantity="one">et %d autre notification</item>
<item quantity="other">et %d autres notifications</item> <item quantity="other">et %d autres notifications</item>
</plurals> </plurals>
<plurals name="other_notif_hometimeline">
<item quantity="one">et un autre pouet à découvrir</item>
<item quantity="other">et %d autres pouets à découvrir</item>
</plurals>
<!-- HEADER --> <!-- HEADER -->
<string name="status">Pouets</string> <string name="status">Pouets</string>
<string name="following">Abonnements</string> <string name="following">Abonnements</string>
@ -184,7 +194,7 @@
<string name="set_notif_silent">Utiliser le vibreur</string> <string name="set_notif_silent">Utiliser le vibreur</string>
<string name="set_title_news">Actualités</string> <string name="set_title_news">Actualités</string>
<string name="set_notification_news">Notifier lors de nouveaux pouets sur le fil local</string> <string name="set_notification_news">Notifier lors de nouveaux pouets sur la page d\'accueil</string>
<string name="action_follow">Suivre</string> <string name="action_follow">Suivre</string>
<string name="action_unfollow">Se désabonner</string> <string name="action_unfollow">Se désabonner</string>