Revamp notifications in settings

This commit is contained in:
M M Arif
2025-06-24 18:37:45 +05:00
parent c7584b4096
commit 8970c155ea
8 changed files with 346 additions and 289 deletions

View File

@@ -162,9 +162,6 @@
<activity
android:name=".activities.SettingsGeneralActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
<activity
android:name=".activities.SettingsNotificationsActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
<activity
android:name=".activities.AdminCronTasksActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>

View File

@@ -1,112 +0,0 @@
package org.mian.gitnex.activities;
import android.os.Bundle;
import android.view.View;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.mian.gitnex.R;
import org.mian.gitnex.databinding.ActivitySettingsNotificationsBinding;
import org.mian.gitnex.fragments.SettingsFragment;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.SnackBar;
import org.mian.gitnex.notifications.Notifications;
/**
* @author M M Arif
* @author opyale
*/
public class SettingsNotificationsActivity extends BaseActivity {
private ActivitySettingsNotificationsBinding viewBinding;
private static String[] pollingDelayList;
private static int pollingDelayListSelectedChoice;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewBinding = ActivitySettingsNotificationsBinding.inflate(getLayoutInflater());
setContentView(viewBinding.getRoot());
viewBinding.topAppBar.setNavigationOnClickListener(v -> finish());
viewBinding.enableNotificationsMode.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_NOTIFICATIONS_KEY)));
if (!viewBinding.enableNotificationsMode.isChecked()) {
AppUtil.setMultiVisibility(View.GONE, viewBinding.pollingDelayFrame);
}
viewBinding.enableNotificationsMode.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(isChecked),
AppDatabaseSettings.APP_NOTIFICATIONS_KEY);
if (isChecked) {
Notifications.startWorker(ctx);
AppUtil.setMultiVisibility(View.VISIBLE, viewBinding.pollingDelayFrame);
} else {
Notifications.stopWorker(ctx);
AppUtil.setMultiVisibility(View.GONE, viewBinding.pollingDelayFrame);
}
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
viewBinding.enableNotificationsFrame.setOnClickListener(
v ->
viewBinding.enableNotificationsMode.setChecked(
!viewBinding.enableNotificationsMode.isChecked()));
// polling delay
pollingDelayList = getResources().getStringArray(R.array.notificationsPollingDelay);
pollingDelayListSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_NOTIFICATIONS_DELAY_KEY));
viewBinding.pollingDelaySelected.setText(pollingDelayList[pollingDelayListSelectedChoice]);
viewBinding.pollingDelayFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.pollingDelayDialogHeaderText)
.setSingleChoiceItems(
pollingDelayList,
pollingDelayListSelectedChoice,
(dialogInterfaceColor, i) -> {
pollingDelayListSelectedChoice = i;
viewBinding.pollingDelaySelected.setText(
pollingDelayList[
pollingDelayListSelectedChoice]);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings
.APP_NOTIFICATIONS_DELAY_KEY);
Notifications.stopWorker(ctx);
Notifications.startWorker(ctx);
SettingsFragment.refreshParent = true;
this.recreate();
this.overridePendingTransition(0, 0);
dialogInterfaceColor.dismiss();
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
materialAlertDialogBuilder.create().show();
});
}
}

View File

@@ -0,0 +1,79 @@
package org.mian.gitnex.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.BaseActivity;
import org.mian.gitnex.databinding.BottomSheetSettingsAboutBinding;
import org.mian.gitnex.helpers.AppUtil;
/**
* @author mmarif
*/
public class BottomSheetSettingsAboutFragment extends BottomSheetDialogFragment {
private BottomSheetSettingsAboutBinding binding;
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = BottomSheetSettingsAboutBinding.inflate(inflater, container, false);
// Set app version and build
binding.appVersionBuild.setText(
getString(
R.string.appVersionBuild,
AppUtil.getAppVersion(requireContext()),
AppUtil.getAppBuildNo(requireContext())));
// Set server version
binding.userServerVersion.setText(
((BaseActivity) requireActivity()).getAccount().getServerVersion().toString());
// Set up link click listeners
binding.donationLinkPatreon.setOnClickListener(
v -> {
AppUtil.openUrlInBrowser(
requireContext(), getString(R.string.supportLinkPatreon));
dismiss();
});
binding.translateLink.setOnClickListener(
v -> {
AppUtil.openUrlInBrowser(requireContext(), getString(R.string.crowdInLink));
dismiss();
});
binding.appWebsite.setOnClickListener(
v -> {
AppUtil.openUrlInBrowser(requireContext(), getString(R.string.appWebsiteLink));
dismiss();
});
binding.feedback.setOnClickListener(
v -> {
AppUtil.openUrlInBrowser(requireContext(), getString(R.string.feedbackLink));
dismiss();
});
// Hide donation link for pro users
if (AppUtil.isPro(requireContext())) {
binding.layoutFrame1.setVisibility(View.GONE);
}
return binding.getRoot();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null; // Prevent memory leaks
}
}

