Improve notifications (#764)

Moving translation.

Adding option to enable/disable notifications.

Fix typos

Improve notifications

Co-authored-by: opyale <opyale@noreply.codeberg.org>
Co-authored-by: M M Arif <mmarif@swatian.com>
Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/764
Reviewed-by: opyale <opyale@noreply.codeberg.org>
This commit is contained in:
M M Arif 2020-11-03 20:14:24 +01:00 committed by opyale
parent 87379ae0b2
commit a92da6a6d4
16 changed files with 593 additions and 185 deletions

View File

@ -153,6 +153,9 @@
<activity
android:name=".activities.SettingsGeneralActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|uiMode|keyboard|keyboardHidden|navigation" />
<activity
android:name=".activities.SettingsNotificationsActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|uiMode|keyboard|keyboardHidden|navigation" />
<!-- Version < 3.0. DeX Mode and Screen Mirroring support -->
<meta-data android:name="com.samsung.android.keepalive.density" android:value="true"/>

View File

@ -15,6 +15,7 @@ import org.acra.data.StringFormat;
import org.mian.gitnex.R;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.FontsOverride;
import org.mian.gitnex.helpers.StaticGlobalVariables;
import org.mian.gitnex.helpers.TimeHelper;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.notifications.NotificationsMaster;
@ -115,9 +116,9 @@ public abstract class BaseActivity extends AppCompatActivity {
}
if(tinyDB.getInt("pollingDelayMinutes") == 0) {
if(tinyDB.getInt("pollingDelayMinutes", 0) <= 0) {
tinyDB.putInt("pollingDelayMinutes", 15);
tinyDB.putInt("pollingDelayMinutes", StaticGlobalVariables.defaultPollingDelay);
}
// FIXME Performance nightmare

View File

@ -0,0 +1,123 @@
package org.mian.gitnex.activities;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.NumberPicker;
import androidx.appcompat.app.AlertDialog;
import com.pes.androidmaterialcolorpickerdialog.ColorPicker;
import org.mian.gitnex.R;
import org.mian.gitnex.databinding.ActivitySettingsNotificationsBinding;
import org.mian.gitnex.helpers.StaticGlobalVariables;
import org.mian.gitnex.helpers.Toasty;
import org.mian.gitnex.notifications.NotificationsMaster;
/**
* Template Author M M Arif
* Author opyale
*/
public class SettingsNotificationsActivity extends BaseActivity {
private ActivitySettingsNotificationsBinding viewBinding;
@Override
protected int getLayoutResourceId() {
return R.layout.activity_settings_notifications;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewBinding = ActivitySettingsNotificationsBinding.inflate(getLayoutInflater());
View view = viewBinding.getRoot();
setContentView(view);
View.OnClickListener onClickListener = viewClose -> finish();
viewBinding.close.setOnClickListener(onClickListener);
viewBinding.pollingDelaySelected.setText(String.format(getString(R.string.pollingDelaySelectedText), tinyDB.getInt("pollingDelayMinutes", StaticGlobalVariables.defaultPollingDelay)));
viewBinding.chooseColorState.setCardBackgroundColor(tinyDB.getInt("notificationsLightColor", Color.GREEN));
viewBinding.enableNotificationsMode.setChecked(tinyDB.getBoolean("notificationsEnabled", true));
viewBinding.enableLightsMode.setChecked(tinyDB.getBoolean("notificationsEnableLights", true));
viewBinding.enableVibrationMode.setChecked(tinyDB.getBoolean("notificationsEnableVibration", true));
viewBinding.enableNotificationsMode.setOnCheckedChangeListener((buttonView, isChecked) -> {
tinyDB.putBoolean("notificationsEnabled", isChecked);
if(!isChecked) NotificationsMaster.fireWorker(ctx);
Toasty.info(appCtx, getResources().getString(R.string.settingsSave));
});
// polling delay
viewBinding.pollingDelayFrame.setOnClickListener(v -> {
NumberPicker numberPicker = new NumberPicker(ctx);
numberPicker.setMinValue(StaticGlobalVariables.minimumPollingDelay);
numberPicker.setMaxValue(StaticGlobalVariables.maximumPollingDelay);
numberPicker.setValue(tinyDB.getInt("pollingDelayMinutes", StaticGlobalVariables.defaultPollingDelay));
numberPicker.setWrapSelectorWheel(true);
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(getString(R.string.pollingDelayDialogHeaderText));
builder.setMessage(getString(R.string.pollingDelayDialogDescriptionText));
builder.setCancelable(true);
builder.setPositiveButton(getString(R.string.okButton), (dialog, which) -> {
tinyDB.putInt("pollingDelayMinutes", numberPicker.getValue());
NotificationsMaster.fireWorker(ctx);
NotificationsMaster.hireWorker(ctx);
viewBinding.pollingDelaySelected.setText(String.format(getString(R.string.pollingDelaySelectedText), numberPicker.getValue()));
Toasty.info(appCtx, getResources().getString(R.string.settingsSave));
});
builder.setNegativeButton(R.string.cancelButton, (dialog, which) -> dialog.dismiss());
builder.setView(numberPicker);
builder.create().show();
});
// lights switcher
viewBinding.enableLightsMode.setOnCheckedChangeListener((buttonView, isChecked) -> {
tinyDB.putBoolean("notificationsEnableLights", isChecked);
Toasty.info(appCtx, getResources().getString(R.string.settingsSave));
});
// lights color chooser
viewBinding.chooseColorFrame.setOnClickListener(v -> {
ColorPicker colorPicker = new ColorPicker(SettingsNotificationsActivity.this);
colorPicker.setColor(tinyDB.getInt("notificationsLightColor", Color.GREEN));
colorPicker.setCallback(color -> {
tinyDB.putInt("notificationsLightColor", color);
viewBinding.chooseColorState.setCardBackgroundColor(color);
colorPicker.dismiss();
Toasty.info(appCtx, getResources().getString(R.string.settingsSave));
});
colorPicker.show();
});
// vibration switcher
viewBinding.enableVibrationMode.setOnCheckedChangeListener((buttonView, isChecked) -> {
tinyDB.putBoolean("notificationsEnableVibration", isChecked);
Toasty.info(appCtx, getResources().getString(R.string.settingsSave));
});
}
}

View File

@ -7,15 +7,12 @@ import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.NumberPicker;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import org.apache.commons.io.FileUtils;
import org.mian.gitnex.R;
import org.mian.gitnex.helpers.Toasty;
import org.mian.gitnex.helpers.Version;
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
import org.mian.gitnex.notifications.NotificationsMaster;
import java.io.File;
import java.io.IOException;
@ -27,16 +24,12 @@ public class SettingsSecurityActivity extends BaseActivity {
private View.OnClickListener onClickListener;
private static String[] cacheSizeDataList = {"50 MB", "100 MB", "250 MB", "500 MB", "1 GB"};
private static final String[] cacheSizeDataList = {"50 MB", "100 MB", "250 MB", "500 MB", "1 GB"};
private static int cacheSizeDataSelectedChoice = 0;
private static String[] cacheSizeImagesList = {"50 MB", "100 MB", "250 MB", "500 MB", "1 GB"};
private static final String[] cacheSizeImagesList = {"50 MB", "100 MB", "250 MB", "500 MB", "1 GB"};
private static int cacheSizeImagesSelectedChoice = 0;
private static int MINIMUM_POLLING_DELAY = 1;
private static int DEFAULT_POLLING_DELAY = 20;
private static int MAXIMUM_POLLING_DELAY = 720;
@Override
protected int getLayoutResourceId() {
@ -48,8 +41,6 @@ public class SettingsSecurityActivity extends BaseActivity {
super.onCreate(savedInstanceState);
String currentVersion = tinyDB.getString("giteaVersion");
ImageView closeActivity = findViewById(R.id.close);
initCloseListener();
@ -58,10 +49,8 @@ public class SettingsSecurityActivity extends BaseActivity {
TextView cacheSizeDataSelected = findViewById(R.id.cacheSizeDataSelected); // setter for data cache size
TextView cacheSizeImagesSelected = findViewById(R.id.cacheSizeImagesSelected); // setter for images cache size
TextView clearCacheSelected = findViewById(R.id.clearCacheSelected); // setter for clear cache
TextView pollingDelaySelected = findViewById(R.id.pollingDelaySelected);
LinearLayout certsFrame = findViewById(R.id.certsFrame);
LinearLayout pollingDelayFrame = findViewById(R.id.pollingDelayFrame);
LinearLayout cacheSizeDataFrame = findViewById(R.id.cacheSizeDataSelectionFrame);
LinearLayout cacheSizeImagesFrame = findViewById(R.id.cacheSizeImagesSelectionFrame);
LinearLayout clearCacheFrame = findViewById(R.id.clearCacheSelectionFrame);
@ -86,13 +75,6 @@ public class SettingsSecurityActivity extends BaseActivity {
cacheSizeImagesSelectedChoice = tinyDB.getInt("cacheSizeImagesId");
}
if(new Version(currentVersion).less("1.12.3")) {
pollingDelayFrame.setVisibility(View.GONE);
}
pollingDelaySelected.setText(String.format(getString(R.string.pollingDelaySelectedText), tinyDB.getInt("pollingDelayMinutes", DEFAULT_POLLING_DELAY)));
// clear cache setter
File cacheDir = appCtx.getCacheDir();
clearCacheSelected.setText(FileUtils.byteCountToDisplaySize((int) FileUtils.sizeOfDirectory(cacheDir)));
@ -193,36 +175,6 @@ public class SettingsSecurityActivity extends BaseActivity {
builder.setNeutralButton(R.string.cancelButton, (dialog, which) -> dialog.dismiss());
builder.create().show();
});
// polling delay
pollingDelayFrame.setOnClickListener(v -> {
NumberPicker numberPicker = new NumberPicker(ctx);
numberPicker.setMinValue(MINIMUM_POLLING_DELAY);
numberPicker.setMaxValue(MAXIMUM_POLLING_DELAY);
numberPicker.setValue(tinyDB.getInt("pollingDelayMinutes", DEFAULT_POLLING_DELAY));
numberPicker.setWrapSelectorWheel(true);
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(getString(R.string.pollingDelayDialogHeaderText));
builder.setMessage(getString(R.string.pollingDelayDialogDescriptionText));
builder.setCancelable(true);
builder.setPositiveButton(getString(R.string.okButton), (dialog, which) -> {
tinyDB.putInt("pollingDelayMinutes", numberPicker.getValue());
NotificationsMaster.fireWorker(ctx);
NotificationsMaster.hireWorker(ctx);
pollingDelaySelected.setText(String.format(getString(R.string.pollingDelaySelectedText), numberPicker.getValue()));
Toasty.success(appCtx, getResources().getString(R.string.settingsSave));
});
builder.setNeutralButton(R.string.cancelButton, null);
builder.setView(numberPicker);
builder.create().show();
});
}
private void initCloseListener() {

View File

@ -1,6 +1,7 @@
package org.mian.gitnex.fragments;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -17,10 +18,12 @@ import org.mian.gitnex.activities.SettingsAppearanceActivity;
import org.mian.gitnex.activities.SettingsDraftsActivity;
import org.mian.gitnex.activities.SettingsFileViewerActivity;
import org.mian.gitnex.activities.SettingsGeneralActivity;
import org.mian.gitnex.activities.SettingsNotificationsActivity;
import org.mian.gitnex.activities.SettingsReportsActivity;
import org.mian.gitnex.activities.SettingsSecurityActivity;
import org.mian.gitnex.activities.SettingsTranslationActivity;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.helpers.Version;
/**
* Author M M Arif
@ -28,11 +31,16 @@ import org.mian.gitnex.helpers.TinyDB;
public class SettingsFragment extends Fragment {
private Context ctx;
private TinyDB tinyDB;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_settings, container, false);
ctx = getContext();
tinyDB = TinyDB.getInstance(ctx);
((MainActivity) requireActivity()).setActionBarTitle(getResources().getString(R.string.navSettings));
@ -41,39 +49,48 @@ public class SettingsFragment extends Fragment {
LinearLayout fileViewerFrame = v.findViewById(R.id.fileViewerFrame);
LinearLayout draftsFrame = v.findViewById(R.id.draftsFrame);
LinearLayout securityFrame = v.findViewById(R.id.securityFrame);
LinearLayout notificationsFrame = v.findViewById(R.id.notificationsFrame);
LinearLayout languagesFrame = v.findViewById(R.id.languagesFrame);
LinearLayout reportsFrame = v.findViewById(R.id.reportsFrame);
LinearLayout rateAppFrame = v.findViewById(R.id.rateAppFrame);
LinearLayout aboutAppFrame = v.findViewById(R.id.aboutAppFrame);
generalFrame.setOnClickListener(generalFrameCall -> startActivity(new Intent(getContext(), SettingsGeneralActivity.class)));
if(new Version(tinyDB.getString("giteaVersion")).higherOrEqual("1.12.3")) {
appearanceFrame.setOnClickListener(v1 -> startActivity(new Intent(getContext(), SettingsAppearanceActivity.class)));
notificationsFrame.setVisibility(View.VISIBLE);
}
fileViewerFrame.setOnClickListener(v1 -> startActivity(new Intent(getContext(), SettingsFileViewerActivity.class)));
generalFrame.setOnClickListener(generalFrameCall -> startActivity(new Intent(ctx, SettingsGeneralActivity.class)));
draftsFrame.setOnClickListener(v1 -> startActivity(new Intent(getContext(), SettingsDraftsActivity.class)));
appearanceFrame.setOnClickListener(v1 -> startActivity(new Intent(ctx, SettingsAppearanceActivity.class)));
securityFrame.setOnClickListener(v1 -> startActivity(new Intent(getContext(), SettingsSecurityActivity.class)));
fileViewerFrame.setOnClickListener(v1 -> startActivity(new Intent(ctx, SettingsFileViewerActivity.class)));
languagesFrame.setOnClickListener(v1 -> startActivity(new Intent(getContext(), SettingsTranslationActivity.class)));
draftsFrame.setOnClickListener(v1 -> startActivity(new Intent(ctx, SettingsDraftsActivity.class)));
reportsFrame.setOnClickListener(v1 -> startActivity(new Intent(getContext(), SettingsReportsActivity.class)));
securityFrame.setOnClickListener(v1 -> startActivity(new Intent(ctx, SettingsSecurityActivity.class)));
notificationsFrame.setOnClickListener(v1 -> startActivity(new Intent(ctx, SettingsNotificationsActivity.class)));
languagesFrame.setOnClickListener(v1 -> startActivity(new Intent(ctx, SettingsTranslationActivity.class)));
reportsFrame.setOnClickListener(v1 -> startActivity(new Intent(ctx, SettingsReportsActivity.class)));
rateAppFrame.setOnClickListener(aboutApp -> rateThisApp());
aboutAppFrame.setOnClickListener(aboutApp -> requireActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new AboutFragment()).commit());
return v;
}
public void rateThisApp() {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + requireActivity().getPackageName())));
}
catch(ActivityNotFoundException e) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + requireActivity().getPackageName())));
}
}
@ -83,14 +100,12 @@ public class SettingsFragment extends Fragment {
super.onResume();
TinyDB tinyDb = TinyDB.getInstance(getContext());
if(tinyDB.getBoolean("refreshParent")) {
if(tinyDb.getBoolean("refreshParent")) {
requireActivity().recreate();
requireActivity().overridePendingTransition(0, 0);
tinyDb.putBoolean("refreshParent", false);
tinyDB.putBoolean("refreshParent", false);
}
}
}

View File

@ -92,14 +92,10 @@ public class StarredRepositoriesFragment extends Fragment {
createNewRepo = v.findViewById(R.id.addNewRepo);
createNewRepo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(view.getContext(), CreateRepoActivity.class);
startActivity(intent);
}
createNewRepo.setOnClickListener(view -> {
Intent intent = new Intent(view.getContext(), CreateRepoActivity.class);
startActivity(intent);
});
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@ -133,6 +129,7 @@ public class StarredRepositoriesFragment extends Fragment {
@Override
public void onResume() {
super.onResume();
TinyDB tinyDb = TinyDB.getInstance(getContext());
final String loginUid = tinyDb.getString("loginUid");

View File

@ -46,4 +46,8 @@ public abstract class StaticGlobalVariables {
public static String draftTypeIssue = "Issue";
public static String draftTypePull = "Pull";
// polling - notifications
public static int minimumPollingDelay = 1;
public static int defaultPollingDelay = 15;
public static int maximumPollingDelay = 720;
}

View File

@ -7,6 +7,7 @@ import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import org.mian.gitnex.helpers.StaticGlobalVariables;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.helpers.Version;
import java.util.concurrent.TimeUnit;
@ -38,30 +39,33 @@ public class NotificationsMaster {
TinyDB tinyDB = TinyDB.getInstance(context);
if(notificationsSupported == -1) {
checkVersion(tinyDB);
}
if(tinyDB.getBoolean("notificationsEnabled", true)) {
if(notificationsSupported == 1) {
if(notificationsSupported == -1) checkVersion(tinyDB);
Constraints.Builder constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(false)
.setRequiresStorageNotLow(false)
.setRequiresCharging(false);
if(notificationsSupported == 1) {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Constraints.Builder constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(false)
.setRequiresStorageNotLow(false)
.setRequiresCharging(false);
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
constraints.setRequiresDeviceIdle(false);
}
int pollingDelayMinutes = Math.max(tinyDB.getInt("pollingDelayMinutes", StaticGlobalVariables.defaultPollingDelay), 15);
PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(NotificationsWorker.class, pollingDelayMinutes, TimeUnit.MINUTES)
.setConstraints(constraints.build())
.addTag(context.getPackageName())
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(context.getPackageName(), ExistingPeriodicWorkPolicy.KEEP, periodicWorkRequest);
constraints.setRequiresDeviceIdle(false);
}
PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(NotificationsWorker.class, tinyDB.getInt("pollingDelayMinutes"), TimeUnit.MINUTES)
.setConstraints(constraints.build())
.addTag(context.getPackageName())
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(context.getPackageName(), ExistingPeriodicWorkPolicy.KEEP, periodicWorkRequest);
}
}
}

View File

@ -1,5 +1,6 @@
package org.mian.gitnex.notifications;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
@ -11,12 +12,14 @@ import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.StaticGlobalVariables;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.models.NotificationThread;
import java.util.Date;
@ -42,7 +45,6 @@ public class NotificationsWorker extends Worker {
this.context = context;
this.tinyDB = TinyDB.getInstance(context);
}
@NonNull
@ -51,7 +53,7 @@ public class NotificationsWorker extends Worker {
String token = "token " + tinyDB.getString(tinyDB.getString("loginUid") + "-token");
int notificationLoops = tinyDB.getInt("pollingDelayMinutes") >= 15 ? 1 : Math.min(15 - tinyDB.getInt("pollingDelayMinutes"), 10);
int notificationLoops = tinyDB.getInt("pollingDelayMinutes", StaticGlobalVariables.defaultPollingDelay) >= 15 ? 1 : Math.min(15 - tinyDB.getInt("pollingDelayMinutes"), 10);
for(int i=0; i<notificationLoops; i++) {
@ -73,23 +75,20 @@ public class NotificationsWorker extends Worker {
assert response.body() != null;
List<NotificationThread> notificationThreads = response.body();
Log.i("ReceivedNotifications", String.valueOf(notificationThreads.size()));
if(!notificationThreads.isEmpty()) {
for(NotificationThread notificationThread : notificationThreads) {
sendNotification(notificationThread);
}
sendNotification(notificationThreads);
}
tinyDB.putString("previousRefreshTimestamp", AppUtil.getTimestampFromDate(context, new Date()));
} else {
}
else {
Log.e("onError", String.valueOf(response.code()));
}
} catch(Exception e) {
}
catch(Exception e) {
Log.e("onError", e.toString());
}
@ -100,60 +99,125 @@ public class NotificationsWorker extends Worker {
Thread.sleep(60000 - (System.currentTimeMillis() - startPollingTime));
}
} catch (InterruptedException ignored) {}
}
catch (InterruptedException ignored) {}
}
return Result.success();
}
private void sendNotification(NotificationThread notificationThread) {
private void sendNotification(List<NotificationThread> notificationThreads) {
int summaryId = 0;
PendingIntent pendingIntent = getPendingIntent();
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
attachNotificationChannel(notificationManagerCompat);
Notification summaryNotification = new NotificationCompat.Builder(context, context.getPackageName())
.setContentTitle(context.getString(R.string.newMessages))
.setContentText(String.format(context.getString(R.string.youHaveGotNewNotifications), notificationThreads.size()))
.setSmallIcon(R.drawable.gitnex_transparent)
.setGroup(context.getPackageName())
.setGroupSummary(true)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build();
notificationManagerCompat.notify(summaryId, summaryNotification);
for(NotificationThread notificationThread : notificationThreads) {
NotificationManagerCompat notificationManagerCompat1 = NotificationManagerCompat.from(context);
attachNotificationChannel(notificationManagerCompat1);
String subjectUrl = notificationThread.getSubject().getUrl();
String issueId = context.getResources().getString(R.string.hash) + subjectUrl.substring(subjectUrl.lastIndexOf("/") + 1);
String notificationHeader = issueId + " " + notificationThread.getSubject().getTitle() + " " + String.format(context.getResources().getString(R.string.notificationExtraInfo), notificationThread.getRepository().getFull_name(), notificationThread.getSubject().getType());
NotificationCompat.Builder builder1 = getBaseNotificationBuilder()
.setContentTitle(notificationHeader)
.setGroup(context.getPackageName())
.setContentIntent(pendingIntent);
pushNotification(notificationManagerCompat1, builder1.build());
}
}
private void pushNotification(NotificationManagerCompat notificationManagerCompat, Notification notification) {
int previousNotificationId = tinyDB.getInt("previousNotificationId", 0);
int nextPreviousNotificationId = previousNotificationId > 71951418 ? 0 : previousNotificationId + 1;
tinyDB.putInt("previousNotificationId", nextPreviousNotificationId);
notificationManagerCompat.notify(previousNotificationId, notification);
}
private void attachNotificationChannel(NotificationManagerCompat notificationManagerCompat) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(context.getPackageName(), context.getString(R.string.app_name),
NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.setDescription(context.getString(R.string.notificationChannelDescription));
if(tinyDB.getBoolean("notificationsEnableVibration", true)) {
notificationChannel.setVibrationPattern(VIBRATION_PATTERN);
notificationChannel.enableVibration(true);
}
else {
notificationChannel.enableVibration(false);
}
if(tinyDB.getBoolean("notificationsEnableLights", true)) {
notificationChannel.setLightColor(tinyDB.getInt("notificationsLightColor", Color.GREEN));
notificationChannel.enableLights(true);
}
else {
notificationChannel.enableLights(false);
}
notificationManagerCompat.createNotificationChannel(notificationChannel);
}
}
private NotificationCompat.Builder getBaseNotificationBuilder() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, context.getPackageName())
.setSmallIcon(R.drawable.gitnex_transparent)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true);
if(tinyDB.getBoolean("notificationsEnableLights", true)) {
builder.setLights(tinyDB.getInt("notificationsLightColor", Color.GREEN), 1500, 1500);
}
if(tinyDB.getBoolean("notificationsEnableVibration", true)) {
builder.setVibrate(VIBRATION_PATTERN);
}
else {
builder.setVibrate(null);
}
return builder;
}
private PendingIntent getPendingIntent() {
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("launchFragment", "notifications");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if(notificationManager != null) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(context.getPackageName(), context.getString(R.string.app_name),
NotificationManager.IMPORTANCE_HIGH);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.GREEN);
notificationChannel.enableVibration(true);
notificationChannel.setVibrationPattern(VIBRATION_PATTERN);
notificationManager.createNotificationChannel(notificationChannel);
}
String subjectUrl = notificationThread.getSubject().getUrl();
String issueId = context.getResources().getString(R.string.hash) + subjectUrl.substring(subjectUrl.lastIndexOf("/") + 1);
String notificationHeader = issueId + " " + notificationThread.getSubject().getTitle();
String notificationBody = String.format(context.getResources().getString(R.string.notificationBody),
notificationThread.getSubject().getType());
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, context.getPackageName())
.setSmallIcon(R.drawable.gitnex_transparent).setContentTitle(notificationHeader)
.setContentText(notificationBody)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent).setVibrate(VIBRATION_PATTERN).setAutoCancel(true);
int previousNotificationId = tinyDB.getInt("previousNotificationId", 0);
int newPreviousNotificationId = previousNotificationId > 71951418 ? 0 : previousNotificationId + 1;
tinyDB.putInt("previousNotificationId", newPreviousNotificationId);
notificationManager.notify(previousNotificationId, builder.build());
}
return PendingIntent.getActivity(context, 0, intent, 0);
}
}

View File

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layoutSettingsNotifications"
android:orientation="vertical"
android:background="?attr/primaryBackgroundColor">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Widget.AppCompat.SearchView">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/primaryBackgroundColor"
android:theme="@style/AppTheme.AppBarOverlay"
tools:ignore="UnusedAttribute">
<ImageView
android:id="@+id/close"
android:layout_width="@dimen/close_button_size"
android:layout_height="@dimen/close_button_size"
android:layout_marginRight="15dp"
android:layout_marginLeft="15dp"
android:gravity="center_vertical"
android:contentDescription="@string/close"
android:src="@drawable/ic_close" />
<TextView
android:id="@+id/toolbar_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/pageTitleNotifications"
android:textColor="?attr/primaryTextColor"
android:maxLines="1"
android:textSize="20sp" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<RelativeLayout
android:id="@+id/enableNotificationsFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:orientation="horizontal">
<TextView
android:id="@+id/enableNotificationsHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="44dp"
android:layout_marginEnd="24dp"
android:text="@string/enableNotificationsHeaderText"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enableNotificationsMode"
android:layout_toEndOf="@+id/enableNotificationsHeader"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:switchMinWidth="56dp"
android:paddingStart="0dp"
android:paddingEnd="25dp"
android:layout_alignParentEnd="true"
android:gravity="end"
android:layout_gravity="end" />
</RelativeLayout>
<LinearLayout
android:id="@+id/pollingDelayFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/pollingDelayHeaderSelector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_marginTop="10dp"
android:layout_marginStart="44dp"
android:layout_marginEnd="4dp"
android:text="@string/notificationsPollingHeaderText"
android:textColor="?attr/primaryTextColor" />
<TextView
android:id="@+id/pollingDelaySelected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginStart="44dp"
android:layout_marginEnd="4dp"
android:text="@string/pollingDelaySelectedText"
android:textColor="?attr/selectedTextColor" />
</LinearLayout>
<RelativeLayout
android:id="@+id/enableLightsFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:orientation="horizontal">
<TextView
android:id="@+id/enableLightsHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_marginStart="44dp"
android:layout_marginEnd="24dp"
android:text="@string/enableLightsHeaderText"
android:textColor="?attr/primaryTextColor" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enableLightsMode"
android:layout_toEndOf="@+id/enableLightsHeader"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:switchMinWidth="56dp"
android:paddingStart="0dp"
android:paddingEnd="25dp"
android:layout_alignParentEnd="true"
android:gravity="end"
android:layout_gravity="end" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/chooseColorFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:orientation="horizontal">
<TextView
android:id="@+id/chooseColorHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_marginStart="44dp"
android:layout_marginEnd="24dp"
android:text="@string/chooseColorSelectorHeader"
android:textColor="?attr/primaryTextColor" />
<androidx.cardview.widget.CardView
android:id="@+id/chooseColorState"
android:layout_alignEnd="@id/chooseColorHeader"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="30dp"
android:gravity="end"
app:cardCornerRadius="15dp"
app:cardElevation="0dp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/enableVibrationFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:orientation="horizontal">
<TextView
android:id="@+id/enableVibrationHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_marginStart="44dp"
android:layout_marginEnd="24dp"
android:text="@string/enableVibrationHeaderText"
android:textColor="?attr/primaryTextColor" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enableVibrationMode"
android:layout_toEndOf="@+id/enableVibrationHeader"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:switchMinWidth="56dp"
android:paddingStart="0dp"
android:paddingEnd="25dp"
android:layout_alignParentEnd="true"
android:gravity="end"
android:layout_gravity="end" />
</RelativeLayout>
</LinearLayout>

View File

@ -153,34 +153,4 @@
</LinearLayout>
<LinearLayout
android:id="@+id/pollingDelayFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/pollingDelayHeaderSelector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_marginTop="10dp"
android:layout_marginStart="44dp"
android:layout_marginEnd="4dp"
android:text="@string/notificationsPollingHeaderText"
android:textColor="?attr/primaryTextColor"/>
<TextView
android:id="@+id/pollingDelaySelected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginStart="44dp"
android:layout_marginEnd="4dp"
android:text="@string/pollingDelaySelectedText"
android:textColor="?attr/selectedTextColor"/>
</LinearLayout>
</LinearLayout>

View File

@ -177,6 +177,39 @@
</LinearLayout>
<LinearLayout
android:id="@+id/notificationsFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:id="@+id/notificationsHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="24dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="@string/pageTitleNotifications"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"
app:drawableStartCompat="@drawable/ic_notifications" />
<TextView
android:id="@+id/notificationsHintText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingStart="60dp"
android:paddingEnd="12dp"
android:text="@string/notificationsHintText"
android:textColor="?attr/primaryTextColor"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/languagesFrame"
android:layout_width="match_parent"

View File

@ -642,7 +642,7 @@
<string name="appearanceHintText">Themes, fonts, badges</string>
<string name="fileViewerHintText">PDF mode, source code theme</string>
<string name="securityHintText">SSL certificates, cache, polling delay</string>
<string name="securityHintText">SSL certificates, cache</string>
<string name="languagesHintText">Languages</string>
<string name="reportsHintText">Crash reports</string>
<string name="rateAppHintText">If you like GitNex you can give it a thumbs up</string>
@ -662,18 +662,23 @@
<!-- Notifications -->
<string name="pageTitleNotifications">Notifications</string>
<string name="noDataNotifications">No notifications found</string>
<string name="notificationBody">You have received a new notification. (%s)</string>
<string name="notificationsPollingHeaderText">Notifications Polling Delay</string>
<string name="pollingDelaySelectedText">%d Minutes</string>
<string name="pollingDelayDialogHeaderText">Select Polling Delay</string>
<string name="pollingDelayDialogDescriptionText">Choose a minutely delay in which GitNex tries to poll new notifications</string>
<string name="markAsRead">Mark as Read</string>
<string name="markAsUnread">Mark as Unread</string>
<string name="pinNotification">Pin Notification</string>
<string name="markedNotificationsAsRead">Successfully marked all notifications as read</string>
<string name="notificationsHintText">Polling delay, light, vibration</string>
<string name="enableNotificationsHeaderText">Enable Notifications</string>
<string name="enableLightsHeaderText">Enable Light</string>
<string name="enableVibrationHeaderText">Enable Vibration</string>
<string name="chooseColorSelectorHeader">Choose Color</string>
<string name="newMessages">New messages</string>
<string name="youHaveGotNewNotifications">You\'ve got %d new notifications.</string>
<string name="notificationChannelDescription" translatable="false">This is the main notification channel of GitNex.</string>
<string name="notificationExtraInfo" translatable="false">- %s (%s)</string>
<string name="isRead">Read</string>
<string name="isUnread">Unread</string>

View File

@ -0,0 +1,34 @@
package org.mian.gitnex.helpers;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Author opyale
*/
public class PathsHelperTest {
@Test
public void testJoin() {
assertEquals(PathsHelper.join("test", "/test", "test/", "/test/"), "/test/test/test/test/");
assertEquals(PathsHelper.join("test", "test", "test", "test"), "/test/test/test/test/");
assertEquals(PathsHelper.join("/test", "/test", "/test", "/test"), "/test/test/test/test/");
assertEquals(PathsHelper.join("/test/", "/test/", "test/", "/test/"), "/test/test/test/test/");
assertEquals(PathsHelper.join("test", "test", "/test", "/test"), "/test/test/test/test/");
assertEquals(PathsHelper.join("test/", "test", "/test", "/test"), "/test/test/test/test/");
assertEquals(PathsHelper.join("test/test/test/test"), "/test/test/test/test/");
assertEquals(PathsHelper.join("/test/test/test/test"), "/test/test/test/test/");
assertEquals(PathsHelper.join("test/test/test/test/"), "/test/test/test/test/");
assertEquals(PathsHelper.join("test"), "/test/");
assertEquals(PathsHelper.join("test/"), "/test/");
assertEquals(PathsHelper.join("/test/"), "/test/");
assertEquals(PathsHelper.join("/test"), "/test/");
}
}

View File

@ -1,13 +1,13 @@
package org.mian.gitnex.helpers;
/**
* Author 6543
*/
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Author 6543
*/
public class VersionTest {
@Test

View File

@ -8,7 +8,7 @@
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects