Revamp different screens, add new features and improvements (#1504)
All checks were successful
ci/woodpecker/push/locale Pipeline was successful
ci/woodpecker/push/check Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/finish Pipeline was successful
ci/woodpecker/cron/check Pipeline was successful
ci/woodpecker/cron/build Pipeline was successful
ci/woodpecker/cron/locale Pipeline was successful

closes #939 closes #1505 Reviewed-on: #1504 Co-authored-by: M M Arif <mmarif@swatian.com> Co-committed-by: M M Arif <mmarif@swatian.com>
This commit is contained in:
M M Arif 2025-07-01 18:32:36 +02:00 committed by M M Arif
commit 1acf19d9af

View file

@ -67,6 +67,8 @@ dependencies {
implementation 'androidx.viewpager2:viewpager2:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.navigation:navigation-fragment:2.9.0"
implementation "androidx.navigation:navigation-ui:2.9.0"
implementation "androidx.lifecycle:lifecycle-viewmodel:2.9.1"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
@ -106,9 +108,9 @@ dependencies {
implementation 'ch.acra:acra-mail:5.11.3'
implementation 'ch.acra:acra-limiter:5.11.3'
implementation 'ch.acra:acra-notification:5.11.3'
implementation 'androidx.room:room-runtime:2.7.1'
annotationProcessor 'androidx.room:room-compiler:2.7.1'
implementation "androidx.work:work-runtime:2.10.1"
implementation 'androidx.room:room-runtime:2.7.2'
annotationProcessor 'androidx.room:room-compiler:2.7.2'
implementation "androidx.work:work-runtime:2.10.2"
implementation "io.mikael:urlbuilder:2.0.9"
implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2"
//noinspection GradleDependency
@ -117,7 +119,7 @@ dependencies {
implementation 'com.github.chrisvest:stormpot:2.4.2'
implementation 'androidx.browser:browser:1.8.0'
implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation('org.codeberg.gitnex:tea4j-autodeploy:c4dd83143c') {
implementation('org.codeberg.gitnex:tea4j-autodeploy:0ad7eaf429') {
exclude module: 'org.apache.oltu.oauth2.common'
}
implementation 'io.github.amrdeveloper:codeview:1.3.9'

View file

@ -127,18 +127,9 @@
android:name=".activities.CommitDetailActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
android:theme="@android:style/Theme.NoTitleBar"/>
<activity
android:name=".activities.SettingsAppearanceActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
<activity
android:name=".activities.SettingsCodeEditorActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
<activity
android:name=".activities.ProfileActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
<activity
android:name=".activities.SettingsSecurityActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
<activity
android:name=".activities.AddNewTeamMemberActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
@ -159,12 +150,6 @@
android:name=".activities.CreatePullRequestActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
android:windowSoftInputMode="adjustResize"/>
<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"/>
@ -179,9 +164,6 @@
android:name=".activities.CreateNoteActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".activities.SettingsBackupRestoreActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation" />
<meta-data
android:name="com.samsung.android.keepalive.density"

View file

@ -169,10 +169,12 @@ public class CommitsActivity extends BaseActivity {
repoName,
branchName,
null,
null,
null,
true,
false,
true,
1,
true,
pageSize,
resultLimit,
"");
@ -232,8 +234,10 @@ public class CommitsActivity extends BaseActivity {
repoName,
branchName,
null,
null,
null,
true,
true,
false,
true,
page,
resultLimit,

View file

@ -170,6 +170,7 @@ public class IssueDetailActivity extends BaseActivity
private int loadingFinished = 0;
private MentionHelper mentionHelper;
private boolean pullRequestFetchAttempted = false;
private boolean issueInitialized = false;
private enum Mode {
EDIT,
@ -1094,6 +1095,11 @@ public class IssueDetailActivity extends BaseActivity
private void initWithIssue() {
if (issueInitialized) {
return;
}
issueInitialized = true;
if (!issue.getRepository().hasRepository()) {
getRepoInfo();
} else {
@ -1564,7 +1570,7 @@ public class IssueDetailActivity extends BaseActivity
}
private void checkAndInitWithIssue() {
if (loadingFinishedIssue || pullRequestFetchAttempted) {
if ((loadingFinishedIssue || pullRequestFetchAttempted) && !issueInitialized) {
initWithIssue();
}
}

File diff suppressed because it is too large Load diff

View file

@ -225,16 +225,15 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetListe
return true;
} else if (id == R.id.filterPr) {
BottomSheetPullRequestFilterFragment filterPrBottomSheet =
new BottomSheetPullRequestFilterFragment();
filterPrBottomSheet.show(getSupportFragmentManager(), "repoFilterMenuPrBottomSheet");
BottomSheetPullRequestFilterFragment bottomSheet =
BottomSheetPullRequestFilterFragment.newInstance(repository);
bottomSheet.show(getSupportFragmentManager(), "pullRequestFilterBottomSheet");
return true;
} else if (id == R.id.filterMilestone) {
BottomSheetMilestonesFilterFragment filterMilestoneBottomSheet =
new BottomSheetMilestonesFilterFragment();
filterMilestoneBottomSheet.show(
getSupportFragmentManager(), "repoFilterMenuMilestoneBottomSheet");
BottomSheetMilestonesFilterFragment bottomSheet =
BottomSheetMilestonesFilterFragment.newInstance(repository);
bottomSheet.show(getSupportFragmentManager(), "milestonesFilterBottomSheet");
return true;
} else if (id == R.id.branchCommits) {
@ -242,11 +241,10 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetListe
ctx.startActivity(intent);
return true;
} else if (id == R.id.filterReleases && getAccount().requiresVersion("1.15.0")) {
BottomSheetReleasesTagsFragment bottomSheetReleasesTagsFragment =
new BottomSheetReleasesTagsFragment();
bottomSheetReleasesTagsFragment.show(
getSupportFragmentManager(), "repoFilterReleasesMenuBottomSheet");
} else if (id == R.id.filterReleases) {
BottomSheetReleasesTagsFragment bottomSheet =
BottomSheetReleasesTagsFragment.newInstance(repository);
bottomSheet.show(getSupportFragmentManager(), "releasesTagsFilterBottomSheet");
return true;
}

View file

@ -1,463 +0,0 @@
package org.mian.gitnex.activities;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.timepicker.MaterialTimePicker;
import java.util.LinkedHashMap;
import java.util.Locale;
import org.mian.gitnex.R;
import org.mian.gitnex.databinding.ActivitySettingsAppearanceBinding;
import org.mian.gitnex.fragments.SettingsFragment;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.FontsOverride;
import org.mian.gitnex.helpers.SnackBar;
/**
* @author M M Arif
*/
public class SettingsAppearanceActivity extends BaseActivity {
private static String[] customFontList;
private static int customFontSelectedChoice;
private static String[] themeList;
private static int themeSelectedChoice;
private static int langSelectedChoice;
private static String[] fragmentTabsAnimationList;
private static int fragmentTabsAnimationSelectedChoice;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivitySettingsAppearanceBinding activitySettingsAppearanceBinding =
ActivitySettingsAppearanceBinding.inflate(getLayoutInflater());
setContentView(activitySettingsAppearanceBinding.getRoot());
LinkedHashMap<String, String> lang = new LinkedHashMap<>();
lang.put("sys", getString(R.string.settingsLanguageSystem));
for (String langCode : getResources().getStringArray(R.array.languages)) {
lang.put(langCode, getLanguageDisplayName(langCode));
}
customFontList = getResources().getStringArray(R.array.fonts);
fragmentTabsAnimationList = getResources().getStringArray(R.array.fragmentTabsAnimation);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || "S".equals(Build.VERSION.CODENAME)) {
themeList = getResources().getStringArray(R.array.themesAndroid12);
} else {
themeList = getResources().getStringArray(R.array.themes);
}
activitySettingsAppearanceBinding.topAppBar.setNavigationOnClickListener(v -> finish());
String lightMinute =
String.valueOf(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_THEME_AUTO_LIGHT_MIN_KEY));
String lightHour =
String.valueOf(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_THEME_AUTO_LIGHT_HOUR_KEY));
if (lightMinute.length() == 1) {
lightMinute = "0" + lightMinute;
}
if (lightHour.length() == 1) {
lightHour = "0" + lightHour;
}
String darkMinute =
String.valueOf(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_THEME_AUTO_DARK_MIN_KEY));
String darkHour =
String.valueOf(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_THEME_AUTO_DARK_HOUR_KEY));
if (darkMinute.length() == 1) {
darkMinute = "0" + darkMinute;
}
if (darkHour.length() == 1) {
darkHour = "0" + darkHour;
}
fragmentTabsAnimationSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_TABS_ANIMATION_KEY));
customFontSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_FONT_KEY));
themeSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_THEME_KEY));
activitySettingsAppearanceBinding.lightThemeSelectedTime.setText(
ctx.getResources()
.getString(R.string.settingsThemeTimeSelectedHint, lightHour, lightMinute));
activitySettingsAppearanceBinding.darkThemeSelectedTime.setText(
ctx.getResources()
.getString(R.string.settingsThemeTimeSelectedHint, darkHour, darkMinute));
activitySettingsAppearanceBinding.customFontSelected.setText(
customFontList[customFontSelectedChoice]);
activitySettingsAppearanceBinding.themeSelected.setText(themeList[themeSelectedChoice]);
activitySettingsAppearanceBinding.fragmentTabsAnimationFrameSelected.setText(
fragmentTabsAnimationList[fragmentTabsAnimationSelectedChoice]);
if (themeList[themeSelectedChoice].startsWith("Auto")) {
activitySettingsAppearanceBinding.darkThemeTimeSelectionFrame.setVisibility(
View.VISIBLE);
activitySettingsAppearanceBinding.lightThemeTimeSelectionFrame.setVisibility(
View.VISIBLE);
} else {
activitySettingsAppearanceBinding.darkThemeTimeSelectionFrame.setVisibility(View.GONE);
activitySettingsAppearanceBinding.lightThemeTimeSelectionFrame.setVisibility(View.GONE);
}
activitySettingsAppearanceBinding.switchCounterBadge.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_COUNTER_KEY)));
// counter badge switcher
activitySettingsAppearanceBinding.switchCounterBadge.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
ctx, String.valueOf(isChecked), AppDatabaseSettings.APP_COUNTER_KEY);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
activitySettingsAppearanceBinding.counterBadgeFrame.setOnClickListener(
v ->
activitySettingsAppearanceBinding.switchCounterBadge.setChecked(
!activitySettingsAppearanceBinding.switchCounterBadge.isChecked()));
// hide email and language in user profile screen switcher
activitySettingsAppearanceBinding.switchHideEmailLangInProfile.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
ctx,
AppDatabaseSettings.APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY)));
activitySettingsAppearanceBinding.switchHideEmailLangInProfile.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(isChecked),
AppDatabaseSettings.APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
activitySettingsAppearanceBinding.hideEmailLangInProfileFrame.setOnClickListener(
v ->
activitySettingsAppearanceBinding.switchHideEmailLangInProfile.setChecked(
!activitySettingsAppearanceBinding.switchHideEmailLangInProfile
.isChecked()));
// hide email in app navigation drawer switcher
activitySettingsAppearanceBinding.switchHideEmailNavDrawer.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_USER_HIDE_EMAIL_IN_NAV_KEY)));
activitySettingsAppearanceBinding.switchHideEmailNavDrawer.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(isChecked),
AppDatabaseSettings.APP_USER_HIDE_EMAIL_IN_NAV_KEY);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
activitySettingsAppearanceBinding.hideEmailNavDrawerFrame.setOnClickListener(
v ->
activitySettingsAppearanceBinding.switchHideEmailNavDrawer.setChecked(
!activitySettingsAppearanceBinding.switchHideEmailNavDrawer
.isChecked()));
// show labels in lists(issues, pr) - default is color dots
activitySettingsAppearanceBinding.switchLabelsInListBadge.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_LABELS_IN_LIST_KEY)));
activitySettingsAppearanceBinding.switchLabelsInListBadge.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(isChecked),
AppDatabaseSettings.APP_LABELS_IN_LIST_KEY);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
activitySettingsAppearanceBinding.labelsInListFrame.setOnClickListener(
v ->
activitySettingsAppearanceBinding.switchLabelsInListBadge.setChecked(
!activitySettingsAppearanceBinding.switchLabelsInListBadge
.isChecked()));
// theme selection dialog
activitySettingsAppearanceBinding.themeSelectionFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.themeSelectorDialogTitle)
.setSingleChoiceItems(
themeList,
themeSelectedChoice,
(dialogInterfaceTheme, i) -> {
themeSelectedChoice = i;
activitySettingsAppearanceBinding.themeSelected
.setText(themeList[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings.APP_THEME_KEY);
SettingsFragment.refreshParent = true;
this.recreate();
this.overridePendingTransition(0, 0);
dialogInterfaceTheme.dismiss();
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
materialAlertDialogBuilder.create().show();
});
activitySettingsAppearanceBinding.lightThemeTimeSelectionFrame.setOnClickListener(
view -> lightTimePicker());
activitySettingsAppearanceBinding.darkThemeTimeSelectionFrame.setOnClickListener(
view -> darkTimePicker());
// custom font dialog
activitySettingsAppearanceBinding.customFontFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.settingsCustomFontSelectorDialogTitle)
.setCancelable(customFontSelectedChoice != -1)
.setSingleChoiceItems(
customFontList,
customFontSelectedChoice,
(dialogInterfaceCustomFont, i) -> {
customFontSelectedChoice = i;
activitySettingsAppearanceBinding.customFontSelected
.setText(customFontList[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings.APP_FONT_KEY);
new Handler()
.postDelayed(
() -> {
AppUtil.typeface =
null; // reset typeface
FontsOverride.setDefaultFont(
this);
SettingsFragment.refreshParent =
true;
this.recreate();
this.overridePendingTransition(
0, 0);
},
1000);
dialogInterfaceCustomFont.dismiss();
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
materialAlertDialogBuilder.create().show();
});
// fragment tabs animation dialog
activitySettingsAppearanceBinding.fragmentTabsAnimationFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.fragmentTabsAnimationHeader)
.setCancelable(fragmentTabsAnimationSelectedChoice != -1)
.setSingleChoiceItems(
fragmentTabsAnimationList,
fragmentTabsAnimationSelectedChoice,
(dialogInterfaceTabsAnimation, i) -> {
fragmentTabsAnimationSelectedChoice = i;
activitySettingsAppearanceBinding
.fragmentTabsAnimationFrameSelected.setText(
fragmentTabsAnimationList[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings.APP_TABS_ANIMATION_KEY);
SettingsFragment.refreshParent = true;
this.recreate();
this.overridePendingTransition(0, 0);
dialogInterfaceTabsAnimation.dismiss();
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
materialAlertDialogBuilder.create().show();
});
// language selector dialog
activitySettingsAppearanceBinding.helpTranslate.setOnClickListener(
v12 ->
AppUtil.openUrlInBrowser(
this, getResources().getString(R.string.crowdInLink)));
String[] locale =
AppDatabaseSettings.getSettingsValue(ctx, AppDatabaseSettings.APP_LOCALE_KEY)
.split("\\|");
langSelectedChoice = Integer.parseInt(locale[0]);
activitySettingsAppearanceBinding.tvLanguageSelected.setText(
lang.get(lang.keySet().toArray(new String[0])[langSelectedChoice]));
// language dialog
activitySettingsAppearanceBinding.langFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.settingsLanguageSelectorDialogTitle)
.setCancelable(langSelectedChoice != -1)
.setNeutralButton(getString(R.string.cancelButton), null)
.setSingleChoiceItems(
lang.values().toArray(new String[0]),
langSelectedChoice,
(dialogInterface, i) -> {
String selectedLanguage =
lang.keySet().toArray(new String[0])[i];
AppDatabaseSettings.updateSettingsValue(
ctx,
i + "|" + selectedLanguage,
AppDatabaseSettings.APP_LOCALE_KEY);
SettingsFragment.refreshParent = true;
this.overridePendingTransition(0, 0);
dialogInterface.dismiss();
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
this.recreate();
});
materialAlertDialogBuilder.create().show();
});
}
public void lightTimePicker() {
int hour =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_THEME_AUTO_LIGHT_HOUR_KEY));
int minute =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_THEME_AUTO_LIGHT_MIN_KEY));
MaterialTimePicker materialTimePicker =
new MaterialTimePicker.Builder().setHour(hour).setMinute(minute).build();
materialTimePicker.addOnPositiveButtonClickListener(
selection -> {
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(materialTimePicker.getHour()),
AppDatabaseSettings.APP_THEME_AUTO_LIGHT_HOUR_KEY);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(materialTimePicker.getMinute()),
AppDatabaseSettings.APP_THEME_AUTO_LIGHT_MIN_KEY);
SettingsFragment.refreshParent = true;
overridePendingTransition(0, 0);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
recreate();
});
materialTimePicker.show(getSupportFragmentManager(), "fragmentManager");
}
public void darkTimePicker() {
int hour =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_THEME_AUTO_DARK_HOUR_KEY));
int minute =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_THEME_AUTO_DARK_MIN_KEY));
MaterialTimePicker materialTimePicker =
new MaterialTimePicker.Builder().setHour(hour).setMinute(minute).build();
materialTimePicker.addOnPositiveButtonClickListener(
selection -> {
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(materialTimePicker.getHour()),
AppDatabaseSettings.APP_THEME_AUTO_DARK_HOUR_KEY);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(materialTimePicker.getMinute()),
AppDatabaseSettings.APP_THEME_AUTO_DARK_MIN_KEY);
SettingsFragment.refreshParent = true;
overridePendingTransition(0, 0);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
recreate();
});
materialTimePicker.show(getSupportFragmentManager(), "fragmentManager");
}
private static String getLanguageDisplayName(String langCode) {
Locale english = new Locale("en");
String[] multiCodeLang = langCode.split("-");
String countryCode;
if (langCode.contains("-")) {
langCode = multiCodeLang[0];
countryCode = multiCodeLang[1];
} else {
countryCode = "";
}
Locale translated = new Locale(langCode, countryCode);
return String.format(
"%s (%s)",
translated.getDisplayName(translated), translated.getDisplayName(english));
}
}

