Popup confirmation for URL in MD, support PIN for app locking (#1564)

closes #1531
closes #1513

Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1564
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
2026-01-14 17:04:41 +01:00
committed by M M Arif
parent e471399d9f
commit 4ab08c7c1e
11 changed files with 644 additions and 345 deletions

View File

@@ -4,6 +4,7 @@ import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import java.util.concurrent.Executor;
@@ -72,7 +73,9 @@ public class BiometricUnlock extends AppCompatActivity {
new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.biometricAuthTitle))
.setSubtitle(getString(R.string.biometricAuthSubTitle))
.setNegativeButtonText(getString(R.string.cancelButton))
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
| BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.build();
biometricPrompt.authenticate(biometricPromptBuilder);

View File

@@ -319,7 +319,7 @@ public class LoginActivity extends BaseActivity {
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.procy_creds_required_msg));
getString(R.string.proxy_creds_required_msg));
if (usernameFilled) {
passwordInput.setText("");

View File

@@ -37,6 +37,11 @@ public class BottomSheetSettingsGeneralFragment extends BottomSheetDialogFragmen
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_LINK_HANDLER_KEY));
binding.urlPromptSwitch.setChecked(
Boolean.parseBoolean(
AppDatabaseSettings.getSettingsValue(
requireContext(), AppDatabaseSettings.APP_URL_PROMPT_KEY)));
setHomeScreenChipSelection(homeScreenSelectedChoice);
setLinkHandlerChipSelection(defaultLinkHandlerScreenSelectedChoice);
binding.switchTabs.setChecked(
@@ -116,6 +121,22 @@ public class BottomSheetSettingsGeneralFragment extends BottomSheetDialogFragmen
getString(R.string.settingsSave));
});
// URL Prompt switch listener
binding.urlPromptFrame.setOnClickListener(
v -> binding.urlPromptSwitch.setChecked(!binding.urlPromptSwitch.isChecked()));
binding.urlPromptSwitch.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
AppDatabaseSettings.updateSettingsValue(
requireContext(),
String.valueOf(isChecked),
AppDatabaseSettings.APP_URL_PROMPT_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
});
return binding.getRoot();
}

View File

@@ -1,9 +1,5 @@
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;
@@ -70,63 +66,25 @@ public class BottomSheetSettingsSecurityFragment extends BottomSheetDialogFragme
binding.switchBiometric.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked) {
BiometricManager biometricManager = BiometricManager.from(requireContext());
KeyguardManager keyguardManager =
(KeyguardManager)
requireContext().getSystemService(Context.KEYGUARD_SERVICE);
if (!isChecked) {
AppDatabaseSettings.updateSettingsValue(
requireContext(), "false", AppDatabaseSettings.APP_BIOMETRIC_KEY);
SnackBar.success(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.settingsSave));
return;
}
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 {
BiometricManager biometricManager = BiometricManager.from(requireContext());
int result =
biometricManager.canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_STRONG
| BiometricManager.Authenticators.DEVICE_CREDENTIAL);
switch (result) {
case BiometricManager.BIOMETRIC_SUCCESS:
AppDatabaseSettings.updateSettingsValue(
requireContext(),
"true",
@@ -135,14 +93,34 @@ public class BottomSheetSettingsSecurityFragment extends BottomSheetDialogFragme
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));
break;
case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
binding.switchBiometric.setChecked(false);
SnackBar.info(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.enrollBiometric));
break;
case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
case BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED:
case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
binding.switchBiometric.setChecked(false);
SnackBar.error(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.biometricNotSupported));
break;
case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
default:
binding.switchBiometric.setChecked(false);
SnackBar.error(
requireContext(),
requireActivity().findViewById(android.R.id.content),
getString(R.string.biometricNotAvailable));
break;
}
});

View File