View File

@@ -0,0 +1,130 @@
package org.mian.gitnex.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.mian.gitnex.R;
import org.mian.gitnex.databinding.BottomSheetSettingsNotificationsBinding;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.SnackBar;
import org.mian.gitnex.notifications.Notifications;
/**
* @author mmarif
*/
public class BottomSheetSettingsNotificationsFragment extends BottomSheetDialogFragment {
private BottomSheetSettingsNotificationsBinding binding;
private static int pollingDelayListSelectedChoice;
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = BottomSheetSettingsNotificationsBinding.inflate(inflater, container, false);
// Initialize polling delay
pollingDelayListSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_NOTIFICATIONS_DELAY_KEY));
setChipSelection(pollingDelayListSelectedChoice);
// Enable notifications switch
binding.enableNotificationsMode.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_NOTIFICATIONS_KEY)));
if (!binding.enableNotificationsMode.isChecked()) {
AppUtil.setMultiVisibility(View.GONE, binding.pollingDelayFrame);
}
binding.enableNotificationsMode.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(isChecked),
AppDatabaseSettings.APP_NOTIFICATIONS_KEY);
if (isChecked) {
Notifications.startWorker(requireContext());
AppUtil.setMultiVisibility(View.VISIBLE, binding.pollingDelayFrame);
} else {
Notifications.stopWorker(requireContext());
AppUtil.setMultiVisibility(View.GONE, binding.pollingDelayFrame);
}
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
binding.enableNotificationsFrame.setOnClickListener(
v ->
binding.enableNotificationsMode.setChecked(
!binding.enableNotificationsMode.isChecked()));
// Polling delay selection
binding.pollingDelayChipGroup.setOnCheckedChangeListener(
(group, checkedId) -> {
int newSelection = getChipPosition(checkedId);
if (newSelection != pollingDelayListSelectedChoice) {
pollingDelayListSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_NOTIFICATIONS_DELAY_KEY);
Notifications.stopWorker(requireContext());
Notifications.startWorker(requireContext());
SettingsFragment.refreshParent = true;
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
});
return binding.getRoot();
}
private void setChipSelection(int position) {
switch (position) {
case 0:
binding.chip15Minutes.setChecked(true);
break;
case 1:
binding.chip30Minutes.setChecked(true);
break;
case 2:
binding.chip45Minutes.setChecked(true);
break;
case 3:
binding.chip1Hour.setChecked(true);
break;
}
}
private int getChipPosition(int checkedId) {
if (checkedId == R.id.chip15Minutes) return 0;
if (checkedId == R.id.chip30Minutes) return 1;
if (checkedId == R.id.chip45Minutes) return 2;
if (checkedId == R.id.chip1Hour) return 3;
return pollingDelayListSelectedChoice; // Fallback to current selection
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null; // Prevent memory leaks
}
}

View File