View file

@ -1,285 +0,0 @@
package org.mian.gitnex.activities;
import static org.mian.gitnex.helpers.BackupUtil.backupDatabaseFile;
import static org.mian.gitnex.helpers.BackupUtil.checkpointIfWALEnabled;
import static org.mian.gitnex.helpers.BackupUtil.copyFile;
import static org.mian.gitnex.helpers.BackupUtil.copyFileWithStreams;
import static org.mian.gitnex.helpers.BackupUtil.getTempDir;
import static org.mian.gitnex.helpers.BackupUtil.unzip;
import static org.mian.gitnex.helpers.BackupUtil.zip;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import org.mian.gitnex.R;
import org.mian.gitnex.database.api.BaseApi;
import org.mian.gitnex.database.api.UserAccountsApi;
import org.mian.gitnex.database.models.UserAccount;
import org.mian.gitnex.databinding.ActivitySettingsBackupRestoreBinding;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.SnackBar;
/**
* @author M M Arif
*/
public class SettingsBackupRestoreActivity extends BaseActivity {
private final String DATABASE_NAME = "gitnex";
private String BACKUP_DATABASE_NAME;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivitySettingsBackupRestoreBinding viewBinding =
ActivitySettingsBackupRestoreBinding.inflate(getLayoutInflater());
setContentView(viewBinding.getRoot());
viewBinding.topAppBar.setNavigationOnClickListener(v -> finish());
viewBinding.topAppBar.setTitle(
getResources()
.getString(
R.string.backupRestore,
getString(R.string.backup),
getString(R.string.restore)));
BACKUP_DATABASE_NAME = ctx.getString(R.string.appName) + "-" + LocalDate.now() + ".backup";
viewBinding.backupDataFrame.setOnClickListener(
v -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.backup)
.setMessage(
getResources().getString(R.string.backupFilePopupText))
.setNeutralButton(
R.string.cancelButton,
(dialog, which) -> dialog.dismiss())
.setPositiveButton(
R.string.backup,
(dialog, which) -> requestBackupFileDownload());
materialAlertDialogBuilder.create().show();
});
viewBinding.restoreDataFrame.setOnClickListener(
restoreDb -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.restore)
.setMessage(
getResources().getString(R.string.restoreFilePopupText))
.setNeutralButton(
R.string.cancelButton,
(dialog, which) -> dialog.dismiss())
.setPositiveButton(
R.string.restore,
(dialog, which) -> requestRestoreFile());
materialAlertDialogBuilder.create().show();
});
}
ActivityResultLauncher<Intent> activityBackupFileLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
assert result.getData() != null;
Uri backupFileUri = result.getData().getData();
backupDatabaseThread(backupFileUri);
}
});
private void requestBackupFileDownload() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.EXTRA_TITLE, BACKUP_DATABASE_NAME);
intent.setType("application/octet-stream");
activityBackupFileLauncher.launch(intent);
}
private void backupDatabaseThread(Uri backupFileUri) {
List<File> filesToZip = new ArrayList<>();
Thread backupDatabaseThread =
new Thread(
() -> {
File tempDir = getTempDir(ctx);
try {
checkpointIfWALEnabled(ctx, DATABASE_NAME);
File databaseBackupFile =
backupDatabaseFile(
getDatabasePath(DATABASE_NAME).getPath(),
tempDir.getPath() + "/" + DATABASE_NAME);
filesToZip.add(databaseBackupFile);
String tempZipFilename = "temp.backup";
boolean zipFileStatus =
zip(filesToZip, tempDir.getPath(), tempZipFilename);
if (zipFileStatus) {
File tempZipFile = new File(tempDir, tempZipFilename);
Uri zipFileUri = Uri.fromFile(tempZipFile);
InputStream inputStream =
getContentResolver().openInputStream(zipFileUri);
OutputStream outputStream =
getContentResolver().openOutputStream(backupFileUri);
boolean copySucceeded =
copyFileWithStreams(inputStream, outputStream);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.backupFileSuccess));
if (copySucceeded) {
tempZipFile.delete();
} else {
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.backupFileError));
}
} else {
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.backupFileError));
}
} catch (final Exception e) {
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.backupFileError));
} finally {
for (File file : filesToZip) {
if (file != null && file.exists()) {
file.delete();
}
}
}
});
backupDatabaseThread.setDaemon(false);
backupDatabaseThread.start();
}
private void requestRestoreFile() {
Intent intentRestore = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intentRestore.addCategory(Intent.CATEGORY_OPENABLE);
intentRestore.setType("*/*");
String[] mimeTypes = {"application/octet-stream", "application/x-zip"};
intentRestore.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
activityRestoreFileLauncher.launch(intentRestore);
}
ActivityResultLauncher<Intent> activityRestoreFileLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
assert result.getData() != null;
Uri restoreFileUri = result.getData().getData();
assert restoreFileUri != null;
try {
InputStream inputStream =
getContentResolver().openInputStream(restoreFileUri);
restoreDatabaseThread(inputStream);
} catch (FileNotFoundException e) {
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.restoreError));
}
}
});
private void restoreDatabaseThread(InputStream inputStream) {
Thread restoreDatabaseThread =
new Thread(
() -> {
boolean exceptionOccurred = false;
try {
String tempDir = getTempDir(ctx).getPath();
unzip(inputStream, tempDir);
checkpointIfWALEnabled(ctx, DATABASE_NAME);
restoreDatabaseFile(ctx, tempDir, DATABASE_NAME);
UserAccountsApi userAccountsApi =
BaseApi.getInstance(ctx, UserAccountsApi.class);
assert userAccountsApi != null;
UserAccount account = userAccountsApi.getAccountById(1);
AppUtil.switchToAccount(ctx, account);
} catch (final Exception e) {
exceptionOccurred = true;
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.restoreError));
} finally {
if (!exceptionOccurred) {
runOnUiThread(this::restartApp);
}
}
});
restoreDatabaseThread.setDaemon(false);
restoreDatabaseThread.start();
}
public void restoreDatabaseFile(Context context, String tempDir, String nameOfFileToRestore)
throws IOException {
File currentDbFile = new File(context.getDatabasePath(DATABASE_NAME).getPath());
File newDbFile = new File(tempDir + "/" + nameOfFileToRestore);
if (newDbFile.exists()) {
copyFile(newDbFile, currentDbFile, false);
}
}
public void restartApp() {
Intent i = ctx.getPackageManager().getLaunchIntentForPackage(ctx.getPackageName());
assert i != null;
startActivity(Intent.makeRestartActivityTask(i.getComponent()));
Runtime.getRuntime().exit(0);
}
}

View file

@ -1,162 +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.ActivitySettingsCodeEditorBinding;
import org.mian.gitnex.fragments.SettingsFragment;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.SnackBar;
/**
* @author M M Arif
*/
public class SettingsCodeEditorActivity extends BaseActivity {
private static String[] colorList;
private static int colorSelectedChoice;
private static String[] indentationList;
private static int indentationSelectedChoice;
private static String[] indentationTabsList;
private static int indentationTabsSelectedChoice;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivitySettingsCodeEditorBinding activitySettingsCodeEditorBinding =
ActivitySettingsCodeEditorBinding.inflate(getLayoutInflater());
setContentView(activitySettingsCodeEditorBinding.getRoot());
activitySettingsCodeEditorBinding.topAppBar.setNavigationOnClickListener(v -> finish());
// color selector dialog
colorList = getResources().getStringArray(R.array.ceColors);
colorSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_CE_SYNTAX_HIGHLIGHT_KEY));
activitySettingsCodeEditorBinding.ceColorSelected.setText(colorList[colorSelectedChoice]);
activitySettingsCodeEditorBinding.ceColorSelectionFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.ceSyntaxHighlightColor)
.setSingleChoiceItems(
colorList,
colorSelectedChoice,
(dialogInterfaceColor, i) -> {
colorSelectedChoice = i;
activitySettingsCodeEditorBinding.ceColorSelected
.setText(colorList[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings
.APP_CE_SYNTAX_HIGHLIGHT_KEY);
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();
});
// indentation selector dialog
indentationList = getResources().getStringArray(R.array.ceIndentation);
indentationSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_CE_INDENTATION_KEY));
activitySettingsCodeEditorBinding.indentationSelected.setText(
indentationList[indentationSelectedChoice]);
activitySettingsCodeEditorBinding.indentationSelectionFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.ceIndentation)
.setSingleChoiceItems(
indentationList,
indentationSelectedChoice,
(dialogInterfaceColor, i) -> {
indentationSelectedChoice = i;
activitySettingsCodeEditorBinding
.indentationSelected.setText(
indentationList[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings.APP_CE_INDENTATION_KEY);
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();
});
// indentation tabs selector dialog
if (indentationList[indentationSelectedChoice].startsWith("Tabs")) {
activitySettingsCodeEditorBinding.indentationTabsSelectionFrame.setVisibility(
View.VISIBLE);
} else {
activitySettingsCodeEditorBinding.indentationTabsSelectionFrame.setVisibility(
View.GONE);
}
indentationTabsList = getResources().getStringArray(R.array.ceIndentationTabsWidth);
indentationTabsSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_CE_TABS_WIDTH_KEY));
activitySettingsCodeEditorBinding.indentationTabsSelected.setText(
indentationTabsList[indentationTabsSelectedChoice]);
activitySettingsCodeEditorBinding.indentationTabsSelectionFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.ceIndentationTabsWidth)
.setSingleChoiceItems(
indentationTabsList,
indentationTabsSelectedChoice,
(dialogInterfaceColor, i) -> {
indentationTabsSelectedChoice = i;
activitySettingsCodeEditorBinding
.indentationTabsSelected.setText(
indentationTabsList[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings.APP_CE_TABS_WIDTH_KEY);
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

@ -1,161 +0,0 @@
package org.mian.gitnex.activities;
import android.os.Bundle;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.mian.gitnex.R;
import org.mian.gitnex.databinding.ActivitySettingsGeneralBinding;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.SnackBar;
/**
* @author M M Arif
*/
public class SettingsGeneralActivity extends BaseActivity {
private static int homeScreenSelectedChoice;
private static int defaultLinkHandlerScreenSelectedChoice;
private ActivitySettingsGeneralBinding viewBinding;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewBinding = ActivitySettingsGeneralBinding.inflate(getLayoutInflater());
setContentView(viewBinding.getRoot());
viewBinding.topAppBar.setNavigationOnClickListener(v -> finish());
// home screen
String[] appHomeDefaultScreen =
getResources().getStringArray(R.array.appDefaultHomeScreenNew);
homeScreenSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_HOME_SCREEN_KEY));
viewBinding.homeScreenSelected.setText(appHomeDefaultScreen[homeScreenSelectedChoice]);
viewBinding.homeScreenFrame.setOnClickListener(
setDefaultHomeScreen -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.settingsHomeScreenSelectorDialogTitle)
.setCancelable(homeScreenSelectedChoice != -1)
.setSingleChoiceItems(
appHomeDefaultScreen,
homeScreenSelectedChoice,
(dialogInterfaceHomeScreen, i) -> {
homeScreenSelectedChoice = i;
viewBinding.homeScreenSelected.setText(
appHomeDefaultScreen[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings.APP_HOME_SCREEN_KEY);
dialogInterfaceHomeScreen.dismiss();
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
materialAlertDialogBuilder.create().show();
});
// home screen
// link handler
String[] linkHandlerDefaultScreenList =
getResources().getStringArray(R.array.linkHandlerDefaultScreen);
List<String> linkHandlerDefaultScreen =
new ArrayList<>(Arrays.asList(linkHandlerDefaultScreenList));
String[] linksArray = new String[linkHandlerDefaultScreen.size()];
linkHandlerDefaultScreen.toArray(linksArray);
defaultLinkHandlerScreenSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_LINK_HANDLER_KEY));
viewBinding.generalDeepLinkSelected.setText(
linksArray[defaultLinkHandlerScreenSelectedChoice]);
viewBinding.setDefaultLinkHandler.setOnClickListener(
setDefaultLinkHandler -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.linkSelectorDialogTitle)
.setCancelable(defaultLinkHandlerScreenSelectedChoice != -1)
.setSingleChoiceItems(
linksArray,
defaultLinkHandlerScreenSelectedChoice,
(dialogInterfaceHomeScreen, i) -> {
defaultLinkHandlerScreenSelectedChoice = i;
viewBinding.generalDeepLinkSelected.setText(
linksArray[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings.APP_LINK_HANDLER_KEY);
dialogInterfaceHomeScreen.dismiss();
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
materialAlertDialogBuilder.create().show();
});
// link handler
// custom tabs switcher
viewBinding.switchTabs.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_CUSTOM_BROWSER_KEY)));
viewBinding.switchTabs.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(isChecked),
AppDatabaseSettings.APP_CUSTOM_BROWSER_KEY);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
viewBinding.customTabsFrame.setOnClickListener(
v -> viewBinding.switchTabs.setChecked(!viewBinding.switchTabs.isChecked()));
// custom tabs switcher
// crash reports switcher
viewBinding.crashReportsSwitch.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_CRASH_REPORTS_KEY)));
viewBinding.crashReportsSwitch.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(isChecked),
AppDatabaseSettings.APP_CRASH_REPORTS_KEY);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
viewBinding.enableSendReports.setOnClickListener(
v ->
viewBinding.crashReportsSwitch.setChecked(
!viewBinding.crashReportsSwitch.isChecked()));
// crash reports switcher
}
}

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

@ -1,279 +0,0 @@
package org.mian.gitnex.activities;
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
import android.app.KeyguardManager;
import android.content.Context;
import android.os.Bundle;
import androidx.biometric.BiometricManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.mian.gitnex.R;
import org.mian.gitnex.databinding.ActivitySettingsSecurityBinding;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.SnackBar;
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
/**
* @author M M Arif
*/
public class SettingsSecurityActivity extends BaseActivity {
private static String[] cacheSizeDataList;
private static int cacheSizeDataSelectedChoice;
private static String[] cacheSizeImagesList;
private static int cacheSizeImagesSelectedChoice;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivitySettingsSecurityBinding activitySettingsSecurityBinding =
ActivitySettingsSecurityBinding.inflate(getLayoutInflater());
setContentView(activitySettingsSecurityBinding.getRoot());
activitySettingsSecurityBinding.topAppBar.setNavigationOnClickListener(v -> finish());
cacheSizeDataList = getResources().getStringArray(R.array.cacheSizeList);
cacheSizeImagesList = getResources().getStringArray(R.array.cacheSizeList);
cacheSizeDataSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_DATA_CACHE_KEY));
cacheSizeImagesSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_IMAGES_CACHE_KEY));
activitySettingsSecurityBinding.cacheSizeDataSelected.setText(
cacheSizeDataList[cacheSizeDataSelectedChoice]);
activitySettingsSecurityBinding.cacheSizeImagesSelected.setText(
cacheSizeImagesList[cacheSizeImagesSelectedChoice]);
activitySettingsSecurityBinding.switchBiometric.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_BIOMETRIC_KEY)));
// biometric switcher
activitySettingsSecurityBinding.switchBiometric.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked) {
BiometricManager biometricManager = BiometricManager.from(ctx);
KeyguardManager keyguardManager =
(KeyguardManager) ctx.getSystemService(Context.KEYGUARD_SERVICE);
if (!keyguardManager.isDeviceSecure()) {
switch (biometricManager.canAuthenticate(
BIOMETRIC_STRONG | DEVICE_CREDENTIAL)) {
case BiometricManager.BIOMETRIC_SUCCESS:
AppDatabaseSettings.updateSettingsValue(
ctx, "true", AppDatabaseSettings.APP_BIOMETRIC_KEY);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
break;
case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
case BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED:
case BiometricManager.BIOMETRIC_STATUS_UNKNOWN:
AppDatabaseSettings.updateSettingsValue(
ctx, "false", AppDatabaseSettings.APP_BIOMETRIC_KEY);
activitySettingsSecurityBinding.switchBiometric.setChecked(
false);
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.biometricNotSupported));
break;
case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
AppDatabaseSettings.updateSettingsValue(
ctx, "false", AppDatabaseSettings.APP_BIOMETRIC_KEY);
activitySettingsSecurityBinding.switchBiometric.setChecked(
false);
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.biometricNotAvailable));
break;
case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
AppDatabaseSettings.updateSettingsValue(
ctx, "false", AppDatabaseSettings.APP_BIOMETRIC_KEY);
activitySettingsSecurityBinding.switchBiometric.setChecked(
false);
SnackBar.info(
ctx,
findViewById(android.R.id.content),
getString(R.string.enrollBiometric));
break;
}
} else {
AppDatabaseSettings.updateSettingsValue(
ctx, "true", AppDatabaseSettings.APP_BIOMETRIC_KEY);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
} else {
AppDatabaseSettings.updateSettingsValue(
ctx, "false", AppDatabaseSettings.APP_BIOMETRIC_KEY);
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
});
activitySettingsSecurityBinding.biometricFrame.setOnClickListener(
v ->
activitySettingsSecurityBinding.switchBiometric.setChecked(
!activitySettingsSecurityBinding.switchBiometric.isChecked()));
// clear cache setter
File cacheDir = appCtx.getCacheDir();
activitySettingsSecurityBinding.clearCacheSelected.setText(
FileUtils.byteCountToDisplaySize((int) FileUtils.sizeOfDirectory(cacheDir)));
// clear cache
activitySettingsSecurityBinding.clearCacheSelectionFrame.setOnClickListener(
v1 -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.clearCacheDialogHeader)
.setMessage(
getResources()
.getString(R.string.clearCacheDialogMessage))
.setNeutralButton(
R.string.cancelButton,
(dialog, which) -> dialog.dismiss())
.setPositiveButton(
R.string.menuDeleteText,
(dialog, which) -> {
try {
FileUtils.deleteDirectory(cacheDir);
FileUtils.forceMkdir(cacheDir);
this.recreate();
this.overridePendingTransition(0, 0);
} catch (IOException e) {
// Log.e("SettingsSecurity", e.toString());
}
});
materialAlertDialogBuilder.create().show();
});
// cache size images selection dialog
activitySettingsSecurityBinding.cacheSizeImagesSelectionFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.cacheSizeImagesDialogHeader)
.setCancelable(cacheSizeImagesSelectedChoice != -1)
.setSingleChoiceItems(
cacheSizeImagesList,
cacheSizeImagesSelectedChoice,
(dialogInterfaceTheme, i) -> {
cacheSizeImagesSelectedChoice = i;
activitySettingsSecurityBinding
.cacheSizeImagesSelected.setText(
cacheSizeImagesList[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
cacheSizeImagesList[i],
AppDatabaseSettings
.APP_IMAGES_CACHE_SIZE_KEY);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings.APP_IMAGES_CACHE_KEY);
dialogInterfaceTheme.dismiss();
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
materialAlertDialogBuilder.create().show();
});
// cache size data selection dialog
activitySettingsSecurityBinding.cacheSizeDataSelectionFrame.setOnClickListener(
view -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.cacheSizeDataDialogHeader)
.setCancelable(cacheSizeDataSelectedChoice != -1)
.setSingleChoiceItems(
cacheSizeDataList,
cacheSizeDataSelectedChoice,
(dialogInterfaceTheme, i) -> {
cacheSizeDataSelectedChoice = i;
activitySettingsSecurityBinding
.cacheSizeDataSelected.setText(
cacheSizeDataList[i]);
AppDatabaseSettings.updateSettingsValue(
ctx,
cacheSizeDataList[i],
AppDatabaseSettings
.APP_DATA_CACHE_SIZE_KEY);
AppDatabaseSettings.updateSettingsValue(
ctx,
String.valueOf(i),
AppDatabaseSettings.APP_DATA_CACHE_KEY);
dialogInterfaceTheme.dismiss();
SnackBar.success(
ctx,
findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
materialAlertDialogBuilder.create().show();
});
// certs deletion
activitySettingsSecurityBinding.certsFrame.setOnClickListener(
v1 -> {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.settingsCertsPopupTitle)
.setMessage(
getResources()
.getString(R.string.settingsCertsPopupMessage))
.setNeutralButton(
R.string.cancelButton,
(dialog, which) -> dialog.dismiss())
.setPositiveButton(
R.string.menuDeleteText,
(dialog, which) -> {
appCtx.getSharedPreferences(
MemorizingTrustManager
.KEYSTORE_NAME,
Context.MODE_PRIVATE)
.edit()
.remove(MemorizingTrustManager.KEYSTORE_KEY)
.apply();
AppUtil.logout(this);
});
materialAlertDialogBuilder.create().show();
});
}
}