@@ -67,6 +67,8 @@ public class AppDatabaseSettings {
public static String APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_DEFAULT = "false";
public static String APP_USER_HIDE_EMAIL_IN_NAV_KEY = "app_user_hide_email_nav";
public static String APP_USER_HIDE_EMAIL_IN_NAV_DEFAULT = "false";
public static String APP_URL_PROMPT_KEY = "app_url_prompt";
public static String APP_URL_PROMPT_DEFAULT = "false";
public static void initDefaultSettings(Context ctx) {
@@ -208,6 +210,11 @@ public class AppDatabaseSettings {
APP_USER_HIDE_EMAIL_IN_NAV_DEFAULT);
}
if (appSettingsApi.fetchSettingCountByKey(APP_URL_PROMPT_KEY) == 0) {
appSettingsApi.insertNewSetting(
APP_URL_PROMPT_KEY, APP_URL_PROMPT_DEFAULT, APP_URL_PROMPT_DEFAULT);
}
if (appSettingsApi.fetchSettingCountByKey("prefsMigration") == 0) {
appSettingsApi.insertNewSetting("prefsMigration", "true", "true");
}

View File

@@ -14,6 +14,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonConfiguration;
@@ -65,6 +66,8 @@ import org.mian.gitnex.helpers.codeeditor.theme.Theme;
import org.mian.gitnex.helpers.contexts.IssueContext;
import org.mian.gitnex.helpers.contexts.RepositoryContext;
import org.mian.gitnex.helpers.markdown.AlertPlugin;
import org.mian.gitnex.helpers.markdown.AutoLinkPlugin;
import org.mian.gitnex.helpers.markdown.UrlPromptPlugin;
import stormpot.Allocator;
import stormpot.BlazePool;
import stormpot.Config;
@@ -201,6 +204,8 @@ public class Markdown {
Markwon.Builder builder =
Markwon.builder(context)
.usePlugin(AlertPlugin.create(context))
.usePlugin(UrlPromptPlugin.create())
.usePlugin(AutoLinkPlugin.create())
.usePlugin(CorePlugin.create())
.usePlugin(HtmlPlugin.create())
.usePlugin(LinkifyPlugin.create(true))
@@ -278,6 +283,64 @@ public class Markdown {
builder.headingTypeface(
Typeface.create(tf, Typeface.BOLD));
}
@Override
public void configureConfiguration(
@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver(
(view, link) -> {
boolean showPrompt =
Boolean.parseBoolean(
AppDatabaseSettings
.getSettingsValue(
view
.getContext(),
AppDatabaseSettings
.APP_URL_PROMPT_KEY));
if (showPrompt) {
// Show Material Dialog
MaterialAlertDialogBuilder
dialogBuilder =
new MaterialAlertDialogBuilder(
view
.getContext());
dialogBuilder
.setTitle(R.string.isOpen)
.setMessage(link)
.setPositiveButton(
R.string.isOpen,
(dialog, which) ->
AppUtil
.openUrlInBrowser(
view
.getContext(),
link))
.setNegativeButton(
R.string.menuCopyText,
(dialog, which) ->
AppUtil
.copyToClipboard(
view
.getContext(),
link,
view.getContext()
.getString(
R
.string
.copyIssueUrlToastMsg)))
.setNeutralButton(
R.string.cancelButton,
null)
.show();
} else {
AppUtil.openUrlInBrowser(
view.getContext(), link);
}
});
super.configureConfiguration(builder);
}
});
markwon = builder.build();
@@ -357,6 +420,8 @@ public class Markdown {
Markwon.Builder builder =
Markwon.builder(context)
.usePlugin(AlertPlugin.create(context))
.usePlugin(UrlPromptPlugin.create())
.usePlugin(AutoLinkPlugin.create())
.usePlugin(CorePlugin.create())
.usePlugin(HtmlPlugin.create())
.usePlugin(LinkifyPlugin.create(true))
@@ -462,8 +527,7 @@ public class Markdown {
view.getContext().startActivity(i);
} else if (link.startsWith(
"gitnexissue://")) {
link = link.substring(14); // remove
// gitnexissue://
link = link.substring(14);
String index;
if (link.contains("/")) {
index = link.split("#")[1];
@@ -501,7 +565,6 @@ public class Markdown {
i.putExtra(
"openedFromLink", "true");
}
view.getContext().startActivity(i);
} else if (link.startsWith(
"gitnexcommit://")) {
@@ -517,12 +580,60 @@ public class Markdown {
} else {
sha = link.substring(1);
}
i.putExtra("sha", sha);
view.getContext().startActivity(i);
} else {
AppUtil.openUrlInBrowser(
view.getContext(), link);
boolean showPrompt =
Boolean.parseBoolean(
AppDatabaseSettings
.getSettingsValue(
view
.getContext(),
AppDatabaseSettings
.APP_URL_PROMPT_KEY));
if (showPrompt) {
MaterialAlertDialogBuilder
dialogBuilder =
new MaterialAlertDialogBuilder(
view
.getContext());
String finalLink = link;
dialogBuilder
.setTitle(R.string.isOpen)
.setMessage(link)
.setPositiveButton(
R.string.isOpen,
(dialog, which) ->
AppUtil
.openUrlInBrowser(
view
.getContext(),
finalLink))
.setNegativeButton(
R.string
.menuCopyText,
(dialog, which) ->
AppUtil
.copyToClipboard(
view
.getContext(),
finalLink,
view.getContext()
.getString(
R
.string
.copyIssueUrlToastMsg)))
.setNeutralButton(
R.string
.cancelButton,
null)
.show();
} else {
AppUtil.openUrlInBrowser(
view.getContext(), link);
}
}
});
super.configureConfiguration(builder);