@@ -11,18 +11,13 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.BaseActivity;
import org.mian.gitnex.activities.SettingsAppearanceActivity;
import org.mian.gitnex.activities.SettingsBackupRestoreActivity;
import org.mian.gitnex.activities.SettingsCodeEditorActivity;
import org.mian.gitnex.activities.SettingsGeneralActivity;
import org.mian.gitnex.activities.SettingsNotificationsActivity;
import org.mian.gitnex.activities.SettingsSecurityActivity;
import org.mian.gitnex.databinding.BottomSheetAboutBinding;
import org.mian.gitnex.databinding.FragmentSettingsBinding;
import org.mian.gitnex.helpers.AppUtil;
/**
* @author mmarif
@@ -59,7 +54,11 @@ public class SettingsFragment extends Fragment {
v1 -> startActivity(new Intent(ctx, SettingsSecurityActivity.class)));
fragmentSettingsBinding.notificationsFrame.setOnClickListener(
v1 -> startActivity(new Intent(ctx, SettingsNotificationsActivity.class)));
v1 ->
new BottomSheetSettingsNotificationsFragment()
.show(
getChildFragmentManager(),
"BottomSheetSettingsNotifications"));
fragmentSettingsBinding.backupData.setText(
getString(
@@ -73,77 +72,12 @@ public class SettingsFragment extends Fragment {
fragmentSettingsBinding.aboutAppFrame.setOnClickListener(
aboutApp ->
new AboutBottomSheetFragment()
new BottomSheetSettingsAboutFragment()
.show(getChildFragmentManager(), "AboutBottomSheet"));
return fragmentSettingsBinding.getRoot();
}
public static class AboutBottomSheetFragment extends BottomSheetDialogFragment {
private BottomSheetAboutBinding binding;
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = BottomSheetAboutBinding.inflate(inflater, container, false);
// Set app version and build
binding.appVersionBuild.setText(
getString(
R.string.appVersionBuild,
AppUtil.getAppVersion(requireContext()),
AppUtil.getAppBuildNo(requireContext())));
// Set server version
binding.userServerVersion.setText(
((BaseActivity) requireActivity()).getAccount().getServerVersion().toString());
// Set up link click listeners
binding.donationLinkPatreon.setOnClickListener(
v -> {
AppUtil.openUrlInBrowser(
requireContext(), getString(R.string.supportLinkPatreon));
dismiss();
});
binding.translateLink.setOnClickListener(
v -> {
AppUtil.openUrlInBrowser(requireContext(), getString(R.string.crowdInLink));
dismiss();
});
binding.appWebsite.setOnClickListener(
v -> {
AppUtil.openUrlInBrowser(
requireContext(), getString(R.string.appWebsiteLink));
dismiss();
});
binding.feedback.setOnClickListener(
v -> {
AppUtil.openUrlInBrowser(
requireContext(), getString(R.string.feedbackLink));
dismiss();
});
// Hide donation link for pro users
if (AppUtil.isPro(requireContext())) {
binding.layoutFrame1.setVisibility(View.GONE);
}
return binding.getRoot();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null; // Prevent memory leaks
}
}
public void rateThisApp() {
try {

View File

@@ -1,102 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/primaryBackgroundColor"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/primaryBackgroundColor">
<com.google.android.material.appbar.CollapsingToolbarLayout
style="?attr/collapsingToolbarLayoutLargeStyle"
android:layout_width="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
android:background="?attr/primaryBackgroundColor"
app:contentScrim="?attr/primaryBackgroundColor"
android:layout_height="?attr/collapsingToolbarLayoutLargeSize">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
android:layout_width="match_parent"
android:elevation="0dp"
android:layout_height="?attr/actionBarSize"
app:title="@string/pageTitleNotifications"
app:layout_collapseMode="pin"
app:navigationIcon="@drawable/ic_close" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/dimen16dp">
<LinearLayout
android:id="@+id/enableNotificationsFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/enableNotificationsHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight=".90"
android:text="@string/enableNotificationsHeaderText"
android:textColor="?attr/primaryTextColor"
android:layout_marginTop="@dimen/dimen4dp"
android:textSize="@dimen/dimen18sp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/enableNotificationsMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/enableNotificationsHeaderText"
android:layout_weight=".10" />
</LinearLayout>
<LinearLayout
android:id="@+id/pollingDelayFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:orientation="vertical">
<TextView
android:id="@+id/pollingDelayHeaderSelector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/notificationsPollingHeaderText"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen18sp" />
<TextView
android:id="@+id/pollingDelaySelected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pollingDelay15Minutes"
android:textColor="?attr/selectedTextColor"
android:textSize="@dimen/dimen16sp" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,131 @@
<?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="wrap_content"
android:background="?attr/primaryBackgroundColor"
android:orientation="vertical"
android:paddingTop="@dimen/dimen6dp"
android:paddingBottom="@dimen/dimen12dp">
<LinearLayout
android:id="@+id/headerFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/dimen8dp">
<TextView
android:id="@+id/bottomSheetHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/pageTitleNotifications"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp"/>
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="@dimen/dimen28dp"
android:layout_height="@dimen/dimen4dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/dimen8dp"
android:layout_marginBottom="@dimen/dimen16dp"
app:cardCornerRadius="@dimen/dimen24dp"
app:cardElevation="@dimen/dimen0dp">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/fabColor" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:id="@+id/enableNotificationsFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp"
android:orientation="horizontal">
<TextView
android:id="@+id/enableNotificationsHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.90"
android:text="@string/enableNotificationsHeaderText"
android:textColor="?attr/primaryTextColor"
android:layout_marginTop="@dimen/dimen4dp"
android:textSize="@dimen/dimen18sp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/enableNotificationsMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/enableNotificationsHeaderText"
android:layout_weight="0.10" />
</LinearLayout>
<LinearLayout
android:id="@+id/pollingDelayFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp"
android:orientation="vertical">
<TextView
android:id="@+id/pollingDelayHeaderSelector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/notificationsPollingHeaderText"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen18sp" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/pollingDelayChipGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen8dp"
app:singleSelection="true"
app:selectionRequired="true">
<com.google.android.material.chip.Chip
android:id="@+id/chip15Minutes"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pollingDelay15Minutes" />
<com.google.android.material.chip.Chip
android:id="@+id/chip30Minutes"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pollingDelay30Minutes" />
<com.google.android.material.chip.Chip
android:id="@+id/chip45Minutes"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pollingDelay45Minutes" />
<com.google.android.material.chip.Chip
android:id="@+id/chip1Hour"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pollingDelay1Hour" />
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
</LinearLayout>