View file

@ -40,7 +40,7 @@ import org.mian.gitnex.helpers.contexts.RepositoryContext;
/**
* @author M M Arif
*/
public class DashboardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public class ActivitiesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context;
TinyDB tinyDb;
@ -50,7 +50,7 @@ public class DashboardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
private Intent intent;
public boolean isUserOrg = false;
public DashboardAdapter(List<Activity> dataList, Context ctx) {
public ActivitiesAdapter(List<Activity> dataList, Context ctx) {
this.context = ctx;
this.activityList = dataList;
this.tinyDb = TinyDB.getInstance(ctx);
@ -59,8 +59,8 @@ public class DashboardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
@NonNull @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
return new DashboardAdapter.DashboardHolder(
inflater.inflate(R.layout.list_dashboard_activity, parent, false));
return new ActivitiesAdapter.DashboardHolder(
inflater.inflate(R.layout.list_activities, parent, false));
}
@Override
@ -73,7 +73,7 @@ public class DashboardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
loadMoreListener.onLoadMore();
}
((DashboardAdapter.DashboardHolder) holder).bindData(activityList.get(position), position);
((ActivitiesAdapter.DashboardHolder) holder).bindData(activityList.get(position), position);
}
@Override

View file

@ -78,30 +78,30 @@ public class CommitStatusesAdapter
holder.status = currentItem;
holder.name.setText(currentItem.getContext());
holder.description.setText(currentItem.getDescription());
switch (currentItem.getStatus().toLowerCase()) {
case "pending":
switch (currentItem.getStatus()) {
case PENDING:
holder.icon.setImageResource(R.drawable.ic_dot_fill);
ImageViewCompat.setImageTintList(
holder.icon,
ColorStateList.valueOf(
ctx.getResources().getColor(R.color.lightYellow, null)));
break;
case "success":
case SUCCESS:
holder.icon.setImageResource(R.drawable.ic_check);
ImageViewCompat.setImageTintList(
holder.icon,
ColorStateList.valueOf(
ctx.getResources().getColor(R.color.colorLightGreen, null)));
break;
case "error":
case "failure":
case ERROR:
case FAILURE:
holder.icon.setImageResource(R.drawable.ic_close);
ImageViewCompat.setImageTintList(
holder.icon,
ColorStateList.valueOf(
ctx.getResources().getColor(R.color.iconIssuePrClosedColor, null)));
break;
case "warning":
case WARNING:
holder.icon.setImageResource(R.drawable.ic_warning);
ImageViewCompat.setImageTintList(
holder.icon,

View file

@ -0,0 +1,191 @@
package org.mian.gitnex.adapters;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.ProfileActivity;
import org.mian.gitnex.helpers.TinyDB;
/**
* @author mmarif
*/
public class HomeDashboardAdapter
extends RecyclerView.Adapter<HomeDashboardAdapter.CategoryViewHolder> {
private final Context context;
private final List<CategoryData> categories;
private final TinyDB tinyDB;
private String username;
public static class ItemData {
public int iconResId;
public String title;
public String destinationId;
public ItemData(int iconResId, String title, String destinationId) {
this.iconResId = iconResId;
this.title = title;
this.destinationId = destinationId;
}
}
public static class CategoryData {
public String header;
public List<ItemData> items;
public CategoryData(String header, List<ItemData> items) {
this.header = header;
this.items = items;
}
}
public HomeDashboardAdapter(Context context) {
this.context = context;
this.tinyDB = TinyDB.getInstance(context);
this.categories = new ArrayList<>();
this.username = tinyDB.getString("username");
// Personal
List<ItemData> personalItems = new ArrayList<>();
personalItems.add(
new ItemData(
R.drawable.ic_trending,
context.getString(R.string.navMostVisited),
"nav_most_visited"));
personalItems.add(
new ItemData(
R.drawable.ic_person,
context.getString(R.string.navProfile),
"profileActivity"));
personalItems.add(
new ItemData(
R.drawable.ic_notes, context.getString(R.string.navNotes), "nav_notes"));
categories.add(new CategoryData(context.getString(R.string.personal), personalItems));
// Settings
List<ItemData> settingsItems = new ArrayList<>();
settingsItems.add(
new ItemData(
R.drawable.ic_account_settings,
context.getString(R.string.navAccount),
"nav_account_settings"));
settingsItems.add(
new ItemData(
R.drawable.ic_tool,
context.getString(R.string.navAdministration),
"nav_administration"));
settingsItems.add(
new ItemData(
R.drawable.ic_settings,
context.getString(R.string.navSettings),
"nav_settings"));
categories.add(new CategoryData(context.getString(R.string.navSettings), settingsItems));
}
@NonNull @Override
public CategoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view =
LayoutInflater.from(context)
.inflate(R.layout.list_home_dashboard_item, parent, false);
return new CategoryViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CategoryViewHolder holder, int position) {
CategoryData category = categories.get(position);
holder.header.setVisibility(View.VISIBLE);
holder.header.setText(category.header);
holder.categoryCard.setVisibility(View.VISIBLE);
holder.itemContainer.setOrientation(LinearLayout.VERTICAL);
holder.itemContainer.removeAllViews();
for (ItemData item : category.items) {
View itemView =
LayoutInflater.from(context)
.inflate(
R.layout.list_home_dashboard_subitem,
holder.itemContainer,
false);
ImageView icon = itemView.findViewById(R.id.itemIcon);
TextView title = itemView.findViewById(R.id.itemTitle);
icon.setImageResource(item.iconResId);
title.setText(item.title);
if (item.destinationId.equals("nav_administration")) {
itemView.setVisibility(tinyDB.getBoolean("isAdmin") ? View.VISIBLE : View.GONE);
} else {
itemView.setVisibility(View.VISIBLE);
}
itemView.setOnClickListener(
v -> {
NavController navController = Navigation.findNavController(v);
switch (item.destinationId) {
case "nav_my_issues":
navController.navigate(R.id.action_to_myIssues);
break;
case "nav_most_visited":
navController.navigate(R.id.action_to_mostVisitedRepos);
break;
case "profileActivity":
Intent intentProfile = new Intent(context, ProfileActivity.class);
intentProfile.putExtra("username", username);
context.startActivity(intentProfile);
break;
case "nav_notes":
navController.navigate(R.id.action_to_notes);
break;
case "nav_account_settings":
navController.navigate(R.id.action_to_accountSettings);
break;
case "nav_administration":
navController.navigate(R.id.action_to_administration);
break;
case "nav_settings":
navController.navigate(R.id.action_to_settings);
break;
}
});
holder.itemContainer.addView(itemView);
}
}
@Override
public int getItemCount() {
return categories.size();
}
public static class CategoryViewHolder extends RecyclerView.ViewHolder {
TextView header;
LinearLayout itemContainer;
com.google.android.material.card.MaterialCardView categoryCard;
CategoryViewHolder(View itemView) {
super(itemView);
header = itemView.findViewById(R.id.sectionHeader);
itemContainer = itemView.findViewById(R.id.itemContainer);
categoryCard = itemView.findViewById(R.id.categoryCard);
}
}
@SuppressLint("NotifyDataSetChanged")
public void updateUserInfo(String username, boolean isAdmin, String serverVersion) {
this.username = username;
tinyDB.putString("username", username);
tinyDB.putBoolean("isAdmin", isAdmin);
tinyDB.putString("serverVersion", serverVersion);
notifyDataSetChanged();
}
}

View file

@ -175,13 +175,14 @@ public class NotificationsAdapter extends RecyclerView.Adapter<RecyclerView.View
type,
ColorStateList.valueOf(
context.getResources()
.getColor(R.color.iconIssuePrClosedColor)));
.getColor(R.color.iconIssuePrClosedColor, null)));
break;
case "merged":
ImageViewCompat.setImageTintList(
type,
ColorStateList.valueOf(
context.getResources().getColor(R.color.iconPrMergedColor)));
context.getResources()
.getColor(R.color.iconPrMergedColor, null)));
break;
default:

View file

@ -12,7 +12,6 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
@ -29,16 +28,13 @@ import org.mian.gitnex.helpers.UrlHelper;
public class UserAccountsNavAdapter
extends RecyclerView.Adapter<UserAccountsNavAdapter.UserAccountsViewHolder> {
private final DrawerLayout drawer;
private final List<UserAccount> userAccountsList;
private final Context context;
public UserAccountsNavAdapter(
Context ctx, List<UserAccount> userAccountsListMain, DrawerLayout drawerLayout) {
public UserAccountsNavAdapter(Context ctx, List<UserAccount> userAccountsListMain) {
this.context = ctx;
this.userAccountsList = userAccountsListMain;
this.drawer = drawerLayout;
}
@NonNull @Override
@ -109,7 +105,6 @@ public class UserAccountsNavAdapter
itemView.setOnClickListener(
item -> {
customDialogUserAccountsList();
drawer.closeDrawers();
});
}
}

View file

@ -15,15 +15,12 @@ import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.BaseActivity;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.ViewPager2Transformers;
import org.mian.gitnex.helpers.contexts.AccountContext;
/**
* @author M M Arif
* @author mmarif
*/
public class AccountSettingsFragment extends Fragment {
@ -41,23 +38,9 @@ public class AccountSettingsFragment extends Fragment {
ctx = getContext();
view = inflater.inflate(R.layout.fragment_account_settings, container, false);
setHasOptionsMenu(false);
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.navAccount));
myTypeface = AppUtil.getTypeface(ctx);
AccountContext account = ((BaseActivity) requireActivity()).getAccount();
if (account.getUserInfo() != null) {
viewData();
} else {
((MainActivity) requireActivity())
.setProfileInitListener(
(text) -> {
viewData();
});
}
viewData();
return view;
}

View file

