Add instance providers (#1511) 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/build Pipeline was successful ci/woodpecker/cron/check Pipeline was successful ci/woodpecker/cron/locale Pipeline was successful
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/build Pipeline was successful
ci/woodpecker/cron/check Pipeline was successful
ci/woodpecker/cron/locale Pipeline was successful
Reviewed-on: #1511 Co-authored-by: M M Arif <mmarif@swatian.com> Co-committed-by: M M Arif <mmarif@swatian.com>
This commit is contained in:
parent 17c1e03ec7
commit 2c20aacf59
18 changed files with 201 additions and 592 deletions
| @ -139,10 +139,6 @@ | |||
<activity | ||||
android:name=".activities.RepoForksActivity" | ||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/> | ||||
<activity | ||||
android:name=".activities.AddNewAccountActivity" | ||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation" | ||||
android:windowSoftInputMode="adjustResize"/> | ||||
<activity | ||||
android:name=".activities.RepositorySettingsActivity" | ||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/> | ||||
| |
| @ -1,396 +0,0 @@ | |||
package org.mian.gitnex.activities; | ||||
| ||||
import android.graphics.Color; | ||||
import android.graphics.drawable.ColorDrawable; | ||||
import android.os.Bundle; | ||||
import android.os.Handler; | ||||
import android.util.TypedValue; | ||||
import android.widget.ArrayAdapter; | ||||
import android.widget.TextView; | ||||
import androidx.annotation.NonNull; | ||||
import androidx.appcompat.app.AlertDialog; | ||||
import androidx.core.text.HtmlCompat; | ||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder; | ||||
import io.mikael.urlbuilder.UrlBuilder; | ||||
import java.net.URI; | ||||
import java.util.Objects; | ||||
import org.gitnex.tea4j.v2.models.GeneralAPISettings; | ||||
import org.gitnex.tea4j.v2.models.ServerVersion; | ||||
import org.gitnex.tea4j.v2.models.User; | ||||
import org.mian.gitnex.R; | ||||
import org.mian.gitnex.clients.RetrofitClient; | ||||
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.ActivityAddNewAccountBinding; | ||||
import org.mian.gitnex.helpers.AppUtil; | ||||
import org.mian.gitnex.helpers.PathsHelper; | ||||
import org.mian.gitnex.helpers.SnackBar; | ||||
import org.mian.gitnex.helpers.UrlHelper; | ||||
import org.mian.gitnex.helpers.Version; | ||||
import org.mian.gitnex.structs.Protocol; | ||||
import retrofit2.Call; | ||||
import retrofit2.Callback; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
*/ | ||||
public class AddNewAccountActivity extends BaseActivity { | ||||
| ||||
private ActivityAddNewAccountBinding viewBinding; | ||||
| ||||
private String spinnerSelectedValue; | ||||
private Version giteaVersion; | ||||
private int maxResponseItems = 50; | ||||
private int defaultPagingNumber = 30; | ||||
| ||||
@Override | ||||
public void onCreate(Bundle savedInstanceState) { | ||||
| ||||
super.onCreate(savedInstanceState); | ||||
| ||||
viewBinding = ActivityAddNewAccountBinding.inflate(getLayoutInflater()); | ||||
setContentView(viewBinding.getRoot()); | ||||
| ||||
getWindow().getDecorView().setBackground(new ColorDrawable(Color.TRANSPARENT)); | ||||
| ||||
viewBinding.instanceUrl.setText(getIntent().getStringExtra("instanceUrl")); | ||||
viewBinding.loginToken.setText(getIntent().getStringExtra("token")); | ||||
String scheme = getIntent().getStringExtra("scheme"); | ||||
if (scheme != null && scheme.equals("http")) { | ||||
viewBinding.protocolSpinner.setText(Protocol.HTTP.toString()); | ||||
spinnerSelectedValue = Protocol.HTTP.toString(); | ||||
} else { // default is https | ||||
viewBinding.protocolSpinner.setText(Protocol.HTTPS.toString()); | ||||
spinnerSelectedValue = Protocol.HTTPS.toString(); | ||||
} | ||||
| ||||
viewBinding.tokenHelper.setOnClickListener(token -> showTokenHelpDialog()); | ||||
| ||||
ArrayAdapter<Protocol> adapterProtocols = | ||||
new ArrayAdapter<>(ctx, R.layout.list_spinner_items, Protocol.values()); | ||||
| ||||
viewBinding.topAppBar.setNavigationOnClickListener(v -> finish()); | ||||
| ||||
viewBinding.protocolSpinner.setAdapter(adapterProtocols); | ||||
viewBinding.protocolSpinner.setOnItemClickListener( | ||||
(parent, view1, position, id) -> | ||||
spinnerSelectedValue = String.valueOf(parent.getItemAtPosition(position))); | ||||
| ||||
viewBinding.topAppBar.setOnMenuItemClickListener( | ||||
menuItem -> { | ||||
int id = menuItem.getItemId(); | ||||
| ||||
if (id == R.id.addAccount) { | ||||
processLogin(); | ||||
return true; | ||||
} else { | ||||
return super.onOptionsItemSelected(menuItem); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
private void showTokenHelpDialog() { | ||||
| ||||
MaterialAlertDialogBuilder dialogBuilder = | ||||
new MaterialAlertDialogBuilder(this) | ||||
.setMessage( | ||||
HtmlCompat.fromHtml( | ||||
getString(R.string.where_to_get_token_message), | ||||
HtmlCompat.FROM_HTML_MODE_LEGACY)) | ||||
.setPositiveButton(R.string.close, null) | ||||
.setCancelable(true); | ||||
| ||||
AlertDialog dialog = dialogBuilder.create(); | ||||
dialog.show(); | ||||
| ||||
TextView messageView = dialog.findViewById(android.R.id.message); | ||||
if (messageView != null) { | ||||
messageView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); | ||||
int paddingTop = | ||||
(int) | ||||
TypedValue.applyDimension( | ||||
TypedValue.COMPLEX_UNIT_DIP, | ||||
16, | ||||
getResources().getDisplayMetrics()); | ||||
messageView.setPadding( | ||||
messageView.getPaddingLeft(), | ||||
paddingTop, | ||||
messageView.getPaddingRight(), | ||||
messageView.getPaddingBottom()); | ||||
} | ||||
} | ||||
| ||||
private void processLogin() { | ||||
| ||||
try { | ||||
| ||||
String instanceUrlET = | ||||
String.valueOf(viewBinding.instanceUrl.getText()) | ||||
.replaceAll("[\\uFEFF|#]", "") | ||||
.trim(); | ||||
String loginToken = | ||||
String.valueOf(viewBinding.loginToken.getText()) | ||||
.replaceAll("[\\uFEFF|#]", "") | ||||
.trim(); | ||||
String protocol = spinnerSelectedValue; | ||||
| ||||
if (protocol == null) { | ||||
| ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.protocolEmptyError)); | ||||
return; | ||||
} | ||||
| ||||
if (instanceUrlET.isEmpty()) { | ||||
| ||||
SnackBar.error( | ||||
ctx, findViewById(android.R.id.content), getString(R.string.emptyFieldURL)); | ||||
return; | ||||
} | ||||
| ||||
if (loginToken.isEmpty()) { | ||||
| ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.loginTokenError)); | ||||
return; | ||||
} | ||||
| ||||
URI rawInstanceUrl = | ||||
UrlBuilder.fromString(UrlHelper.fixScheme(instanceUrlET, "http")).toUri(); | ||||
| ||||
URI instanceUrl = | ||||
UrlBuilder.fromUri(rawInstanceUrl) | ||||
.withScheme(protocol.toLowerCase()) | ||||
.withPath(PathsHelper.join(rawInstanceUrl.getPath(), "/api/v1/")) | ||||
.toUri(); | ||||
| ||||
versionCheck(instanceUrl.toString(), loginToken); | ||||
serverPageLimitSettings(); | ||||
| ||||
} catch (Exception e) { | ||||
| ||||
SnackBar.error( | ||||
ctx, findViewById(android.R.id.content), getString(R.string.malformedUrl)); | ||||
} | ||||
} | ||||
| ||||
private void versionCheck(final String instanceUrl, final String loginToken) { | ||||
| ||||
Call<ServerVersion> callVersion = | ||||
RetrofitClient.getApiInterface(ctx, instanceUrl, "token " + loginToken, null) | ||||
.getVersion(); | ||||
callVersion.enqueue( | ||||
new Callback<>() { | ||||
| ||||
@Override | ||||
public void onResponse( | ||||
@NonNull final Call<ServerVersion> callVersion, | ||||
@NonNull retrofit2.Response<ServerVersion> responseVersion) { | ||||
| ||||
if (responseVersion.code() == 200) { | ||||
| ||||
ServerVersion version = responseVersion.body(); | ||||
| ||||
assert version != null; | ||||
| ||||
if (!Version.valid(version.getVersion())) { | ||||
| ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.versionUnknown)); | ||||
return; | ||||
} | ||||
| ||||
giteaVersion = new Version(version.getVersion()); | ||||
| ||||
if (giteaVersion.less(getString(R.string.versionLow))) { | ||||
| ||||
MaterialAlertDialogBuilder materialAlertDialogBuilder = | ||||
new MaterialAlertDialogBuilder(ctx) | ||||
.setTitle( | ||||
getString( | ||||
R.string.versionAlertDialogHeader)) | ||||
.setMessage( | ||||
getResources() | ||||
.getString( | ||||
R.string | ||||
.versionUnsupportedOld, | ||||
version.getVersion())) | ||||
.setNeutralButton( | ||||
getString(R.string.cancelButton), null) | ||||
.setPositiveButton( | ||||
getString(R.string.textContinue), | ||||
(dialog, which) -> { | ||||
dialog.dismiss(); | ||||
login(instanceUrl, loginToken); | ||||
}); | ||||
| ||||
materialAlertDialogBuilder.create().show(); | ||||
} else if (giteaVersion.lessOrEqual(getString(R.string.versionHigh))) { | ||||
| ||||
login(instanceUrl, loginToken); | ||||
} else { | ||||
| ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.versionUnsupportedNew)); | ||||
login(instanceUrl, loginToken); | ||||
} | ||||
} else if (responseVersion.code() == 401) { | ||||
| ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.unauthorizedApiError)); | ||||
} else if (responseVersion.code() == 403) { | ||||
| ||||
login(instanceUrl, loginToken); | ||||
} | ||||
} | ||||
| ||||
private void login(String instanceUrl, String loginToken) { | ||||
| ||||
setupNewAccountWithToken(instanceUrl, loginToken); | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure( | ||||
@NonNull Call<ServerVersion> callVersion, @NonNull Throwable t) { | ||||
| ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.genericServerResponseError)); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
private void serverPageLimitSettings() { | ||||
| ||||
Call<GeneralAPISettings> generalAPISettings = | ||||
RetrofitClient.getApiInterface(ctx).getGeneralAPISettings(); | ||||
generalAPISettings.enqueue( | ||||
new Callback<>() { | ||||
| ||||
@Override | ||||
public void onResponse( | ||||
@NonNull final Call<GeneralAPISettings> generalAPISettings, | ||||
@NonNull retrofit2.Response<GeneralAPISettings> response) { | ||||
| ||||
if (response.code() == 200 && response.body() != null) { | ||||
| ||||
if (response.body().getMaxResponseItems() != null) { | ||||
maxResponseItems = | ||||
Math.toIntExact(response.body().getMaxResponseItems()); | ||||
} | ||||
if (response.body().getDefaultPagingNum() != null) { | ||||
defaultPagingNumber = | ||||
Math.toIntExact(response.body().getDefaultPagingNum()); | ||||
} | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure( | ||||
@NonNull Call<GeneralAPISettings> generalAPISettings, | ||||
@NonNull Throwable t) {} | ||||
}); | ||||
} | ||||
| ||||
private void setupNewAccountWithToken(String instanceUrl, final String loginToken) { | ||||
| ||||
Call<User> call = | ||||
RetrofitClient.getApiInterface(ctx, instanceUrl, "token " + loginToken, null) | ||||
.userGetCurrent(); | ||||
| ||||
call.enqueue( | ||||
new Callback<>() { | ||||
| ||||
@Override | ||||
public void onResponse( | ||||
@NonNull Call<User> call, @NonNull retrofit2.Response<User> response) { | ||||
| ||||
User userDetails = response.body(); | ||||
| ||||
switch (response.code()) { | ||||
case 200: | ||||
assert userDetails != null; | ||||
// insert new account to db if does not exist | ||||
String accountName = userDetails.getLogin() + "@" + instanceUrl; | ||||
UserAccountsApi userAccountsApi = | ||||
BaseApi.getInstance(ctx, UserAccountsApi.class); | ||||
boolean userAccountExists = | ||||
Objects.requireNonNull(userAccountsApi) | ||||
.userAccountExists(accountName); | ||||
| ||||
if (!userAccountExists) { | ||||
| ||||
long id = | ||||
userAccountsApi.createNewAccount( | ||||
accountName, | ||||
instanceUrl, | ||||
userDetails.getLogin(), | ||||
loginToken, | ||||
giteaVersion.toString(), | ||||
maxResponseItems, | ||||
defaultPagingNumber); | ||||
UserAccount account = userAccountsApi.getAccountById((int) id); | ||||
AppUtil.switchToAccount(AddNewAccountActivity.this, account); | ||||
SnackBar.success( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.accountAddedMessage)); | ||||
MainActivity.refActivity = true; | ||||
new Handler().postDelayed(() -> finish(), 3000); | ||||
} else { | ||||
UserAccount account = | ||||
userAccountsApi.getAccountByName(accountName); | ||||
if (account.isLoggedIn()) { | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.accountAlreadyExistsError)); | ||||
AppUtil.switchToAccount(ctx, account); | ||||
} else { | ||||
userAccountsApi.updateTokenByAccountName( | ||||
accountName, loginToken); | ||||
userAccountsApi.login(account.getAccountId()); | ||||
AppUtil.switchToAccount( | ||||
AddNewAccountActivity.this, account); | ||||
} | ||||
} | ||||
break; | ||||
| ||||
case 401: | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.unauthorizedApiError)); | ||||
break; | ||||
| ||||
default: | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.genericApiError, response.code())); | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure(@NonNull Call<User> call, @NonNull Throwable t) { | ||||
| ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.genericError)); | ||||
} | ||||
}); | ||||
} | ||||
} |
| @ -140,7 +140,8 @@ public class DeepLinksActivity extends BaseActivity { | |||
finish(); | ||||
} else if (data.getPathSegments().get(0).equals("user") | ||||
&& data.getPathSegments().get(1).equals("login")) { // open login | ||||
Intent loginIntent = new Intent(ctx, AddNewAccountActivity.class); | ||||
Intent loginIntent = new Intent(ctx, LoginActivity.class); | ||||
intent.putExtra("mode", "new_account"); | ||||
loginIntent.putExtra("instanceUrl", data.getHost()); | ||||
loginIntent.putExtra("instanceProtocol", data.getScheme()); | ||||
ctx.startActivity(loginIntent); | ||||
| @ -453,8 +454,9 @@ public class DeepLinksActivity extends BaseActivity { | |||
| ||||
viewBinding.addNewAccount.setOnClickListener( | ||||
addNewAccount -> { | ||||
Intent accountIntent = new Intent(ctx, AddNewAccountActivity.class); | ||||
Intent accountIntent = new Intent(ctx, LoginActivity.class); | ||||
accountIntent.putExtra("instanceUrl", data.getHost()); | ||||
intent.putExtra("mode", "new_account"); | ||||
startActivity(accountIntent); | ||||
finish(); | ||||
}); | ||||
| |
| @ -11,6 +11,7 @@ import android.content.Intent; | |||
import android.net.Uri; | ||||
import android.os.Bundle; | ||||
import android.util.TypedValue; | ||||
import android.view.View; | ||||
import android.widget.ArrayAdapter; | ||||
import android.widget.TextView; | ||||
import androidx.activity.result.ActivityResultLauncher; | ||||
| @ -46,7 +47,7 @@ import retrofit2.Call; | |||
import retrofit2.Callback; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
* @author mmarif | ||||
*/ | ||||
public class LoginActivity extends BaseActivity { | ||||
| ||||
| @ -58,6 +59,8 @@ public class LoginActivity extends BaseActivity { | |||
private int defaultPagingNumber = 25; | ||||
private final String DATABASE_NAME = "gitnex"; | ||||
private boolean hasShownInitialNetworkError = false; | ||||
private int btnText; | ||||
private String selectedProvider = "gitea"; | ||||
| ||||
@Override | ||||
public void onCreate(Bundle savedInstanceState) { | ||||
| @ -67,6 +70,22 @@ public class LoginActivity extends BaseActivity { | |||
activityLoginBinding = ActivityLoginBinding.inflate(getLayoutInflater()); | ||||
setContentView(activityLoginBinding.getRoot()); | ||||
| ||||
String mode = getIntent().getStringExtra("mode"); | ||||
if (mode == null) { | ||||
mode = "login"; | ||||
btnText = R.string.btnLogin; | ||||
} else { | ||||
btnText = R.string.addNewAccountText; | ||||
} | ||||
| ||||
if (mode.equals("new_account")) { | ||||
activityLoginBinding.loginButton.setText(btnText); | ||||
activityLoginBinding.restoreFromBackup.setVisibility(View.GONE); | ||||
} else { | ||||
activityLoginBinding.loginButton.setText(btnText); | ||||
activityLoginBinding.restoreFromBackup.setVisibility(View.VISIBLE); | ||||
} | ||||
| ||||
NetworkStatusObserver networkStatusObserver = NetworkStatusObserver.getInstance(ctx); | ||||
| ||||
activityLoginBinding.appVersion.setText(AppUtil.getAppVersion(appCtx)); | ||||
| @ -75,14 +94,25 @@ public class LoginActivity extends BaseActivity { | |||
new ArrayAdapter<>( | ||||
LoginActivity.this, R.layout.list_spinner_items, Protocol.values()); | ||||
| ||||
ArrayAdapter<CharSequence> adapterProviders = | ||||
ArrayAdapter.createFromResource( | ||||
this, R.array.provider_options, R.layout.list_spinner_items); | ||||
| ||||
activityLoginBinding.instanceUrl.setText(getIntent().getStringExtra("instanceUrl")); | ||||
String scheme = getIntent().getStringExtra("scheme"); | ||||
if (scheme != null && scheme.equals("http")) { | ||||
activityLoginBinding.httpsSpinner.setText(Protocol.HTTP.toString()); | ||||
selectedProtocol = Protocol.HTTP.toString(); | ||||
} else { | ||||
activityLoginBinding.httpsSpinner.setText(Protocol.HTTPS.toString()); | ||||
selectedProtocol = Protocol.HTTPS.toString(); | ||||
} | ||||
| ||||
activityLoginBinding.httpsSpinner.setAdapter(adapterProtocols); | ||||
activityLoginBinding.httpsSpinner.setSelection(0); | ||||
activityLoginBinding.httpsSpinner.setOnItemClickListener( | ||||
(parent, view, position, id) -> { | ||||
selectedProtocol = String.valueOf(parent.getItemAtPosition(position)); | ||||
| ||||
if (selectedProtocol.equals(String.valueOf(Protocol.HTTP))) { | ||||
SnackBar.warning( | ||||
ctx, | ||||
| @ -91,6 +121,17 @@ public class LoginActivity extends BaseActivity { | |||
} | ||||
}); | ||||
| ||||
activityLoginBinding.providerSpinner.setAdapter(adapterProviders); | ||||
activityLoginBinding.providerSpinner.setSelection(0); | ||||
activityLoginBinding.providerSpinner.setText(adapterProviders.getItem(0), false); | ||||
activityLoginBinding.providerSpinner.setOnItemClickListener( | ||||
(parent, view, position, id) -> { | ||||
selectedProvider = | ||||
position == 0 | ||||
? "gitea" | ||||
: position == 1 || position == 2 ? "forgejo" : "infer"; | ||||
}); | ||||
| ||||
if (AppUtil.hasNetworkConnection(ctx)) { | ||||
enableProcessButton(); | ||||
} else { | ||||
| @ -107,8 +148,7 @@ public class LoginActivity extends BaseActivity { | |||
enableProcessButton(); | ||||
} else { | ||||
disableProcessButton(); | ||||
activityLoginBinding.loginButton.setText( | ||||
getResources().getString(R.string.btnLogin)); | ||||
activityLoginBinding.loginButton.setText(btnText); | ||||
if (hasShownInitialNetworkError) { | ||||
SnackBar.error( | ||||
ctx, | ||||
| @ -179,6 +219,15 @@ public class LoginActivity extends BaseActivity { | |||
| ||||
try { | ||||
| ||||
if (selectedProvider == null || selectedProvider.isEmpty()) { | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.provider_empty_error)); | ||||
enableProcessButton(); | ||||
return; | ||||
} | ||||
| ||||
if (selectedProtocol == null) { | ||||
| ||||
SnackBar.error( | ||||
| @ -189,12 +238,32 @@ public class LoginActivity extends BaseActivity { | |||
return; | ||||
} | ||||
| ||||
if (Objects.requireNonNull(activityLoginBinding.instanceUrl.getText()) | ||||
.toString() | ||||
.isEmpty()) { | ||||
| ||||
SnackBar.error( | ||||
ctx, findViewById(android.R.id.content), getString(R.string.emptyFieldURL)); | ||||
enableProcessButton(); | ||||
return; | ||||
} | ||||
| ||||
String loginToken = | ||||
Objects.requireNonNull(activityLoginBinding.loginTokenCode.getText()) | ||||
.toString() | ||||
.replaceAll("[\\uFEFF|#]", "") | ||||
.trim(); | ||||
| ||||
if (loginToken.isEmpty()) { | ||||
| ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.loginTokenError)); | ||||
enableProcessButton(); | ||||
return; | ||||
} | ||||
| ||||
URI rawInstanceUrl = | ||||
UrlBuilder.fromString( | ||||
UrlHelper.fixScheme( | ||||
| @ -213,24 +282,6 @@ public class LoginActivity extends BaseActivity { | |||
.withPath(PathsHelper.join(rawInstanceUrl.getPath(), "/api/v1/")) | ||||
.toUri(); | ||||
| ||||
if (activityLoginBinding.instanceUrl.getText().toString().isEmpty()) { | ||||
| ||||
SnackBar.error( | ||||
ctx, findViewById(android.R.id.content), getString(R.string.emptyFieldURL)); | ||||
enableProcessButton(); | ||||
return; | ||||
} | ||||
| ||||
if (loginToken.isEmpty()) { | ||||
| ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.loginTokenError)); | ||||
enableProcessButton(); | ||||
return; | ||||
} | ||||
| ||||
versionCheck(loginToken); | ||||
serverPageLimitSettings(String.valueOf(instanceUrl), loginToken); | ||||
| ||||
| @ -305,6 +356,9 @@ public class LoginActivity extends BaseActivity { | |||
} | ||||
| ||||
giteaVersion = new Version(version.getVersion()); | ||||
if (selectedProvider.equals("infer")) { | ||||
selectedProvider = AppUtil.inferProvider(version.getVersion()); | ||||
} | ||||
| ||||
if (giteaVersion.less(getString(R.string.versionLow))) { | ||||
| ||||
| @ -406,11 +460,17 @@ public class LoginActivity extends BaseActivity { | |||
loginToken, | ||||
giteaVersion.toString(), | ||||
maxResponseItems, | ||||
defaultPagingNumber); | ||||
defaultPagingNumber, | ||||
selectedProvider); | ||||
account = userAccountsApi.getAccountById((int) accountId); | ||||
} else { | ||||
userAccountsApi.updateTokenByAccountName( | ||||
accountName, loginToken); | ||||
userAccountsApi.updateProvider( | ||||
selectedProvider, | ||||
userAccountsApi | ||||
.getAccountByName(accountName) | ||||
.getAccountId()); | ||||
userAccountsApi.login( | ||||
userAccountsApi | ||||
.getAccountByName(accountName) | ||||
| @ -460,15 +520,10 @@ public class LoginActivity extends BaseActivity { | |||
| ||||
private void enableProcessButton() { | ||||
| ||||
activityLoginBinding.loginButton.setText(R.string.btnLogin); | ||||
activityLoginBinding.loginButton.setText(btnText); | ||||
activityLoginBinding.loginButton.setEnabled(true); | ||||
} | ||||
| ||||
private enum LoginType { | ||||
BASIC, | ||||
TOKEN | ||||
} | ||||
| ||||
private void requestRestoreFile() { | ||||
| ||||
Intent intentRestore = new Intent(Intent.ACTION_OPEN_DOCUMENT); | ||||
| |
| @ -4,7 +4,6 @@ import android.annotation.SuppressLint; | |||
import android.content.Intent; | ||||
import android.os.Bundle; | ||||
import android.os.Handler; | ||||
import android.text.Html; | ||||
import android.util.TypedValue; | ||||
import android.view.View; | ||||
import android.widget.ImageView; | ||||
| @ -12,6 +11,7 @@ import android.widget.TextView; | |||
import androidx.activity.EdgeToEdge; | ||||
import androidx.activity.OnBackPressedCallback; | ||||
import androidx.annotation.NonNull; | ||||
import androidx.core.text.HtmlCompat; | ||||
import androidx.navigation.NavController; | ||||
import androidx.navigation.NavOptions; | ||||
import androidx.navigation.fragment.NavHostFragment; | ||||
| @ -95,11 +95,23 @@ public class MainActivity extends BaseActivity | |||
return; | ||||
} | ||||
| ||||
if (tinyDB.getInt("currentActiveAccountId", -1) <= 0) { | ||||
int currentAccountId = tinyDB.getInt("currentActiveAccountId", -1); | ||||
if (currentAccountId <= 0) { | ||||
AppUtil.logout(this); | ||||
return; | ||||
} | ||||
| ||||
UserAccountsApi userAccountsApi = BaseApi.getInstance(this, UserAccountsApi.class); | ||||
if (userAccountsApi != null) { | ||||
UserAccount currentAccount = userAccountsApi.getAccountById(currentAccountId); | ||||
if (currentAccount != null | ||||
&& (currentAccount.getProvider() == null | ||||
|| currentAccount.getProvider().isEmpty())) { | ||||
String inferredProvider = AppUtil.inferProvider(currentAccount.getServerVersion()); | ||||
userAccountsApi.updateProvider(inferredProvider, currentAccountId); | ||||
} | ||||
} | ||||
| ||||
setSupportActionBar(binding.toolbar); | ||||
binding.toolbar.setVisibility(View.GONE); | ||||
binding.toolbar.invalidate(); | ||||
| @ -322,7 +334,9 @@ public class MainActivity extends BaseActivity | |||
ImageView userAvatar = binding.userAvatar; | ||||
| ||||
if (name != null && !name.isEmpty()) { | ||||
userFullname.setText(Html.fromHtml(name)); | ||||
userFullname.setText( | ||||
HtmlCompat.fromHtml( | ||||
name, HtmlCompat.FROM_HTML_MODE_LEGACY)); | ||||
} else { | ||||
userFullname.setText(username); | ||||
} | ||||
| |
| @ -18,12 +18,12 @@ import com.bumptech.glide.Glide; | |||
import com.bumptech.glide.load.engine.DiskCacheStrategy; | ||||
import java.util.List; | ||||
import org.mian.gitnex.R; | ||||
import org.mian.gitnex.activities.AddNewAccountActivity; | ||||
import org.mian.gitnex.activities.LoginActivity; | ||||
import org.mian.gitnex.database.models.UserAccount; | ||||
import org.mian.gitnex.helpers.UrlHelper; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
* @author mmarif | ||||
*/ | ||||
public class UserAccountsNavAdapter | ||||
extends RecyclerView.Adapter<UserAccountsNavAdapter.UserAccountsViewHolder> { | ||||
| @ -82,7 +82,9 @@ public class UserAccountsNavAdapter | |||
| ||||
newAccount.setOnClickListener( | ||||
item -> { | ||||
context.startActivity(new Intent(context, AddNewAccountActivity.class)); | ||||
Intent intent = new Intent(context, LoginActivity.class); | ||||
intent.putExtra("mode", "new_account"); | ||||
context.startActivity(intent); | ||||
dialog.dismiss(); | ||||
}); | ||||
| ||||
| |
| @ -7,7 +7,7 @@ import org.mian.gitnex.database.dao.UserAccountsDao; | |||
import org.mian.gitnex.database.models.UserAccount; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
* @author mmarif | ||||
*/ | ||||
public class UserAccountsApi extends BaseApi { | ||||
| ||||
| @ -25,7 +25,8 @@ public class UserAccountsApi extends BaseApi { | |||
String token, | ||||
String serverVersion, | ||||
int maxResponseItems, | ||||
int defaultPagingNumber) { | ||||
int defaultPagingNumber, | ||||
String provider) { | ||||
| ||||
UserAccount userAccount = new UserAccount(); | ||||
userAccount.setAccountName(accountName); | ||||
| @ -36,6 +37,7 @@ public class UserAccountsApi extends BaseApi { | |||
userAccount.setLoggedIn(true); | ||||
userAccount.setMaxResponseItems(maxResponseItems); | ||||
userAccount.setDefaultPagingNumber(defaultPagingNumber); | ||||
userAccount.setProvider(provider); | ||||
| ||||
return userAccountsDao.createAccount(userAccount); | ||||
} | ||||
| @ -117,4 +119,8 @@ public class UserAccountsApi extends BaseApi { | |||
public void login(int accountId) { | ||||
executorService.execute(() -> userAccountsDao.login(accountId)); | ||||
} | ||||
| ||||
public void updateProvider(final String provider, final int accountId) { | ||||
executorService.execute(() -> userAccountsDao.updateProvider(provider, accountId)); | ||||
} | ||||
} | ||||
| |
| @ -8,7 +8,7 @@ import java.util.List; | |||
import org.mian.gitnex.database.models.UserAccount; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
* @author mmarif | ||||
*/ | ||||
@Dao | ||||
public interface UserAccountsDao { | ||||
| @ -81,4 +81,7 @@ public interface UserAccountsDao { | |||
| ||||
@Query("DELETE FROM UserAccounts WHERE accountId = :accountId") | ||||
void deleteAccount(int accountId); | ||||
| ||||
@Query("UPDATE UserAccounts SET provider = :provider WHERE accountId = :accountId") | ||||
void updateProvider(String provider, int accountId); | ||||
} | ||||
| |
| @ -17,11 +17,11 @@ import org.mian.gitnex.database.models.Repository; | |||
import org.mian.gitnex.database.models.UserAccount; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
* @author mmarif | ||||
*/ | ||||
@Database( | ||||
entities = {Repository.class, UserAccount.class, Notes.class, AppSettings.class}, | ||||
version = 10, | ||||
version = 11, | ||||
exportSchema = false) | ||||
public abstract class GitnexDatabase extends RoomDatabase { | ||||
| ||||
| @ -30,7 +30,6 @@ public abstract class GitnexDatabase extends RoomDatabase { | |||
new Migration(1, 2) { | ||||
@Override | ||||
public void migrate(@NonNull SupportSQLiteDatabase database) { | ||||
// database.execSQL("DROP TABLE Drafts"); | ||||
database.execSQL("ALTER TABLE 'Drafts' ADD COLUMN 'commentId' TEXT"); | ||||
} | ||||
}; | ||||
| @ -110,6 +109,16 @@ public abstract class GitnexDatabase extends RoomDatabase { | |||
database.execSQL("DROP table Drafts"); | ||||
} | ||||
}; | ||||
| ||||
private static final Migration MIGRATION_10_11 = | ||||
new Migration(10, 11) { | ||||
| ||||
@Override | ||||
public void migrate(@NonNull SupportSQLiteDatabase database) { | ||||
database.execSQL("ALTER TABLE 'userAccounts' ADD COLUMN 'provider' TEXT"); | ||||
} | ||||
}; | ||||
| ||||
private static volatile GitnexDatabase gitnexDatabase; | ||||
| ||||
public static GitnexDatabase getDatabaseInstance(Context context) { | ||||
| @ -131,7 +140,8 @@ public abstract class GitnexDatabase extends RoomDatabase { | |||
MIGRATION_6_7, | ||||
MIGRATION_7_8, | ||||
MIGRATION_8_9, | ||||
MIGRATION_9_10) | ||||
MIGRATION_9_10, | ||||
MIGRATION_10_11) | ||||
.build(); | ||||
} | ||||
} | ||||
| |
| @ -6,7 +6,7 @@ import androidx.room.PrimaryKey; | |||
import java.io.Serializable; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
* @author mmarif | ||||
*/ | ||||
@Entity(tableName = "userAccounts") | ||||
public class UserAccount implements Serializable { | ||||
| @ -24,6 +24,7 @@ public class UserAccount implements Serializable { | |||
private int defaultPagingNumber; | ||||
private int maxAttachmentsSize; | ||||
private int maxNumberOfAttachments; | ||||
private String provider; | ||||
| ||||
public int getAccountId() { | ||||
return accountId; | ||||
| @ -112,4 +113,12 @@ public class UserAccount implements Serializable { | |||
public void setMaxNumberOfAttachments(int maxNumberOfAttachments) { | ||||
this.maxNumberOfAttachments = maxNumberOfAttachments; | ||||
} | ||||
| ||||
public String getProvider() { | ||||
return provider; | ||||
} | ||||
| ||||
public void setProvider(String provider) { | ||||
this.provider = provider; | ||||
} | ||||
} | ||||
| |
| @ -10,8 +10,8 @@ import org.mian.gitnex.R; | |||
import org.mian.gitnex.actions.CollaboratorActions; | ||||
import org.mian.gitnex.actions.PullRequestActions; | ||||
import org.mian.gitnex.actions.TeamActions; | ||||
import org.mian.gitnex.activities.AddNewAccountActivity; | ||||
import org.mian.gitnex.activities.CreateLabelActivity; | ||||
import org.mian.gitnex.activities.LoginActivity; | ||||
import org.mian.gitnex.databinding.CustomPrUpdateStrategyDialogBinding; | ||||
import org.mian.gitnex.helpers.contexts.RepositoryContext; | ||||
| ||||
| @ -35,9 +35,11 @@ public class AlertDialogs { | |||
.setNeutralButton(R.string.cancelButton, null) | ||||
.setPositiveButton( | ||||
R.string.addNewAccountText, | ||||
(dialog, which) -> | ||||
context.startActivity( | ||||
new Intent(context, AddNewAccountActivity.class))) | ||||
(dialog, which) -> { | ||||
Intent intent = new Intent(context, LoginActivity.class); | ||||
intent.putExtra("mode", "new_account"); | ||||
context.startActivity(intent); | ||||
}) | ||||
.show(); | ||||
} | ||||
| ||||
| |
| @ -721,4 +721,13 @@ public class AppUtil { | |||
} | ||||
return resource; | ||||
} | ||||
| ||||
public static String inferProvider(String version) { | ||||
if (Version.valid(version)) { | ||||
Version v = new Version(version); | ||||
int majorVersion = v.getValues().isEmpty() ? 0 : v.getValues().get(0); | ||||
return majorVersion == 1 ? "gitea" : "forgejo"; | ||||
} | ||||
return "gitea"; | ||||
} | ||||
} | ||||
| |
| @ -236,4 +236,8 @@ public class Version { | |||
| ||||
return raw; | ||||
} | ||||
| ||||
public List<Integer> getValues() { | ||||
return values; | ||||
} | ||||
} | ||||
| |
| @ -83,4 +83,8 @@ public class AccountContext implements Serializable { | |||
assert account.getAccountName() != null; | ||||
return new File(context.getCacheDir() + "responses", account.getAccountName()); | ||||
} | ||||
| ||||
public String getProvider() { | ||||
return getAccount().getProvider(); | ||||
} | ||||
} | ||||
| |
| @ -1,143 +0,0 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | ||||
<androidx.coordinatorlayout.widget.CoordinatorLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="match_parent" | ||||
android:background="?attr/primaryBackgroundColor" | ||||
xmlns:android="http://schemas.android.com/apk/res/android" | ||||
android:fitsSystemWindows="true" | ||||
xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
| ||||
<com.google.android.material.appbar.AppBarLayout | ||||
android:id="@+id/appBarLayout" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:background="?attr/primaryBackgroundColor"> | ||||
| ||||
<com.google.android.material.appbar.CollapsingToolbarLayout | ||||
style="?attr/collapsingToolbarLayoutLargeStyle" | ||||
android:layout_width="match_parent" | ||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" | ||||
android:background="?attr/primaryBackgroundColor" | ||||
app:contentScrim="?attr/primaryBackgroundColor" | ||||
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"> | ||||
| ||||
<com.google.android.material.appbar.MaterialToolbar | ||||
android:id="@+id/topAppBar" | ||||
android:layout_width="match_parent" | ||||
android:elevation="0dp" | ||||
android:layout_height="?attr/actionBarSize" | ||||
app:title="@string/addNewAccount" | ||||
app:layout_collapseMode="pin" | ||||
app:menu="@menu/add_new_account_menu" | ||||
app:navigationIcon="@drawable/ic_arrow_back" /> | ||||
| ||||
</com.google.android.material.appbar.CollapsingToolbarLayout> | ||||
| ||||
</com.google.android.material.appbar.AppBarLayout> | ||||
| ||||
<androidx.core.widget.NestedScrollView | ||||
android:layout_width="match_parent" | ||||
android:layout_height="match_parent" | ||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="vertical" | ||||
android:padding="@dimen/dimen16dp" | ||||
app:layout_constraintTop_toTopOf="parent"> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/protocolSpinnerLayout" | ||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
android:hint="@string/protocol" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:endIconTint="?attr/iconsColor" | ||||
app:hintTextColor="?attr/hintColor"> | ||||
| ||||
<AutoCompleteTextView | ||||
android:id="@+id/protocolSpinner" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:imeOptions="actionNext" | ||||
android:inputType="none" | ||||
android:labelFor="@+id/protocolSpinner" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/instanceUrlLayout" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
android:hint="@string/instanceUrl" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:endIconMode="clear_text" | ||||
app:endIconTint="?attr/iconsColor" | ||||
app:hintTextColor="?attr/hintColor" | ||||
app:startIconDrawable="@drawable/ic_link" | ||||
app:startIconTint="?attr/iconsColor" | ||||
app:helperText="@string/instanceHelperText"> | ||||
| ||||
<com.google.android.material.textfield.TextInputEditText | ||||
android:id="@+id/instanceUrl" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:imeOptions="actionNext" | ||||
android:singleLine="true" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textColorHint="?attr/hintColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/loginTokenLayout" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
android:hint="@string/copyToken" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:endIconMode="clear_text" | ||||
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/loginToken" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:imeOptions="actionNext" | ||||
android:singleLine="true" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textColorHint="?attr/hintColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.textview.MaterialTextView | ||||
android:id="@+id/token_helper" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen16dp" | ||||
android:layout_gravity="center_horizontal" | ||||
android:textSize="@dimen/dimen14sp" | ||||
android:autoLink="web" | ||||
android:text="@string/where_to_get_token_text" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</androidx.core.widget.NestedScrollView> | ||||
| ||||
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
| @ -65,6 +65,29 @@ | |||
android:orientation="vertical" | ||||
android:padding="@dimen/dimen28dp"> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/providerSpinnerLayout" | ||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
android:hint="@string/select_provider" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:endIconTint="?attr/iconsColor" | ||||
app:hintTextColor="?attr/hintColor"> | ||||
| ||||
<AutoCompleteTextView | ||||
android:id="@+id/providerSpinner" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:inputType="none" | ||||
android:labelFor="@+id/providerSpinner" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/httpsSpinnerLayout" | ||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu" | ||||
| |
| @ -23,6 +23,13 @@ | |||
<item>zh-TW</item> | ||||
</string-array> | ||||
| ||||
<string-array name="provider_options"> | ||||
<item>Gitea</item> | ||||
<item>Forgejo</item> | ||||
<item>Codeberg (Forgejo-based)</item> | ||||
<item>I am not sure</item> | ||||
</string-array> | ||||
| ||||
<string-array name="fonts"> | ||||
<item>Roboto</item> | ||||
<item>Manrope</item> | ||||
| |
| @ -1002,4 +1002,6 @@ | |||
<string name="switch_account">Switch account</string> | ||||
<string name="clear_cache_button_text">Clear Cache (%1$s)</string> | ||||
<string name="repository_not_exist">Repository does not exist</string> | ||||
<string name="select_provider">Select instance provider</string> | ||||
<string name="provider_empty_error">Please select an instance provider</string> | ||||
</resources> | ||||
| |
Loading…
Add table
Add a link
Reference in a new issue