View File

@@ -0,0 +1,68 @@
package org.mian.gitnex.helpers.markdown;
import android.text.SpannableStringBuilder;
import android.text.style.URLSpan;
import android.widget.TextView;
import androidx.annotation.NonNull;
import io.noties.markwon.AbstractMarkwonPlugin;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author mmarif
*/
public class AutoLinkPlugin extends AbstractMarkwonPlugin {
private static final Pattern URL_PATTERN =
Pattern.compile(
"\\b((https?|ftp)://|www\\.)[\\w-]+(\\.[\\w-]+)+([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?\\b",
Pattern.CASE_INSENSITIVE);
public static AutoLinkPlugin create() {
return new AutoLinkPlugin();
}
@Override
public void afterSetText(@NonNull TextView textView) {
CharSequence text = textView.getText();
if (text instanceof SpannableStringBuilder builder) {
linkifyUrls(builder);
}
}
private void linkifyUrls(SpannableStringBuilder builder) {
String text = builder.toString();
Matcher matcher = URL_PATTERN.matcher(text);
java.util.ArrayList<int[]> matches = new java.util.ArrayList<>();
while (matcher.find()) {
if (!hasUrlSpan(builder, matcher.start(), matcher.end())) {
matches.add(new int[] {matcher.start(), matcher.end()});
}
}
for (int[] match : matches) {
String url = text.substring(match[0], match[1]);
if (url.startsWith("www.")) {
url = "https://" + url;
}
builder.setSpan(
new URLSpan(url),
match[0],
match[1],
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private boolean hasUrlSpan(SpannableStringBuilder builder, int start, int end) {
URLSpan[] spans = builder.getSpans(start, end, URLSpan.class);
for (URLSpan span : spans) {
int spanStart = builder.getSpanStart(span);
int spanEnd = builder.getSpanEnd(span);
if (spanStart <= start && spanEnd >= end) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,44 @@
package org.mian.gitnex.helpers.markdown;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.LinkResolver;
import io.noties.markwon.MarkwonConfiguration;
import org.mian.gitnex.R;
import org.mian.gitnex.helpers.AppUtil;
/**
* @author mmarif
*/
public class UrlPromptPlugin extends AbstractMarkwonPlugin {
public static UrlPromptPlugin create() {
return new UrlPromptPlugin();
}
private UrlPromptPlugin() {}
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
LinkResolver promptResolver =
(view, link) -> {
if (link.startsWith("gitnex://")) {
return;
}
new AlertDialog.Builder(view.getContext())
.setTitle(R.string.isOpen)
.setMessage(link)
.setPositiveButton(
R.string.isOpen,
(dialog, which) ->
AppUtil.openUrlInBrowser(view.getContext(), link))
.setNegativeButton(R.string.cancelButton, null)
.show();
};
builder.linkResolver(promptResolver);
}
}

View File

@@ -1,328 +1,391 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/primaryBackgroundColor"
android:orientation="vertical"
android:paddingTop="@dimen/dimen6dp"
android:paddingBottom="@dimen/dimen12dp">
<View
android:layout_width="@dimen/dimen32dp"
android:layout_height="@dimen/dimen6dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="@dimen/dimen8dp"
android:background="@drawable/bottom_sheet_handle"
android:backgroundTint="?attr/primaryTextColor" />
android:layout_height="match_parent"
android:background="?attr/primaryBackgroundColor">
<LinearLayout
android:id="@+id/headerFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/dimen8dp">
android:paddingTop="@dimen/dimen6dp"
android:paddingBottom="@dimen/dimen12dp">
<TextView
android:id="@+id/bottomSheetHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/settingsGeneralHeader"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen18sp"/>
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="@dimen/dimen28dp"
android:layout_height="@dimen/dimen4dp"
<View
android:layout_width="@dimen/dimen32dp"
android:layout_height="@dimen/dimen6dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/dimen8dp"
android:layout_marginBottom="@dimen/dimen16dp"
app:cardCornerRadius="@dimen/dimen24dp"
app:cardElevation="@dimen/dimen0dp">
android:layout_marginBottom="@dimen/dimen8dp"
android:background="@drawable/bottom_sheet_handle"
android:backgroundTint="?attr/primaryTextColor" />
<View
<LinearLayout
android:id="@+id/headerFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/dimen8dp">
<TextView
android:id="@+id/bottomSheetHeader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/fabColor" />
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/settingsGeneralHeader"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen18sp"/>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="@dimen/dimen28dp"
android:layout_height="@dimen/dimen4dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/dimen8dp"
android:layout_marginBottom="@dimen/dimen16dp"
app:cardCornerRadius="@dimen/dimen24dp"
app:cardElevation="@dimen/dimen0dp">
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/fabColor" />
<LinearLayout
android:id="@+id/linkHandlerFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp"
android:orientation="vertical">
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/linkHandlerHeaderSelector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/generalDeepLinkDefaultScreen"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen4dp"
android:text="@string/generalDeepLinkDefaultScreenHintText"
android:textColor="?attr/hintColor"
android:textSize="@dimen/dimen12sp" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/linkHandlerChipGroup"
<LinearLayout
android:id="@+id/linkHandlerFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen8dp"
app:singleSelection="true"
app:selectionRequired="true">
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp"
android:orientation="vertical">
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler0"
style="@style/CustomChipFilter"
<TextView
android:id="@+id/linkHandlerHeaderSelector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/none" />
android:text="@string/generalDeepLinkDefaultScreen"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler1"
style="@style/CustomChipFilter"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navRepos" />
android:layout_marginTop="@dimen/dimen4dp"
android:text="@string/generalDeepLinkDefaultScreenHintText"
android:textColor="?attr/hintColor"
android:textSize="@dimen/dimen12sp" />
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler2"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
<com.google.android.material.chip.ChipGroup
android:id="@+id/linkHandlerChipGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/navOrg" />
android:layout_marginTop="@dimen/dimen8dp"
app:singleSelection="true"
app:selectionRequired="true">
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler3"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pageTitleNotifications" />
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler0"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/none" />
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler4"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pageTitleExplore" />
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler1"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navRepos" />
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler2"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navOrg" />
</LinearLayout>
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler3"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pageTitleNotifications" />
<LinearLayout
android:id="@+id/homeScreenFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp"
android:orientation="vertical">
<com.google.android.material.chip.Chip
android:id="@+id/chipLinkHandler4"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pageTitleExplore" />
<TextView
android:id="@+id/homeScreenHeaderSelector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/settingsHomeScreenHeaderText"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.chip.ChipGroup
android:id="@+id/homeScreenChipGroup"
</LinearLayout>
<LinearLayout
android:id="@+id/homeScreenFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen8dp"
app:singleSelection="true"
app:selectionRequired="true">
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp"
android:orientation="vertical">
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen0"
style="@style/CustomChipFilter"
<TextView
android:id="@+id/homeScreenHeaderSelector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home" />
android:text="@string/settingsHomeScreenHeaderText"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen1"
style="@style/CustomChipFilter"
<com.google.android.material.chip.ChipGroup
android:id="@+id/homeScreenChipGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen8dp"
app:singleSelection="true"
app:selectionRequired="true">
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen0"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen1"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navMyRepos" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen2"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navStarredRepos" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen3"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navOrg" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen4"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navRepos" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen5"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pageTitleExplore" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen6"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pageTitleNotifications" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen7"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navMyIssues" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen8"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navMostVisited" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen9"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navNotes" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen10"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/activities" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen11"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navWatchedRepositories" />
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/customTabsFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp">
<TextView
android:id="@+id/customTabsHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/useCustomTabs"
android:textColor="?attr/primaryTextColor"
android:layout_marginTop="@dimen/dimen8dp"
android:textSize="@dimen/dimen16sp"
android:singleLine="false"
android:ellipsize="none"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/guidelineTabs"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="@dimen/dimen12dp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchTabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navMyRepos" />
android:contentDescription="@string/useCustomTabs"
style="@style/m3SwitchStyle"
android:layout_marginStart="@dimen/dimen12dp"
app:layout_constraintStart_toEndOf="@id/customTabsHeader"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/customTabsHeader"
app:layout_constraintBottom_toBottomOf="@id/customTabsHeader" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen2"
style="@style/CustomChipFilter"
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineTabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navStarredRepos" />
android:orientation="vertical"
app:layout_constraintGuide_percent="0.85" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen3"
style="@style/CustomChipFilter"
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/urlPromptFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp">
<TextView
android:id="@+id/urlPromptHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/url_prompt_title"
android:textColor="?attr/primaryTextColor"
android:layout_marginTop="@dimen/dimen8dp"
android:textSize="@dimen/dimen16sp"
android:singleLine="false"
android:ellipsize="none"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/guidelineUrlPrompt"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="@dimen/dimen12dp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/urlPromptSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navOrg" />
android:contentDescription="@string/url_prompt_title"
style="@style/m3SwitchStyle"
android:layout_marginStart="@dimen/dimen12dp"
app:layout_constraintStart_toEndOf="@id/urlPromptHeader"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/urlPromptHeader"
app:layout_constraintBottom_toBottomOf="@id/urlPromptHeader" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen4"
style="@style/CustomChipFilter"
<TextView
android:id="@+id/urlPromptHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen4dp"
android:text="@string/url_prompt_description"
android:textColor="?attr/hintColor"
android:textSize="@dimen/dimen12sp"
android:layout_marginEnd="@dimen/dimen12dp"
app:layout_constraintStart_toStartOf="@id/urlPromptHeader"
app:layout_constraintEnd_toStartOf="@id/guidelineUrlPrompt"
app:layout_constraintTop_toBottomOf="@id/urlPromptHeader" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineUrlPrompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navRepos" />
android:orientation="vertical"
app:layout_constraintGuide_percent="0.85" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen5"
style="@style/CustomChipFilter"
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/enableSendReports"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp">
<TextView
android:id="@+id/enableReportsHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/settingsEnableReportsText"
android:textColor="?attr/primaryTextColor"
android:layout_marginTop="@dimen/dimen8dp"
android:textSize="@dimen/dimen16sp"
android:singleLine="false"
android:ellipsize="none"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/guidelineReports"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="@dimen/dimen12dp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/crashReportsSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pageTitleExplore" />
android:contentDescription="@string/settingsEnableReportsText"
style="@style/m3SwitchStyle"
android:layout_marginStart="@dimen/dimen12dp"
app:layout_constraintStart_toEndOf="@id/enableReportsHeader"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/enableReportsHeader"
app:layout_constraintBottom_toBottomOf="@id/enableReportsHeader" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen6"
style="@style/CustomChipFilter"
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineReports"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pageTitleNotifications" />
android:orientation="vertical"
app:layout_constraintGuide_percent="0.85" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen7"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navMyIssues" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen8"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navMostVisited" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen9"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navNotes" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen10"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/activities" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHomeScreen11"
style="@style/CustomChipFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navWatchedRepositories" />
</com.google.android.material.chip.ChipGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/customTabsFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp">
<TextView
android:id="@+id/customTabsHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/useCustomTabs"
android:textColor="?attr/primaryTextColor"
android:layout_marginTop="@dimen/dimen8dp"
android:textSize="@dimen/dimen16sp"
android:singleLine="false"
android:ellipsize="none"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/guidelineTabs"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="@dimen/dimen12dp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchTabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/useCustomTabs"
style="@style/m3SwitchStyle"
android:layout_marginStart="@dimen/dimen12dp"
app:layout_constraintStart_toEndOf="@id/customTabsHeader"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/customTabsHeader"
app:layout_constraintBottom_toBottomOf="@id/customTabsHeader" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineTabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.85" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/enableSendReports"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp">
<TextView
android:id="@+id/enableReportsHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/settingsEnableReportsText"
android:textColor="?attr/primaryTextColor"
android:layout_marginTop="@dimen/dimen8dp"
android:textSize="@dimen/dimen16sp"
android:singleLine="false"
android:ellipsize="none"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/guidelineReports"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="@dimen/dimen12dp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/crashReportsSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/settingsEnableReportsText"
style="@style/m3SwitchStyle"
android:layout_marginStart="@dimen/dimen12dp"
app:layout_constraintStart_toEndOf="@id/enableReportsHeader"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/enableReportsHeader"
app:layout_constraintBottom_toBottomOf="@id/enableReportsHeader" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineReports"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.85" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1068,5 +1068,8 @@
<string name="clear_proxy_creds">Clear Credentials</string>
<string name="proxy_creds_saved">Proxy credentials saved</string>
<string name="proxy_creds_cleared">Proxy credentials cleared</string>
<string name="procy_creds_required_msg">Please provide both username and password or leave both empty</string>
<string name="proxy_creds_required_msg">Please provide both username and password or leave both empty</string>
<string name="url_prompt_title">Show URL confirmation dialog</string>
<string name="url_prompt_description">Show a confirmation dialog before opening external links in Markdown rendered contents</string>
</resources>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/>
<certificates src="user"
tools:ignore="AcceptsUserCertificates" />
</trust-anchors>
</base-config>
</network-security-config>