@ -14,23 +14,21 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList;
import java.util.List;
import org.gitnex.tea4j.v2.models.Activity;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.BaseActivity;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.adapters.DashboardAdapter;
import org.mian.gitnex.databinding.FragmentDashboardBinding;
import org.mian.gitnex.adapters.ActivitiesAdapter;
import org.mian.gitnex.databinding.FragmentActivitiesBinding;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.viewmodels.DashboardViewModel;
import org.mian.gitnex.viewmodels.ActivitiesViewModel;
/**
* @author M M Arif
*/
public class DashboardFragment extends Fragment {
public class ActivitiesFragment extends Fragment {
protected TinyDB tinyDB;
private DashboardViewModel dashboardViewModel;
private FragmentDashboardBinding binding;
private DashboardAdapter adapter;
private ActivitiesViewModel viewModel;
private FragmentActivitiesBinding binding;
private ActivitiesAdapter adapter;
private List<Activity> activityList;
private int page = 1;
private String username;
@ -39,23 +37,20 @@ public class DashboardFragment extends Fragment {
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentDashboardBinding.inflate(inflater, container, false);
binding = FragmentActivitiesBinding.inflate(inflater, container, false);
Context ctx = getContext();
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.dashboard));
activityList = new ArrayList<>();
dashboardViewModel = new ViewModelProvider(this).get(DashboardViewModel.class);
viewModel = new ViewModelProvider(this).get(ActivitiesViewModel.class);
username = ((BaseActivity) requireActivity()).getAccount().getAccount().getUserName();
binding.recyclerView.setHasFixedSize(true);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
adapter = new DashboardAdapter(activityList, ctx);
adapter = new ActivitiesAdapter(activityList, ctx);
binding.pullToRefresh.setOnRefreshListener(
() ->
@ -82,20 +77,20 @@ public class DashboardFragment extends Fragment {
private void fetchDataAsync(String username) {
dashboardViewModel
viewModel
.getActivitiesList(username, getContext(), binding)
.observe(
getViewLifecycleOwner(),
activityListMain -> {
adapter = new DashboardAdapter(activityListMain, getContext());
adapter = new ActivitiesAdapter(activityListMain, getContext());
adapter.setLoadMoreListener(
new DashboardAdapter.OnLoadMoreListener() {
new ActivitiesAdapter.OnLoadMoreListener() {
@Override
public void onLoadMore() {
page += 1;
dashboardViewModel.loadMoreActivities(
viewModel.loadMoreActivities(
username, page, getContext(), adapter, binding);
binding.progressBar.setVisibility(View.VISIBLE);
}

View file

@ -159,7 +159,7 @@ public class BottomSheetIssueDependenciesFragment extends BottomSheetDialogFragm
Call<Void> call =
RetrofitClient.getApiInterface(requireContext())
.customIssueRemoveIssueDependencies(
.issueRemoveIssueDependencies2(
issue.getRepository().getOwner(),
issue.getRepository().getName(),
String.valueOf(issue.getIssue().getId()),

View file

@ -2,7 +2,6 @@ package org.mian.gitnex.fragments;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -10,6 +9,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.mian.gitnex.databinding.BottomSheetMilestonesFilterBinding;
import org.mian.gitnex.helpers.contexts.RepositoryContext;
import org.mian.gitnex.structs.BottomSheetListener;
/**
@ -18,6 +18,16 @@ import org.mian.gitnex.structs.BottomSheetListener;
public class BottomSheetMilestonesFilterFragment extends BottomSheetDialogFragment {
private BottomSheetListener bmListener;
private BottomSheetMilestonesFilterBinding binding;
private RepositoryContext repository;
public static BottomSheetMilestonesFilterFragment newInstance(RepositoryContext repository) {
BottomSheetMilestonesFilterFragment fragment = new BottomSheetMilestonesFilterFragment();
Bundle args = new Bundle();
args.putSerializable(RepositoryContext.INTENT_EXTRA, repository);
fragment.setArguments(args);
return fragment;
}
@Nullable @Override
public View onCreateView(
@ -25,33 +35,61 @@ public class BottomSheetMilestonesFilterFragment extends BottomSheetDialogFragme
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
BottomSheetMilestonesFilterBinding bottomSheetMilestonesFilterBinding =
BottomSheetMilestonesFilterBinding.inflate(inflater, container, false);
binding = BottomSheetMilestonesFilterBinding.inflate(inflater, container, false);
bottomSheetMilestonesFilterBinding.openMilestone.setOnClickListener(
v1 -> {
bmListener.onButtonClicked("openMilestone");
dismiss();
if (getArguments() != null) {
repository =
(RepositoryContext)
getArguments().getSerializable(RepositoryContext.INTENT_EXTRA);
}
if (repository == null) {
throw new IllegalStateException("RepositoryContext is required");
}
binding.openChip.setChecked(repository.getMilestoneState() == RepositoryContext.State.OPEN);
binding.closedChip.setChecked(
repository.getMilestoneState() == RepositoryContext.State.CLOSED);
binding.openChip.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked
&& repository.getMilestoneState() != RepositoryContext.State.OPEN) {
repository.setMilestoneState(RepositoryContext.State.OPEN);
bmListener.onButtonClicked("openMilestone");
dismiss();
} else if (!isChecked) {
buttonView.setChecked(true);
}
});
bottomSheetMilestonesFilterBinding.closedMilestone.setOnClickListener(
v12 -> {
bmListener.onButtonClicked("closedMilestone");
dismiss();
binding.closedChip.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked
&& repository.getMilestoneState() != RepositoryContext.State.CLOSED) {
repository.setMilestoneState(RepositoryContext.State.CLOSED);
bmListener.onButtonClicked("closedMilestone");
dismiss();
} else if (!isChecked) {
buttonView.setChecked(true);
}
});
return bottomSheetMilestonesFilterBinding.getRoot();
return binding.getRoot();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
bmListener = (BottomSheetListener) context;
} catch (ClassCastException e) {
Log.e("MilestonesFilterBs", e.toString());
throw new ClassCastException(context + " must implement BottomSheetListener");
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

View file

@ -1,116 +0,0 @@
package org.mian.gitnex.fragments;
import android.content.Context;
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.databinding.BottomSheetMyIssuesFilterBinding;
/**
* @author M M Arif
*/
public class BottomSheetMyIssuesFilterFragment extends BottomSheetDialogFragment {
private BottomSheetListener bmListener;
private String selectedState = "open";
private String selectedFilter = "created_by_me";
private static final String ARG_FILTER = "currentFilter";
public static BottomSheetMyIssuesFilterFragment newInstance(String currentFilter) {
BottomSheetMyIssuesFilterFragment fragment = new BottomSheetMyIssuesFilterFragment();
Bundle args = new Bundle();
args.putString(ARG_FILTER, currentFilter);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null && args.containsKey(ARG_FILTER)) {
String currentFilter = args.getString(ARG_FILTER, "open_created_by_me");
String[] parts = currentFilter.split("_");
selectedState = parts[0];
selectedFilter = parts.length > 1 ? parts[1] : "created_by_me";
}
}
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
BottomSheetMyIssuesFilterBinding binding =
BottomSheetMyIssuesFilterBinding.inflate(inflater, container, false);
binding.chipOpen.setChecked(false);
binding.chipClosed.setChecked(false);
binding.chipCreatedByMe.setChecked(false);
binding.chipAssignedToMe.setChecked(false);
binding.chipOpen.setChecked(selectedState.equals("open"));
binding.chipClosed.setChecked(selectedState.equals("closed"));
binding.chipCreatedByMe.setChecked(selectedFilter.equals("created_by_me"));
binding.chipAssignedToMe.setChecked(selectedFilter.equals("assignedToMe"));
binding.stateChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (!checkedIds.isEmpty()) {
int checkedId = checkedIds.get(0);
if (checkedId == binding.chipOpen.getId()) {
selectedState = "open";
} else if (checkedId == binding.chipClosed.getId()) {
selectedState = "closed";
}
applyFilter();
}
});
binding.filterChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (!checkedIds.isEmpty()) {
int checkedId = checkedIds.get(0);
if (checkedId == binding.chipCreatedByMe.getId()) {
selectedFilter = "created_by_me";
binding.chipAssignedToMe.setChecked(false);
} else if (checkedId == binding.chipAssignedToMe.getId()) {
selectedFilter = "assignedToMe";
binding.chipCreatedByMe.setChecked(false);
}
applyFilter();
}
});
return binding.getRoot();
}
private void applyFilter() {
String result = selectedState + "_" + selectedFilter;
bmListener.onButtonClicked(result);
dismiss();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
bmListener = (BottomSheetListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement BottomSheetListener");
}
}
public interface BottomSheetListener {
void onButtonClicked(String text);
}
}

View file

@ -1,46 +0,0 @@
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.databinding.BottomSheetNotificationsFilterBinding;
import org.mian.gitnex.structs.BottomSheetListener;
/**
* @author opyale
*/
public class BottomSheetNotificationsFilterFragment extends BottomSheetDialogFragment {
private BottomSheetListener listener;
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
BottomSheetNotificationsFilterBinding binding =
BottomSheetNotificationsFilterBinding.inflate(inflater, container, false);
binding.readNotifications.setOnClickListener(
v1 -> {
listener.onButtonClicked("read");
dismiss();
});
binding.unreadNotifications.setOnClickListener(
v12 -> {
listener.onButtonClicked("unread");
dismiss();
});
return binding.getRoot();
}
public void setOnClickListener(BottomSheetListener listener) {
this.listener = listener;
}
}

View file

@ -9,14 +9,25 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.mian.gitnex.databinding.BottomSheetPullRequestFilterBinding;
import org.mian.gitnex.helpers.contexts.RepositoryContext;
import org.mian.gitnex.structs.BottomSheetListener;
/**
* @author M M Arif
* @author mmarif
*/
public class BottomSheetPullRequestFilterFragment extends BottomSheetDialogFragment {
private BottomSheetListener bmListener;
private BottomSheetPullRequestFilterBinding binding;
private RepositoryContext repository;
public static BottomSheetPullRequestFilterFragment newInstance(RepositoryContext repository) {
BottomSheetPullRequestFilterFragment fragment = new BottomSheetPullRequestFilterFragment();
Bundle args = new Bundle();
args.putSerializable(RepositoryContext.INTENT_EXTRA, repository);
fragment.setArguments(args);
return fragment;
}
@Nullable @Override
public View onCreateView(
@ -24,33 +35,58 @@ public class BottomSheetPullRequestFilterFragment extends BottomSheetDialogFragm
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
BottomSheetPullRequestFilterBinding bottomSheetPullRequestFilterBinding =
BottomSheetPullRequestFilterBinding.inflate(inflater, container, false);
binding = BottomSheetPullRequestFilterBinding.inflate(inflater, container, false);
bottomSheetPullRequestFilterBinding.openPr.setOnClickListener(
v1 -> {
bmListener.onButtonClicked("openPr");
dismiss();
if (getArguments() != null) {
repository =
(RepositoryContext)
getArguments().getSerializable(RepositoryContext.INTENT_EXTRA);
}
if (repository == null) {
throw new IllegalStateException("RepositoryContext is required");
}
binding.openChip.setChecked(repository.getPrState() == RepositoryContext.State.OPEN);
binding.closedChip.setChecked(repository.getPrState() == RepositoryContext.State.CLOSED);
binding.openChip.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked && repository.getPrState() != RepositoryContext.State.OPEN) {
repository.setPrState(RepositoryContext.State.OPEN);
bmListener.onButtonClicked("openPr");
dismiss();
} else if (!isChecked) {
buttonView.setChecked(true);
}
});
bottomSheetPullRequestFilterBinding.closedPr.setOnClickListener(
v12 -> {
bmListener.onButtonClicked("closedPr");
dismiss();
binding.closedChip.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked && repository.getPrState() != RepositoryContext.State.CLOSED) {
repository.setPrState(RepositoryContext.State.CLOSED);
bmListener.onButtonClicked("closedPr");
dismiss();
} else if (!isChecked) {
buttonView.setChecked(true);
}
});
return bottomSheetPullRequestFilterBinding.getRoot();
return binding.getRoot();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
bmListener = (BottomSheetListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement BottomSheetListener");
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

View file

@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.mian.gitnex.databinding.BottomSheetReleasesTagsBinding;
import org.mian.gitnex.helpers.contexts.RepositoryContext;
import org.mian.gitnex.structs.BottomSheetListener;
/**
@ -17,17 +18,15 @@ import org.mian.gitnex.structs.BottomSheetListener;
public class BottomSheetReleasesTagsFragment extends BottomSheetDialogFragment {
private BottomSheetListener bmListener;
private BottomSheetReleasesTagsBinding binding;
private RepositoryContext repository;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
bmListener = (BottomSheetListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement BottomSheetListener");
}
public static BottomSheetReleasesTagsFragment newInstance(RepositoryContext repository) {
BottomSheetReleasesTagsFragment fragment = new BottomSheetReleasesTagsFragment();
Bundle args = new Bundle();
args.putSerializable(RepositoryContext.INTENT_EXTRA, repository);
fragment.setArguments(args);
return fragment;
}
@Nullable @Override
@ -36,21 +35,58 @@ public class BottomSheetReleasesTagsFragment extends BottomSheetDialogFragment {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
BottomSheetReleasesTagsBinding binding =
BottomSheetReleasesTagsBinding.inflate(inflater, container, false);
binding = BottomSheetReleasesTagsBinding.inflate(inflater, container, false);
binding.tags.setOnClickListener(
v1 -> {
bmListener.onButtonClicked("tags");
dismiss();
if (getArguments() != null) {
repository =
(RepositoryContext)
getArguments().getSerializable(RepositoryContext.INTENT_EXTRA);
}
if (repository == null) {
throw new IllegalStateException("RepositoryContext is required");
}
binding.releasesChip.setChecked(!repository.isReleasesViewTypeIsTag());
binding.tagsChip.setChecked(repository.isReleasesViewTypeIsTag());
binding.releasesChip.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked && repository.isReleasesViewTypeIsTag()) {
repository.setReleasesViewTypeIsTag(false);
bmListener.onButtonClicked("releases");
dismiss();
} else if (!isChecked) {
buttonView.setChecked(true);
}
});
binding.releases.setOnClickListener(
v12 -> {
bmListener.onButtonClicked("releases");
dismiss();
binding.tagsChip.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked && !repository.isReleasesViewTypeIsTag()) {
repository.setReleasesViewTypeIsTag(true);
bmListener.onButtonClicked("tags");
dismiss();
} else if (!isChecked) {
buttonView.setChecked(true);
}
});
return binding.getRoot();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
bmListener = (BottomSheetListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement BottomSheetListener");
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

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,492 @@
package org.mian.gitnex.fragments;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
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 com.google.android.material.chip.Chip;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.timepicker.MaterialTimePicker;
import java.util.LinkedHashMap;
import java.util.Locale;
import org.mian.gitnex.R;
import org.mian.gitnex.databinding.BottomSheetSettingsAppearanceBinding;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.FontsOverride;
import org.mian.gitnex.helpers.SnackBar;
/**
* @author mmarif
*/
public class BottomSheetSettingsAppearanceFragment extends BottomSheetDialogFragment {
private static final String TAG = "BottomSheetSettingsAppearance";
private BottomSheetSettingsAppearanceBinding binding;
private static int customFontSelectedChoice;
private static String[] themeList;
private static int themeSelectedChoice;
private static int langSelectedChoice;
private static int fragmentTabsAnimationSelectedChoice;
private LinkedHashMap<String, String> lang;
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = BottomSheetSettingsAppearanceBinding.inflate(inflater, container, false);
lang = new LinkedHashMap<>();
lang.put("sys", getString(R.string.settingsLanguageSystem));
for (String langCode : getResources().getStringArray(R.array.languages)) {
lang.put(langCode, getLanguageDisplayName(langCode));
}
String[] customFontList = getResources().getStringArray(R.array.fonts);
String[] fragmentTabsAnimationList =
getResources().getStringArray(R.array.fragmentTabsAnimation);
themeList =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || "S".equals(Build.VERSION.CODENAME)
? getResources().getStringArray(R.array.themesAndroid12)
: getResources().getStringArray(R.array.themes);
customFontSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_FONT_KEY));
themeSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_THEME_KEY));
fragmentTabsAnimationSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_TABS_ANIMATION_KEY));
String[] locale =
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_LOCALE_KEY)
.split("\\|");
langSelectedChoice = Integer.parseInt(locale[0]);
String lightMinute =
String.valueOf(
AppDatabaseSettings.getSettingsValue(
requireContext(),
AppDatabaseSettings.APP_THEME_AUTO_LIGHT_MIN_KEY));
String lightHour =
String.valueOf(
AppDatabaseSettings.getSettingsValue(
requireContext(),
AppDatabaseSettings.APP_THEME_AUTO_LIGHT_HOUR_KEY));
lightMinute = lightMinute.length() == 1 ? "0" + lightMinute : lightMinute;
lightHour = lightHour.length() == 1 ? "0" + lightHour : lightHour;
String darkMinute =
String.valueOf(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_THEME_AUTO_DARK_MIN_KEY));
String darkHour =
String.valueOf(
AppDatabaseSettings.getSettingsValue(
requireContext(),
AppDatabaseSettings.APP_THEME_AUTO_DARK_HOUR_KEY));
darkMinute = darkMinute.length() == 1 ? "0" + darkMinute : darkMinute;
darkHour = darkHour.length() == 1 ? "0" + darkHour : darkHour;
for (int i = 0; i < themeList.length; i++) {
Chip chip = (Chip) inflater.inflate(R.layout.chip_item, binding.themeChipGroup, false);
chip.setId(View.generateViewId());
chip.setText(themeList[i]);
chip.setCheckable(true);
chip.setClickable(true);
chip.setFocusable(true);
if (i == themeSelectedChoice) chip.setChecked(true);
binding.themeChipGroup.addView(chip);
}
for (int i = 0; i < customFontList.length; i++) {
Chip chip =
(Chip) inflater.inflate(R.layout.chip_item, binding.customFontChipGroup, false);
chip.setId(View.generateViewId());
chip.setText(customFontList[i]);
chip.setCheckable(true);
chip.setClickable(true);
chip.setFocusable(true);
if (i == customFontSelectedChoice) chip.setChecked(true);
binding.customFontChipGroup.addView(chip);
}
for (int i = 0; i < fragmentTabsAnimationList.length; i++) {
Chip chip =
(Chip)
inflater.inflate(
R.layout.chip_item,
binding.fragmentTabsAnimationChipGroup,
false);
chip.setId(View.generateViewId());
chip.setText(fragmentTabsAnimationList[i]);
chip.setCheckable(true);
chip.setClickable(true);
chip.setFocusable(true);
if (i == fragmentTabsAnimationSelectedChoice) chip.setChecked(true);
binding.fragmentTabsAnimationChipGroup.addView(chip);
}
binding.lightThemeSelectedTime.setText(
getResources()
.getString(R.string.settingsThemeTimeSelectedHint, lightHour, lightMinute));
binding.darkThemeSelectedTime.setText(
getResources()
.getString(R.string.settingsThemeTimeSelectedHint, darkHour, darkMinute));
binding.tvLanguageSelected.setText(
lang.get(lang.keySet().toArray(new String[0])[langSelectedChoice]));
binding.switchCounterBadge.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_COUNTER_KEY)));
binding.switchHideEmailLangInProfile.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
requireContext(),
AppDatabaseSettings.APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY)));
binding.switchHideEmailNavDrawer.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
requireContext(),
AppDatabaseSettings.APP_USER_HIDE_EMAIL_IN_NAV_KEY)));
binding.switchLabelsInListBadge.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_LABELS_IN_LIST_KEY)));
binding.lightThemeTimeSelectionFrame.setVisibility(
themeList[themeSelectedChoice].startsWith("Auto") ? View.VISIBLE : View.GONE);
binding.darkThemeTimeSelectionFrame.setVisibility(
themeList[themeSelectedChoice].startsWith("Auto") ? View.VISIBLE : View.GONE);
binding.themeChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
Log.d(TAG, "Theme chip checked: " + checkedIds);
if (checkedIds.size() == 1) {
int newSelection = getThemeChipPosition(checkedIds.get(0));
Log.d(TAG, "Theme new selection: " + newSelection);
if (newSelection != themeSelectedChoice) {
themeSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_THEME_KEY);
binding.lightThemeTimeSelectionFrame.setVisibility(
themeList[newSelection].startsWith("Auto")
? View.VISIBLE
: View.GONE);
binding.darkThemeTimeSelectionFrame.setVisibility(
themeList[newSelection].startsWith("Auto")
? View.VISIBLE
: View.GONE);
SettingsFragment.refreshParent = true;
requireActivity().recreate();
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
binding.lightThemeTimeSelectionFrame.setOnClickListener(v -> lightTimePicker());
binding.darkThemeTimeSelectionFrame.setOnClickListener(v -> darkTimePicker());
binding.customFontChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
Log.d(TAG, "Font chip checked: " + checkedIds);
if (checkedIds.size() == 1) {
int newSelection = getCustomFontChipPosition(checkedIds.get(0));
Log.d(TAG, "Font new selection: " + newSelection);
if (newSelection != customFontSelectedChoice) {
customFontSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_FONT_KEY);
new Handler()
.postDelayed(
() -> {
AppUtil.typeface = null; // reset typeface
FontsOverride.setDefaultFont(requireContext());
SettingsFragment.refreshParent = true;
requireActivity().recreate();
},
1000);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
binding.fragmentTabsAnimationChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
Log.d(TAG, "Tabs animation chip checked: " + checkedIds);
if (checkedIds.size() == 1) {
int newSelection = getFragmentTabsAnimationChipPosition(checkedIds.get(0));
Log.d(TAG, "Tabs animation new selection: " + newSelection);
if (newSelection != fragmentTabsAnimationSelectedChoice) {
fragmentTabsAnimationSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_TABS_ANIMATION_KEY);
SettingsFragment.refreshParent = true;
requireActivity().recreate();
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
binding.counterBadgeFrame.setOnClickListener(
v ->
binding.switchCounterBadge.setChecked(
!binding.switchCounterBadge.isChecked()));
binding.switchCounterBadge.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(isChecked),
AppDatabaseSettings.APP_COUNTER_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
binding.hideEmailLangInProfileFrame.setOnClickListener(
v ->
binding.switchHideEmailLangInProfile.setChecked(
!binding.switchHideEmailLangInProfile.isChecked()));
binding.switchHideEmailLangInProfile.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(isChecked),
AppDatabaseSettings.APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
binding.hideEmailNavDrawerFrame.setOnClickListener(
v ->
binding.switchHideEmailNavDrawer.setChecked(
!binding.switchHideEmailNavDrawer.isChecked()));
binding.switchHideEmailNavDrawer.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(isChecked),
AppDatabaseSettings.APP_USER_HIDE_EMAIL_IN_NAV_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
binding.labelsInListFrame.setOnClickListener(
v ->
binding.switchLabelsInListBadge.setChecked(
!binding.switchLabelsInListBadge.isChecked()));
binding.switchLabelsInListBadge.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(isChecked),
AppDatabaseSettings.APP_LABELS_IN_LIST_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
binding.langFrame.setOnClickListener(
v -> {
MaterialAlertDialogBuilder builder =
new MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.settingsLanguageSelectorDialogTitle)
.setCancelable(langSelectedChoice != -1)
.setNeutralButton(R.string.cancelButton, null)
.setSingleChoiceItems(
lang.values().toArray(new String[0]),
langSelectedChoice,
(dialog, i) -> {
String selectedLanguage =
lang.keySet().toArray(new String[0])[i];
AppDatabaseSettings.updateSettingsValue(
requireContext(),
i + "|" + selectedLanguage,
AppDatabaseSettings.APP_LOCALE_KEY);
SettingsFragment.refreshParent = true;
requireActivity().recreate();
dialog.dismiss();
SnackBar.success(
requireContext(),
requireActivity()
.findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
builder.create().show();
});
binding.helpTranslate.setOnClickListener(
v ->
AppUtil.openUrlInBrowser(
requireContext(), getResources().getString(R.string.crowdInLink)));
return binding.getRoot();
}
private void lightTimePicker() {
int hour =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(),
AppDatabaseSettings.APP_THEME_AUTO_LIGHT_HOUR_KEY));
int minute =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(),
AppDatabaseSettings.APP_THEME_AUTO_LIGHT_MIN_KEY));
MaterialTimePicker picker =
new MaterialTimePicker.Builder().setHour(hour).setMinute(minute).build();
picker.addOnPositiveButtonClickListener(
v -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(picker.getHour()),
AppDatabaseSettings.APP_THEME_AUTO_LIGHT_HOUR_KEY);
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(picker.getMinute()),
AppDatabaseSettings.APP_THEME_AUTO_LIGHT_MIN_KEY);
SettingsFragment.refreshParent = true;
requireActivity().recreate();
String minuteStr =
picker.getMinute() < 10
? "0" + picker.getMinute()
: String.valueOf(picker.getMinute());
String hourStr =
picker.getHour() < 10
? "0" + picker.getHour()
: String.valueOf(picker.getHour());
binding.lightThemeSelectedTime.setText(
getResources()
.getString(
R.string.settingsThemeTimeSelectedHint,
hourStr,
minuteStr));
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
picker.show(getParentFragmentManager(), "lightTimePicker");
}
private void darkTimePicker() {
int hour =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(),
AppDatabaseSettings.APP_THEME_AUTO_DARK_HOUR_KEY));
int minute =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_THEME_AUTO_DARK_MIN_KEY));
MaterialTimePicker picker =
new MaterialTimePicker.Builder().setHour(hour).setMinute(minute).build();
picker.addOnPositiveButtonClickListener(
v -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(picker.getHour()),
AppDatabaseSettings.APP_THEME_AUTO_DARK_HOUR_KEY);
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(picker.getMinute()),
AppDatabaseSettings.APP_THEME_AUTO_DARK_MIN_KEY);
SettingsFragment.refreshParent = true;
requireActivity().recreate();
String minuteStr =
picker.getMinute() < 10
? "0" + picker.getMinute()
: String.valueOf(picker.getMinute());
String hourStr =
picker.getHour() < 10
? "0" + picker.getHour()
: String.valueOf(picker.getHour());
binding.darkThemeSelectedTime.setText(
getResources()
.getString(
R.string.settingsThemeTimeSelectedHint,
hourStr,
minuteStr));
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
picker.show(getParentFragmentManager(), "darkTimePicker");
}
private int getThemeChipPosition(int checkedId) {
for (int i = 0; i < binding.themeChipGroup.getChildCount(); i++) {
Chip chip = (Chip) binding.themeChipGroup.getChildAt(i);
if (chip.getId() == checkedId) return i;
}
return themeSelectedChoice;
}
private int getCustomFontChipPosition(int checkedId) {
for (int i = 0; i < binding.customFontChipGroup.getChildCount(); i++) {
Chip chip = (Chip) binding.customFontChipGroup.getChildAt(i);
if (chip.getId() == checkedId) return i;
}
return customFontSelectedChoice;
}
private int getFragmentTabsAnimationChipPosition(int checkedId) {
for (int i = 0; i < binding.fragmentTabsAnimationChipGroup.getChildCount(); i++) {
Chip chip = (Chip) binding.fragmentTabsAnimationChipGroup.getChildAt(i);
if (chip.getId() == checkedId) return i;
}
return fragmentTabsAnimationSelectedChoice;
}
private String getLanguageDisplayName(String langCode) {
Locale english = new Locale("en");
String[] multiCodeLang = langCode.split("-");
String countryCode = langCode.contains("-") ? multiCodeLang[1] : "";
langCode = multiCodeLang[0];
Locale translated = new Locale(langCode, countryCode);
return String.format(
"%s (%s)",
translated.getDisplayName(translated), translated.getDisplayName(english));
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

View file

@ -0,0 +1,266 @@
package org.mian.gitnex.fragments;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import org.mian.gitnex.R;
import org.mian.gitnex.database.api.BaseApi;
import org.mian.gitnex.database.api.UserAccountsApi;
import org.mian.gitnex.database.models.UserAccount;
import org.mian.gitnex.databinding.BottomSheetSettingsBackupRestoreBinding;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.BackupUtil;
import org.mian.gitnex.helpers.SnackBar;
/**
* @author mmarif
*/
public class BottomSheetSettingsBackupRestoreFragment extends BottomSheetDialogFragment {
private BottomSheetSettingsBackupRestoreBinding binding;
private Context ctx;
private final String DATABASE_NAME = "gitnex";
private String BACKUP_DATABASE_NAME;
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = BottomSheetSettingsBackupRestoreBinding.inflate(inflater, container, false);
ctx = requireContext();
BACKUP_DATABASE_NAME = ctx.getString(R.string.appName) + "-" + LocalDate.now() + ".backup";
binding.backupButton.setOnClickListener(v -> requestBackupFileDownload());
binding.restoreButton.setOnClickListener(v -> requestRestoreFile());
binding.bottomSheetHeader.setText(
getString(
R.string.backupRestore,
getString(R.string.backup),
getString(R.string.restore)));
return binding.getRoot();
}
private final ActivityResultLauncher<Intent> activityBackupFileLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK
&& result.getData() != null) {
Uri backupFileUri = result.getData().getData();
backupDatabaseThread(backupFileUri);
}
});
private void requestBackupFileDownload() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.EXTRA_TITLE, BACKUP_DATABASE_NAME);
intent.setType("application/octet-stream");
activityBackupFileLauncher.launch(intent);
}
private void backupDatabaseThread(Uri backupFileUri) {
List<File> filesToZip = new ArrayList<>();
Thread backupDatabaseThread =
new Thread(
() -> {
File tempDir = BackupUtil.getTempDir(ctx);
try {
BackupUtil.checkpointIfWALEnabled(ctx, DATABASE_NAME);
File databaseBackupFile =
BackupUtil.backupDatabaseFile(
ctx.getDatabasePath(DATABASE_NAME).getPath(),
tempDir.getPath() + "/" + DATABASE_NAME);
filesToZip.add(databaseBackupFile);
String tempZipFilename = "temp.backup";
boolean zipFileStatus =
BackupUtil.zip(
filesToZip, tempDir.getPath(), tempZipFilename);
if (zipFileStatus) {
File tempZipFile = new File(tempDir, tempZipFilename);
Uri zipFileUri = Uri.fromFile(tempZipFile);
InputStream inputStream =
ctx.getContentResolver().openInputStream(zipFileUri);
OutputStream outputStream =
ctx.getContentResolver()
.openOutputStream(backupFileUri);
boolean copySucceeded =
BackupUtil.copyFileWithStreams(
inputStream, outputStream);
requireActivity()
.runOnUiThread(
() -> {
if (copySucceeded) {
SnackBar.success(
ctx,
requireActivity()
.findViewById(
android.R.id
.content),
getString(
R.string
.backupFileSuccess));
} else {
SnackBar.error(
ctx,
requireActivity()
.findViewById(
android.R.id
.content),
getString(
R.string
.backupFileError));
}
});
if (copySucceeded) {
tempZipFile.delete();
}
} else {
requireActivity()
.runOnUiThread(
() ->
SnackBar.error(
ctx,
requireActivity()
.findViewById(
android.R.id
.content),
getString(
R.string
.backupFileError)));
}
} catch (Exception e) {
requireActivity()
.runOnUiThread(
() ->
SnackBar.error(
ctx,
requireActivity()
.findViewById(
android.R.id
.content),
getString(
R.string.backupFileError)));
} finally {
for (File file : filesToZip) {
if (file != null && file.exists()) {
file.delete();
}
}
}
});
backupDatabaseThread.setDaemon(false);
backupDatabaseThread.start();
}
private final ActivityResultLauncher<Intent> activityRestoreFileLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK
&& result.getData() != null) {
Uri restoreFileUri = result.getData().getData();
try {
assert restoreFileUri != null;
InputStream inputStream =
ctx.getContentResolver().openInputStream(restoreFileUri);
restoreDatabaseThread(inputStream);
} catch (FileNotFoundException e) {
SnackBar.error(
ctx,
requireActivity().findViewById(android.R.id.content),
getString(R.string.restoreError));
}
}
});
private void requestRestoreFile() {
Intent intentRestore = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intentRestore.addCategory(Intent.CATEGORY_OPENABLE);
intentRestore.setType("*/*");
String[] mimeTypes = {"application/octet-stream", "application/x-zip"};
intentRestore.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
activityRestoreFileLauncher.launch(intentRestore);
}
private void restoreDatabaseThread(InputStream inputStream) {
Thread restoreDatabaseThread =
new Thread(
() -> {
boolean exceptionOccurred = false;
try {
String tempDir = BackupUtil.getTempDir(ctx).getPath();
BackupUtil.unzip(inputStream, tempDir);
BackupUtil.checkpointIfWALEnabled(ctx, DATABASE_NAME);
restoreDatabaseFile(ctx, tempDir, DATABASE_NAME);
UserAccountsApi userAccountsApi =
BaseApi.getInstance(ctx, UserAccountsApi.class);
assert userAccountsApi != null;
UserAccount account = userAccountsApi.getAccountById(1);
AppUtil.switchToAccount(ctx, account);
} catch (Exception e) {
exceptionOccurred = true;
requireActivity()
.runOnUiThread(
() ->
SnackBar.error(
ctx,
requireActivity()
.findViewById(
android.R.id
.content),
getString(R.string.restoreError)));
} finally {
if (!exceptionOccurred) {
requireActivity().runOnUiThread(this::restartApp);
}
}
});
restoreDatabaseThread.setDaemon(false);
restoreDatabaseThread.start();
}
private void restoreDatabaseFile(Context context, String tempDir, String nameOfFileToRestore)
throws IOException {
File currentDbFile = new File(context.getDatabasePath(DATABASE_NAME).getPath());
File newDbFile = new File(tempDir + "/" + nameOfFileToRestore);
if (newDbFile.exists()) {
BackupUtil.copyFile(newDbFile, currentDbFile, false);
}
}
private void restartApp() {
Intent i = ctx.getPackageManager().getLaunchIntentForPackage(ctx.getPackageName());
assert i != null;
startActivity(Intent.makeRestartActivityTask(i.getComponent()));
Runtime.getRuntime().exit(0);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null; // Prevent memory leaks
}
}

