mirror of
https://github.com/gitnex-org/gitnex.git
synced 2026-03-22 13:05:25 -05:00
Merge branch 'main' of codeberg.org:gitnex/GitNex
This commit is contained in:
@@ -76,6 +76,11 @@ JSON Configuration:
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP Basic Auth
|
||||
|
||||
GitNex from version **12.0.0** supports HTTP Basic Authentication for servers behind reverse proxies (nginx, Apache).
|
||||
[Read the Wiki](https://codeberg.org/gitnex/GitNex/wiki/HTTP-Basic-Auth-for-Reverse-Proxies)
|
||||
|
||||
## Links
|
||||
|
||||
- [Website](https://gitnex.com)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id "com.diffplug.spotless" version "8.0.0"
|
||||
id "com.diffplug.spotless" version "8.1.0"
|
||||
}
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
@@ -8,8 +8,8 @@ android {
|
||||
applicationId = "org.mian.gitnex"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 36
|
||||
versionCode = 1100
|
||||
versionName = "11.0.0"
|
||||
versionCode = 1195
|
||||
versionName = "12.0.0-dev"
|
||||
multiDexEnabled = true
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
compileSdk = 36
|
||||
@@ -82,19 +82,19 @@ 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.5"
|
||||
implementation "androidx.navigation:navigation-ui:2.9.5"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel:2.9.4"
|
||||
implementation "androidx.navigation:navigation-fragment:2.9.6"
|
||||
implementation "androidx.navigation:navigation-ui:2.9.6"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel:2.10.0"
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:5.3.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:5.3.2'
|
||||
implementation 'com.google.code.gson:gson:2.13.2'
|
||||
implementation 'com.github.ramseth001:TextDrawable:1.1.3'
|
||||
implementation 'com.squareup.retrofit2:retrofit:3.0.0'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:3.0.0'
|
||||
implementation 'com.squareup.retrofit2:converter-scalars:3.0.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.3.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.3.2'
|
||||
implementation 'org.ocpsoft.prettytime:prettytime:5.0.7.Final'
|
||||
implementation "com.github.skydoves:colorpickerview:2.3.0"
|
||||
implementation "io.noties.markwon:core:4.6.2"
|
||||
@@ -114,17 +114,17 @@ dependencies {
|
||||
implementation "com.github.bumptech.glide:okhttp3-integration:5.0.5"
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:5.0.5'
|
||||
implementation "com.caverock:androidsvg-aar:1.4"
|
||||
implementation "pl.droidsonroids.gif:android-gif-drawable:1.2.29"
|
||||
implementation "pl.droidsonroids.gif:android-gif-drawable:1.2.30"
|
||||
implementation 'com.google.guava:guava:33.5.0-jre'
|
||||
//noinspection NewerVersionAvailable,GradleDependency
|
||||
implementation 'commons-io:commons-io:2.5'
|
||||
implementation 'org.apache.commons:commons-lang3:3.19.0'
|
||||
implementation 'org.apache.commons:commons-lang3:3.20.0'
|
||||
implementation "com.github.chrisbanes:PhotoView:2.3.0"
|
||||
implementation 'ch.acra:acra-mail:5.13.1'
|
||||
implementation 'ch.acra:acra-limiter:5.13.1'
|
||||
implementation 'ch.acra:acra-notification:5.13.1'
|
||||
implementation 'androidx.room:room-runtime:2.8.3'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.8.3'
|
||||
implementation 'androidx.room:room-runtime:2.8.4'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.8.4'
|
||||
implementation "androidx.work:work-runtime:2.11.0"
|
||||
implementation "io.mikael:urlbuilder:2.0.9"
|
||||
implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2"
|
||||
|
||||
@@ -13,6 +13,7 @@ import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
@@ -20,6 +21,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import io.mikael.urlbuilder.UrlBuilder;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -61,6 +63,8 @@ public class LoginActivity extends BaseActivity {
|
||||
private boolean hasShownInitialNetworkError = false;
|
||||
private int btnText;
|
||||
private String selectedProvider = "gitea";
|
||||
private String proxyAuthUsername = null;
|
||||
private String proxyAuthPassword = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@@ -96,6 +100,9 @@ public class LoginActivity extends BaseActivity {
|
||||
UserAccount account = userAccountsApi.getAccountById(accountId);
|
||||
if (account != null) {
|
||||
|
||||
proxyAuthUsername = account.getProxyAuthUsername();
|
||||
proxyAuthPassword = account.getProxyAuthPassword();
|
||||
|
||||
// Prefill provider
|
||||
selectedProvider = account.getProvider();
|
||||
if (selectedProvider.equals("gitea")) {
|
||||
@@ -150,6 +157,8 @@ public class LoginActivity extends BaseActivity {
|
||||
activityLoginBinding.restoreFromBackup.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
activityLoginBinding.setupProxyAuth.setOnClickListener(view -> showProxyAuthDialog());
|
||||
|
||||
NetworkStatusObserver networkStatusObserver = NetworkStatusObserver.getInstance(ctx);
|
||||
|
||||
activityLoginBinding.appVersion.setText(AppUtil.getAppVersion(appCtx));
|
||||
@@ -253,6 +262,101 @@ public class LoginActivity extends BaseActivity {
|
||||
});
|
||||
}
|
||||
|
||||
private void showProxyAuthDialog() {
|
||||
View dialogView = getLayoutInflater().inflate(R.layout.custom_dialog_proxy_auth, null);
|
||||
|
||||
TextInputEditText usernameInput = dialogView.findViewById(R.id.proxyUsername);
|
||||
TextInputEditText passwordInput = dialogView.findViewById(R.id.proxyPassword);
|
||||
|
||||
if (proxyAuthUsername != null && !proxyAuthUsername.isEmpty()) {
|
||||
usernameInput.setText(proxyAuthUsername);
|
||||
}
|
||||
if (proxyAuthPassword != null && !proxyAuthPassword.isEmpty()) {
|
||||
passwordInput.setText(proxyAuthPassword);
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder dialogBuilder =
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setView(dialogView)
|
||||
.setNegativeButton(R.string.clear_proxy_creds, null)
|
||||
.setNeutralButton(R.string.skip_proxy_creds, null)
|
||||
.setPositiveButton(R.string.save_proxy_creds, null);
|
||||
|
||||
AlertDialog dialog = dialogBuilder.create();
|
||||
|
||||
dialog.setOnShowListener(
|
||||
dialogInterface -> {
|
||||
Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
Button negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
|
||||
Button neutralButton = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
|
||||
|
||||
positiveButton.setOnClickListener(
|
||||
view -> {
|
||||
String enteredUsername =
|
||||
usernameInput.getText() != null
|
||||
? usernameInput.getText().toString().trim()
|
||||
: "";
|
||||
String enteredPassword =
|
||||
passwordInput.getText() != null
|
||||
? passwordInput.getText().toString().trim()
|
||||
: "";
|
||||
|
||||
if (enteredUsername.isEmpty() && enteredPassword.isEmpty()) {
|
||||
proxyAuthUsername = null;
|
||||
proxyAuthPassword = null;
|
||||
SnackBar.info(
|
||||
ctx,
|
||||
findViewById(android.R.id.content),
|
||||
getString(R.string.proxy_creds_cleared));
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean usernameFilled = !enteredUsername.isEmpty();
|
||||
boolean passwordFilled = !enteredPassword.isEmpty();
|
||||
|
||||
if (usernameFilled != passwordFilled) {
|
||||
SnackBar.error(
|
||||
ctx,
|
||||
findViewById(android.R.id.content),
|
||||
getString(R.string.procy_creds_required_msg));
|
||||
|
||||
if (usernameFilled) {
|
||||
passwordInput.setText("");
|
||||
passwordInput.requestFocus();
|
||||
} else {
|
||||
usernameInput.setText("");
|
||||
usernameInput.requestFocus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
proxyAuthUsername = enteredUsername;
|
||||
proxyAuthPassword = enteredPassword;
|
||||
SnackBar.info(
|
||||
ctx,
|
||||
findViewById(android.R.id.content),
|
||||
getString(R.string.proxy_creds_saved));
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
negativeButton.setOnClickListener(
|
||||
view -> {
|
||||
proxyAuthUsername = null;
|
||||
proxyAuthPassword = null;
|
||||
SnackBar.info(
|
||||
ctx,
|
||||
findViewById(android.R.id.content),
|
||||
getString(R.string.proxy_creds_cleared));
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
neutralButton.setOnClickListener(view -> dialog.dismiss());
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void showTokenHelpDialog() {
|
||||
|
||||
MaterialAlertDialogBuilder dialogBuilder =
|
||||
@@ -364,7 +468,13 @@ public class LoginActivity extends BaseActivity {
|
||||
|
||||
private void serverPageLimitSettings(String instanceUrl, String loginToken) {
|
||||
Call<GeneralAPISettings> generalAPISettings =
|
||||
RetrofitClient.getApiInterface(ctx, instanceUrl, "token " + loginToken, null)
|
||||
RetrofitClient.getApiInterface(
|
||||
ctx,
|
||||
instanceUrl,
|
||||
"token " + loginToken,
|
||||
null,
|
||||
proxyAuthUsername,
|
||||
proxyAuthPassword)
|
||||
.getGeneralAPISettings();
|
||||
generalAPISettings.enqueue(
|
||||
new Callback<>() {
|
||||
@@ -398,7 +508,12 @@ public class LoginActivity extends BaseActivity {
|
||||
|
||||
Call<ServerVersion> callVersion =
|
||||
RetrofitClient.getApiInterface(
|
||||
ctx, instanceUrl.toString(), "token " + loginToken, null)
|
||||
ctx,
|
||||
instanceUrl.toString(),
|
||||
"token " + loginToken,
|
||||
null,
|
||||
proxyAuthUsername,
|
||||
proxyAuthPassword)
|
||||
.getVersion();
|
||||
|
||||
callVersion.enqueue(
|
||||
@@ -496,7 +611,12 @@ public class LoginActivity extends BaseActivity {
|
||||
|
||||
Call<User> call =
|
||||
RetrofitClient.getApiInterface(
|
||||
ctx, instanceUrl.toString(), "token " + loginToken, null)
|
||||
ctx,
|
||||
instanceUrl.toString(),
|
||||
"token " + loginToken,
|
||||
null,
|
||||
proxyAuthUsername,
|
||||
proxyAuthPassword)
|
||||
.userGetCurrent();
|
||||
|
||||
call.enqueue(
|
||||
@@ -531,6 +651,23 @@ public class LoginActivity extends BaseActivity {
|
||||
maxResponseItems,
|
||||
defaultPagingNumber,
|
||||
selectedProvider);
|
||||
|
||||
// Save or clear proxy credentials
|
||||
userAccountsApi.updateProxyAuthCredentials(
|
||||
(int) accountId,
|
||||
(proxyAuthUsername != null
|
||||
&& !proxyAuthUsername.isEmpty()
|
||||
&& proxyAuthPassword != null
|
||||
&& !proxyAuthPassword.isEmpty())
|
||||
? proxyAuthUsername
|
||||
: null,
|
||||
(proxyAuthUsername != null
|
||||
&& !proxyAuthUsername.isEmpty()
|
||||
&& proxyAuthPassword != null
|
||||
&& !proxyAuthPassword.isEmpty())
|
||||
? proxyAuthPassword
|
||||
: null);
|
||||
|
||||
account = userAccountsApi.getAccountById((int) accountId);
|
||||
} else {
|
||||
userAccountsApi.updateTokenByAccountName(
|
||||
@@ -540,6 +677,24 @@ public class LoginActivity extends BaseActivity {
|
||||
userAccountsApi
|
||||
.getAccountByName(accountName)
|
||||
.getAccountId());
|
||||
|
||||
UserAccount existingAccount =
|
||||
userAccountsApi.getAccountByName(accountName);
|
||||
userAccountsApi.updateProxyAuthCredentials(
|
||||
existingAccount.getAccountId(),
|
||||
(proxyAuthUsername != null
|
||||
&& !proxyAuthUsername.isEmpty()
|
||||
&& proxyAuthPassword != null
|
||||
&& !proxyAuthPassword.isEmpty())
|
||||
? proxyAuthUsername
|
||||
: null,
|
||||
(proxyAuthUsername != null
|
||||
&& !proxyAuthUsername.isEmpty()
|
||||
&& proxyAuthPassword != null
|
||||
&& !proxyAuthPassword.isEmpty())
|
||||
? proxyAuthPassword
|
||||
: null);
|
||||
|
||||
userAccountsApi.login(
|
||||
userAccountsApi
|
||||
.getAccountByName(accountName)
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
@@ -37,6 +38,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import org.gitnex.tea4j.v2.models.Label;
|
||||
import org.gitnex.tea4j.v2.models.Milestone;
|
||||
@@ -386,43 +388,129 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetListe
|
||||
}
|
||||
|
||||
private void showLabelFilterDialog() {
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
View dialogView =
|
||||
LayoutInflater.from(this).inflate(R.layout.custom_filter_issues_by_labels, null);
|
||||
|
||||
FlexboxLayout labelsContainer = dialogView.findViewById(R.id.labelsContainer);
|
||||
Button filterButton = dialogView.findViewById(R.id.filterButton);
|
||||
LinearProgressIndicator progressIndicator = dialogView.findViewById(R.id.progressBar);
|
||||
|
||||
ViewGroup parent = (ViewGroup) labelsContainer.getParent();
|
||||
NestedScrollView scrollView = null;
|
||||
if (parent != null && parent.getParent() instanceof NestedScrollView) {
|
||||
scrollView = (NestedScrollView) parent.getParent();
|
||||
}
|
||||
|
||||
labelsContainer.removeAllViews();
|
||||
|
||||
for (Label label : labelsList) {
|
||||
Chip chip =
|
||||
(Chip)
|
||||
LayoutInflater.from(this)
|
||||
.inflate(
|
||||
R.layout.list_filter_issues_by_labels,
|
||||
labelsContainer,
|
||||
false);
|
||||
chip.setText(label.getName());
|
||||
chip.setCheckable(true);
|
||||
chip.setChecked(
|
||||
Boolean.TRUE.equals(selectedStates.getOrDefault(label.getName(), false)));
|
||||
final Map<String, Boolean> selectedStates = new HashMap<>();
|
||||
final int[] currentPage = {1};
|
||||
final boolean[] isLoading = {false};
|
||||
final int pageSize = 50;
|
||||
|
||||
GradientDrawable dot = new GradientDrawable();
|
||||
dot.setShape(GradientDrawable.OVAL);
|
||||
dot.setSize(16, 16);
|
||||
dot.setColor(Color.parseColor("#" + label.getColor()));
|
||||
chip.setChipIcon(dot);
|
||||
Consumer<List<Label>> addLabelsToView =
|
||||
(newLabels) -> {
|
||||
for (Label label : newLabels) {
|
||||
Chip chip =
|
||||
(Chip)
|
||||
LayoutInflater.from(this)
|
||||
.inflate(
|
||||
R.layout.list_filter_issues_by_labels,
|
||||
labelsContainer,
|
||||
false);
|
||||
chip.setText(label.getName());
|
||||
chip.setCheckable(true);
|
||||
chip.setChecked(Boolean.TRUE.equals(selectedStates.get(label.getName())));
|
||||
|
||||
chip.setOnCheckedChangeListener(
|
||||
(buttonView, isChecked) -> {
|
||||
selectedStates.put(label.getName(), isChecked);
|
||||
});
|
||||
GradientDrawable dot = new GradientDrawable();
|
||||
dot.setShape(GradientDrawable.OVAL);
|
||||
dot.setSize(16, 16);
|
||||
dot.setColor(Color.parseColor("#" + label.getColor()));
|
||||
chip.setChipIcon(dot);
|
||||
|
||||
labelsContainer.addView(chip);
|
||||
chip.setOnCheckedChangeListener(
|
||||
(buttonView, isChecked) -> {
|
||||
selectedStates.put(label.getName(), isChecked);
|
||||
});
|
||||
|
||||
labelsContainer.addView(chip);
|
||||
}
|
||||
};
|
||||
|
||||
Consumer<Integer> loadLabels =
|
||||
(page) -> {
|
||||
if (isLoading[0]) return;
|
||||
|
||||
isLoading[0] = true;
|
||||
progressIndicator.setVisibility(View.VISIBLE);
|
||||
|
||||
Call<List<Label>> call =
|
||||
RetrofitClient.getApiInterface(this)
|
||||
.issueListLabels(
|
||||
repository.getOwner(),
|
||||
repository.getName(),
|
||||
page,
|
||||
pageSize);
|
||||
|
||||
call.enqueue(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<List<Label>> call,
|
||||
@NonNull Response<List<Label>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
List<Label> newLabels = response.body();
|
||||
|
||||
if (page == 1) {
|
||||
selectedStates.clear();
|
||||
labelsContainer.removeAllViews();
|
||||
}
|
||||
|
||||
if (page == 1) {
|
||||
labelsList.clear();
|
||||
}
|
||||
labelsList.addAll(newLabels);
|
||||
|
||||
for (Label label : newLabels) {
|
||||
selectedStates.putIfAbsent(label.getName(), false);
|
||||
}
|
||||
|
||||
addLabelsToView.accept(newLabels);
|
||||
}
|
||||
|
||||
isLoading[0] = false;
|
||||
progressIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(
|
||||
@NonNull Call<List<Label>> call, @NonNull Throwable t) {
|
||||
isLoading[0] = false;
|
||||
progressIndicator.setVisibility(View.GONE);
|
||||
Toasty.error(
|
||||
RepoDetailActivity.this,
|
||||
getString(R.string.genericServerResponseError));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (scrollView != null) {
|
||||
scrollView.setOnScrollChangeListener(
|
||||
(NestedScrollView.OnScrollChangeListener)
|
||||
(v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
|
||||
if (!isLoading[0] && v.getChildAt(0) != null) {
|
||||
View child = v.getChildAt(0);
|
||||
if (child.getBottom() <= (v.getHeight() + v.getScrollY())) {
|
||||
currentPage[0]++;
|
||||
loadLabels.accept(currentPage[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadLabels.accept(currentPage[0]);
|
||||
|
||||
AlertDialog dialog = builder.setView(dialogView).create();
|
||||
|
||||
filterButton.setOnClickListener(
|
||||
|
||||
@@ -16,6 +16,7 @@ import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.mian.gitnex.activities.BaseActivity;
|
||||
import org.mian.gitnex.clients.BasicAuthInterceptor;
|
||||
import org.mian.gitnex.helpers.AppDatabaseSettings;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
import org.mian.gitnex.helpers.FilesData;
|
||||
@@ -38,15 +39,28 @@ public class ApiRetrofitClient {
|
||||
var account = ((BaseActivity) context).getAccount().getAccount();
|
||||
String url = account.getInstanceUrl();
|
||||
String token = ((BaseActivity) context).getAccount().getAuthorization();
|
||||
String proxyUsername = account.getProxyAuthUsername();
|
||||
String proxyPassword = account.getProxyAuthPassword();
|
||||
File cacheFile = new File(context.getCacheDir(), "http-cache");
|
||||
|
||||
String key = token.hashCode() + "@" + url;
|
||||
return instances.computeIfAbsent(key, k -> createApi(context, url, token, cacheFile));
|
||||
if (proxyUsername != null && proxyPassword != null) {
|
||||
key += "@proxy@" + proxyUsername.hashCode();
|
||||
}
|
||||
|
||||
return instances.computeIfAbsent(
|
||||
key, k -> createApi(context, url, token, cacheFile, proxyUsername, proxyPassword));
|
||||
}
|
||||
|
||||
private static ApiInterface createApi(
|
||||
Context context, String url, String token, File cacheFile) {
|
||||
OkHttpClient client = buildOkHttpClient(context, token, cacheFile);
|
||||
Context context,
|
||||
String url,
|
||||
String token,
|
||||
File cacheFile,
|
||||
String proxyUsername,
|
||||
String proxyPassword) {
|
||||
OkHttpClient client =
|
||||
buildOkHttpClient(context, token, cacheFile, proxyUsername, proxyPassword);
|
||||
Retrofit retrofit =
|
||||
new Retrofit.Builder()
|
||||
.baseUrl(url)
|
||||
@@ -57,7 +71,17 @@ public class ApiRetrofitClient {
|
||||
return retrofit.create(ApiInterface.class);
|
||||
}
|
||||
|
||||
private static OkHttpClient buildOkHttpClient(Context context, String token, File cacheFile) {
|
||||
private static ApiInterface createApi(
|
||||
Context context, String url, String token, File cacheFile) {
|
||||
return createApi(context, url, token, cacheFile, null, null);
|
||||
}
|
||||
|
||||
private static OkHttpClient buildOkHttpClient(
|
||||
Context context,
|
||||
String token,
|
||||
File cacheFile,
|
||||
String proxyUsername,
|
||||
String proxyPassword) {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
MemorizingTrustManager trustManager = new MemorizingTrustManager(context);
|
||||
@@ -73,8 +97,16 @@ public class ApiRetrofitClient {
|
||||
.hostnameVerifier(
|
||||
trustManager.wrapHostnameVerifier(
|
||||
HttpsURLConnection.getDefaultHostnameVerifier()))
|
||||
.addInterceptor(userAgentInterceptor(context))
|
||||
.addInterceptor(authInterceptor(token));
|
||||
.addInterceptor(userAgentInterceptor(context));
|
||||
|
||||
if (proxyUsername != null
|
||||
&& !proxyUsername.isEmpty()
|
||||
&& proxyPassword != null
|
||||
&& !proxyPassword.isEmpty()) {
|
||||
builder.addInterceptor(new BasicAuthInterceptor(proxyUsername, proxyPassword));
|
||||
}
|
||||
|
||||
builder.addInterceptor(authInterceptor(token));
|
||||
|
||||
if (cacheFile != null) {
|
||||
int cacheSize = getCacheSize(context);
|
||||
@@ -91,6 +123,10 @@ public class ApiRetrofitClient {
|
||||
}
|
||||
}
|
||||
|
||||
private static OkHttpClient buildOkHttpClient(Context context, String token, File cacheFile) {
|
||||
return buildOkHttpClient(context, token, cacheFile, null, null);
|
||||
}
|
||||
|
||||
private static Interceptor userAgentInterceptor(Context ctx) {
|
||||
return chain -> {
|
||||
Request req =
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.mian.gitnex.clients;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* @author mmarif
|
||||
*/
|
||||
public class BasicAuthInterceptor implements Interceptor {
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
|
||||
public BasicAuthInterceptor(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@NonNull @Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request originalRequest = chain.request();
|
||||
|
||||
String credentials = username + ":" + password;
|
||||
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
|
||||
|
||||
Request modifiedRequest =
|
||||
originalRequest
|
||||
.newBuilder()
|
||||
.header("X-Proxy-Auth", "Basic " + encodedCredentials)
|
||||
.build();
|
||||
|
||||
return chain.proceed(modifiedRequest);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import javax.net.ssl.X509TrustManager;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import org.gitnex.tea4j.v2.auth.ApiKeyAuth;
|
||||
import org.mian.gitnex.activities.BaseActivity;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
|
||||
|
||||
@@ -16,7 +17,8 @@ import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
|
||||
*/
|
||||
public class GlideHttpClient {
|
||||
|
||||
public static OkHttpClient getOkHttpClient(Context context, String token) {
|
||||
public static OkHttpClient getOkHttpClient(
|
||||
Context context, String token, String proxyUsername, String proxyPassword) {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(context);
|
||||
@@ -49,8 +51,16 @@ public class GlideHttpClient {
|
||||
+ ")")
|
||||
.build();
|
||||
return chain.proceed(modifiedRequest);
|
||||
})
|
||||
.addInterceptor(auth);
|
||||
});
|
||||
|
||||
if (proxyUsername != null
|
||||
&& !proxyUsername.isEmpty()
|
||||
&& proxyPassword != null
|
||||
&& !proxyPassword.isEmpty()) {
|
||||
builder.addInterceptor(new BasicAuthInterceptor(proxyUsername, proxyPassword));
|
||||
}
|
||||
|
||||
builder.addInterceptor(auth);
|
||||
|
||||
return builder.build();
|
||||
|
||||
@@ -59,7 +69,30 @@ public class GlideHttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
public static OkHttpClient getUnsafeOkHttpClient(String token) {
|
||||
public static OkHttpClient getOkHttpClient(Context context, String token) {
|
||||
return getOkHttpClient(context, token, null, null);
|
||||
}
|
||||
|
||||
public static OkHttpClient getOkHttpClientForCurrentAccount(Context context) {
|
||||
if (!(context instanceof BaseActivity)) {
|
||||
return getOkHttpClient(context, "");
|
||||
}
|
||||
|
||||
var accountWrapper = ((BaseActivity) context).getAccount();
|
||||
if (accountWrapper == null || accountWrapper.getAccount() == null) {
|
||||
return getOkHttpClient(context, "");
|
||||
}
|
||||
|
||||
var account = accountWrapper.getAccount();
|
||||
String token = accountWrapper.getAuthorization();
|
||||
String proxyUsername = account.getProxyAuthUsername();
|
||||
String proxyPassword = account.getProxyAuthPassword();
|
||||
|
||||
return getOkHttpClient(context, token, proxyUsername, proxyPassword);
|
||||
}
|
||||
|
||||
public static OkHttpClient getUnsafeOkHttpClient(
|
||||
String token, String proxyUsername, String proxyPassword) {
|
||||
try {
|
||||
@SuppressWarnings("CustomX509TrustManager")
|
||||
final X509TrustManager trustAllCerts =
|
||||
@@ -90,8 +123,16 @@ public class GlideHttpClient {
|
||||
OkHttpClient.Builder builder =
|
||||
new OkHttpClient.Builder()
|
||||
.sslSocketFactory(sslSocketFactory, trustAllCerts)
|
||||
.hostnameVerifier((hostname, session) -> true)
|
||||
.addInterceptor(auth);
|
||||
.hostnameVerifier((hostname, session) -> true);
|
||||
|
||||
if (proxyUsername != null
|
||||
&& !proxyUsername.isEmpty()
|
||||
&& proxyPassword != null
|
||||
&& !proxyPassword.isEmpty()) {
|
||||
builder.addInterceptor(new BasicAuthInterceptor(proxyUsername, proxyPassword));
|
||||
}
|
||||
|
||||
builder.addInterceptor(auth);
|
||||
|
||||
return builder.build();
|
||||
|
||||
|
||||
@@ -27,11 +27,16 @@ public class GlideService extends AppGlideModule {
|
||||
@Override
|
||||
public void registerComponents(
|
||||
@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
||||
String token = "";
|
||||
|
||||
OkHttpClient okHttpClient;
|
||||
|
||||
if (context instanceof BaseActivity) {
|
||||
token = ((BaseActivity) context).getAccount().getAuthorization();
|
||||
okHttpClient = GlideHttpClient.getOkHttpClientForCurrentAccount(context);
|
||||
} else {
|
||||
String token = "";
|
||||
okHttpClient = GlideHttpClient.getOkHttpClient(context, token);
|
||||
}
|
||||
OkHttpClient okHttpClient = GlideHttpClient.getOkHttpClient(context, token);
|
||||
|
||||
registry.replace(
|
||||
GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient));
|
||||
}
|
||||
|
||||
@@ -58,7 +58,12 @@ public class RetrofitClient {
|
||||
private static final int CACHE_SIZE_MB = 50;
|
||||
private static final int MAX_STALE_SECONDS = 60 * 60 * 24 * 30;
|
||||
|
||||
private static OkHttpClient buildOkHttpClient(Context context, String token, File cacheFile) {
|
||||
private static OkHttpClient buildOkHttpClient(
|
||||
Context context,
|
||||
String token,
|
||||
File cacheFile,
|
||||
String proxyUsername,
|
||||
String proxyPassword) {
|
||||
|
||||
// HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
|
||||
// logging.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
@@ -97,6 +102,13 @@ public class RetrofitClient {
|
||||
return chain.proceed(modifiedRequest);
|
||||
});
|
||||
|
||||
if (proxyUsername != null
|
||||
&& !proxyUsername.isEmpty()
|
||||
&& proxyPassword != null
|
||||
&& !proxyPassword.isEmpty()) {
|
||||
okHttpClient.addInterceptor(new BasicAuthInterceptor(proxyUsername, proxyPassword));
|
||||
}
|
||||
|
||||
if (cacheFile != null) {
|
||||
int cacheSize = CACHE_SIZE_MB;
|
||||
try {
|
||||
@@ -178,7 +190,18 @@ public class RetrofitClient {
|
||||
|
||||
private static Retrofit createRetrofit(
|
||||
Context context, String instanceUrl, String token, File cacheFile) {
|
||||
OkHttpClient okHttpClient = buildOkHttpClient(context, token, cacheFile);
|
||||
return createRetrofit(context, instanceUrl, token, cacheFile, null, null);
|
||||
}
|
||||
|
||||
private static Retrofit createRetrofit(
|
||||
Context context,
|
||||
String instanceUrl,
|
||||
String token,
|
||||
File cacheFile,
|
||||
String proxyUsername,
|
||||
String proxyPassword) {
|
||||
OkHttpClient okHttpClient =
|
||||
buildOkHttpClient(context, token, cacheFile, proxyUsername, proxyPassword);
|
||||
return new Retrofit.Builder()
|
||||
.baseUrl(instanceUrl)
|
||||
.client(okHttpClient)
|
||||
@@ -194,7 +217,13 @@ public class RetrofitClient {
|
||||
|
||||
public static OkHttpClient getOkHttpClient(Context context, String token) {
|
||||
File cacheFile = new File(context.getCacheDir(), "http-cache");
|
||||
return buildOkHttpClient(context, token, cacheFile);
|
||||
return buildOkHttpClient(context, token, cacheFile, null, null);
|
||||
}
|
||||
|
||||
public static OkHttpClient getOkHttpClient(
|
||||
Context context, String token, String proxyUsername, String proxyPassword) {
|
||||
File cacheFile = new File(context.getCacheDir(), "http-cache");
|
||||
return buildOkHttpClient(context, token, cacheFile, proxyUsername, proxyPassword);
|
||||
}
|
||||
|
||||
public static ApiInterface getApiInterface(Context context) {
|
||||
@@ -204,39 +233,79 @@ public class RetrofitClient {
|
||||
throw new IllegalStateException(
|
||||
"No active account available. Use explicit URL and token.");
|
||||
}
|
||||
|
||||
var account = ((BaseActivity) context).getAccount().getAccount();
|
||||
String proxyUsername = account.getProxyAuthUsername();
|
||||
String proxyPassword = account.getProxyAuthPassword();
|
||||
|
||||
return getApiInterface(
|
||||
context,
|
||||
((BaseActivity) context).getAccount().getAccount().getInstanceUrl(),
|
||||
account.getInstanceUrl(),
|
||||
((BaseActivity) context).getAccount().getAuthorization(),
|
||||
((BaseActivity) context).getAccount().getCacheDir(context));
|
||||
((BaseActivity) context).getAccount().getCacheDir(context),
|
||||
proxyUsername,
|
||||
proxyPassword);
|
||||
}
|
||||
|
||||
public static WebApi getWebInterface(Context context) {
|
||||
String instanceUrl = ((BaseActivity) context).getAccount().getAccount().getInstanceUrl();
|
||||
instanceUrl = instanceUrl.substring(0, instanceUrl.lastIndexOf("api/v1/"));
|
||||
|
||||
var account = ((BaseActivity) context).getAccount().getAccount();
|
||||
String proxyUsername = account.getProxyAuthUsername();
|
||||
String proxyPassword = account.getProxyAuthPassword();
|
||||
|
||||
return getWebInterface(
|
||||
context,
|
||||
instanceUrl,
|
||||
((BaseActivity) context).getAccount().getWebAuthorization(),
|
||||
((BaseActivity) context).getAccount().getCacheDir(context));
|
||||
((BaseActivity) context).getAccount().getCacheDir(context),
|
||||
proxyUsername,
|
||||
proxyPassword);
|
||||
}
|
||||
|
||||
public static WebApi getWebInterface(Context context, String url) {
|
||||
var account = ((BaseActivity) context).getAccount().getAccount();
|
||||
String proxyUsername = account.getProxyAuthUsername();
|
||||
String proxyPassword = account.getProxyAuthPassword();
|
||||
|
||||
return getWebInterface(
|
||||
context,
|
||||
url,
|
||||
((BaseActivity) context).getAccount().getAuthorization(),
|
||||
((BaseActivity) context).getAccount().getCacheDir(context));
|
||||
((BaseActivity) context).getAccount().getCacheDir(context),
|
||||
proxyUsername,
|
||||
proxyPassword);
|
||||
}
|
||||
|
||||
public static ApiInterface getApiInterface(
|
||||
Context context, String url, String token, File cacheFile) {
|
||||
return getApiInterface(context, url, token, cacheFile, null, null);
|
||||
}
|
||||
|
||||
public static ApiInterface getApiInterface(
|
||||
Context context,
|
||||
String url,
|
||||
String token,
|
||||
File cacheFile,
|
||||
String proxyUsername,
|
||||
String proxyPassword) {
|
||||
String key = (token != null ? token.hashCode() : 0) + "@" + url;
|
||||
if (proxyUsername != null && proxyPassword != null) {
|
||||
key += "@proxy@" + proxyUsername.hashCode();
|
||||
}
|
||||
if (cacheFile == null || !apiInterfaces.containsKey(key)) {
|
||||
synchronized (RetrofitClient.class) {
|
||||
if (cacheFile == null || !apiInterfaces.containsKey(key)) {
|
||||
ApiInterface apiInterface =
|
||||
Objects.requireNonNull(createRetrofit(context, url, token, cacheFile))
|
||||
Objects.requireNonNull(
|
||||
createRetrofit(
|
||||
context,
|
||||
url,
|
||||
token,
|
||||
cacheFile,
|
||||
proxyUsername,
|
||||
proxyPassword))
|
||||
.create(ApiInterface.class);
|
||||
if (cacheFile != null) {
|
||||
apiInterfaces.put(key, apiInterface);
|
||||
@@ -250,12 +319,32 @@ public class RetrofitClient {
|
||||
|
||||
public static WebApi getWebInterface(
|
||||
Context context, String url, String token, File cacheFile) {
|
||||
return getWebInterface(context, url, token, cacheFile, null, null);
|
||||
}
|
||||
|
||||
public static WebApi getWebInterface(
|
||||
Context context,
|
||||
String url,
|
||||
String token,
|
||||
File cacheFile,
|
||||
String proxyUsername,
|
||||
String proxyPassword) {
|
||||
String key = (token != null ? token.hashCode() : 0) + "@" + url;
|
||||
if (proxyUsername != null && proxyPassword != null) {
|
||||
key += "@proxy@" + proxyUsername.hashCode();
|
||||
}
|
||||
if (!webInterfaces.containsKey(key)) {
|
||||
synchronized (RetrofitClient.class) {
|
||||
if (!webInterfaces.containsKey(key)) {
|
||||
WebApi webInterface =
|
||||
Objects.requireNonNull(createRetrofit(context, url, token, cacheFile))
|
||||
Objects.requireNonNull(
|
||||
createRetrofit(
|
||||
context,
|
||||
url,
|
||||
token,
|
||||
cacheFile,
|
||||
proxyUsername,
|
||||
proxyPassword))
|
||||
.create(WebApi.class);
|
||||
webInterfaces.put(key, webInterface);
|
||||
return webInterface;
|
||||
|
||||
@@ -123,4 +123,10 @@ public class UserAccountsApi extends BaseApi {
|
||||
public void updateProvider(final String provider, final int accountId) {
|
||||
executorService.execute(() -> userAccountsDao.updateProvider(provider, accountId));
|
||||
}
|
||||
|
||||
public void updateProxyAuthCredentials(
|
||||
final int accountId, final String username, final String password) {
|
||||
executorService.execute(
|
||||
() -> userAccountsDao.updateProxyAuthCredentials(username, password, accountId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,4 +84,8 @@ public interface UserAccountsDao {
|
||||
|
||||
@Query("UPDATE UserAccounts SET provider = :provider WHERE accountId = :accountId")
|
||||
void updateProvider(String provider, int accountId);
|
||||
|
||||
@Query(
|
||||
"UPDATE UserAccounts SET proxyAuthUsername = :username, proxyAuthPassword = :password WHERE accountId = :accountId")
|
||||
void updateProxyAuthCredentials(String username, String password, int accountId);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.mian.gitnex.database.models.UserAccount;
|
||||
*/
|
||||
@Database(
|
||||
entities = {Repository.class, UserAccount.class, Notes.class, AppSettings.class},
|
||||
version = 11,
|
||||
version = 12,
|
||||
exportSchema = false)
|
||||
public abstract class GitnexDatabase extends RoomDatabase {
|
||||
|
||||
@@ -119,6 +119,17 @@ public abstract class GitnexDatabase extends RoomDatabase {
|
||||
}
|
||||
};
|
||||
|
||||
private static final Migration MIGRATION_11_12 =
|
||||
new Migration(11, 12) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
database.execSQL(
|
||||
"ALTER TABLE 'userAccounts' ADD COLUMN 'proxyAuthUsername' TEXT");
|
||||
database.execSQL(
|
||||
"ALTER TABLE 'userAccounts' ADD COLUMN 'proxyAuthPassword' TEXT");
|
||||
}
|
||||
};
|
||||
|
||||
private static volatile GitnexDatabase gitnexDatabase;
|
||||
|
||||
public static GitnexDatabase getDatabaseInstance(Context context) {
|
||||
@@ -141,7 +152,8 @@ public abstract class GitnexDatabase extends RoomDatabase {
|
||||
MIGRATION_7_8,
|
||||
MIGRATION_8_9,
|
||||
MIGRATION_9_10,
|
||||
MIGRATION_10_11)
|
||||
MIGRATION_10_11,
|
||||
MIGRATION_11_12)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ public class UserAccount implements Serializable {
|
||||
private int maxAttachmentsSize;
|
||||
private int maxNumberOfAttachments;
|
||||
private String provider;
|
||||
@Nullable private String proxyAuthUsername;
|
||||
@Nullable private String proxyAuthPassword;
|
||||
|
||||
public int getAccountId() {
|
||||
return accountId;
|
||||
@@ -121,4 +123,20 @@ public class UserAccount implements Serializable {
|
||||
public void setProvider(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Nullable public String getProxyAuthUsername() {
|
||||
return proxyAuthUsername;
|
||||
}
|
||||
|
||||
public void setProxyAuthUsername(@Nullable String username) {
|
||||
this.proxyAuthUsername = username;
|
||||
}
|
||||
|
||||
@Nullable public String getProxyAuthPassword() {
|
||||
return proxyAuthPassword;
|
||||
}
|
||||
|
||||
public void setProxyAuthPassword(@Nullable String password) {
|
||||
this.proxyAuthPassword = password;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +196,9 @@ public class AppUtil {
|
||||
"next",
|
||||
"nvmrc",
|
||||
"lock",
|
||||
"vue"
|
||||
"vue",
|
||||
"zig",
|
||||
"zon"
|
||||
},
|
||||
FileType.TEXT);
|
||||
extensions.put(new String[] {"ttf", "otf", "woff", "woff2", "ttc", "eot"}, FileType.FONT);
|
||||
|
||||
@@ -128,6 +128,8 @@ public class FileIcon {
|
||||
extensionIcons.put("nvmrc", R.drawable.ic_node_js);
|
||||
extensionIcons.put("license", R.drawable.ic_license);
|
||||
extensionIcons.put("vue", R.drawable.ic_vue);
|
||||
extensionIcons.put("zig", R.drawable.ic_file_zig);
|
||||
extensionIcons.put("zon", R.drawable.ic_file_zig);
|
||||
}
|
||||
|
||||
public static int getIconResource(String fileName, String type) {
|
||||
|
||||
@@ -207,6 +207,7 @@ public class LanguageColor {
|
||||
colors.put("sed", R.color.sed);
|
||||
colors.put("xBase", R.color.x_base);
|
||||
colors.put("D", R.color.wiki);
|
||||
colors.put("Zig", R.color.zig);
|
||||
}
|
||||
|
||||
public static int languageColor(String key) {
|
||||
|
||||
9
app/src/main/res/drawable/ic_file_zig.xml
Normal file
9
app/src/main/res/drawable/ic_file_zig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="32" android:viewportWidth="32" android:width="24dp">
|
||||
|
||||
<path android:fillColor="#f7a41d" android:pathData="M5.733,19.731l0,-7.467l2.8,0l0,-3.733l-6.533,0l0,14.933l3.547,0l3.36,-3.733l-3.174,0z"/>
|
||||
|
||||
<path android:fillColor="#f7a41d" android:pathData="M26.453,8.531l-3.36,3.733l3.174,0l0,7.467l-2.8,0l0,3.733l6.533,0l0,-14.933l-3.547,0z"/>
|
||||
|
||||
<path android:fillColor="#f7a41d" android:pathData="M26.875,6.707l-6.362,1.824l-11.046,0l0,3.733l7.38,0l-11.732,13.029l6.382,-1.829l11.036,0l0,-3.733l-7.385,0l11.727,-13.024z"/>
|
||||
|
||||
</vector>
|
||||
@@ -202,6 +202,22 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/setup_proxy_auth"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:text="@string/setup_proxy_auth"
|
||||
app:icon="@drawable/ic_lock"
|
||||
app:iconGravity="textStart"
|
||||
android:textStyle="bold"
|
||||
app:iconTint="?attr/materialCardBackgroundColor"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/login_button"
|
||||
android:textColor="?attr/materialCardBackgroundColor" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -56,8 +56,6 @@
|
||||
app:backgroundTint="?attr/navigationBarColor"
|
||||
app:menu="@menu/bottom_nav_menu"
|
||||
app:labelVisibilityMode="labeled"
|
||||
app:itemIconTint="?attr/iconsColor"
|
||||
app:itemTextColor="?attr/primaryTextColor"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
73
app/src/main/res/layout/custom_dialog_proxy_auth.xml
Normal file
73
app/src/main/res/layout/custom_dialog_proxy_auth.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/dimen24dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/dimen16dp"
|
||||
android:text="@string/proxy_auth_title"
|
||||
android:textAppearance="?attr/textAppearanceHeadlineSmall" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/dimen16dp"
|
||||
android:text="@string/proxy_auth_description"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/proxyUsernameLayout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/dimen16dp"
|
||||
android:hint="@string/proxy_username_hint"
|
||||
app:endIconMode="clear_text"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor"
|
||||
app:startIconDrawable="@drawable/ic_person"
|
||||
app:startIconTint="?attr/iconsColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/proxyUsername"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/proxyPasswordLayout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/dimen24dp"
|
||||
android:hint="@string/proxy_password_hint"
|
||||
app:endIconMode="password_toggle"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor"
|
||||
app:startIconDrawable="@drawable/ic_lock"
|
||||
app:startIconTint="?attr/iconsColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/proxyPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -7,20 +7,36 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/dimen16dp">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
style="@style/Widget.MaterialComponents.LinearProgressIndicator"
|
||||
app:indicatorColor="?attr/progressIndicatorColor"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:maxHeight="@dimen/dimen320dp">
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:id="@+id/labelsContainer"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_start"
|
||||
app:alignItems="flex_start"
|
||||
android:padding="@dimen/dimen4dp"/>
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:id="@+id/labelsContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_start"
|
||||
app:alignItems="flex_start"
|
||||
android:padding="@dimen/dimen4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
android:layout_height="@dimen/dimen96dp"
|
||||
style="?attr/materialCardViewFilledStyle"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:cardElevation="@dimen/dimen0dp"
|
||||
android:layout_marginTop="@dimen/dimen12dp"
|
||||
android:layout_marginBottom="@dimen/dimen12dp"
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<resources>
|
||||
|
||||
<string name="versionLow" translatable="false">1.24</string>
|
||||
<string name="versionHigh" translatable="false">14.0.0</string>
|
||||
<string name="versionHigh" translatable="false">15.0.0</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -163,4 +163,5 @@
|
||||
<color name="zep">#118f9e</color>
|
||||
<color name="sed">#64b970</color>
|
||||
<color name="x_base">#403a40</color>
|
||||
<color name="zig">#EC915C</color>
|
||||
</resources>
|
||||
|
||||
@@ -1057,4 +1057,16 @@
|
||||
<string name="invalidTopicName">Invalid topic name</string>
|
||||
|
||||
<string name="http_git">HTTP Git</string>
|
||||
</resources>
|
||||
|
||||
<string name="proxy_auth_title">Reverse Proxy Credentials</string>
|
||||
<string name="proxy_auth_description">If your Gitea/Forgejo is behind a reverse proxy with HTTP Basic Authentication, enter the credentials here.\n\nGitNex sends these in the \'X-Proxy-Auth: Basic …\' header. Administrators needs to configure the proxy to read from this header.</string>
|
||||
<string name="proxy_username_hint">Proxy Username</string>
|
||||
<string name="proxy_password_hint">Proxy Password</string>
|
||||
<string name="setup_proxy_auth">Setup Proxy Authorization</string>
|
||||
<string name="save_proxy_creds">Save Proxy Credentials</string>
|
||||
<string name="skip_proxy_creds">Skip</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<changelog>
|
||||
|
||||
<release version="11.0.0" versioncode="1100">
|
||||
<release version="12.0.0-dev" versioncode="1195">
|
||||
<type name="🎉 Features 🎉">
|
||||
<change>Custom URL scheme: gitnex:// (details in README.md)</change>
|
||||
<change>Repository topics</change>
|
||||
<change>Add or delete repository topics</change>
|
||||
<change>View global repository settings as an instance admin</change>
|
||||
<change>Under development</change>
|
||||
</type>
|
||||
<type name="🚀 Improvements 🚀">
|
||||
<change>Added Vue language to file icons</change>
|
||||
<change>Under development</change>
|
||||
</type>
|
||||
<type name="🐛 Bug Fixes 🐛">
|
||||
<change>Fixed number formatting in activity logs</change>
|
||||
<change>Fixed translation issue in general settings screen</change>
|
||||
<change>Fixed crash after swiping the app away</change>
|
||||
<change>Fixed My Issues filter bug</change>
|
||||
<change>Potential fix for repeating notifications</change>
|
||||
<change>Fixed pull request info when opened from a notification</change>
|
||||
<change>Under development</change>
|
||||
</type>
|
||||
</release>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user