View file

@ -0,0 +1,186 @@
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.BottomSheetSettingsCodeEditorBinding;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.SnackBar;
/**
* @author mmarif
*/
public class BottomSheetSettingsCodeEditorFragment extends BottomSheetDialogFragment {
private BottomSheetSettingsCodeEditorBinding binding;
private static int colorSelectedChoice;
private static int indentationSelectedChoice;
private static int indentationTabsSelectedChoice;
private static String[] indentationList;
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = BottomSheetSettingsCodeEditorBinding.inflate(inflater, container, false);
indentationList = getResources().getStringArray(R.array.ceIndentation);
colorSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_CE_SYNTAX_HIGHLIGHT_KEY));
indentationSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_CE_INDENTATION_KEY));
indentationTabsSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_CE_TABS_WIDTH_KEY));
setColorChipSelection(colorSelectedChoice);
setIndentationChipSelection(indentationSelectedChoice);
setIndentationTabsChipSelection(indentationTabsSelectedChoice);
updateTabsWidthVisibility();
binding.ceColorChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (checkedIds.size() == 1) {
int newSelection = getColorChipPosition(checkedIds.get(0));
if (newSelection != colorSelectedChoice) {
colorSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_CE_SYNTAX_HIGHLIGHT_KEY);
SettingsFragment.refreshParent = true;
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
binding.indentationChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (checkedIds.size() == 1) {
int newSelection = getIndentationChipPosition(checkedIds.get(0));
if (newSelection != indentationSelectedChoice) {
indentationSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_CE_INDENTATION_KEY);
updateTabsWidthVisibility();
SettingsFragment.refreshParent = true;
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
binding.indentationTabsChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (checkedIds.size() == 1) {
int newSelection = getIndentationTabsChipPosition(checkedIds.get(0));
if (newSelection != indentationTabsSelectedChoice) {
indentationTabsSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_CE_TABS_WIDTH_KEY);
SettingsFragment.refreshParent = true;
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
return binding.getRoot();
}
private void setColorChipSelection(int position) {
switch (position) {
case 0:
binding.chipColorDark.setChecked(true);
break;
case 1:
binding.chipColorLight.setChecked(true);
break;
}
}
private int getColorChipPosition(int checkedId) {
if (checkedId == R.id.chipColorDark) return 0;
if (checkedId == R.id.chipColorLight) return 1;
return colorSelectedChoice;
}
private void setIndentationChipSelection(int position) {
switch (position) {
case 0:
binding.chipIndentSpaces.setChecked(true);
break;
case 1:
binding.chipIndentTabs.setChecked(true);
break;
}
}
private int getIndentationChipPosition(int checkedId) {
if (checkedId == R.id.chipIndentSpaces) return 0;
if (checkedId == R.id.chipIndentTabs) return 1;
return indentationSelectedChoice;
}
private void setIndentationTabsChipSelection(int position) {
switch (position) {
case 0:
binding.chipTabs2.setChecked(true);
break;
case 1:
binding.chipTabs4.setChecked(true);
break;
case 2:
binding.chipTabs6.setChecked(true);
break;
case 3:
binding.chipTabs8.setChecked(true);
break;
}
}
private int getIndentationTabsChipPosition(int checkedId) {
if (checkedId == R.id.chipTabs2) return 0;
if (checkedId == R.id.chipTabs4) return 1;
if (checkedId == R.id.chipTabs6) return 2;
if (checkedId == R.id.chipTabs8) return 3;
return indentationTabsSelectedChoice;
}
private void updateTabsWidthVisibility() {
boolean isTabsSelected =
indentationList[indentationSelectedChoice].startsWith(
getString(R.string.ceIndentationTabs));
binding.indentationTabsSelectionFrame.setVisibility(
isTabsSelected ? View.VISIBLE : View.GONE);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

View file

@ -0,0 +1,213 @@
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.BottomSheetSettingsGeneralBinding;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.SnackBar;
/**
* @author mmarif
*/
public class BottomSheetSettingsGeneralFragment extends BottomSheetDialogFragment {
private BottomSheetSettingsGeneralBinding binding;
private static int homeScreenSelectedChoice;
private static int defaultLinkHandlerScreenSelectedChoice;
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = BottomSheetSettingsGeneralBinding.inflate(inflater, container, false);
homeScreenSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_HOME_SCREEN_KEY));
defaultLinkHandlerScreenSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_LINK_HANDLER_KEY));
setHomeScreenChipSelection(homeScreenSelectedChoice);
setLinkHandlerChipSelection(defaultLinkHandlerScreenSelectedChoice);
binding.switchTabs.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_CUSTOM_BROWSER_KEY)));
binding.crashReportsSwitch.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_CRASH_REPORTS_KEY)));
binding.homeScreenChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (checkedIds.size() == 1) {
int newSelection = getHomeScreenChipPosition(checkedIds.get(0));
if (newSelection != homeScreenSelectedChoice) {
homeScreenSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_HOME_SCREEN_KEY);
SettingsFragment.refreshParent = true;
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
binding.linkHandlerChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (checkedIds.size() == 1) {
int newSelection = getLinkHandlerChipPosition(checkedIds.get(0));
if (newSelection != defaultLinkHandlerScreenSelectedChoice) {
defaultLinkHandlerScreenSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_LINK_HANDLER_KEY);
SettingsFragment.refreshParent = true;
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
binding.customTabsFrame.setOnClickListener(
v -> binding.switchTabs.setChecked(!binding.switchTabs.isChecked()));
binding.switchTabs.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(isChecked),
AppDatabaseSettings.APP_CUSTOM_BROWSER_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
binding.enableSendReports.setOnClickListener(
v ->
binding.crashReportsSwitch.setChecked(
!binding.crashReportsSwitch.isChecked()));
binding.crashReportsSwitch.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(isChecked),
AppDatabaseSettings.APP_CRASH_REPORTS_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
return binding.getRoot();
}
private void setHomeScreenChipSelection(int position) {
switch (position) {
case 0:
binding.chipHomeScreen0.setChecked(true);
break;
case 1:
binding.chipHomeScreen1.setChecked(true);
break;
case 2:
binding.chipHomeScreen2.setChecked(true);
break;
case 3:
binding.chipHomeScreen3.setChecked(true);
break;
case 4:
binding.chipHomeScreen4.setChecked(true);
break;
case 5:
binding.chipHomeScreen5.setChecked(true);
break;
case 6:
binding.chipHomeScreen6.setChecked(true);
break;
case 7:
binding.chipHomeScreen7.setChecked(true);
break;
case 8:
binding.chipHomeScreen8.setChecked(true);
break;
case 9:
binding.chipHomeScreen9.setChecked(true);
break;
case 10:
binding.chipHomeScreen10.setChecked(true);
break;
case 11:
binding.chipHomeScreen11.setChecked(true);
break;
}
}
private int getHomeScreenChipPosition(int checkedId) {
if (checkedId == R.id.chipHomeScreen0) return 0;
if (checkedId == R.id.chipHomeScreen1) return 1;
if (checkedId == R.id.chipHomeScreen2) return 2;
if (checkedId == R.id.chipHomeScreen3) return 3;
if (checkedId == R.id.chipHomeScreen4) return 4;
if (checkedId == R.id.chipHomeScreen5) return 5;
if (checkedId == R.id.chipHomeScreen6) return 6;
if (checkedId == R.id.chipHomeScreen7) return 7;
if (checkedId == R.id.chipHomeScreen8) return 8;
if (checkedId == R.id.chipHomeScreen9) return 9;
if (checkedId == R.id.chipHomeScreen10) return 10;
if (checkedId == R.id.chipHomeScreen11) return 11;
return homeScreenSelectedChoice;
}
private void setLinkHandlerChipSelection(int position) {
switch (position) {
case 0:
binding.chipLinkHandler0.setChecked(true);
break;
case 1:
binding.chipLinkHandler1.setChecked(true);
break;
case 2:
binding.chipLinkHandler2.setChecked(true);
break;
case 3:
binding.chipLinkHandler3.setChecked(true);
break;
case 4:
binding.chipLinkHandler4.setChecked(true);
break;
}
}
private int getLinkHandlerChipPosition(int checkedId) {
if (checkedId == R.id.chipLinkHandler0) return 0;
if (checkedId == R.id.chipLinkHandler1) return 1;
if (checkedId == R.id.chipLinkHandler2) return 2;
if (checkedId == R.id.chipLinkHandler3) return 3;
if (checkedId == R.id.chipLinkHandler4) return 4;
return defaultLinkHandlerScreenSelectedChoice;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

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

@ -0,0 +1,304 @@
package org.mian.gitnex.fragments;
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
import android.app.KeyguardManager;
import android.content.Context;
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 androidx.biometric.BiometricManager;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.mian.gitnex.R;
import org.mian.gitnex.databinding.BottomSheetSettingsSecurityBinding;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.SnackBar;
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
/**
* @author mmarif
*/
public class BottomSheetSettingsSecurityFragment extends BottomSheetDialogFragment {
private BottomSheetSettingsSecurityBinding binding;
private static String[] cacheSizeDataList;
private static int cacheSizeDataSelectedChoice;
private static String[] cacheSizeImagesList;
private static int cacheSizeImagesSelectedChoice;
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = BottomSheetSettingsSecurityBinding.inflate(inflater, container, false);
cacheSizeDataList = getResources().getStringArray(R.array.cacheSizeList);
cacheSizeImagesList = getResources().getStringArray(R.array.cacheSizeList);
cacheSizeDataSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_DATA_CACHE_KEY));
cacheSizeImagesSelectedChoice =
Integer.parseInt(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_IMAGES_CACHE_KEY));
setCacheSizeDataChipSelection(cacheSizeDataSelectedChoice);
setCacheSizeImagesChipSelection(cacheSizeImagesSelectedChoice);
binding.switchBiometric.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_BIOMETRIC_KEY)));
File cacheDir = requireContext().getCacheDir();
binding.clearCacheButton.setText(
getString(
R.string.clear_cache_button_text,
FileUtils.byteCountToDisplaySize(
(int) FileUtils.sizeOfDirectory(cacheDir))));
binding.switchBiometric.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked) {
BiometricManager biometricManager = BiometricManager.from(requireContext());
KeyguardManager keyguardManager =
(KeyguardManager)
requireContext().getSystemService(Context.KEYGUARD_SERVICE);
if (!keyguardManager.isDeviceSecure()) {
switch (biometricManager.canAuthenticate(
BIOMETRIC_STRONG | DEVICE_CREDENTIAL)) {
case BiometricManager.BIOMETRIC_SUCCESS:
AppDatabaseSettings.updateSettingsValue(
requireContext(),
"true",
AppDatabaseSettings.APP_BIOMETRIC_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
break;
case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
case BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED:
case BiometricManager.BIOMETRIC_STATUS_UNKNOWN:
AppDatabaseSettings.updateSettingsValue(
requireContext(),
"false",
AppDatabaseSettings.APP_BIOMETRIC_KEY);
binding.switchBiometric.setChecked(false);
SnackBar.error(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.biometricNotSupported));
break;
case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
AppDatabaseSettings.updateSettingsValue(
requireContext(),
"false",
AppDatabaseSettings.APP_BIOMETRIC_KEY);
binding.switchBiometric.setChecked(false);
SnackBar.error(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.biometricNotAvailable));
break;
case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
AppDatabaseSettings.updateSettingsValue(
requireContext(),
"false",
AppDatabaseSettings.APP_BIOMETRIC_KEY);
binding.switchBiometric.setChecked(false);
SnackBar.info(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.enrollBiometric));
break;
}
} else {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
"true",
AppDatabaseSettings.APP_BIOMETRIC_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
} else {
AppDatabaseSettings.updateSettingsValue(
requireContext(), "false", AppDatabaseSettings.APP_BIOMETRIC_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
});
binding.biometricFrame.setOnClickListener(
v -> binding.switchBiometric.setChecked(!binding.switchBiometric.isChecked()));
binding.cacheSizeDataChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (checkedIds.size() == 1) {
int newSelection = getCacheSizeDataChipPosition(checkedIds.get(0));
if (newSelection != cacheSizeDataSelectedChoice) {
cacheSizeDataSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
cacheSizeDataList[newSelection],
AppDatabaseSettings.APP_DATA_CACHE_SIZE_KEY);
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_DATA_CACHE_KEY);
SettingsFragment.refreshParent = true;
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
binding.cacheSizeImagesChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (checkedIds.size() == 1) {
int newSelection = getCacheSizeImagesChipPosition(checkedIds.get(0));
if (newSelection != cacheSizeImagesSelectedChoice) {
cacheSizeImagesSelectedChoice = newSelection;
AppDatabaseSettings.updateSettingsValue(
requireContext(),
cacheSizeImagesList[newSelection],
AppDatabaseSettings.APP_IMAGES_CACHE_SIZE_KEY);
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(newSelection),
AppDatabaseSettings.APP_IMAGES_CACHE_KEY);
SettingsFragment.refreshParent = true;
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
}
}
});
binding.clearCacheButton.setOnClickListener(
v -> {
MaterialAlertDialogBuilder dialog =
new MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.clearCacheDialogHeader)
.setMessage(getString(R.string.clearCacheDialogMessage))
.setNeutralButton(
R.string.cancelButton, (d, which) -> d.dismiss())
.setPositiveButton(
R.string.menuDeleteText,
(d, which) -> {
try {
File cacheDir1 = requireContext().getCacheDir();
FileUtils.deleteDirectory(cacheDir1);
FileUtils.forceMkdir(cacheDir1);
requireActivity().recreate();
requireActivity()
.overridePendingTransition(0, 0);
} catch (IOException ignored) {
}
});
dialog.show();
});
binding.deleteCertsButton.setOnClickListener(
v -> {
MaterialAlertDialogBuilder dialog =
new MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.settingsCertsPopupTitle)
.setMessage(getString(R.string.settingsCertsPopupMessage))
.setNeutralButton(
R.string.cancelButton, (d, which) -> d.dismiss())
.setPositiveButton(
R.string.menuDeleteText,
(d, which) -> {
requireContext()
.getSharedPreferences(
MemorizingTrustManager
.KEYSTORE_NAME,
Context.MODE_PRIVATE)
.edit()
.remove(MemorizingTrustManager.KEYSTORE_KEY)
.apply();
AppUtil.logout(requireContext());
});
dialog.show();
});
return binding.getRoot();
}
private void setCacheSizeDataChipSelection(int position) {
switch (position) {
case 0:
binding.chipDataCache0.setChecked(true);
break;
case 1:
binding.chipDataCache1.setChecked(true);
break;
case 2:
binding.chipDataCache2.setChecked(true);
break;
case 3:
binding.chipDataCache3.setChecked(true);
break;
}
}
private int getCacheSizeDataChipPosition(int checkedId) {
if (checkedId == R.id.chipDataCache0) return 0;
if (checkedId == R.id.chipDataCache1) return 1;
if (checkedId == R.id.chipDataCache2) return 2;
if (checkedId == R.id.chipDataCache3) return 3;
return cacheSizeDataSelectedChoice;
}
private void setCacheSizeImagesChipSelection(int position) {
switch (position) {
case 0:
binding.chipImagesCache0.setChecked(true);
break;
case 1:
binding.chipImagesCache1.setChecked(true);
break;
case 2:
binding.chipImagesCache2.setChecked(true);
break;
case 3:
binding.chipImagesCache3.setChecked(true);
break;
}
}
private int getCacheSizeImagesChipPosition(int checkedId) {
if (checkedId == R.id.chipImagesCache0) return 0;
if (checkedId == R.id.chipImagesCache1) return 1;
if (checkedId == R.id.chipImagesCache2) return 2;
if (checkedId == R.id.chipImagesCache3) return 3;
return cacheSizeImagesSelectedChoice;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

View file

@ -15,7 +15,6 @@ import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.ViewPager2Transformers;
@ -35,9 +34,6 @@ public class ExploreFragment extends Fragment {
Context ctx = getContext();
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.pageTitleExplore));
ViewPager2 viewPager = view.findViewById(R.id.containerExplore);
viewPager.setOffscreenPageLimit(1);
TabLayout tabLayout = view.findViewById(R.id.tabsExplore);

View file

@ -16,7 +16,7 @@ import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import java.util.ArrayList;
import java.util.List;
import org.gitnex.tea4j.v2.models.Repository;
@ -25,7 +25,7 @@ import org.mian.gitnex.R;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.adapters.ExploreRepositoriesAdapter;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.databinding.CustomExploreRepositoriesDialogBinding;
import org.mian.gitnex.databinding.BottomSheetExploreFiltersBinding;
import org.mian.gitnex.databinding.FragmentExploreRepoBinding;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.helpers.SnackBar;
@ -35,7 +35,7 @@ import retrofit2.Callback;
import retrofit2.Response;
/**
* @author M M Arif
* @author mmarif
*/
public class ExploreRepositoriesFragment extends Fragment {
@ -48,9 +48,6 @@ public class ExploreRepositoriesFragment extends Fragment {
private int resultLimit;
private List<Repository> dataList;
private ExploreRepositoriesAdapter adapter;
private CustomExploreRepositoriesDialogBinding filterBinding;
private boolean includeTopic = false;
private boolean includeDescription = false;
private boolean includeTemplate = false;
@ -75,16 +72,15 @@ public class ExploreRepositoriesFragment extends Fragment {
@Override
public void onCreateMenu(
@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
menu.clear();
menuInflater.inflate(R.menu.search_menu, menu);
menuInflater.inflate(R.menu.filter_menu_explore, menu);
MenuItem filter = menu.findItem(R.id.filter_explore);
menuInflater.inflate(R.menu.generic_nav_dotted_menu, menu);
filter.setOnMenuItemClickListener(
filter_ -> {
showFilterOptions();
return false;
MenuItem filterItem = menu.findItem(R.id.genericMenu);
filterItem.setOnMenuItemClickListener(
item -> {
showFilterBottomSheet();
return true;
});
MenuItem searchItem = menu.findItem(R.id.action_search);
@ -97,7 +93,6 @@ public class ExploreRepositoriesFragment extends Fragment {
searchView.setOnQueryTextListener(
new androidx.appcompat.widget.SearchView
.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
viewBinding.progressBar.setVisibility(View.VISIBLE);
@ -126,7 +121,7 @@ public class ExploreRepositoriesFragment extends Fragment {
searchQuery = query;
searchView.setQuery(null, false);
searchItem.collapseActionView();
return false;
return true;
}
@Override
@ -175,7 +170,6 @@ public class ExploreRepositoriesFragment extends Fragment {
}
private void loadInitial(String searchKeyword, int resultLimit) {
Call<SearchResults> call =
RetrofitClient.getApiInterface(context)
.repoSearch(
@ -218,28 +212,19 @@ public class ExploreRepositoriesFragment extends Fragment {
viewBinding.noData.setVisibility(View.VISIBLE);
viewBinding.progressBar.setVisibility(View.GONE);
} else {
Toasty.error(
requireActivity(),
requireActivity()
.getResources()
.getString(R.string.genericError));
Toasty.error(requireActivity(), getString(R.string.genericError));
}
}
@Override
public void onFailure(@NonNull Call<SearchResults> call, @NonNull Throwable t) {
Toasty.error(
requireActivity(),
requireActivity()
.getResources()
.getString(R.string.genericServerResponseError));
requireActivity(), getString(R.string.genericServerResponseError));
}
});
}
private void loadMore(String searchKeyword, int resultLimit, int page) {
viewBinding.progressBar.setVisibility(View.VISIBLE);
Call<SearchResults> call =
RetrofitClient.getApiInterface(context)
@ -284,67 +269,111 @@ public class ExploreRepositoriesFragment extends Fragment {
adapter.notifyDataChanged();
viewBinding.progressBar.setVisibility(View.GONE);
} else {
Toasty.error(
requireActivity(),
requireActivity()
.getResources()
.getString(R.string.genericError));
Toasty.error(requireActivity(), getString(R.string.genericError));
}
}
@Override
public void onFailure(@NonNull Call<SearchResults> call, @NonNull Throwable t) {
Toasty.error(
requireActivity(),
requireActivity()
.getResources()
.getString(R.string.genericServerResponseError));
requireActivity(), getString(R.string.genericServerResponseError));
}
});
}
private void showFilterOptions() {
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(
context, R.style.ThemeOverlay_Material3_Dialog_Alert);
filterBinding =
CustomExploreRepositoriesDialogBinding.inflate(LayoutInflater.from(context));
View view = filterBinding.getRoot();
materialAlertDialogBuilder.setView(view);
filterBinding.includeTopic.setOnClickListener(
includeTopic -> this.includeTopic = filterBinding.includeTopic.isChecked());
filterBinding.includeDesc.setOnClickListener(
includeDesc -> this.includeDescription = filterBinding.includeDesc.isChecked());
filterBinding.includeTemplate.setOnClickListener(
includeTemplate ->
this.includeTemplate = filterBinding.includeTemplate.isChecked());
filterBinding.onlyArchived.setOnClickListener(
onlyArchived -> this.onlyArchived = filterBinding.onlyArchived.isChecked());
filterBinding.includeTopic.setChecked(includeTopic);
filterBinding.includeDesc.setChecked(includeDescription);
filterBinding.includeTemplate.setChecked(includeTemplate);
filterBinding.onlyArchived.setChecked(onlyArchived);
materialAlertDialogBuilder.setNeutralButton(getString(R.string.close), null);
materialAlertDialogBuilder.show();
private void showFilterBottomSheet() {
BottomSheetFilterFragment bottomSheet =
BottomSheetFilterFragment.newInstance(
includeTopic,
includeDescription,
includeTemplate,
onlyArchived,
(topic, desc, template, archived) -> {
includeTopic = topic;
includeDescription = desc;
includeTemplate = template;
onlyArchived = archived;
loadInitial(searchQuery, resultLimit);
});
bottomSheet.show(getChildFragmentManager(), "exploreFiltersBottomSheet");
}
@Override
public void onResume() {
super.onResume();
if (MainActivity.reloadRepos) {
dataList.clear();
loadInitial(searchQuery, resultLimit);
MainActivity.reloadRepos = false;
}
}
public static class BottomSheetFilterFragment extends BottomSheetDialogFragment {
private static final String ARG_INCLUDE_TOPIC = "includeTopic";
private static final String ARG_INCLUDE_DESC = "includeDescription";
private static final String ARG_INCLUDE_TEMPLATE = "includeTemplate";
private static final String ARG_ONLY_ARCHIVED = "onlyArchived";
private FilterCallback callback;
public static BottomSheetFilterFragment newInstance(
boolean includeTopic,
boolean includeDescription,
boolean includeTemplate,
boolean onlyArchived,
FilterCallback callback) {
BottomSheetFilterFragment fragment = new BottomSheetFilterFragment();
Bundle args = new Bundle();
args.putBoolean(ARG_INCLUDE_TOPIC, includeTopic);
args.putBoolean(ARG_INCLUDE_DESC, includeDescription);
args.putBoolean(ARG_INCLUDE_TEMPLATE, includeTemplate);
args.putBoolean(ARG_ONLY_ARCHIVED, onlyArchived);
fragment.setArguments(args);
fragment.callback = callback;
return fragment;
}
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
BottomSheetExploreFiltersBinding binding =
BottomSheetExploreFiltersBinding.inflate(inflater, container, false);
Bundle args = getArguments();
boolean includeTopic = args != null && args.getBoolean(ARG_INCLUDE_TOPIC, false);
boolean includeDescription = args != null && args.getBoolean(ARG_INCLUDE_DESC, false);
boolean includeTemplate = args != null && args.getBoolean(ARG_INCLUDE_TEMPLATE, false);
boolean onlyArchived = args != null && args.getBoolean(ARG_ONLY_ARCHIVED, false);
binding.includeTopicChip.setChecked(includeTopic);
binding.includeDescChip.setChecked(includeDescription);
binding.includeTemplateChip.setChecked(includeTemplate);
binding.onlyArchivedChip.setChecked(onlyArchived);
binding.filterChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
boolean newIncludeTopic = checkedIds.contains(R.id.includeTopicChip);
boolean newIncludeDescription = checkedIds.contains(R.id.includeDescChip);
boolean newIncludeTemplate = checkedIds.contains(R.id.includeTemplateChip);
boolean newOnlyArchived = checkedIds.contains(R.id.onlyArchivedChip);
if (callback != null) {
callback.onFiltersApplied(
newIncludeTopic,
newIncludeDescription,
newIncludeTemplate,
newOnlyArchived);
}
});
return binding.getRoot();
}
public interface FilterCallback {
void onFiltersApplied(
boolean includeTopic,
boolean includeDescription,
boolean includeTemplate,
boolean onlyArchived);
}
}
}

View file

@ -0,0 +1,134 @@
package org.mian.gitnex.fragments;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList;
import java.util.List;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.activities.ProfileActivity;
import org.mian.gitnex.adapters.HomeDashboardAdapter;
import org.mian.gitnex.adapters.UserAccountsNavAdapter;
import org.mian.gitnex.database.models.UserAccount;
import org.mian.gitnex.databinding.FragmentHomeDashboardBinding;
/**
* @author mmarif
*/
public class HomeDashboardFragment extends Fragment {
private FragmentHomeDashboardBinding binding;
private String username;
private List<UserAccount> userAccountsList;
private UserAccountsNavAdapter accountsAdapter;
private HomeDashboardAdapter dashboardAdapter;
@SuppressLint("NotifyDataSetChanged")
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeDashboardBinding.inflate(inflater, container, false);
userAccountsList = new ArrayList<>();
accountsAdapter = new UserAccountsNavAdapter(requireContext(), userAccountsList);
dashboardAdapter = new HomeDashboardAdapter(requireContext());
binding.mainScreensRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
binding.mainScreensRecyclerView.setAdapter(dashboardAdapter);
binding.userAccountsRecyclerView.setLayoutManager(
new LinearLayoutManager(requireContext()));
binding.userAccountsRecyclerView.setAdapter(accountsAdapter);
NavController navController =
Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
binding.repoOrgCard.setOnClickListener(
v -> navController.navigate(R.id.action_to_organizations));
binding.repoMyReposCard.setOnClickListener(
v -> navController.navigate(R.id.action_to_myRepositories));
binding.repoStarredCard.setOnClickListener(
v -> navController.navigate(R.id.action_to_starredRepositories));
binding.repoWatchedCard.setOnClickListener(
v -> navController.navigate(R.id.action_to_watchedRepositories));
binding.repoActivitiesCard.setOnClickListener(
v -> navController.navigate(R.id.activitiesFragment));
binding.repoMyIssuesCard.setOnClickListener(
v -> navController.navigate(R.id.action_to_myIssues));
// Load user info from MainActivity
if (requireActivity() instanceof MainActivity) {
((MainActivity) requireActivity())
.loadUserInfo(
this,
binding,
dashboardAdapter,
userAccountsList,
accountsAdapter,
new MainActivity.UserInfoCallback() {
@Override
public void onUserInfoLoaded(
String username, boolean isAdmin, String serverVersion) {
HomeDashboardFragment.this.username = username;
}
@Override
public void onUserAccountsLoaded() {}
});
}
binding.userAvatar.setOnClickListener(
v -> {
if (username != null) {
Intent intentProfile = new Intent(requireContext(), ProfileActivity.class);
intentProfile.putExtra("username", username);
startActivity(intentProfile);
}
});
binding.refreshButton.setOnClickListener(
v -> {
binding.userAvatar.setImageResource(R.drawable.loader_animated);
binding.userFullname.setText("");
binding.userEmail.setText("");
userAccountsList.clear();
accountsAdapter.notifyDataSetChanged();
// Call MainActivity methods
if (requireActivity() instanceof MainActivity mainActivity) {
mainActivity.getNotificationsCount();
mainActivity.giteaVersion();
mainActivity.serverPageLimitSettings();
mainActivity.updateGeneralAttachmentSettings();
mainActivity.loadUserInfo(
this,
binding,
dashboardAdapter,
userAccountsList,
accountsAdapter,
new MainActivity.UserInfoCallback() {
@Override
public void onUserInfoLoaded(
String username,
boolean isAdmin,
String serverVersion) {
HomeDashboardFragment.this.username = username;
}
@Override
public void onUserAccountsLoaded() {}
});
}
});
return binding.getRoot();
}
}

View file

@ -22,7 +22,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.adapters.MostVisitedReposAdapter;
import org.mian.gitnex.database.api.BaseApi;
import org.mian.gitnex.database.api.RepositoriesApi;
@ -51,9 +50,6 @@ public class MostVisitedReposFragment extends Fragment {
ctx = getContext();
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.navMostVisited));
TinyDB tinyDb = TinyDB.getInstance(ctx);
mostVisitedReposList = new ArrayList<>();

View file

@ -17,21 +17,23 @@ import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.adapters.ExploreIssuesAdapter;
import org.mian.gitnex.databinding.BottomSheetMyIssuesFilterBinding;
import org.mian.gitnex.databinding.FragmentIssuesBinding;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.viewmodels.IssuesViewModel;
/**
* @author M M Arif
* @author mmarif
*/
public class MyIssuesFragment extends Fragment {
private FragmentIssuesBinding fragmentIssuesBinding;
private FragmentIssuesBinding binding;
private IssuesViewModel issuesViewModel;
private ExploreIssuesAdapter adapter;
private Menu menu;
private TinyDB tinyDB;
private String state = "open";
private boolean assignedToMe = false;
private boolean createdByMe = true;
@ -41,39 +43,34 @@ public class MyIssuesFragment extends Fragment {
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
fragmentIssuesBinding = FragmentIssuesBinding.inflate(inflater, container, false);
Bundle savedInstanceState) {
binding = FragmentIssuesBinding.inflate(inflater, container, false);
tinyDB = TinyDB.getInstance(requireContext());
issuesViewModel = new ViewModelProvider(this).get(IssuesViewModel.class);
fragmentIssuesBinding.recyclerView.setHasFixedSize(true);
fragmentIssuesBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
fragmentIssuesBinding.createNewIssue.setVisibility(View.GONE);
String savedFilter = tinyDB.getString("myIssuesFilter", "open_created_by_me");
updateFilterState(savedFilter);
binding.recyclerView.setHasFixedSize(true);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
binding.createNewIssue.setVisibility(View.GONE);
setupMenu();
((MainActivity) requireActivity())
.setFragmentRefreshListenerMyIssues(
myIssues -> {
updateFilterState(myIssues);
fetchDataAsync(null);
});
fragmentIssuesBinding.pullToRefresh.setOnRefreshListener(
binding.pullToRefresh.setOnRefreshListener(
() ->
new Handler(Looper.getMainLooper())
.postDelayed(
() -> {
page = 1;
fragmentIssuesBinding.pullToRefresh.setRefreshing(
false);
binding.pullToRefresh.setRefreshing(false);
fetchDataAsync(null);
},
50));
fetchDataAsync(null);
return fragmentIssuesBinding.getRoot();
return binding.getRoot();
}
private void setupMenu() {
@ -82,13 +79,11 @@ public class MyIssuesFragment extends Fragment {
new MenuProvider() {
@Override
public void onCreateMenu(
@NonNull Menu menu1, @NonNull MenuInflater menuInflater) {
@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
menuInflater.inflate(R.menu.search_menu, menu);
menuInflater.inflate(R.menu.generic_nav_dotted_menu, menu);
menu = menu1;
menuInflater.inflate(R.menu.search_menu, menu1);
menuInflater.inflate(R.menu.filter_menu, menu1);
MenuItem searchItem = menu1.findItem(R.id.action_search);
MenuItem searchItem = menu.findItem(R.id.action_search);
androidx.appcompat.widget.SearchView searchView =
(androidx.appcompat.widget.SearchView)
searchItem.getActionView();
@ -98,7 +93,6 @@ public class MyIssuesFragment extends Fragment {
searchView.setOnQueryTextListener(
new androidx.appcompat.widget.SearchView
.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
fetchDataAsync(query);
@ -116,20 +110,9 @@ public class MyIssuesFragment extends Fragment {
@Override
public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
if (menuItem.getItemId() == R.id.filter) {
String currentFilter =
state
+ "_"
+ (assignedToMe
? "assignedToMe"
: "created_by_me");
BottomSheetMyIssuesFilterFragment bottomSheet =
BottomSheetMyIssuesFilterFragment.newInstance(
currentFilter);
bottomSheet.show(getParentFragmentManager(), "myIssuesFilter");
if (menuItem.getItemId() == R.id.genericMenu) {
new FilterBottomSheetDialogFragment()
.show(getChildFragmentManager(), "MyIssuesFilter");
return true;
}
return false;
@ -140,27 +123,15 @@ public class MyIssuesFragment extends Fragment {
}
public void updateFilterState(String filter) {
String[] parts = filter.split("_", 2);
String stateValue = parts[0];
String filterValue = parts[1];
state = parts[0];
String filterValue = parts.length > 1 ? parts[1] : "created_by_me";
state = stateValue;
menu.getItem(1)
.setIcon(
state.equals("closed")
? R.drawable.ic_filter_closed
: R.drawable.ic_filter);
createdByMe = filterValue.equals("created_by_me");
assignedToMe = filterValue.equals("assignedToMe");
if (filterValue.equals("created_by_me")) {
createdByMe = true;
assignedToMe = false;
} else if (filterValue.equals("assignedToMe")) {
createdByMe = false;
assignedToMe = true;
}
fetchDataAsync(null);
tinyDB.putString("myIssuesFilter", filter);
page = 1;
}
public String getCurrentFilter() {
@ -168,9 +139,8 @@ public class MyIssuesFragment extends Fragment {
}
private void fetchDataAsync(String query) {
fragmentIssuesBinding.progressBar.setVisibility(View.VISIBLE);
fragmentIssuesBinding.noDataIssues.setVisibility(View.GONE);
binding.progressBar.setVisibility(View.VISIBLE);
binding.noDataIssues.setVisibility(View.GONE);
issuesViewModel
.getIssuesList(query, "issues", createdByMe, state, assignedToMe, getContext())
@ -192,27 +162,81 @@ public class MyIssuesFragment extends Fragment {
assignedToMe,
getContext(),
adapter);
fragmentIssuesBinding.progressBar.setVisibility(
View.VISIBLE);
binding.progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onLoadFinished() {
fragmentIssuesBinding.progressBar.setVisibility(
View.GONE);
binding.progressBar.setVisibility(View.GONE);
}
});
if (adapter.getItemCount() > 0) {
fragmentIssuesBinding.recyclerView.setAdapter(adapter);
fragmentIssuesBinding.noDataIssues.setVisibility(View.GONE);
binding.recyclerView.setAdapter(adapter);
binding.noDataIssues.setVisibility(View.GONE);
} else {
adapter.notifyDataChanged();
fragmentIssuesBinding.recyclerView.setAdapter(adapter);
fragmentIssuesBinding.noDataIssues.setVisibility(View.VISIBLE);
binding.recyclerView.setAdapter(adapter);
binding.noDataIssues.setVisibility(View.VISIBLE);
}
fragmentIssuesBinding.progressBar.setVisibility(View.GONE);
binding.progressBar.setVisibility(View.GONE);
});
}
public static class FilterBottomSheetDialogFragment extends BottomSheetDialogFragment {
private String selectedState = "open";
private String selectedFilter = "created_by_me";
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
BottomSheetMyIssuesFilterBinding binding =
BottomSheetMyIssuesFilterBinding.inflate(inflater, container, false);
// Initialize based on parent fragment's current filter
MyIssuesFragment parent = (MyIssuesFragment) requireParentFragment();
String currentFilter = parent.getCurrentFilter();
String[] parts = currentFilter.split("_");
selectedState = parts[0];
selectedFilter = parts.length > 1 ? parts[1] : "created_by_me";
binding.chipOpen.setChecked(selectedState.equals("open"));
binding.chipClosed.setChecked(selectedState.equals("closed"));
binding.chipCreatedByMe.setChecked(selectedFilter.equals("created_by_me"));
binding.chipAssignedToMe.setChecked(selectedFilter.equals("assignedToMe"));
binding.stateChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (!checkedIds.isEmpty()) {
int checkedId = checkedIds.get(0);
selectedState =
checkedId == binding.chipOpen.getId() ? "open" : "closed";
applyFilter(parent);
}
});
binding.filterChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (!checkedIds.isEmpty()) {
int checkedId = checkedIds.get(0);
selectedFilter =
checkedId == binding.chipCreatedByMe.getId()
? "created_by_me"
: "assignedToMe";
applyFilter(parent);
}
});
return binding.getRoot();
}
private void applyFilter(MyIssuesFragment parent) {
String result = selectedState + "_" + selectedFilter;
parent.updateFilterState(result);
parent.fetchDataAsync(null);
dismiss();
}
}
}

View file

@ -48,8 +48,6 @@ public class MyRepositoriesFragment extends Fragment {
fragmentRepositoriesBinding =
FragmentRepositoriesBinding.inflate(inflater, container, false);
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.navMyRepos));
repositoriesViewModel = new ViewModelProvider(this).get(RepositoriesViewModel.class);
final String userLogin =

View file

@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.List;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.CreateNoteActivity;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.adapters.NotesAdapter;
import org.mian.gitnex.database.api.BaseApi;
import org.mian.gitnex.database.api.NotesApi;
@ -113,9 +112,6 @@ public class NotesFragment extends Fragment {
getViewLifecycleOwner(),
Lifecycle.State.RESUMED);
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.navNotes));
noteIntent = new Intent(ctx, CreateNoteActivity.class);
binding.newNote.setOnClickListener(

View file

@ -5,16 +5,11 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
@ -53,13 +48,17 @@ public class NotificationsFragment extends Fragment
private int pageResultLimit;
private String currentFilterMode = "unread";
public static String emptyErrorResponse;
private NotificationCountListener notificationCountListener;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public interface NotificationCountListener {
void onNotificationsMarkedRead();
}
@Nullable @Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@ -81,12 +80,26 @@ public class NotificationsFragment extends Fragment
viewBinding.notifications.setLayoutManager(linearLayoutManager);
viewBinding.notifications.setAdapter(notificationsAdapter);
viewBinding.filterChipGroup.setOnCheckedStateChangeListener(
(group, checkedIds) -> {
if (checkedIds.isEmpty()) return;
int checkedId = checkedIds.get(0);
String newFilterMode = checkedId == R.id.unreadChip ? "unread" : "read";
if (!newFilterMode.equals(currentFilterMode)) {
currentFilterMode = newFilterMode;
pageCurrentIndex = 1;
loadNotifications(false);
viewBinding.markAllAsRead.setVisibility(
currentFilterMode.equals("unread") ? View.VISIBLE : View.GONE);
}
});
viewBinding.unreadChip.setChecked(true);
viewBinding.notifications.addOnScrollListener(
new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (!recyclerView.canScrollVertically(1) && dy != 0) {
pageCurrentIndex++;
loadNotifications(true);
@ -129,13 +142,15 @@ public class NotificationsFragment extends Fragment
.markedNotificationsAsRead));
pageCurrentIndex = 1;
loadNotifications(false);
if (notificationCountListener != null) {
notificationCountListener
.onNotificationsMarkedRead();
}
} else {
if (emptyErrorResponse != null) {
if (!emptyErrorResponse.isEmpty()) {
if (emptyErrorResponse.contains(
"205")) {
SnackBar.success(
context,
requireActivity()
@ -149,9 +164,13 @@ public class NotificationsFragment extends Fragment
.markedNotificationsAsRead));
pageCurrentIndex = 1;
loadNotifications(false);
if (notificationCountListener
!= null) {
notificationCountListener
.onNotificationsMarkedRead();
}
}
} else {
activity.runOnUiThread(
() ->
SnackBar.error(
@ -180,72 +199,6 @@ public class NotificationsFragment extends Fragment
loadNotifications(true);
requireActivity()
.addMenuProvider(
new MenuProvider() {
Menu menu;
@Override
public void onCreateMenu(
@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
this.menu = menu;
menuInflater.inflate(R.menu.filter_menu_notifications, menu);
int filterIcon =
currentFilterMode.equalsIgnoreCase("read")
? R.drawable.ic_filter_closed
: R.drawable.ic_filter;
menu.getItem(0).setIcon(filterIcon);
if (currentFilterMode.equalsIgnoreCase("read")) {
viewBinding.markAllAsRead.setVisibility(View.GONE);
} else {
viewBinding.markAllAsRead.setVisibility(View.VISIBLE);
}
}
@Override
public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
if (menu.getItem(0).getItemId() == R.id.filterNotifications) {
BottomSheetNotificationsFilterFragment
bottomSheetNotificationsFilterFragment =
new BottomSheetNotificationsFilterFragment();
bottomSheetNotificationsFilterFragment.show(
getChildFragmentManager(),
"notificationsFilterBottomSheet");
bottomSheetNotificationsFilterFragment.setOnClickListener(
(text) -> {
currentFilterMode = text;
pageCurrentIndex = 1;
loadNotifications(false);
int filterIcon =
currentFilterMode.equalsIgnoreCase("read")
? R.drawable.ic_filter_closed
: R.drawable.ic_filter;
menu.getItem(0).setIcon(filterIcon);
if (currentFilterMode.equalsIgnoreCase("read")) {
viewBinding.markAllAsRead.setVisibility(
View.GONE);
} else {
viewBinding.markAllAsRead.setVisibility(
View.VISIBLE);
}
});
}
return false;
}
},
getViewLifecycleOwner(),
Lifecycle.State.RESUMED);
return viewBinding.getRoot();
}
@ -320,27 +273,21 @@ public class NotificationsFragment extends Fragment
.enqueue(
(SimpleCallback<NotificationThread>)
(call, voidResponse) -> {
// reload without any checks, because Gitea returns a 205
// and Java expects this to be empty
// but Gitea send a response -> results in a call of
// onFailure and no response is present
// if(voidResponse.isPresent() &&
// voidResponse.get().isSuccessful()) {
pageCurrentIndex = 1;
loadNotifications(false);
// }
if (notificationCountListener != null) {
notificationCountListener.onNotificationsMarkedRead();
}
});
}
if (StringUtils.containsAny(
notificationThread.getSubject().getType().toLowerCase(), "pull", "issue")) {
RepositoryContext repo =
new RepositoryContext(
notificationThread.getRepository().getOwner().getLogin(),
notificationThread.getRepository().getName(),
context); // we can't use the repository object here directly because
// the permissions are missing
context);
String issueUrl = notificationThread.getSubject().getUrl();
repo.saveToDB(context);
@ -371,8 +318,19 @@ public class NotificationsFragment extends Fragment
() -> {
pageCurrentIndex = 1;
loadNotifications(false);
if (notificationCountListener != null) {
notificationCountListener.onNotificationsMarkedRead();
}
});
bottomSheetNotificationsFragment.show(
getChildFragmentManager(), "notificationsBottomSheet");
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof NotificationCountListener) {
notificationCountListener = (NotificationCountListener) context;
}
}
}

View file

@ -19,7 +19,6 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.CreateOrganizationActivity;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.adapters.OrganizationsListAdapter;
import org.mian.gitnex.databinding.FragmentOrganizationsBinding;
import org.mian.gitnex.helpers.Constants;
@ -88,8 +87,6 @@ public class OrganizationsFragment extends Fragment {
getViewLifecycleOwner(),
Lifecycle.State.RESUMED);
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.navOrg));
organizationsViewModel = new ViewModelProvider(this).get(OrganizationsViewModel.class);
resultLimit = Constants.getCurrentResultLimit(getContext());

View file

@ -4,7 +4,6 @@ import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -36,19 +35,17 @@ import retrofit2.Callback;
import retrofit2.Response;
/**
* @author M M Arif
* @author mmarif
*/
public class PullRequestsFragment extends Fragment {
public static boolean resumePullRequests = false;
private final String TAG = "PullRequestFragment";
private FragmentPullRequestsBinding fragmentPullRequestsBinding;
private List<PullRequest> prList;
private PullRequestsAdapter adapter;
private Context context;
private int pageSize = Constants.prPageInit;
private int resultLimit;
private RepositoryContext repository;
public static PullRequestsFragment newInstance(RepositoryContext repository) {
@ -72,6 +69,7 @@ public class PullRequestsFragment extends Fragment {
resultLimit = Constants.getCurrentResultLimit(context);
prList = new ArrayList<>();
repository = RepositoryContext.fromBundle(requireArguments());
boolean archived = repository.getRepository().isArchived();
swipeRefresh.setOnRefreshListener(
@ -90,7 +88,7 @@ public class PullRequestsFragment extends Fragment {
},
200));
adapter = new PullRequestsAdapter(getContext(), prList);
adapter = new PullRequestsAdapter(context, prList);
adapter.setLoadMoreListener(
() ->
fragmentPullRequestsBinding.recyclerView.post(
@ -114,7 +112,6 @@ public class PullRequestsFragment extends Fragment {
.setFragmentRefreshListenerPr(
prState -> {
prList.clear();
adapter = new PullRequestsAdapter(context, prList);
adapter.setLoadMoreListener(
() ->
@ -135,10 +132,8 @@ public class PullRequestsFragment extends Fragment {
resultLimit);
}
}));
fragmentPullRequestsBinding.progressBar.setVisibility(View.VISIBLE);
fragmentPullRequestsBinding.noData.setVisibility(View.GONE);
loadInitial(
repository.getOwner(),
repository.getName(),
@ -160,28 +155,23 @@ public class PullRequestsFragment extends Fragment {
}
if (repository.getRepository().isHasPullRequests() && !archived) {
fragmentPullRequestsBinding.createPullRequest.setVisibility(View.VISIBLE);
fragmentPullRequestsBinding.createPullRequest.setOnClickListener(
v12 -> {
((RepoDetailActivity) requireActivity())
.createPrLauncher.launch(
repository.getIntent(
getContext(), CreatePullRequestActivity.class));
});
v ->
((RepoDetailActivity) requireActivity())
.createPrLauncher.launch(
repository.getIntent(
context, CreatePullRequestActivity.class)));
} else {
fragmentPullRequestsBinding.createPullRequest.setVisibility(View.GONE);
}
requireActivity()
.addMenuProvider(
new MenuProvider() {
@Override
public void onCreateMenu(
@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
menuInflater.inflate(R.menu.search_menu, menu);
menuInflater.inflate(R.menu.filter_menu_pr, menu);
@ -201,7 +191,6 @@ public class PullRequestsFragment extends Fragment {
searchView.setOnQueryTextListener(
new androidx.appcompat.widget.SearchView
.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
@ -228,9 +217,7 @@ public class PullRequestsFragment extends Fragment {
@Override
public void onResume() {
super.onResume();
if (resumePullRequests) {
loadInitial(
repository.getOwner(),
@ -244,12 +231,12 @@ public class PullRequestsFragment extends Fragment {
private void loadInitial(
String repoOwner, String repoName, int page, String prState, int resultLimit) {
Call<List<PullRequest>> call =
RetrofitClient.getApiInterface(context)
.repoListPullRequests(
repoOwner,
repoName,
null,
prState,
null,
null,
@ -260,14 +247,11 @@ public class PullRequestsFragment extends Fragment {
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<List<PullRequest>> call,
@NonNull Response<List<PullRequest>> response) {
if (response.code() == 200) {
assert response.body() != null;
if (!response.body().isEmpty()) {
prList.clear();
@ -283,10 +267,7 @@ public class PullRequestsFragment extends Fragment {
} else if (response.code() == 404) {
fragmentPullRequestsBinding.noData.setVisibility(View.VISIBLE);
fragmentPullRequestsBinding.progressBar.setVisibility(View.GONE);
} else {
Log.i(TAG, String.valueOf(response.code()));
}
Log.i(TAG, String.valueOf(response.code()));
}
@Override
@ -297,14 +278,13 @@ public class PullRequestsFragment extends Fragment {
private void loadMore(
String repoOwner, String repoName, int page, String prState, int resultLimit) {
fragmentPullRequestsBinding.progressBar.setVisibility(View.VISIBLE);
Call<List<PullRequest>> call =
RetrofitClient.getApiInterface(context)
.repoListPullRequests(
repoOwner,
repoName,
null,
prState,
null,
null,
@ -315,18 +295,13 @@ public class PullRequestsFragment extends Fragment {
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<List<PullRequest>> call,
@NonNull Response<List<PullRequest>> response) {
if (response.code() == 200) {
// remove loading view
prList.remove(prList.size() - 1);
List<PullRequest> result = response.body();
assert result != null;
if (!result.isEmpty()) {
pageSize = result.size();
@ -337,8 +312,6 @@ public class PullRequestsFragment extends Fragment {
}
adapter.notifyDataChanged();
fragmentPullRequestsBinding.progressBar.setVisibility(View.GONE);
} else {
Log.e(TAG, String.valueOf(response.code()));
}
}
@ -349,9 +322,7 @@ public class PullRequestsFragment extends Fragment {
}
private void filter(String text) {
List<PullRequest> arr = new ArrayList<>();
for (PullRequest d : prList) {
if (d == null || d.getTitle() == null || d.getBody() == null) {
continue;

View file

@ -105,8 +105,6 @@ public class RepositoriesFragment extends Fragment {
getViewLifecycleOwner(),
Lifecycle.State.RESUMED);
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.navRepos));
repositoriesViewModel = new ViewModelProvider(this).get(RepositoriesViewModel.class);
resultLimit = Constants.getCurrentResultLimit(getContext());

View file

@ -11,29 +11,16 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.BaseActivity;
import org.mian.gitnex.activities.MainActivity;
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.CustomAboutDialogBinding;
import org.mian.gitnex.databinding.FragmentSettingsBinding;
import org.mian.gitnex.helpers.AppUtil;
/**
* @author M M Arif
* @author mmarif
*/
public class SettingsFragment extends Fragment {
public static boolean refreshParent = false;
private Context ctx;
private MaterialAlertDialogBuilder materialAlertDialogBuilder;
@Nullable @Override
public View onCreateView(
@ -46,28 +33,35 @@ public class SettingsFragment extends Fragment {
ctx = getContext();
assert ctx != null;
materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.navSettings));
fragmentSettingsBinding.notificationsFrame.setVisibility(View.VISIBLE);
fragmentSettingsBinding.generalFrame.setOnClickListener(
generalFrameCall -> startActivity(new Intent(ctx, SettingsGeneralActivity.class)));
v1 ->
new BottomSheetSettingsGeneralFragment()
.show(getChildFragmentManager(), "BottomSheetSettingsGeneral"));
fragmentSettingsBinding.appearanceFrame.setOnClickListener(
v1 -> startActivity(new Intent(ctx, SettingsAppearanceActivity.class)));
v1 ->
new BottomSheetSettingsAppearanceFragment()
.show(getChildFragmentManager(), "BottomSheetSettingsAppearance"));
fragmentSettingsBinding.codeEditorFrame.setOnClickListener(
v1 -> startActivity(new Intent(ctx, SettingsCodeEditorActivity.class)));
v1 ->
new BottomSheetSettingsCodeEditorFragment()
.show(getChildFragmentManager(), "BottomSheetSettingsCodeEditor"));
fragmentSettingsBinding.securityFrame.setOnClickListener(
v1 -> startActivity(new Intent(ctx, SettingsSecurityActivity.class)));
v1 ->
new BottomSheetSettingsSecurityFragment()
.show(getChildFragmentManager(), "BottomSheetSettingsSecurity"));
fragmentSettingsBinding.notificationsFrame.setOnClickListener(
v1 -> startActivity(new Intent(ctx, SettingsNotificationsActivity.class)));
v1 ->
new BottomSheetSettingsNotificationsFragment()
.show(
getChildFragmentManager(),
"BottomSheetSettingsNotifications"));
fragmentSettingsBinding.backupData.setText(
getString(
@ -75,60 +69,22 @@ public class SettingsFragment extends Fragment {
getString(R.string.backup),
getString(R.string.restore)));
fragmentSettingsBinding.backupFrame.setOnClickListener(
v1 -> startActivity(new Intent(ctx, SettingsBackupRestoreActivity.class)));
v1 ->
new BottomSheetSettingsBackupRestoreFragment()
.show(
getChildFragmentManager(),
"BottomSheetSettingsBackupRestore"));
fragmentSettingsBinding.rateAppFrame.setOnClickListener(rateApp -> rateThisApp());
fragmentSettingsBinding.aboutAppFrame.setOnClickListener(aboutApp -> showAboutAppDialog());
fragmentSettingsBinding.aboutAppFrame.setOnClickListener(
aboutApp ->
new BottomSheetSettingsAboutFragment()
.show(getChildFragmentManager(), "AboutBottomSheet"));
return fragmentSettingsBinding.getRoot();
}
public void showAboutAppDialog() {
CustomAboutDialogBinding aboutAppDialogBinding =
CustomAboutDialogBinding.inflate(LayoutInflater.from(ctx));
View view = aboutAppDialogBinding.getRoot();
materialAlertDialogBuilder.setView(view);
aboutAppDialogBinding.appVersionBuild.setText(
getString(
R.string.appVersionBuild,
AppUtil.getAppVersion(ctx),
AppUtil.getAppBuildNo(ctx)));
aboutAppDialogBinding.userServerVersion.setText(
((BaseActivity) requireActivity()).getAccount().getServerVersion().toString());
aboutAppDialogBinding.donationLinkPatreon.setOnClickListener(
v12 ->
AppUtil.openUrlInBrowser(
requireContext(),
getResources().getString(R.string.supportLinkPatreon)));
aboutAppDialogBinding.translateLink.setOnClickListener(
v13 ->
AppUtil.openUrlInBrowser(
requireContext(), getResources().getString(R.string.crowdInLink)));
aboutAppDialogBinding.appWebsite.setOnClickListener(
v14 ->
AppUtil.openUrlInBrowser(
requireContext(),
getResources().getString(R.string.appWebsiteLink)));
aboutAppDialogBinding.feedback.setOnClickListener(
v14 ->
AppUtil.openUrlInBrowser(
requireContext(), getResources().getString(R.string.feedbackLink)));
if (AppUtil.isPro(requireContext())) {
aboutAppDialogBinding.layoutFrame1.setVisibility(View.GONE);
}
materialAlertDialogBuilder.show();
}
public void rateThisApp() {
try {

View file

@ -87,8 +87,6 @@ public class StarredRepositoriesFragment extends Fragment {
getViewLifecycleOwner(),
Lifecycle.State.RESUMED);
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.navStarredRepos));
repositoriesViewModel = new ViewModelProvider(this).get(RepositoriesViewModel.class);
resultLimit = Constants.getCurrentResultLimit(getContext());

View file

@ -87,8 +87,6 @@ public class WatchedRepositoriesFragment extends Fragment {
getViewLifecycleOwner(),
Lifecycle.State.RESUMED);
((MainActivity) requireActivity())
.setActionBarTitle(getResources().getString(R.string.navWatchedRepositories));
repositoriesViewModel = new ViewModelProvider(this).get(RepositoriesViewModel.class);
resultLimit = Constants.getCurrentResultLimit(getContext());

View file

@ -33,7 +33,7 @@ public class AppDatabaseSettings {
public static String APP_LINK_HANDLER_KEY = "app_link_handler";
public static String APP_LINK_HANDLER_DEFAULT = "0";
public static String APP_HOME_SCREEN_KEY = "app_home_screen";
public static String APP_HOME_SCREEN_DEFAULT = "3";
public static String APP_HOME_SCREEN_DEFAULT = "0";
public static String APP_CUSTOM_BROWSER_KEY = "app_custom_browser_tab";
public static String APP_CUSTOM_BROWSER_DEFAULT = "true";
public static String APP_DRAFTS_DELETION_KEY = "app_drafts_deletion";

View file

@ -9,9 +9,9 @@ import androidx.lifecycle.ViewModel;
import java.util.List;
import org.gitnex.tea4j.v2.models.Activity;
import org.mian.gitnex.R;
import org.mian.gitnex.adapters.DashboardAdapter;
import org.mian.gitnex.adapters.ActivitiesAdapter;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.databinding.FragmentDashboardBinding;
import org.mian.gitnex.databinding.FragmentActivitiesBinding;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.helpers.Toasty;
import retrofit2.Call;
@ -21,13 +21,13 @@ import retrofit2.Response;
/**
* @author M M Arif
*/
public class DashboardViewModel extends ViewModel {
public class ActivitiesViewModel extends ViewModel {
private MutableLiveData<List<Activity>> activityList;
private int resultLimit;
public LiveData<List<Activity>> getActivitiesList(
String username, Context ctx, FragmentDashboardBinding binding) {
String username, Context ctx, FragmentActivitiesBinding binding) {
activityList = new MutableLiveData<>();
resultLimit = Constants.getCurrentResultLimit(ctx);
@ -35,7 +35,7 @@ public class DashboardViewModel extends ViewModel {
return activityList;
}
public void loadActivityList(String username, Context ctx, FragmentDashboardBinding binding) {
public void loadActivityList(String username, Context ctx, FragmentActivitiesBinding binding) {
Call<List<Activity>> call =
RetrofitClient.getApiInterface(ctx)
@ -70,8 +70,8 @@ public class DashboardViewModel extends ViewModel {
String username,
int page,
Context ctx,
DashboardAdapter adapter,
FragmentDashboardBinding binding) {
ActivitiesAdapter adapter,
FragmentActivitiesBinding binding) {
Call<List<Activity>> call =
RetrofitClient.getApiInterface(ctx)

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/dimen12dp" />
<solid android:color="?attr/iconsColor" />
</shape>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M3,12h4l3,8l4,-16l3,8h4"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="?attr/iconsColor"
android:strokeLineCap="round"/>
</vector>

Some files were not shown because too many files have changed in this diff Show more