(Arrays.asList(availableCiphers));
diff --git a/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java b/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java
index 697cff9ca..fc0ea4f36 100644
--- a/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java
+++ b/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java
@@ -141,9 +141,9 @@ public SslFailureReason getFailureReason(Account account) {
}
/**
- * Reorder the certificates chain, since it may not be in the right order when passed to us
- *
- * @see http://stackoverflow.com/questions/7822381/need-help-understanding-certificate-chains
+ * Reorder the certificates chain, since it may not be in the right order when passed to us.
+ *
+ * see http://stackoverflow.com/questions/7822381/need-help-understanding-certificate-chains
*/
public List orderCerts(X509Certificate[] certificates) {
if (certificates == null || certificates.length == 0) {
@@ -207,14 +207,12 @@ public SslFailureReason getReason() {
}
@Override
- public void checkClientTrusted(X509Certificate[] chain, String authType)
- throws CertificateException {
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
defaultTrustManager.checkClientTrusted(chain, authType);
}
@Override
- public void checkServerTrusted(X509Certificate[] chain, String authType)
- throws CertificateException {
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if (chain == null || chain.length == 0) {
defaultTrustManager.checkServerTrusted(chain, authType);
return;
@@ -231,19 +229,13 @@ public void checkServerTrusted(X509Certificate[] chain, String authType)
}
}
-// public String getCeritificateInfo() throws CertificateParsingException {
-// X509Certificate cert = CertsManager.instance().getCertificate(account);
-// return "sigalgName:" + cert.getSigAlgName() + " Type: "
-// + cert.getType() + " Version: " + cert.getVersion()
-// + " IssuerAlternative: " + cert.getIssuerAlternativeNames()
-// + " NotAfter: " + cert.getNotAfter();
-// }
/**
* Interface for checking if a hostname matches the names stored inside the server's X.509 certificate
*/
private void validateHostName(List chain) throws CertificateException {
X509Certificate cert = chain.get(0);
+
// BrowserCompatHostnameVerifier can verify hostnames in the form of IP addresses (like a browser)
// where as the DefaultHostnameVerifier will always try to lookup IP addresses via the DNS.
X509HostnameVerifier mHostnameVerifier = new BrowserCompatHostnameVerifier();
@@ -254,42 +246,33 @@ private void validateHostName(List chain) throws CertificateExc
}
}
- private void customCheck(List chain, String authType)
- throws CertificateException {
+ private void customCheck(List chain, String authType) throws CertificateException {
certsChain = ImmutableList.copyOf(chain);
-
- ConcurrentAsyncTask.submit(new Runnable() {
- @Override
- public void run() {
-
- try {
- X509Certificate cert = chain.get(0);
-
- X509Certificate savedCert = CertsManager.instance().getCertificate(account);
- if (savedCert == null) {
- Log.d(DEBUG_TAG, "no saved cert for " + account.server);
- reason = SslFailureReason.CERT_NOT_TRUSTED;
- throw new CertificateException();
- } else if (savedCert.equals(cert)) {
- // The user has confirmed to trust this certificate
- Log.d(DEBUG_TAG, "the cert of " + account.server + " is trusted");
- return;
- } else {
- // The certificate is different from the one user confirmed to trust,
- // This may be either:
- // 1. The server admin has changed its cert
- // 2. The user is under security attack
- Log.d(DEBUG_TAG, "the cert of " + account.server + " has changed");
- reason = SslFailureReason.CERT_CHANGED;
- throw new CertificateException();
- }
- } catch (CertificateException e) {
- throw new RuntimeException(e);
- }
+ try {
+ X509Certificate cert = chain.get(0);
+
+ X509Certificate savedCert = CertsManager.instance().getCertificate(account);
+ if (savedCert == null) {
+ Log.d(DEBUG_TAG, "no saved cert for " + account.server);
+ reason = SslFailureReason.CERT_NOT_TRUSTED;
+ throw new CertificateException();
+ } else if (savedCert.equals(cert)) {
+ // The user has confirmed to trust this certificate
+ Log.d(DEBUG_TAG, "the cert of " + account.server + " is trusted");
+ } else {
+ // The certificate is different from the one user confirmed to trust,
+ // This may be either:
+ // 1. The server admin has changed its cert
+ // 2. The user is under security attack
+ Log.d(DEBUG_TAG, "the cert of " + account.server + " has changed");
+ reason = SslFailureReason.CERT_CHANGED;
+ throw new CertificateException();
}
- });
+ } catch (CertificateException e) {
+ throw new RuntimeException(e);
+ }
}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java
index 5c18bd3be..c6595be0c 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java
@@ -17,6 +17,7 @@
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.CheckBox;
+import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
@@ -39,6 +40,7 @@
import com.seafile.seadroid2.account.Account;
import com.seafile.seadroid2.account.Authenticator;
import com.seafile.seadroid2.config.Constants;
+import com.seafile.seadroid2.databinding.AccountDetailBinding;
import com.seafile.seadroid2.framework.util.Utils;
import com.seafile.seadroid2.ssl.CertsManager;
import com.seafile.seadroid2.ui.base.BaseActivityWithVM;
@@ -52,19 +54,19 @@ public class AccountDetailActivity extends BaseActivityWithVM
private final String TWO_FACTOR_AUTH = "two_factor_auth";
- private TextView mStatusTv;
- private Button mLoginBtn;
- private EditText mServerEt;
- private TextInputEditText mEmailEt;
- private EditText mPasswdEt;
- private CheckBox mHttpsCheckBox;
- private TextView mSeaHubUrlHintTv;
- private ImageView mClearEmailIv;
- private TextInputLayout mAuthTokenInputLayout;
- private EditText mAuthTokenEt;
+ private AccountDetailBinding binding;
+
+// private TextView mStatusTv;
+// private Button mLoginBtn;
+// private EditText mServerEt;
+// private TextInputEditText mEmailEt;
+// private EditText mPasswdEt;
+// private CheckBox mHttpsCheckBox;
+// private TextView mSeaHubUrlHintTv;
+// private TextInputLayout mAuthTokenInputLayout;
+// private EditText mAuthTokenEt;
private boolean serverTextHasFocus;
- private CheckBox mRemDeviceCheckBox;
private String mSessionKey;
/**
@@ -73,27 +75,22 @@ public class AccountDetailActivity extends BaseActivityWithVM
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.account_detail);
+ binding = AccountDetailBinding.inflate(getLayoutInflater());
+
+ setContentView(binding.getRoot());
- mStatusTv = findViewById(R.id.status_view);
- mHttpsCheckBox = findViewById(R.id.https_checkbox);
- mServerEt = findViewById(R.id.server_url);
- mEmailEt = findViewById(R.id.email_address);
- mPasswdEt = findViewById(R.id.password);
- mSeaHubUrlHintTv = findViewById(R.id.seahub_url_hint);
+ initView();
- mClearEmailIv = findViewById(R.id.iv_delete_email);
+ initViewModel();
+ }
- mAuthTokenInputLayout = findViewById(R.id.auth_token_hint);
- mAuthTokenEt = findViewById(R.id.auth_token);
- mAuthTokenInputLayout.setVisibility(View.GONE);
- mRemDeviceCheckBox = findViewById(R.id.remember_device);
- mRemDeviceCheckBox.setVisibility(View.GONE);
+ private void initView() {
- mLoginBtn = findViewById(R.id.login_button);
- mLoginBtn.setOnClickListener(v -> login());
+ binding.authTokenHint.setVisibility(View.GONE);
+ binding.rememberDevice.setVisibility(View.GONE);
+ binding.loginButton.setOnClickListener(v -> login());
setupServerText();
@@ -114,21 +111,23 @@ public void onCreate(Bundle savedInstanceState) {
// isFromEdit = mAccountManager.getUserData(account, Authenticator.KEY_EMAIL);
if (server.startsWith(Constants.Protocol.HTTPS))
- mHttpsCheckBox.setChecked(true);
+ binding.httpsCheckbox.setChecked(true);
+
+ binding.serverUrl.setText(server);
+ binding.emailAddress.setText(email);
+ binding.emailAddress.requestFocus();
- mServerEt.setText(server);
- mEmailEt.setText(email);
- mEmailEt.requestFocus();
- mSeaHubUrlHintTv.setVisibility(View.GONE);
+ binding.seahubUrlHint.setVisibility(View.GONE);
} else if (defaultServerUri != null) {
if (defaultServerUri.startsWith(Constants.Protocol.HTTPS))
- mHttpsCheckBox.setChecked(true);
- mServerEt.setText(defaultServerUri);
- mEmailEt.requestFocus();
+ binding.httpsCheckbox.setChecked(true);
+ binding.serverUrl.setText(defaultServerUri);
+ binding.emailAddress.requestFocus();
+
} else {
- mServerEt.setText(Constants.Protocol.HTTP);
+ binding.serverUrl.setText(Constants.Protocol.HTTP);
int prefixLen = Constants.Protocol.HTTP.length();
- mServerEt.setSelection(prefixLen, prefixLen);
+ binding.serverUrl.setSelection(prefixLen, prefixLen);
}
@@ -138,66 +137,10 @@ public void onCreate(Bundle savedInstanceState) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.login);
- initListener();
-
- initViewModel();
- }
-
- private void initListener() {
- mEmailEt.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus && mEmailEt.getText().toString().trim().length() > 0) {
- mClearEmailIv.setVisibility(View.VISIBLE);
- } else {
- mClearEmailIv.setVisibility(View.INVISIBLE);
- }
- }
- });
-
- mEmailEt.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
+ binding.httpsCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- if (mEmailEt.getText().toString().trim().length() > 0) {
- mClearEmailIv.setVisibility(View.VISIBLE);
- } else {
- mClearEmailIv.setVisibility(View.INVISIBLE);
- }
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- }
- });
-
-
- mClearEmailIv.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mEmailEt.setText("");
- }
- });
-
- TextInputLayout passwordInputLayout = findViewById(R.id.password_hint);
- passwordInputLayout.setEndIconOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mPasswdEt.getTransformationMethod() instanceof PasswordTransformationMethod) {
- mPasswdEt.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
- passwordInputLayout.setEndIconDrawable(R.drawable.icon_eye_open);
- } else {
- passwordInputLayout.setEndIconDrawable(R.drawable.icon_eye_close);
- mPasswdEt.setTransformationMethod(PasswordTransformationMethod.getInstance());
- }
-
- String input = mPasswdEt.getText().toString().trim();
- if (!TextUtils.isEmpty(input)) {
- mPasswdEt.setSelection(input.length());
- }
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ refreshServerUrlPrefix();
}
});
}
@@ -229,60 +172,84 @@ public void onChanged(Account account) {
});
}
+ private void refreshServerUrlPrefix() {
+ boolean isHttps = binding.httpsCheckbox.isChecked();
+ String url = binding.serverUrl.getText().toString();
+ String prefix = isHttps ? Constants.Protocol.HTTPS : Constants.Protocol.HTTP;
+
+ String urlWithoutScheme = url.replace(Constants.Protocol.HTTPS, "").replace(Constants.Protocol.HTTP, "");
+
+ int oldOffset = binding.serverUrl.getSelectionStart();
+
+ // Change the text
+ binding.serverUrl.setText(String.format("%s%s", prefix, urlWithoutScheme));
+
+ if (serverTextHasFocus) {
+ // Change the cursor position since we changed the text
+ int offset;
+ if (isHttps) {
+ offset = oldOffset + 1;
+ } else {
+ offset = Math.max(0, oldOffset - 1);
+ }
+ binding.serverUrl.setSelection(offset, offset);
+ }
+ }
+
private void onLoginException(Account account, SeafException err) {
if (err == SeafException.sslException) {
- mAuthTokenInputLayout.setVisibility(View.GONE);
- mRemDeviceCheckBox.setVisibility(View.GONE);
-
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
- builder.setTitle(R.string.ssl_confirm_title);
- builder.setMessage(getString(R.string.ssl_not_trusted,account.getServerHost()));
- builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- builder.create().show();
-
-// SslConfirmDialog sslConfirmDialog = new SslConfirmDialog(account,
-// new SslConfirmDialog.Listener() {
-// @Override
-// public void onAccepted(boolean rememberChoice) {
-// CertsManager.instance().saveCertForAccount(account, rememberChoice);
-// login();
-// }
-//
-// @Override
-// public void onRejected() {
-// mStatusTv.setText(R.string.ssl_error);
-// mLoginBtn.setEnabled(true);
-// }
-// });
-// sslConfirmDialog.show(getSupportFragmentManager(), SslConfirmDialog.FRAGMENT_TAG);
+ binding.authTokenHint.setVisibility(View.GONE);
+ binding.rememberDevice.setVisibility(View.GONE);
+
+// MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
+// builder.setTitle(R.string.ssl_confirm_title);
+// builder.setMessage(getString(R.string.ssl_not_trusted,account.getServerHost()));
+// builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+// @Override
+// public void onClick(DialogInterface dialog, int which) {
+// dialog.dismiss();
+// }
+// });
+// builder.create().show();
+
+ SslConfirmDialog sslConfirmDialog = new SslConfirmDialog(account,
+ new SslConfirmDialog.Listener() {
+ @Override
+ public void onAccepted(boolean rememberChoice) {
+ CertsManager.instance().saveCertForAccount(account, rememberChoice);
+ login();
+ }
+
+ @Override
+ public void onRejected() {
+ binding.statusView.setText(R.string.ssl_error);
+ binding.loginButton.setEnabled(true);
+ }
+ });
+ sslConfirmDialog.show(getSupportFragmentManager(), SslConfirmDialog.FRAGMENT_TAG);
} else if (err == SeafException.twoFactorAuthTokenMissing) {
// show auth token input box
- mAuthTokenInputLayout.setVisibility(View.VISIBLE);
- mRemDeviceCheckBox.setVisibility(View.VISIBLE);
- mRemDeviceCheckBox.setChecked(false);
- mAuthTokenEt.setError(getString(R.string.two_factor_auth_error));
+ binding.authTokenHint.setVisibility(View.VISIBLE);
+ binding.rememberDevice.setVisibility(View.VISIBLE);
+ binding.rememberDevice.setChecked(false);
+ binding.authToken.setError(getString(R.string.two_factor_auth_error));
} else if (err == SeafException.twoFactorAuthTokenInvalid) {
// show auth token input box
- mAuthTokenInputLayout.setVisibility(View.VISIBLE);
- mRemDeviceCheckBox.setVisibility(View.VISIBLE);
- mRemDeviceCheckBox.setChecked(false);
- mAuthTokenEt.setError(getString(R.string.two_factor_auth_invalid));
+ binding.authTokenHint.setVisibility(View.VISIBLE);
+ binding.rememberDevice.setVisibility(View.VISIBLE);
+ binding.rememberDevice.setChecked(false);
+ binding.authToken.setError(getString(R.string.two_factor_auth_invalid));
} else if (err.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
- mStatusTv.setText(R.string.invalid_server_address);
+ binding.statusView.setText(R.string.invalid_server_address);
} else if (err.getCode() == HttpURLConnection.HTTP_BAD_REQUEST) {
- mStatusTv.setText(R.string.err_wrong_user_or_passwd);
+ binding.statusView.setText(R.string.err_wrong_user_or_passwd);
} else {
- mAuthTokenInputLayout.setVisibility(View.GONE);
- mRemDeviceCheckBox.setVisibility(View.GONE);
- mStatusTv.setText(err.getMessage());
+ binding.authTokenHint.setVisibility(View.GONE);
+ binding.rememberDevice.setVisibility(View.GONE);
+ binding.statusView.setText(err.getMessage());
}
- mLoginBtn.setEnabled(true);
+ binding.loginButton.setEnabled(true);
}
private void onLoggedIn(Account loginAccount) {
@@ -300,7 +267,7 @@ private void onLoggedIn(Account loginAccount) {
retData.putExtra(SeafileAuthenticatorActivity.ARG_NAME, loginAccount.getName());
retData.putExtra(SeafileAuthenticatorActivity.ARG_AUTH_SESSION_KEY, loginAccount.getSessionKey());
retData.putExtra(SeafileAuthenticatorActivity.ARG_SERVER_URI, loginAccount.getServer());
- retData.putExtra(TWO_FACTOR_AUTH, mRemDeviceCheckBox.isChecked());
+ retData.putExtra(TWO_FACTOR_AUTH, binding.rememberDevice.isChecked());
setResult(RESULT_OK, retData);
finish();
}
@@ -313,9 +280,9 @@ protected void onDestroy() {
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
- savedInstanceState.putString("email", mEmailEt.getText().toString());
- savedInstanceState.putString("password", mPasswdEt.getText().toString());
- savedInstanceState.putBoolean("rememberDevice", mRemDeviceCheckBox.isChecked());
+ savedInstanceState.putString("email", binding.emailAddress.getText().toString());
+ savedInstanceState.putString("password", binding.password.getText().toString());
+ savedInstanceState.putBoolean("rememberDevice", binding.rememberDevice.isChecked());
super.onSaveInstanceState(savedInstanceState);
}
@@ -323,9 +290,9 @@ protected void onSaveInstanceState(Bundle savedInstanceState) {
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
- mEmailEt.setText((String) savedInstanceState.get("email"));
- mPasswdEt.setText((String) savedInstanceState.get("password"));
- mRemDeviceCheckBox.setChecked((boolean) savedInstanceState.get("rememberDevice"));
+ binding.emailAddress.setText((String) savedInstanceState.get("email"));
+ binding.password.setText((String) savedInstanceState.get("password"));
+ binding.rememberDevice.setChecked((boolean) savedInstanceState.get("rememberDevice"));
}
@Override
@@ -362,36 +329,8 @@ public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
- public void onHttpsCheckboxClicked(View view) {
- refreshServerUrlPrefix();
- }
-
- private void refreshServerUrlPrefix() {
- boolean isHttps = mHttpsCheckBox.isChecked();
- String url = mServerEt.getText().toString();
- String prefix = isHttps ? Constants.Protocol.HTTPS : Constants.Protocol.HTTP;
-
- String urlWithoutScheme = url.replace(Constants.Protocol.HTTPS, "").replace(Constants.Protocol.HTTP, "");
-
- int oldOffset = mServerEt.getSelectionStart();
-
- // Change the text
- mServerEt.setText(String.format("%s%s", prefix, urlWithoutScheme));
-
- if (serverTextHasFocus) {
- // Change the cursor position since we changed the text
- if (isHttps) {
- int offset = oldOffset + 1;
- mServerEt.setSelection(offset, offset);
- } else {
- int offset = Math.max(0, oldOffset - 1);
- mServerEt.setSelection(offset, offset);
- }
- }
- }
-
private void setupServerText() {
- mServerEt.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ binding.serverUrl.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
Log.d(DEBUG_TAG, "serverText has focus: " + (hasFocus ? "yes" : "no"));
@@ -399,7 +338,7 @@ public void onFocusChange(View v, boolean hasFocus) {
}
});
- mServerEt.addTextChangedListener(new TextWatcher() {
+ binding.serverUrl.addTextChangedListener(new TextWatcher() {
private String old;
@Override
@@ -408,19 +347,19 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- old = mServerEt.getText().toString();
+ old = binding.serverUrl.getText().toString();
}
@Override
public void afterTextChanged(Editable s) {
// Don't allow the user to edit the "https://" or "http://" part of the serverText
- String url = mServerEt.getText().toString();
- boolean isHttps = mHttpsCheckBox.isChecked();
+ String url = binding.serverUrl.getText().toString();
+ boolean isHttps = binding.httpsCheckbox.isChecked();
String prefix = isHttps ? Constants.Protocol.HTTPS : Constants.Protocol.HTTP;
if (!url.startsWith(prefix)) {
- int oldOffset = Math.max(prefix.length(), mServerEt.getSelectionStart());
- mServerEt.setText(old);
- mServerEt.setSelection(oldOffset, oldOffset);
+ int oldOffset = Math.max(prefix.length(), binding.serverUrl.getSelectionStart());
+ binding.serverUrl.setText(old);
+ binding.serverUrl.setSelection(oldOffset, oldOffset);
}
}
});
@@ -428,48 +367,61 @@ public void afterTextChanged(Editable s) {
private void login() {
- String serverURL = mServerEt.getText().toString().trim();
- String email = mEmailEt.getText().toString().trim();
- String passwd = mPasswdEt.getText().toString();
+ String serverURL = binding.serverUrl.getText().toString().trim();
+ String email = binding.emailAddress.getText().toString().trim();
+ String passwd = binding.password.getText().toString();
if (!NetworkUtils.isConnected()) {
- mStatusTv.setText(R.string.network_down);
+ binding.statusView.setText(R.string.network_down);
return;
}
- if (serverURL.isEmpty()) {
- mStatusTv.setText(R.string.err_server_andress_empty);
+ String urlWithoutScheme = serverURL.replace(Constants.Protocol.HTTPS, "").replace(Constants.Protocol.HTTP, "");
+ if (TextUtils.isEmpty(urlWithoutScheme)) {
+ binding.serverHint.setErrorEnabled(true);
+ binding.serverHint.setError(getResources().getString(R.string.err_server_andress_empty));
return;
+ } else {
+ binding.serverHint.setError(null);
+ binding.serverHint.setErrorEnabled(false);
}
if (email.isEmpty()) {
- mEmailEt.setError(getResources().getString(R.string.err_email_empty));
+ binding.emailHint.setErrorEnabled(true);
+ binding.emailHint.setError(getResources().getString(R.string.err_email_empty));
return;
+ } else {
+ binding.serverHint.setError(null);
+ binding.serverHint.setErrorEnabled(false);
}
if (passwd.isEmpty()) {
- mPasswdEt.setError(getResources().getString(R.string.err_passwd_empty));
+ binding.passwordHint.setErrorEnabled(true);
+ binding.passwordHint.setError(getResources().getString(R.string.err_passwd_empty));
return;
+ } else {
+ binding.passwordHint.setError(null);
+ binding.passwordHint.setErrorEnabled(false);
}
String authToken = null;
- if (mAuthTokenInputLayout.getVisibility() == View.VISIBLE) {
- authToken = mAuthTokenEt.getText().toString().trim();
+ if (binding.authTokenHint.getVisibility() == View.VISIBLE) {
+ authToken = binding.authToken.getText().toString().trim();
if (TextUtils.isEmpty(authToken)) {
- mAuthTokenEt.setError(getResources().getString(R.string.two_factor_auth_token_empty));
+ binding.authToken.setError(getResources().getString(R.string.two_factor_auth_token_empty));
return;
}
}
boolean rememberDevice = false;
- if (mRemDeviceCheckBox.getVisibility() == View.VISIBLE) {
- rememberDevice = mRemDeviceCheckBox.isChecked();
+ if (binding.rememberDevice.getVisibility() == View.VISIBLE) {
+ rememberDevice = binding.rememberDevice.isChecked();
}
try {
serverURL = Utils.cleanServerURL(serverURL);
} catch (MalformedURLException e) {
- mStatusTv.setText(R.string.invalid_server_address);
+ binding.statusView.setText(R.string.invalid_server_address);
Log.d(DEBUG_TAG, "Invalid URL " + serverURL);
return;
}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java
index 9c5c25152..d4840bedd 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java
@@ -18,30 +18,19 @@
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ComponentActivity;
import androidx.core.app.NavUtils;
import androidx.core.app.TaskStackBuilder;
-import androidx.preference.Preference;
-import com.blankj.utilcode.util.ToastUtils;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.seafile.seadroid2.R;
import com.seafile.seadroid2.account.Authenticator;
import com.seafile.seadroid2.account.SupportAccountManager;
-import com.seafile.seadroid2.framework.datastore.sp.SettingsManager;
-import com.seafile.seadroid2.framework.util.Objs;
+import com.seafile.seadroid2.config.Constants;
import com.seafile.seadroid2.framework.util.SLogs;
-import com.seafile.seadroid2.ui.WidgetUtils;
import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager;
-import com.seafile.seadroid2.config.Constants;
-import com.seafile.seadroid2.ui.markdown.MarkdownActivity;
-import com.seafile.seadroid2.ui.repo.RepoQuickFragment;
-import com.seafile.seadroid2.ui.webview.SeaWebViewActivity;
-import java.io.File;
import java.util.Locale;
/**
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java
index a4e616ed8..4f764c1ec 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java
@@ -238,50 +238,35 @@ public void onReceivedError(WebView view, int errorCode, String description, Str
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
Log.d(DEBUG_TAG, "onReceivedSslError " + error.getCertificate().toString());
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(SingleSignOnAuthorizeActivity.this);
- builder.setTitle(R.string.ssl_confirm_title);
- builder.setMessage(getString(R.string.ssl_not_trusted, serverUrl));
- builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- handler.cancel();
- displaySSLError();
- dialog.dismiss();
- }
- });
- builder.create().show();
-
-// final Account account = new Account(serverUrl, null, null, null, null, false);
-// SslCertificate sslCert = error.getCertificate();
-//
-// //todo notice main thread
-// X509Certificate savedCert = CertsManager.instance().getCertificate(account);
-//
-// if (Utils.isSameCert(sslCert, savedCert)) {
-// Log.d(DEBUG_TAG, "trust this cert");
-// handler.proceed();
-// } else {
-// Log.d(DEBUG_TAG, "cert is not trusted");
-// SslConfirmDialog dialog = new SslConfirmDialog(account,
-// Utils.getX509CertFromSslCertHack(sslCert),
-// new SslConfirmDialog.Listener() {
-// @Override
-// public void onAccepted(boolean rememberChoice) {
-// CertsManager.instance().saveCertForAccount(account, rememberChoice);
-// // Ignore SSL certificate validate
-// handler.proceed();
-// }
-//
-// @Override
-// public void onRejected() {
-// displaySSLError();
-// handler.cancel();
-// }
-// });
-// dialog.show(getSupportFragmentManager(), SslConfirmDialog.FRAGMENT_TAG);
-// }
+ final Account account = new Account(serverUrl, null, null, null, null, false);
+ SslCertificate sslCert = error.getCertificate();
+
+ X509Certificate savedCert = CertsManager.instance().getCertificate(account);
+ if (Utils.isSameCert(sslCert, savedCert)) {
+ Log.d(DEBUG_TAG, "trust this cert");
+ handler.proceed();
+ } else {
+ Log.d(DEBUG_TAG, "cert is not trusted");
+ SslConfirmDialog dialog = new SslConfirmDialog(account,
+ Utils.getX509CertFromSslCertHack(sslCert),
+ new SslConfirmDialog.Listener() {
+ @Override
+ public void onAccepted(boolean rememberChoice) {
+ CertsManager.instance().saveCertForAccount(account, rememberChoice);
+ // Ignore SSL certificate validate
+ handler.proceed();
+ }
+
+ @Override
+ public void onRejected() {
+ displaySSLError();
+ handler.cancel();
+ }
+ });
+ dialog.show(getSupportFragmentManager(), SslConfirmDialog.FRAGMENT_TAG);
+ }
}
-
+
@Override
public void onPageFinished(WebView webView, String url) {
Log.d(DEBUG_TAG, "onPageFinished " + serverUrl);
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java
index 9c6d314f3..24d1dfbc4 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java
@@ -42,7 +42,7 @@
import com.seafile.seadroid2.ui.markdown.MarkdownActivity;
import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity;
import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity;
-import com.seafile.seadroid2.ui.webview.SeaWebViewActivity;
+import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity;
import com.seafile.seadroid2.view.TipsViews;
import java.io.File;
@@ -279,7 +279,7 @@ private void open(RepoModel repoModel, ActivityModel activityModel) {
} else if (activityModel.name.endsWith(Constants.Format.DOT_SDOC)) {
- SeaWebViewActivity.openSdoc(getContext(), activityModel.repo_name, activityModel.repo_id, activityModel.path);
+ SDocWebViewActivity.openSdoc(getContext(), activityModel.repo_name, activityModel.repo_id, activityModel.path);
} else if (Utils.isVideoFile(activityModel.name)) {
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java
deleted file mode 100644
index d0d93f299..000000000
--- a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java
+++ /dev/null
@@ -1,731 +0,0 @@
-package com.seafile.seadroid2.ui.camera_upload;
-
-import static android.app.PendingIntent.FLAG_IMMUTABLE;
-
-import android.accounts.Account;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.AbstractThreadedSyncAdapter;
-import android.content.ComponentName;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SyncResult;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.provider.MediaStore;
-import android.util.Log;
-
-import androidx.core.app.NotificationCompat;
-
-import com.google.common.base.Joiner;
-import com.seafile.seadroid2.R;
-import com.seafile.seadroid2.SeadroidApplication;
-import com.seafile.seadroid2.SeafException;
-import com.seafile.seadroid2.framework.datastore.sp.SettingsManager;
-import com.seafile.seadroid2.account.SupportAccountManager;
-import com.seafile.seadroid2.framework.data.CameraSyncEvent;
-import com.seafile.seadroid2.framework.datastore.DataManager;
-import com.seafile.seadroid2.framework.data.SeafRepo;
-import com.seafile.seadroid2.framework.datastore.StorageManager;
-import com.seafile.seadroid2.ui.CustomNotificationBuilder;
-import com.seafile.seadroid2.ui.account.AccountsActivity;
-import com.seafile.seadroid2.ui.settings.SettingsActivity;
-import com.seafile.seadroid2.framework.util.CameraSyncStatus;
-import com.seafile.seadroid2.framework.util.SLogs;
-import com.seafile.seadroid2.framework.util.Utils;
-
-import java.io.File;
-import java.net.HttpURLConnection;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * Sync adapter for media upload.
- *
- * This class uploads images/videos from the gallery to the configured seafile account.
- * It is not called directly, but managed by the Android Sync Manager instead.
- */
-@Deprecated
-public class CameraSyncAdapter extends AbstractThreadedSyncAdapter {
- public CameraSyncAdapter(Context context, boolean autoInitialize) {
- super(context, autoInitialize);
- }
-
- @Override
- public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
-
- }
-// private static final String DEBUG_TAG = "CameraSyncAdapter";
-//
-// private ContentResolver contentResolver;
-//
-// private SettingsManager settingsMgr = SettingsManager.getInstance();
-// private CameraUploadDBHelper dbHelper;
-//
-// private String targetRepoId;
-// private String targetRepoName;
-// private List bucketList;
-//
-// private final String BASE_DIR = "My Photos";
-//
-// /**
-// * Will be set to true if the current sync has been cancelled.
-// */
-// private boolean cancelled = false;
-//
-// /**
-// * Media files we have sent over to the TransferService. Thread-safe.
-// */
-// private List tasksInProgress = new ArrayList<>();
-//
-// TransferService txService = null;
-//
-// ServiceConnection mConnection = new ServiceConnection() {
-// @Override
-// public void onServiceConnected(ComponentName className, IBinder service) {
-// // this will run in a foreign thread!
-//
-// TransferService.TransferBinder binder = (TransferService.TransferBinder) service;
-// synchronized (CameraSyncAdapter.this) {
-// txService = binder.getService();
-// }
-// // Log.d(DEBUG_TAG, "connected to TransferService");
-// }
-//
-// @Override
-// public void onServiceDisconnected(ComponentName arg0) {
-// // this will run in a foreign thread!
-// // Log.d(DEBUG_TAG, "disconnected from TransferService, aborting sync");
-//
-// onSyncCanceled();
-// synchronized (CameraSyncAdapter.this) {
-// txService = null;
-// }
-// }
-// };
-//
-// /**
-// * Set up the sync adapter
-// */
-// public CameraSyncAdapter(Context context) {
-// /*
-// * autoInitialize is set to false because we need to handle initialization
-// * ourselves in performSync() (resetting the photo database).
-// */
-// super(context, false);
-//
-// // Log.d(DEBUG_TAG, "CameraSyncAdapter created.");
-//
-// contentResolver = context.getContentResolver();
-// dbHelper = CameraUploadDBHelper.getInstance();
-// }
-//
-// private synchronized void startTransferService() {
-// if (txService != null)
-// return;
-//
-// Intent bIntent = new Intent(getContext(), TransferService.class);
-// getContext().bindService(bIntent, mConnection, Context.BIND_AUTO_CREATE);
-// }
-//
-// private boolean isCancelled() {
-// synchronized (this) {
-// return cancelled;
-// }
-// }
-//
-// /**
-// * Check if repository on the server exists.
-// *
-// * @param dataManager
-// * @return
-// * @throws SeafException
-// */
-// private boolean validateRepository(DataManager dataManager) throws SeafException {
-// List repos = dataManager.getReposFromServer();
-//
-// for (SeafRepo repo : repos) {
-// if (repo.getRepoId().equals(targetRepoId) && repo.getRepoName().equals(targetRepoName))
-// return true;
-// }
-//
-// return false;
-// }
-//
-// @Override
-// public void onSecurityException(android.accounts.Account account, Bundle extras, String authority, SyncResult syncResult) {
-// super.onSecurityException(account, extras, authority, syncResult);
-// Log.e(DEBUG_TAG, syncResult.toString());
-// }
-//
-// @Override
-// public boolean onUnsyncableAccount() {
-// Log.e(DEBUG_TAG, "onUnsyncableAccount");
-// return super.onUnsyncableAccount();
-// }
-//
-// @Override
-// public void onSyncCanceled(Thread thread) {
-// super.onSyncCanceled(thread);
-// Log.e(DEBUG_TAG, "onSyncCanceled ->" + thread.getName());
-// }
-//
-// @Override
-// public void onSyncCanceled() {
-// super.onSyncCanceled();
-// Log.e(DEBUG_TAG, "onSyncCanceled");
-// synchronized (this) {
-// cancelled = true;
-// }
-// }
-//
-// @Override
-// public void onPerformSync(android.accounts.Account account,
-// Bundle extras, String authority,
-// ContentProviderClient provider,
-// SyncResult syncResult) {
-//
-// SLogs.d("onPerformSync");
-//
-// synchronized (this) {
-// cancelled = false;
-// }
-// SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.SCANNING);
-// EventBus.getDefault().post(new CameraSyncEvent("start"));
-// /*Log.i(DEBUG_TAG, "Syncing images and video to " + account);
-//
-// Log.d(DEBUG_TAG, "Selected buckets for camera upload: "+settingsMgr.getCameraUploadBucketList());
-// Log.d(DEBUG_TAG, "is video upload allowed: "+settingsMgr.isVideosUploadAllowed());
-// Log.d(DEBUG_TAG, "is data plan allowed: "+settingsMgr.isDataPlanAllowed());
-//
-// Log.d(DEBUG_TAG, "Media buckets available on this system: ");*/
-//// for (GalleryBucketUtils.Bucket bucket: GalleryBucketUtils.getMediaBuckets(getContext())) {
-// // Log.d(DEBUG_TAG, "Bucket id="+bucket.id+" name="+bucket.name);
-//// }
-//
-// // resync all media
-//// if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE)) {
-//// Log.i(DEBUG_TAG, "Doing a full resync");
-//// dbHelper.cleanPhotoCache();
-//// }
-//
-// if (!settingsMgr.checkCameraUploadNetworkAvailable()) {
-// // Log.d(DEBUG_TAG, "Not syncing because of data plan restriction.");
-// // treat dataPlan abort the same way as a network connection error
-// syncResult.stats.numIoExceptions++;
-// SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.NETWORK_UNAVAILABLE);
-// EventBus.getDefault().post(new CameraSyncEvent("noNetwork"));
-// return;
-// }
-//
-// Account seafileAccount = SupportAccountManager.getInstance().getSeafileAccount(account);
-// DataManager dataManager = new DataManager(seafileAccount);
-//
-// /**
-// * this should never occur, as camera upload is supposed to be disabled once the camera upload
-// * account signs out.
-// */
-// if (!seafileAccount.hasValidToken()) {
-// Log.d(DEBUG_TAG, "This account has no auth token. Disable camera upload.");
-// syncResult.stats.numAuthExceptions++;
-//
-// // we're logged out on this account. disable camera upload.
-// ContentResolver.cancelSync(account, CameraUploadManager.AUTHORITY);
-// ContentResolver.setIsSyncable(account, CameraUploadManager.AUTHORITY, 0);
-// return;
-// }
-//
-// // make copies so we're unaffected by sudden settings changes
-// targetRepoId = settingsMgr.getCameraUploadRepoId();
-// targetRepoName = settingsMgr.getCameraUploadRepoName();
-// bucketList = settingsMgr.getCameraUploadBucketList();
-//
-// try {
-// // Log.d(DEBUG_TAG, "Validating target repository...");
-//
-// // make sure the repo exists
-// if (!validateRepository(dataManager)) {
-// /**
-// * this is a HARD error (see below). The user has to fix up the settings to make it
-// * work again.
-// *
-// * We do /not/ disable camera upload, as this might hide the problem from the user.
-// * instead we should display an error
-// */
-// Log.e(DEBUG_TAG, "Sync aborted because the target repository does not exist");
-// syncResult.databaseError = true;
-// showNotificationRepoError();
-// return;
-// }
-//
-// // Log.d(DEBUG_TAG, "connecting to TransferService");
-// startTransferService();
-//
-// // wait for TransferService to connect
-// // Log.d(DEBUG_TAG, "waiting for transfer service");
-// int timeout = 1000; // wait up to a second
-// while (!isCancelled() && timeout > 0 && txService == null) {
-// // Log.d(DEBUG_TAG, "waiting for transfer service");
-// Thread.sleep(100);
-// timeout -= 100;
-// }
-//
-// if (txService == null) {
-// Log.e(DEBUG_TAG, "TransferService did not come up in time, aborting sync");
-// syncResult.delayUntil = 60;
-// return;
-// }
-//
-// uploadImages(syncResult, dataManager);
-//
-// if (settingsMgr.isVideosUploadAllowed()) {
-// uploadVideos(syncResult, dataManager);
-// }
-//
-// if (isCancelled()) {
-// // Log.i(DEBUG_TAG, "sync was cancelled.");
-// } else {
-// // Log.i(DEBUG_TAG, "sync finished successfully.");
-// }
-// // Log.d(DEBUG_TAG, "syncResult: " + syncResult);
-//
-// } catch (SeafException e) {
-// switch (e.getCode()) {
-// /*
-// * here we have basically two scenarios
-// * SOFT) the error can be resolved without user interaction
-// * --> mark syncResult as SOFT error (eg. stats.numIoExceptions) and return.
-// * --> SyncManager will retry later
-// * HARD) the error can ONLY be resolved by the user performing actions on the device
-// * --> mark syncResult as HARD error and give user a popup information.
-// */
-// case HttpURLConnection.HTTP_UNAUTHORIZED:
-// // hard error -> we cannot recover without user interaction
-// syncResult.stats.numAuthExceptions++;
-// // Log.i(DEBUG_TAG, "sync aborted because of authentication error.", e);
-// showNotificationAuthError();
-// break;
-// default:
-// syncResult.stats.numIoExceptions++;
-// // Log.i(DEBUG_TAG, "sync aborted because of IO or server-side error.", e);
-// break;
-// }
-// } catch (Exception e) {
-// Log.e(DEBUG_TAG, "sync aborted because an unknown error", e);
-// syncResult.stats.numParseExceptions++;
-// } finally {
-// if (txService != null) {
-//
-// // Log.d(DEBUG_TAG, "Cancelling remaining pending tasks (if any)");
-// txService.cancelUploadTasksByIds(tasksInProgress);
-//
-// // Log.d(DEBUG_TAG, "disconnecting from TransferService");
-// getContext().unbindService(mConnection);
-// txService = null;
-// }
-// }
-// SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.SCAN_END);
-// SettingsManager.getInstance().saveUploadCompletedTime(Utils.getSyncCompletedTime());
-// EventBus.getDefault().post(new CameraSyncEvent("end"));
-// }
-//
-// private void uploadImages(SyncResult syncResult, DataManager dataManager) throws SeafException, InterruptedException {
-// SLogs.d("========Starting to upload images...");
-// // Log.d(DEBUG_TAG, "Starting to upload images...");
-//
-// if (isCancelled())
-// return;
-//
-// List selectedBuckets = new ArrayList<>();
-// if (bucketList.size() > 0) {
-// selectedBuckets = bucketList;
-// } else {
-// List allBuckets = GalleryBucketUtils.getMediaBuckets(SeadroidApplication.getAppContext());
-// for (GalleryBucketUtils.Bucket bucket : allBuckets) {
-// selectedBuckets.add(bucket.id);
-// }
-// }
-//
-// String[] selectionArgs = selectedBuckets.toArray(new String[]{});
-// String selection = MediaStore.Images.ImageColumns.BUCKET_ID + " IN " + varArgs(selectedBuckets.size());
-//
-// // Log.d(DEBUG_TAG, "ContentResolver selection='"+selection+"' selectionArgs='"+Arrays.deepToString(selectionArgs)+"'");
-//
-// // fetch all new images from the ContentProvider since our last sync
-// Cursor cursor = contentResolver.query(
-// MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-// new String[]{
-// MediaStore.Images.Media._ID,
-// MediaStore.Images.Media.DATA,
-// MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME
-// },
-// selection,
-// selectionArgs,
-// MediaStore.Images.ImageColumns.DATE_ADDED + " ASC"
-// );
-//
-// try {
-// if (cursor == null) {
-// SLogs.e("ContentResolver query failed!");
-// return;
-// }
-// // Log.d(DEBUG_TAG, "i see " + cursor.getCount() + " new images.");
-// SLogs.d("===i see " + cursor.getCount() + " images.");
-// if (cursor.getCount() > 0) {
-// // create directories for media buckets
-// createDirectories(dataManager);
-// iterateCursor(syncResult, dataManager, cursor, "images");
-//
-// if (isCancelled())
-// return;
-//
-// }
-// } finally {
-// if (cursor != null)
-// cursor.close();
-// }
-// }
-//
-// private void uploadVideos(SyncResult syncResult, DataManager dataManager) throws SeafException, InterruptedException {
-// SLogs.d("Starting to upload videos...");
-// // Log.d(DEBUG_TAG, "Starting to upload videos...");
-//
-// if (isCancelled())
-// return;
-//
-// List selectedBuckets = new ArrayList<>();
-// if (bucketList.size() > 0) {
-// selectedBuckets = bucketList;
-// } else {
-// List allBuckets = GalleryBucketUtils.getMediaBuckets(SeadroidApplication.getAppContext());
-// for (GalleryBucketUtils.Bucket bucket : allBuckets) {
-// selectedBuckets.add(bucket.id);
-// }
-// }
-//
-// String[] selectionArgs = selectedBuckets.toArray(new String[]{});
-// String selection = MediaStore.Video.VideoColumns.BUCKET_ID + " IN " + varArgs(selectedBuckets.size());
-//
-// // Log.d(DEBUG_TAG, "ContentResolver selection='"+selection+"' selectionArgs='"+Arrays.deepToString(selectionArgs)+"'");
-// // fetch all new videos from the ContentProvider since our last sync
-// Cursor cursor = contentResolver.query(
-// MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
-// new String[]{
-// MediaStore.Video.Media._ID,
-// MediaStore.Video.Media.DATA,
-// MediaStore.Video.VideoColumns.BUCKET_DISPLAY_NAME
-// },
-// selection,
-// selectionArgs,
-// MediaStore.Video.VideoColumns.DATE_ADDED + " ASC"
-// );
-//
-// try {
-// if (cursor == null) {
-// SLogs.e("ContentResolver query failed!");
-// return;
-// }
-// // Log.d(DEBUG_TAG, "i see " + cursor.getCount() + " new videos.");
-// SLogs.d("=====i see " + cursor.getCount() + " videos.");
-// if (cursor.getCount() > 0) {
-// // create directories for media buckets
-// createDirectories(dataManager);
-// iterateCursor(syncResult, dataManager, cursor, "video");
-//
-// if (isCancelled())
-// return;
-//
-// }
-// } finally {
-// if (cursor != null)
-// cursor.close();
-// }
-//
-// }
-//
-// private String varArgs(int count) {
-// String[] chars = new String[count];
-// Arrays.fill(chars, "?");
-// return "( " + Joiner.on(", ").join(chars) + " )";
-// }
-//
-//
-// /**
-// * Create all the subdirectories on the server for the buckets that are about to be uploaded.
-// *
-// * @param dataManager
-// * @throws SeafException
-// */
-// private void createDirectories(DataManager dataManager) throws SeafException {
-// List buckets = GalleryBucketUtils.getMediaBuckets(getContext());
-//
-// // create base directory
-// forceCreateDirectory(dataManager, "/", BASE_DIR);
-// for (GalleryBucketUtils.Bucket bucket : buckets) {
-//
-// // the user has selected specific buckets: only create directories for these
-// if (!bucketList.isEmpty() && !bucketList.contains(bucket.id)) {
-// continue;
-// }
-//
-//// // auto-guessing is on: create directories for camera buckets
-//// if (bucketList.isEmpty() && !bucket.isCameraBucket)
-//// continue;
-//
-// forceCreateDirectory(dataManager, BASE_DIR, bucket.name);
-// // update our cache for that server. we will use it later
-// dataManager.getDirentsFromServer(targetRepoId, Utils.pathJoin(BASE_DIR, bucket.name));
-// }
-// }
-//
-// /**
-// * Create a directory, rename a file away if necessary,
-// *
-// * @param dataManager
-// * @param parent parent dir
-// * @param dir directory to create
-// * @throws SeafException
-// */
-// private void forceCreateDirectory(DataManager dataManager, String parent, String dir) throws SeafException {
-// List dirs = dataManager.getDirentsFromServer(targetRepoId, parent);
-// boolean found = false;
-// for (SeafDirent dirent : dirs) {
-// if (dirent.name.equals(dir) && dirent.isDir()) {
-// found = true;
-// } else if (dirent.name.equals(dir) && !dirent.isDir()) {
-// // there is already a file. move it away.
-// String newFilename = getContext().getString(R.string.camera_sync_rename_file, dirent.name);
-// dataManager.rename(targetRepoId,
-// Utils.pathJoin(Utils.pathJoin("/", parent), dirent.name),
-// newFilename,
-// false);
-// }
-// }
-// if (!found)
-// dataManager.createNewDir(targetRepoId, Utils.pathJoin("/", parent), dir);
-// }
-//
-// /**
-// * Iterate through the content provider and upload all files
-// *
-// * @param syncResult
-// * @param dataManager
-// * @param cursor
-// * @throws SeafException
-// */
-// private void iterateCursor(SyncResult syncResult, DataManager dataManager, Cursor cursor, String media) throws SeafException, InterruptedException {
-//
-// tasksInProgress.clear();
-// File file;
-// // upload them one by one
-// while (!isCancelled() && cursor.moveToNext()) {
-// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-// if (media.equals("images")) {
-// String image_id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
-// Uri image_uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, image_id);
-// if (image_uri == null) {
-// syncResult.stats.numSkippedEntries++;
-// continue;
-// }
-// file = new File(Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), image_uri, media));
-// } else {
-// String video_id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID));
-// Uri video_uri = Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, video_id);
-// if (video_uri == null) {
-// syncResult.stats.numSkippedEntries++;
-// continue;
-// }
-// file = new File(Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), video_uri, media));
-// }
-// } else {
-// int dataColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
-// if (cursor.getString(dataColumn) == null) {
-// syncResult.stats.numSkippedEntries++;
-// continue;
-// }
-// file = new File(cursor.getString(dataColumn));
-//
-// }
-//// Utils.utilsLogInfo(true,"======iterateCursor");
-// int bucketColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME);
-// String bucketName = cursor.getString(bucketColumn);
-//
-// // local file does not exist. some inconsistency in the Media Provider? Ignore and continue
-// if (!file.exists()) {
-// // Log.d(DEBUG_TAG, "Skipping media "+file+" because it doesn't exist");
-//// Utils.utilsLogInfo(true, "=====Skipping media " + file + " because it doesn't exist");
-// syncResult.stats.numSkippedEntries++;
-// continue;
-// }
-//
-// // Ignore all media by Seafile. We don't want to upload our own cached files.
-// if (file.getAbsolutePath().startsWith(StorageManager.getInstance().getMediaDir().getAbsolutePath())) {
-// // Log.d(DEBUG_TAG, "Skipping media "+file+" because it's part of the Seadroid cache");
-// SLogs.d("======Skipping media " + file + " because it's part of the Seadroid cache");
-// continue;
-// }
-//
-// if (dbHelper.isUploaded(file)) {
-// // Log.d(DEBUG_TAG, "Skipping media " + file + " because we have uploaded it in the past.");
-// SLogs.d("=====Skipping media " + file + " because we have uploaded it in the past.");
-// continue;
-// }
-//
-// uploadFile(dataManager, file, bucketName);
-// }
-// SLogs.d("=======waitForUploads===");
-// waitForUploads();
-// checkUploadResult(syncResult);
-// }
-//
-// private void waitForUploads() throws InterruptedException {
-// // Log.d(DEBUG_TAG, "wait for transfer service to finish our tasks");
-// WAITLOOP:
-// while (!isCancelled()) {
-// Thread.sleep(100); // wait
-//
-// for (int id : tasksInProgress) {
-// UploadTaskInfo info = txService.getUploadTaskInfo(id);
-// if (info.state == TaskState.INIT || info.state == TaskState.TRANSFERRING) {
-// // there is still at least one task pending
-// continue WAITLOOP;
-// }
-// }
-// break;
-// }
-// }
-//
-// /**
-// * Upload is finished. Go through task infos and mark files as uploaded in our DB
-// *
-// * @param syncResult
-// * @throws SeafException
-// */
-// private void checkUploadResult(SyncResult syncResult) throws SeafException {
-// for (int id : tasksInProgress) {
-// UploadTaskInfo info = txService.getUploadTaskInfo(id);
-// if (info.err != null) {
-// throw info.err;
-// }
-// if (info.state == TaskState.FINISHED) {
-// File file = new File(info.localFilePath);
-// dbHelper.markAsUploaded(file);
-// syncResult.stats.numInserts++;
-// } else {
-// throw SeafException.unknownException;
-// }
-// }
-// }
-//
-// /**
-// * Upload a media file to the seafile server.
-// *
-// * @param dataManager Handle to the seafile server
-// * @param file the file to be uploaded
-// * @param bucketName the name of the media bucket
-// * @throws SeafException
-// */
-// private void uploadFile(DataManager dataManager, File file, String bucketName) throws SeafException {
-//
-// String serverPath = Utils.pathJoin(BASE_DIR, bucketName);
-// SLogs.d("=======uploadFile===");
-// List list = dataManager.getCachedDirents(targetRepoId, serverPath);
-// if (list == null) {
-// SLogs.e("=======Seadroid dirent cache is empty in uploadFile. Should not happen, aborting.");
-// // the dirents were supposed to be refreshed in createDirectories()
-// // something changed, abort.
-// throw SeafException.unknownException;
-// }
-//
-// /*
-// * We don't want to upload a file twice unless the local and remote files differ.
-// *
-// * It would be cool if the API2 offered a way to query the hash of a remote file.
-// * Currently, comparing the file size is the best we can do.
-// */
-// String filename = file.getName();
-// String prefix = filename.substring(0, filename.lastIndexOf("."));
-// String suffix = filename.substring(filename.lastIndexOf("."));
-// Pattern pattern = Pattern.compile(Pattern.quote(prefix) + "( \\(\\d+\\))?" + Pattern.quote(suffix));
-// for (SeafDirent dirent : list) {
-// if (pattern.matcher(dirent.name).matches() && dirent.size == file.length()) {
-// SLogs.d("====File " + file.getName() + " in bucket " + bucketName + " already exists on the server. Skipping.");
-// dbHelper.markAsUploaded(file);
-// return;
-// }
-// }
-//
-// SLogs.d("====uploading file " + file.getName() + " to " + serverPath);
-// int taskID = txService.addCameraUploadTask(dataManager.getAccount(), targetRepoId, targetRepoName,
-// serverPath, file.getAbsolutePath(), false, false);
-// tasksInProgress.add(taskID);
-// }
-//
-// /**
-// * Show a notification message that an auth error has occured.
-// * This is something the user has to fix.
-// */
-// private void showNotificationAuthError() {
-// NotificationCompat.Builder mBuilder;
-// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-// mBuilder = new NotificationCompat.Builder(getContext(), CustomNotificationBuilder.CHANNEL_ID_ERROR);
-// } else {
-// mBuilder = new NotificationCompat.Builder(getContext());
-// }
-//
-// mBuilder.setSmallIcon(R.drawable.icon)
-// .setOnlyAlertOnce(true)
-// .setContentTitle(getContext().getString(R.string.camera_sync_notification_title_failed))
-// .setContentText(getContext().getString(R.string.camera_sync_notification_auth_error_failed));
-//
-// // Creates an explicit intent for an Activity in your app
-// Intent resultIntent = new Intent(getContext(), AccountsActivity.class);
-//
-// resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-//
-// PendingIntent dPendingIntent = PendingIntent.getActivity(getContext(),
-// (int) System.currentTimeMillis(),
-// resultIntent,
-// 0);
-//
-// mBuilder.setContentIntent(dPendingIntent);
-// NotificationManager mNotificationManager =
-// (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
-//
-// mNotificationManager.notify(0, mBuilder.build());
-// }
-//
-// /**
-// * Show a notification message that the server repo is missing.
-// * This is something the user has to fix.
-// */
-// private void showNotificationRepoError() {
-// NotificationCompat.Builder mBuilder =
-// new NotificationCompat.Builder(getContext())
-// .setSmallIcon(R.drawable.icon)
-// .setContentTitle(getContext().getString(R.string.camera_sync_notification_title_failed))
-// .setContentText(getContext().getString(R.string.camera_sync_notification_repo_missing_failed));
-//
-// // Creates an explicit intent for an Activity in your app
-// Intent resultIntent = new Intent(getContext(), SettingsActivity.class);
-//
-// resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-//
-// PendingIntent dPendingIntent = PendingIntent.getActivity(getContext(),
-// (int) System.currentTimeMillis(), resultIntent, FLAG_IMMUTABLE);
-//
-// mBuilder.setContentIntent(dPendingIntent);
-// NotificationManager mNotificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
-//
-// mNotificationManager.notify(0, mBuilder.build());
-// }
-
-}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadConfigActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadConfigActivity.java
index 9b0c906e4..82bd9a31e 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadConfigActivity.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadConfigActivity.java
@@ -135,28 +135,20 @@ private void saveSettings() {
SystemSwitchUtils.getInstance(this).syncSwitchUtils();
for (Fragment fragment : fragmentList) {
- if (fragment instanceof HowToUploadFragment) {
+ if (fragment instanceof HowToUploadFragment howToUploadFragment) {
- HowToUploadFragment howToUploadFragment = (HowToUploadFragment) fragment;
AlbumBackupSharePreferenceHelper.writeAllowDataPlanSwitch(howToUploadFragment.getHowToUpload());
- } else if (fragment instanceof WhatToUploadFragment) {
-
- WhatToUploadFragment whatToUploadFragment = (WhatToUploadFragment) fragment;
+ } else if (fragment instanceof WhatToUploadFragment whatToUploadFragment) {
AlbumBackupSharePreferenceHelper.writeAllowVideoSwitch(whatToUploadFragment.getWhatToUpload());
-
- } else if (fragment instanceof BucketsFragment) {
-
- BucketsFragment bucketsFragment = (BucketsFragment) fragment;
+ } else if (fragment instanceof BucketsFragment bucketsFragment) {
List selectedBuckets = bucketsFragment.getSelectedBuckets();
if (bucketsFragment.isAutoScanSelected()) {
selectedBuckets.clear();
}
AlbumBackupSharePreferenceHelper.writeBucketIds(selectedBuckets);
- } else if (fragment instanceof ObjSelectorFragment) {
-
- ObjSelectorFragment cloudLibrarySelectorFragment = (ObjSelectorFragment) fragment;
+ } else if (fragment instanceof ObjSelectorFragment cloudLibrarySelectorFragment) {
Pair pair = cloudLibrarySelectorFragment.getBackupInfo();
mAccount = pair.first;
repoModel = pair.second;
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/data_migrate/DataMigrationActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/data_migrate/DataMigrationActivity.java
index 50a74e1f9..e5c71c37c 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/data_migrate/DataMigrationActivity.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/data_migrate/DataMigrationActivity.java
@@ -14,6 +14,7 @@
import androidx.preference.PreferenceManager;
import com.blankj.utilcode.util.CollectionUtils;
+import com.blankj.utilcode.util.EncryptUtils;
import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.GsonUtils;
import com.seafile.seadroid2.SeadroidApplication;
@@ -48,6 +49,7 @@
import com.seafile.seadroid2.framework.util.Utils;
import com.seafile.seadroid2.framework.worker.ExistingFileStrategy;
import com.seafile.seadroid2.framework.monitor.MonitorDBHelper;
+import com.seafile.seadroid2.preferences.Settings;
import com.seafile.seadroid2.ssl.CertsDBHelper;
import com.seafile.seadroid2.ui.account.AccountService;
import com.seafile.seadroid2.ui.camera_upload.CameraUploadDBHelper;
@@ -271,7 +273,6 @@ private void queryAlbumSPConfig() {
SharedPreferences sharedPref = getSharedPreferences(DataStoreKeys.LATEST_ACCOUNT, Context.MODE_PRIVATE);
-
String repoId = sharedPref.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_REPO_ID, null);
String repoName = sharedPref.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_REPO_NAME, null);
Account account = CameraUploadManager.getInstance().getCameraAccount();
@@ -1008,9 +1009,13 @@ private void queryCertsDB() {
SLogs.d("--------------------" + table);
for (CertEntity entity : list) {
SLogs.d(entity.toString());
+
+ String keyPrefix = EncryptUtils.encryptMD5ToString(entity.url);
+ Settings.getCommonPreferences().edit().putString(DataStoreKeys.KEY_SERVER_CERT_INFO + "_" + keyPrefix, entity.cert).apply();
}
- AppDatabase.getInstance().certDAO().insertAll(list);
+ //Not stored in the database
+// AppDatabase.getInstance().certDAO().insertAll(list);
SLogs.d("--------------------" + table + " -> 完成");
}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/SslConfirmDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/SslConfirmDialog.java
index 0b029fdb9..fc1e7ea64 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/SslConfirmDialog.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/SslConfirmDialog.java
@@ -15,9 +15,16 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.google.android.gms.common.internal.Objects;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Ordering;
import com.seafile.seadroid2.R;
import com.seafile.seadroid2.account.Account;
+import com.seafile.seadroid2.databinding.DialogSslConfirmBinding;
import com.seafile.seadroid2.framework.data.CertificateInfo;
import com.seafile.seadroid2.ssl.SSLTrustManager;
import com.seafile.seadroid2.ssl.SSLTrustManager.SslFailureReason;
@@ -41,15 +48,6 @@ public interface Listener {
private Account account;
private Listener listener;
private X509Certificate certificate;
- private TextView messageText;
- private TextView commonNameText;
- // private TextView altSubjNamesText;
- private TextView sha256Text;
- private TextView sha1Text;
- private TextView md5Text;
- private TextView serialNumberText;
- private TextView notBeforeText;
- private TextView notAfterText;
public SslConfirmDialog() {
}
@@ -69,20 +67,10 @@ public SslConfirmDialog(Account account, X509Certificate certificate, Listener l
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
+ DialogSslConfirmBinding binding = DialogSslConfirmBinding.inflate(getLayoutInflater());
+
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity());
builder.setTitle(getString(R.string.ssl_confirm_title));
- LayoutInflater inflater = getActivity().getLayoutInflater();
- LinearLayout view = (LinearLayout) inflater.inflate(R.layout.dialog_ssl_confirm, null);
-
- messageText = (TextView) view.findViewById(R.id.message);
- commonNameText = (TextView) view.findViewById(R.id.common_name);
- // altSubjNamesText = (TextView) view.findViewById(R.id.alt_subj_name);
- sha256Text = (TextView) view.findViewById(R.id.sha256);
- sha1Text = (TextView) view.findViewById(R.id.sha1);
- md5Text = (TextView) view.findViewById(R.id.md5);
- serialNumberText = (TextView) view.findViewById(R.id.serial_number);
- notBeforeText = (TextView) view.findViewById(R.id.not_before);
- notAfterText = (TextView) view.findViewById(R.id.not_after);
String host = null;
@@ -99,43 +87,45 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
} catch (CertificateParsingException e) {
e.printStackTrace();
}
+
String msg = "";
if (reason == SslFailureReason.CERT_NOT_TRUSTED) {
msg = getActivity().getString(R.string.ssl_confirm, host);
} else {
msg = getActivity().getString(R.string.ssl_confirm_cert_changed, host);
}
- messageText.setText(msg);
+
+ binding.message.setText(msg);
if (cert != null) {
CertificateInfo certInfo = new CertificateInfo(cert);
- commonNameText.setText(certInfo.getSubjectName());
+ binding.commonName.setText(certInfo.getSubjectName());
// String[] subjAltNames = certInfo.getSubjectAltNames();
// altSubjNamesText.setText((subjAltNames.length > 0) ? StringUtils.join(subjAltNames, ", ") : "—");
- sha256Text.setText(getActivity().getString(R.string.sha256, certInfo.getSignature("SHA-256")));
- sha1Text.setText(getActivity().getString(R.string.sha1, certInfo.getSignature("SHA-1")));
- md5Text.setText(getActivity().getString(R.string.md5, certInfo.getSignature("MD5")));
- serialNumberText.setText(getActivity().getString(R.string.serial_number, certInfo.getSerialNumber()));
- notBeforeText.setText(getActivity().getString(R.string.not_before, certInfo.getNotBefore().toLocaleString()));
- notAfterText.setText(getActivity().getString(R.string.not_after, certInfo.getNotAfter().toLocaleString()));
+ binding.sha256.setText(getActivity().getString(R.string.sha256, certInfo.getSignature("SHA-256")));
+ binding.sha1.setText(getActivity().getString(R.string.sha1, certInfo.getSignature("SHA-1")));
+ binding.md5.setText(getActivity().getString(R.string.md5, certInfo.getSignature("MD5")));
+ binding.serialNumber.setText(getActivity().getString(R.string.serial_number, certInfo.getSerialNumber()));
+ binding.notBefore.setText(getActivity().getString(R.string.not_before, certInfo.getNotBefore().toLocaleString()));
+ binding.notAfter.setText(getActivity().getString(R.string.not_after, certInfo.getNotAfter().toLocaleString()));
} else if (certificate != null) {
CertificateInfo certInfo = new CertificateInfo(certificate);
- commonNameText.setText(certInfo.getSubjectName());
- sha256Text.setText(getActivity().getString(R.string.sha256, certInfo.getSignature("SHA-256")));
- sha1Text.setText(getActivity().getString(R.string.sha1, certInfo.getSignature("SHA-1")));
- md5Text.setText(getActivity().getString(R.string.md5, certInfo.getSignature("MD5")));
- serialNumberText.setText(getActivity().getString(R.string.serial_number, certInfo.getSerialNumber()));
- notBeforeText.setText(getActivity().getString(R.string.not_before, certInfo.getNotBefore().toLocaleString()));
- notAfterText.setText(getActivity().getString(R.string.not_after, certInfo.getNotAfter().toLocaleString()));
+ binding.commonName.setText(certInfo.getSubjectName());
+ binding.sha256.setText(getActivity().getString(R.string.sha256, certInfo.getSignature("SHA-256")));
+ binding.sha1.setText(getActivity().getString(R.string.sha1, certInfo.getSignature("SHA-1")));
+ binding.md5.setText(getActivity().getString(R.string.md5, certInfo.getSignature("MD5")));
+ binding.serialNumber.setText(getActivity().getString(R.string.serial_number, certInfo.getSerialNumber()));
+ binding.notBefore.setText(getActivity().getString(R.string.not_before, certInfo.getNotBefore().toLocaleString()));
+ binding.notAfter.setText(getActivity().getString(R.string.not_after, certInfo.getNotAfter().toLocaleString()));
} else {
String not_available = getActivity().getString(R.string.not_available);
- commonNameText.setText(not_available);
- sha256Text.setText(not_available);
- sha1Text.setText(not_available);
- md5Text.setText(not_available);
- serialNumberText.setText(not_available);
- notBeforeText.setText(not_available);
- notAfterText.setText(not_available);
+ binding.commonName.setText(not_available);
+ binding.sha256.setText(not_available);
+ binding.sha1.setText(not_available);
+ binding.md5.setText(not_available);
+ binding.serialNumber.setText(not_available);
+ binding.notBefore.setText(not_available);
+ binding.notAfter.setText(not_available);
}
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@@ -153,7 +143,8 @@ public void onClick(DialogInterface dialog, int which) {
listener.onRejected();
}
});
- builder.setView(view);
+
+ builder.setView(binding.getRoot());
return builder.show();
}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/GetShareLinkPasswordDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/GetShareLinkPasswordDialogFragment.java
index f782a29dc..99da550c8 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/GetShareLinkPasswordDialogFragment.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/GetShareLinkPasswordDialogFragment.java
@@ -7,7 +7,7 @@
import com.blankj.utilcode.util.ToastUtils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.switchmaterial.SwitchMaterial;
+import com.google.android.material.materialswitch.MaterialSwitch;
import com.google.android.material.textfield.TextInputLayout;
import com.seafile.seadroid2.R;
import com.seafile.seadroid2.listener.OnCreateDirentShareLinkListener;
@@ -86,14 +86,14 @@ protected void initView(LinearLayout containerView) {
// getResources().getInteger(R.integer.minimum_password_length)
// ));
- SwitchMaterial passwordSwitch = getDialogView().findViewById(R.id.add_password);
+ MaterialSwitch passwordSwitch = getDialogView().findViewById(R.id.add_password);
passwordSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
passwordTextInput.setVisibility(isChecked ? View.VISIBLE : View.GONE);
});
TextInputLayout daysTextInput = getDialogView().findViewById(R.id.days_text_input);
- SwitchMaterial switchMaterial = getDialogView().findViewById(R.id.add_expiration);
- switchMaterial.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ MaterialSwitch daysSwitch = getDialogView().findViewById(R.id.add_expiration);
+ daysSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
daysTextInput.setVisibility(isChecked ? View.VISIBLE : View.GONE);
});
}
@@ -133,7 +133,7 @@ private String getErrorMsg(String errMsg) {
private boolean checkData() {
- SwitchMaterial passwordSwitch = getDialogView().findViewById(R.id.add_password);
+ MaterialSwitch passwordSwitch = getDialogView().findViewById(R.id.add_password);
if (passwordSwitch.isChecked()) {
EditText editText = getDialogView().findViewById(R.id.password);
String password = editText.getText().toString();
@@ -144,8 +144,8 @@ private boolean checkData() {
}
}
- SwitchMaterial switchMaterial = getDialogView().findViewById(R.id.add_expiration);
- if (switchMaterial.isChecked()) {
+ MaterialSwitch daysSwitch = getDialogView().findViewById(R.id.add_expiration);
+ if (daysSwitch.isChecked()) {
EditText daysEditText = getDialogView().findViewById(R.id.days);
String daysText = daysEditText.getText().toString();
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java
index b8ebc629a..18ac86595 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java
@@ -639,15 +639,15 @@ public boolean onPrepareOptionsMenu(Menu menu) {
menuBinding.createRepo.setVisible(false);
if (getNavContext().hasParentWritePermission()) {
menuBinding.add.setEnabled(true);
- menuBinding.edit.setEnabled(true);
+ menuBinding.select.setEnabled(true);
} else {
menuBinding.add.setEnabled(false);
- menuBinding.edit.setEnabled(false);
+ menuBinding.select.setEnabled(false);
}
} else {
menuBinding.createRepo.setVisible(true);
menuBinding.add.setVisible(false);
- menuBinding.edit.setVisible(false);
+ menuBinding.select.setVisible(false);
}
menuBinding.sortGroup.setVisible(true);
@@ -655,7 +655,7 @@ public boolean onPrepareOptionsMenu(Menu menu) {
menuBinding.sortGroup.setVisible(false);
menuBinding.createRepo.setVisible(false);
menuBinding.add.setVisible(false);
- menuBinding.edit.setVisible(false);
+ menuBinding.select.setVisible(false);
}
updateMenu();
@@ -688,7 +688,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
Intent newIntent = new Intent(this, TransferActivity.class);
newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(newIntent);
- } else if (item.getItemId() == R.id.edit) {
+ } else if (item.getItemId() == R.id.select) {
if (binding.pager.getCurrentItem() == INDEX_LIBRARY_TAB) {
getReposFragment().startContextualActionMode();
}
@@ -734,7 +734,7 @@ public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
menuIdState.put("sortGroup", menuBinding.sortGroup.isVisible());
menuIdState.put("createRepo", menuBinding.createRepo.isVisible());
menuIdState.put("add", menuBinding.add.isVisible());
- menuIdState.put("edit", menuBinding.edit.isVisible());
+ menuIdState.put("select", menuBinding.select.isVisible());
menuIdState.put("transferList", menuBinding.transferList.isVisible());
Optional optional = checkServerInfo();
@@ -745,7 +745,7 @@ public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
menuBinding.sortGroup.setVisible(false);
menuBinding.createRepo.setVisible(false);
menuBinding.add.setVisible(false);
- menuBinding.edit.setVisible(false);
+ menuBinding.select.setVisible(false);
menuBinding.transferList.setVisible(false);
return true;
@@ -758,7 +758,7 @@ public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
menuBinding.sortGroup.setVisible(Boolean.TRUE.equals(menuIdState.get("sortGroup")));
menuBinding.createRepo.setVisible(Boolean.TRUE.equals(menuIdState.get("createRepo")));
menuBinding.add.setVisible(Boolean.TRUE.equals(menuIdState.get("add")));
- menuBinding.edit.setVisible(Boolean.TRUE.equals(menuIdState.get("edit")));
+ menuBinding.select.setVisible(Boolean.TRUE.equals(menuIdState.get("select")));
menuBinding.transferList.setVisible(Boolean.TRUE.equals(menuIdState.get("transferList")));
invalidateMenu();
@@ -817,7 +817,7 @@ private static class MenuBinding {
public MenuItem createRepo;
public MenuItem add;
- public MenuItem edit;
+ public MenuItem select;
public MenuItem transferList;
@@ -844,7 +844,7 @@ public static MenuBinding inflate(Menu menu, MenuInflater inflater) {
binding1.createRepo = menu.findItem(R.id.create_repo);
binding1.add = menu.findItem(R.id.add);
- binding1.edit = menu.findItem(R.id.edit);
+ binding1.select = menu.findItem(R.id.select);
binding1.transferList = menu.findItem(R.id.transfer_tasks);
return binding1;
}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java
index 954bc025c..a03840998 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java
@@ -61,9 +61,9 @@
import com.seafile.seadroid2.framework.util.SLogs;
import com.seafile.seadroid2.framework.util.Utils;
import com.seafile.seadroid2.framework.worker.BackgroundJobManagerImpl;
-import com.seafile.seadroid2.framework.worker.download.DownloadWorker;
import com.seafile.seadroid2.framework.worker.TransferEvent;
import com.seafile.seadroid2.framework.worker.TransferWorker;
+import com.seafile.seadroid2.framework.worker.download.DownloadWorker;
import com.seafile.seadroid2.framework.worker.upload.UploadFileManuallyWorker;
import com.seafile.seadroid2.framework.worker.upload.UploadFolderFileAutomaticallyWorker;
import com.seafile.seadroid2.framework.worker.upload.UploadMediaFileAutomaticallyWorker;
@@ -82,8 +82,8 @@
import com.seafile.seadroid2.ui.markdown.MarkdownActivity;
import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity;
import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity;
+import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity;
import com.seafile.seadroid2.ui.selector.ObjSelectorActivity;
-import com.seafile.seadroid2.ui.webview.SeaWebViewActivity;
import com.seafile.seadroid2.view.TipsViews;
import java.io.File;
@@ -995,7 +995,7 @@ private void open(DirentModel dirent) {
}
if (fileName.endsWith(Constants.Format.DOT_SDOC)) {
- SeaWebViewActivity.openSdoc(getContext(), repoModel.repo_name, repoModel.repo_id, dirent.parent_dir + dirent.name);
+ SDocWebViewActivity.openSdoc(getContext(), repoModel.repo_name, repoModel.repo_id, dirent.parent_dir + dirent.name);
return;
}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocService.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocService.java
new file mode 100644
index 000000000..8010a80a4
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocService.java
@@ -0,0 +1,34 @@
+package com.seafile.seadroid2.ui.sdoc;
+
+import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentWrapperModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocDetailModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocRecordWrapperModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocWrapperModel;
+import com.seafile.seadroid2.framework.data.model.user.UserWrapperModel;
+
+import io.reactivex.Single;
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+public interface SDocService {
+ @GET("api2/repos/{repo_id}/file/detail/")
+ Single getFileDetail(@Path("repo_id") String repoId, @Query("p") String path);
+
+ @GET("api/v2.1/repos/{repo_id}/related-users/")
+ Single getRelatedUsers(@Path("repo_id") String repoId);
+
+ @GET("api/v2.1/repos/{repo_id}/metadata/")
+ Single getMetadata(@Path("repo_id") String repoId);
+
+ @GET("api/v2.1/repos/{repo_id}/metadata/record/")
+ Single getRecords(@Path("repo_id") String repoId, @Query("parent_dir") String parentDir, @Query("name") String name);
+
+ //
+ @GET("api/v1/docs/{uuid}/comment/")
+ Single getComments(@Path("uuid") String uuid);
+
+ @GET("api/v1/docs/{uuid}/")
+ Single getElements(@Path("uuid") String uuid);
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java
new file mode 100644
index 000000000..d0e157108
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java
@@ -0,0 +1,223 @@
+package com.seafile.seadroid2.ui.sdoc;
+
+import android.text.TextUtils;
+
+import androidx.lifecycle.MutableLiveData;
+
+import com.blankj.utilcode.util.CloneUtils;
+import com.blankj.utilcode.util.CollectionUtils;
+import com.seafile.seadroid2.account.Account;
+import com.seafile.seadroid2.account.SupportAccountManager;
+import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentWrapperModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocDetailModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocProfileConfigModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocRecordWrapperModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocWrapperModel;
+import com.seafile.seadroid2.framework.data.model.user.UserWrapperModel;
+import com.seafile.seadroid2.framework.http.HttpIO;
+import com.seafile.seadroid2.framework.util.SLogs;
+import com.seafile.seadroid2.framework.util.StringUtils;
+import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import io.reactivex.Single;
+import io.reactivex.functions.Consumer;
+import io.reactivex.functions.Function3;
+
+public class SDocViewModel extends BaseViewModel {
+
+ private final MutableLiveData _fileProfileConfigLiveData = new MutableLiveData<>();
+ private final MutableLiveData _sdocRecordLiveData = new MutableLiveData<>();
+ private final MutableLiveData _sdocCommentLiveData = new MutableLiveData<>();
+ private final MutableLiveData> _sdocElementListLiveData = new MutableLiveData<>();
+
+ public MutableLiveData getFileDetailLiveData() {
+ return _fileProfileConfigLiveData;
+ }
+
+ public MutableLiveData getSdocRecordLiveData() {
+ return _sdocRecordLiveData;
+ }
+
+ public MutableLiveData getSdocCommentLiveData() {
+ return _sdocCommentLiveData;
+ }
+
+ public MutableLiveData> getSdocElementLiveData() {
+ return _sdocElementListLiveData;
+ }
+
+ public void initSDocConfig(String repoId, String path) {
+ Single userSingle = HttpIO.getCurrentInstance().execute(SDocService.class).getRelatedUsers(repoId);
+ Single metadataSingle = HttpIO.getCurrentInstance().execute(SDocService.class).getMetadata(repoId);
+ Single detailSingle = HttpIO.getCurrentInstance().execute(SDocService.class).getFileDetail(repoId, path);
+
+ Single s = Single.zip(detailSingle, userSingle, metadataSingle, new Function3() {
+ @Override
+ public SDocProfileConfigModel apply(SDocDetailModel docDetailModel, UserWrapperModel userWrapperModel, MetadataConfigModel metadataConfigModel) throws Exception {
+ SDocProfileConfigModel configModel = new SDocProfileConfigModel();
+ configModel.setDetail(docDetailModel);
+ configModel.setUsers(userWrapperModel);
+ configModel.setMetadata(metadataConfigModel);
+ return configModel;
+ }
+ });
+
+ addSingleDisposable(s, new Consumer() {
+ @Override
+ public void accept(SDocProfileConfigModel sDocProfileConfigModel) throws Exception {
+ getFileDetailLiveData().setValue(sDocProfileConfigModel);
+ }
+ });
+ }
+
+ public void getRecords(String repoId, String path) {
+ if (TextUtils.isEmpty(path) || TextUtils.equals("/", path)) {
+ return;
+ }
+
+ String parent_dir;
+ String name;
+
+ // 1. /a/b/c/t.txt
+ // 2. /a/t.txt
+ // 3. /t.txt
+ // 4. t.txt
+ // 5. /
+ if (path.contains("/")) {
+ parent_dir = path.substring(0, path.lastIndexOf("/"));
+ name = path.substring(path.lastIndexOf("/") + 1);
+ } else {
+ parent_dir = null;
+ name = path;
+ }
+
+ Single single = HttpIO.getCurrentInstance().execute(SDocService.class).getRecords(repoId, parent_dir, name);
+ addSingleDisposable(single, new Consumer() {
+ @Override
+ public void accept(SDocRecordWrapperModel sDocRecordWrapperModel) throws Exception {
+ getSdocRecordLiveData().setValue(sDocRecordWrapperModel);
+ }
+ });
+ }
+
+ public static final List _AllowedElementTypes = List.of("header1", "header2", "header3");
+
+ public void getSDocElements(SDocPageOptionsModel pageOptionsModel) {
+ if (TextUtils.isEmpty(pageOptionsModel.seadocServerUrl)) {
+ return;
+ }
+
+ String sdocServerUrl = pageOptionsModel.seadocServerUrl;
+ if (!sdocServerUrl.endsWith("/")) {
+ sdocServerUrl = sdocServerUrl + "/";
+ }
+
+ Account curAccount = SupportAccountManager.getInstance().getCurrentAccount();
+ Account partialAccount = CloneUtils.deepClone(curAccount, Account.class);
+ partialAccount.setToken(pageOptionsModel.seadocAccessToken);
+ partialAccount.setServer(sdocServerUrl);
+
+ Single single = HttpIO.getInstanceByAccount(partialAccount).execute(SDocService.class).getElements(pageOptionsModel.docUuid);
+ addSingleDisposable(single, new Consumer() {
+ @Override
+ public void accept(SDocWrapperModel wrapperModel) throws Exception {
+
+ if (wrapperModel == null || wrapperModel.elements == null) {
+ getSdocElementLiveData().setValue(null);
+ return;
+ }
+
+ List newList = wrapperModel.elements.stream().filter(new Predicate() {
+ @Override
+ public boolean test(SDocModel sDocModel) {
+ if (!_AllowedElementTypes.contains(sDocModel.type)) {
+ return false;
+ }
+
+ if (TextUtils.isEmpty(sDocModel.text) && CollectionUtils.isEmpty(sDocModel.children)) {
+ return false;
+ }
+
+ return true;
+ }
+ }).map(new Function() {
+ @Override
+ public SDocModel apply(SDocModel sDocModel) {
+ if (!TextUtils.isEmpty(sDocModel.text)) {
+ return sDocModel;
+ }
+
+ if (CollectionUtils.isEmpty(sDocModel.children)) {
+ return sDocModel;
+ }
+
+ String text = "";
+ for (SDocModel child : sDocModel.children) {
+ if (!TextUtils.isEmpty(child.text)) {
+ String nt = StringUtils.trim(child.text, "\n").trim();
+ text = text.concat(nt);
+ }
+ }
+ sDocModel.text = text;
+ return sDocModel;
+ }
+ }).collect(Collectors.toList());
+
+ getSdocElementLiveData().setValue(newList);
+ }
+ }, new Consumer() {
+ @Override
+ public void accept(Throwable throwable) throws Exception {
+ SLogs.e(throwable);
+ }
+ });
+
+ }
+
+ public void getSDocComments(SDocPageOptionsModel pageOptionsModel) {
+ if (TextUtils.isEmpty(pageOptionsModel.seadocServerUrl)) {
+ return;
+ }
+
+ String sdocServerUrl = pageOptionsModel.seadocServerUrl;
+ if (!sdocServerUrl.endsWith("/")) {
+ sdocServerUrl = sdocServerUrl + "/";
+ }
+
+ Account curAccount = SupportAccountManager.getInstance().getCurrentAccount();
+ Account partialAccount = CloneUtils.deepClone(curAccount, Account.class);
+ partialAccount.setToken(pageOptionsModel.seadocAccessToken);
+ partialAccount.setServer(sdocServerUrl);
+
+ Single commentSingle = HttpIO.getInstanceByAccount(partialAccount).execute(SDocService.class).getComments(pageOptionsModel.docUuid);
+ addSingleDisposable(commentSingle, new Consumer() {
+ @Override
+ public void accept(SDocCommentWrapperModel sDocCommentWrapperModel) throws Exception {
+ sDocCommentWrapperModel.comments = sDocCommentWrapperModel.comments.stream().sorted(new Comparator() {
+ @Override
+ public int compare(SDocCommentModel o1, SDocCommentModel o2) {
+ return -o1.created_at.compareTo(o2.created_at);
+ }
+ }).collect(Collectors.toList());
+
+ getSdocCommentLiveData().setValue(sDocCommentWrapperModel);
+ }
+ }, new Consumer() {
+ @Override
+ public void accept(Throwable throwable) throws Exception {
+ SLogs.e(throwable);
+ }
+ });
+
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java
new file mode 100644
index 000000000..2a1d4f43b
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java
@@ -0,0 +1,339 @@
+package com.seafile.seadroid2.ui.sdoc;
+
+import android.content.Context;
+import android.content.Intent;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+import android.text.TextUtils;
+import android.view.View;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+
+import androidx.activity.OnBackPressedCallback;
+import androidx.annotation.NonNull;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.util.Consumer;
+import androidx.core.widget.NestedScrollView;
+import androidx.lifecycle.Observer;
+import androidx.webkit.WebSettingsCompat;
+import androidx.webkit.WebViewFeature;
+
+import com.blankj.utilcode.util.ActivityUtils;
+import com.blankj.utilcode.util.GsonUtils;
+import com.blankj.utilcode.util.ToastUtils;
+import com.google.android.gms.tasks.Continuation;
+import com.seafile.seadroid2.account.Account;
+import com.seafile.seadroid2.account.SupportAccountManager;
+import com.seafile.seadroid2.annotation.Unstable;
+import com.seafile.seadroid2.databinding.ActivitySeaWebviewProBinding;
+import com.seafile.seadroid2.databinding.ToolbarActionbarProgressBarBinding;
+import com.seafile.seadroid2.enums.WebViewPreviewType;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocProfileConfigModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocRecordWrapperModel;
+import com.seafile.seadroid2.framework.util.SLogs;
+import com.seafile.seadroid2.framework.util.StringUtils;
+import com.seafile.seadroid2.ui.base.BaseActivityWithVM;
+import com.seafile.seadroid2.ui.sdoc.comments.SDocCommentsActivity;
+import com.seafile.seadroid2.ui.sdoc.directory.SDocDirectoryDialog;
+import com.seafile.seadroid2.ui.sdoc.profile.SDocProfileDialog;
+import com.seafile.seadroid2.ui.webview.SeaWebViewActivity;
+import com.seafile.seadroid2.view.webview.PreloadWebView;
+import com.seafile.seadroid2.view.webview.SeaWebView;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import kotlin.Pair;
+
+@Unstable
+public class SDocWebViewActivity extends BaseActivityWithVM {
+ private ActivitySeaWebviewProBinding binding;
+ private ToolbarActionbarProgressBarBinding toolBinding;
+
+ private SeaWebView mWebView;
+ private String repoId;
+ private String path;
+ private String targetUrl;
+
+ private SDocProfileConfigModel configModel;
+
+ public static void openSdoc(Context context, String repoName, String repoID, String path) {
+ Intent intent = new Intent(context, SeaWebViewActivity.class);
+ intent.putExtra("previewType", WebViewPreviewType.SDOC.name());
+ intent.putExtra("repoName", repoName);
+ intent.putExtra("repoID", repoID);
+ intent.putExtra("filePath", path);
+ ActivityUtils.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ binding = ActivitySeaWebviewProBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ toolBinding = ToolbarActionbarProgressBarBinding.bind(binding.toolProgressBar.getRoot());
+
+ init();
+
+ initUI();
+
+ initViewModel();
+
+ //let's go
+ mWebView.load(targetUrl);
+
+ getViewModel().initSDocConfig(repoId, path);
+ }
+
+ private void init() {
+ Intent intent = getIntent();
+
+ if (!intent.hasExtra("previewType")) {
+ throw new IllegalArgumentException("need a previewType param");
+ }
+
+ String previewType = intent.getStringExtra("previewType");
+ if (!WebViewPreviewType.contains(previewType)) {
+ throw new IllegalArgumentException("need a previewType param");
+ }
+
+ WebViewPreviewType previewTypeEnum = WebViewPreviewType.valueOf(previewType);
+
+ if (previewTypeEnum == WebViewPreviewType.SDOC) {
+ String repoName = intent.getStringExtra("repoName");
+ repoId = intent.getStringExtra("repoID");
+ path = intent.getStringExtra("filePath");
+
+ if (TextUtils.isEmpty(repoId) || TextUtils.isEmpty(path)) {
+ throw new IllegalArgumentException("repoId or path is null");
+ }
+
+ Account account = SupportAccountManager.getInstance().getCurrentAccount();
+ if (account != null) {
+ targetUrl = account.server + "lib/" + repoId + "/file" + path;
+ } else {
+ throw new IllegalArgumentException("no login");
+ }
+ } else {
+ throw new IllegalArgumentException("previewType is not SDOC");
+ }
+
+ }
+
+ private void initUI() {
+ Toolbar toolbar = toolBinding.toolbarActionbar;
+ toolbar.setTitle("");
+ setSupportActionBar(toolbar);
+ toolbar.setNavigationOnClickListener(v -> {
+ finish();
+ });
+
+ mWebView = PreloadWebView.getInstance().getWebView(this);
+
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
+ WebSettingsCompat.setAlgorithmicDarkeningAllowed(mWebView.getSettings(), true);
+ }
+
+ NestedScrollView.LayoutParams ll = new NestedScrollView.LayoutParams(-1, -1);
+ mWebView.setLayoutParams(ll);
+ binding.nsv.addView(mWebView);
+
+ //chrome client
+ mWebView.setWebChromeClient(mWebChromeClient);
+
+ binding.sdocDirectory.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showDirectoryDialog();
+ }
+ });
+ binding.sdocProfile.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showProfileDialog();
+ }
+ });
+ binding.sdocComment.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showCommentsActivity();
+ }
+ });
+
+ getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
+ @Override
+ public void handleOnBackPressed() {
+ if (mWebView != null && mWebView.canGoBack()) {
+ mWebView.goBack();
+ } else {
+ finish();
+ }
+ }
+ });
+ }
+
+ private void initViewModel() {
+ getViewModel().getFileDetailLiveData().observe(this, new Observer() {
+ @Override
+ public void onChanged(SDocProfileConfigModel sDocProfileConfigModel) {
+ configModel = sDocProfileConfigModel;
+ hideProgressBar();
+ }
+ });
+ getViewModel().getSdocRecordLiveData().observe(this, new Observer() {
+ @Override
+ public void onChanged(SDocRecordWrapperModel sDocRecordWrapperModel) {
+ SDocProfileDialog dialog = SDocProfileDialog.newInstance(configModel.detail, sDocRecordWrapperModel, configModel.users.user_list);
+ dialog.show(getSupportFragmentManager(), SDocProfileDialog.class.getSimpleName());
+ }
+ });
+ }
+
+ private void showDirectoryDialog() {
+ getSDocConfigData(new Consumer() {
+ @Override
+ public void accept(SDocPageOptionsModel model) {
+ if (TextUtils.isEmpty(model.seadocServerUrl) || TextUtils.isEmpty(model.docUuid)) {
+ return;
+ }
+
+ SDocDirectoryDialog dialog = SDocDirectoryDialog.newInstance(model);
+ dialog.show(getSupportFragmentManager(), SDocDirectoryDialog.class.getSimpleName());
+ }
+ });
+ }
+
+ private void showProfileDialog() {
+ if (configModel == null) {
+ return;
+ }
+
+ if (configModel.metadata.enabled) {
+ getViewModel().getRecords(repoId, path);
+ } else {
+ SDocProfileDialog dialog = SDocProfileDialog.newInstance(configModel.detail, configModel.users.user_list);
+ dialog.show(getSupportFragmentManager(), SDocProfileDialog.class.getSimpleName());
+ }
+ }
+
+ private void showCommentsActivity() {
+ getSDocConfigData(new Consumer() {
+ @Override
+ public void accept(SDocPageOptionsModel model) {
+ SDocCommentsActivity.start(SDocWebViewActivity.this, model);
+ }
+ });
+ }
+
+ private void getSDocConfigData(Consumer continuation) {
+ String js =
+ "(function() {" +
+ " if (window.app && window.app.pageOptions) {" +
+ " return JSON.stringify(window.app.pageOptions);" +
+ " } else {" +
+ " return null;" +
+ " }" +
+ "})();";
+ mWebView.evaluateJavascript(js, new ValueCallback() {
+ @Override
+ public void onReceiveValue(String value) {
+ SLogs.e(value);
+ if (!TextUtils.isEmpty(value)) {
+ value = StringUtils.deString(value).replace("\\", "");
+ SDocPageOptionsModel configModel1 = GsonUtils.fromJson(value, SDocPageOptionsModel.class);
+ if (configModel1 != null) {
+ continuation.accept(configModel1);
+ SLogs.d("获取的 PageOption 对象数据: " + configModel1);
+ } else {
+ SLogs.d("获取的 PageOption 对象数据是空的");
+ }
+
+ } else {
+ SLogs.d("doc uuid is empty.");
+ ToastUtils.showShort("doc uuid is empty.");
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ }
+
+
+ private int curProgress = 0;
+ private final WebChromeClient mWebChromeClient = new WebChromeClient() {
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ super.onProgressChanged(view, newProgress);
+ curProgress = newProgress;
+ setBarProgress(curProgress);
+ }
+
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ super.onReceivedTitle(view, title);
+ toolBinding.toolbarActionbar.setTitle(title);
+ }
+ };
+
+ private void setBarProgress(int p) {
+ toolBinding.toolProgressBar.setProgress(p, true);
+
+ if (p != 100) {
+ if (toolBinding.toolProgressBar.getVisibility() != View.VISIBLE) {
+ toolBinding.toolProgressBar.setVisibility(View.VISIBLE);
+ }
+ }
+
+ hideProgressBar();
+ }
+
+ private void hideProgressBar() {
+ if (configModel != null && curProgress == 100) {
+ toolBinding.toolProgressBar.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mWebView != null) {
+ mWebView.onPause();
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (mWebView != null) {
+ mWebView.onResume();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ destroyWebView();
+ }
+
+ public void destroyWebView() {
+ if (mWebView == null) {
+ return;
+ }
+
+ binding.nsv.removeView(mWebView);
+
+ mWebView.loadUrl("about:blank");
+ mWebView.stopLoading();
+ mWebView.destroy();
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java
new file mode 100644
index 000000000..7037b50fd
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java
@@ -0,0 +1,198 @@
+package com.seafile.seadroid2.ui.sdoc.comments;
+
+import static com.seafile.seadroid2.config.Constants.DP.DP_4;
+import static com.seafile.seadroid2.config.Constants.DP.DP_8;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.DiffUtil;
+
+import com.blankj.utilcode.util.CollectionUtils;
+import com.blankj.utilcode.util.ScreenUtils;
+import com.blankj.utilcode.util.SizeUtils;
+import com.bumptech.glide.Glide;
+import com.google.android.flexbox.FlexboxLayout;
+import com.seafile.seadroid2.R;
+import com.seafile.seadroid2.databinding.ItemSdocCommentBinding;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentModel;
+import com.seafile.seadroid2.ui.base.adapter.BaseAdapter;
+import com.yydcdut.markdown.MarkdownConfiguration;
+import com.yydcdut.markdown.MarkdownProcessor;
+import com.yydcdut.markdown.MarkdownTextView;
+import com.yydcdut.markdown.loader.DefaultLoader;
+import com.yydcdut.markdown.syntax.text.TextFactory;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+public class SDocCommentAdapter extends BaseAdapter {
+ public static int SCREEN_WIDTH = ScreenUtils.getScreenWidth();
+ public static int WIDTH = SizeUtils.dp2px(120);
+ public static int IMAGE_WIDTH = (SCREEN_WIDTH - WIDTH) / 3;
+
+ private MarkdownProcessor processor;
+
+ private MarkdownProcessor getProcessor() {
+ if (processor != null) {
+ return processor;
+ }
+
+ MarkdownConfiguration rxMDConfiguration = new MarkdownConfiguration.Builder(getContext())
+ .setRxMDImageLoader(new DefaultLoader(getContext()))//default image loader
+ .setDefaultImageSize(100, 100)//default image width & height
+ .build();
+ processor = new MarkdownProcessor(getContext());
+ processor.config(rxMDConfiguration);
+ processor.factory(TextFactory.create());
+ return processor;
+ }
+
+ @Override
+ protected void onBindViewHolder(@NonNull SDocCommentViewHolder holder, int i, @Nullable SDocCommentModel model) {
+ if (model == null) {
+ return;
+ }
+
+ //
+ Glide.with(holder.binding.commentUserAvatar)
+ .load(model.avatar_url)
+ .into(holder.binding.commentUserAvatar);
+
+ holder.binding.commentNickName.setText(model.user_name);
+ holder.binding.commentTime.setText(model.getCreatedAtFriendlyText());
+
+// if (TextUtils.isEmpty(model.resolved) || "false".equals(model.resolved.toLowerCase(Locale.getDefault()))) {
+// holder.binding.commentContentContainer.setBackgroundResource(R.drawable.shape_stroke1_radius8_solid_grey);
+// } else {
+ holder.binding.commentContentContainer.setBackgroundResource(R.drawable.shape_stroke1_radius8_solid_white);
+// }
+
+ holder.binding.commentContentContainer.removeAllViews();
+ appendTextToFlex(holder.binding.commentContentContainer, model.comment);
+
+ }
+
+
+// //![](https://dev.seafile.com/e-Hek4ng6iRbCw7h-bXsc6jA.png)\n是是是\n
+// private void addViews(SDocCommentModel model, SDocCommentViewHolder holder) {
+// int index = 0;
+// for (RichEditText.RichContentModel commentModel : model.commentList) {
+// //0 is text, 1 is image
+// if (commentModel.type == 0) {
+// appendTextToFlex(holder.binding.commentContentContainer, commentModel.content, model.isContainImage);
+// } else {
+// appendImageToFlex(holder.binding.commentContentContainer, model.commentList, commentModel.content, index);
+// index++;
+// }
+// }
+// }
+
+
+ @NonNull
+ @Override
+ protected SDocCommentViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) {
+ ItemSdocCommentBinding binding = ItemSdocCommentBinding.inflate(LayoutInflater.from(context), viewGroup, false);
+ return new SDocCommentViewHolder(binding);
+ }
+
+
+// public void appendImageToFlex(FlexboxLayout parent, List commentList, String url, int position) {
+// LayoutImageBinding fileBinding = LayoutImageBinding.inflate(LayoutInflater.from(getContext()));
+// fileBinding.uploadImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
+//
+// FlexboxLayout.LayoutParams f = new FlexboxLayout.LayoutParams(IMAGE_WIDTH, IMAGE_WIDTH);
+// f.setMargins(DP_4, DP_4, DP_4, DP_4);
+// fileBinding.getRoot().setLayoutParams(f);
+//
+// Glide.with(getContext())
+// .load(GlideLoadConfig.getGlideUrl(url))
+// .apply(GlideLoadConfig.getOptions(IMAGE_WIDTH, IMAGE_WIDTH))
+// .into(fileBinding.uploadImage);
+// fileBinding.uploadImage.setOnClickListener(new View.OnClickListener() {
+// @Override
+// public void onClick(View v) {
+// List ll = commentList.stream().filter(f -> f.type == 1).map(m -> m.content).collect(Collectors.toList());
+// MediaPlayerModel model = new MediaPlayerModel();
+// model.index = position;
+// model.urls = ll;
+// MediaPlayerActivity.startThis(getContext(), model);
+// }
+// });
+//
+//
+// parent.addView(fileBinding.getRoot());
+// }
+
+ public void appendTextToFlex(FlexboxLayout parent, String comment) {
+ MarkdownTextView textView = new MarkdownTextView(getContext());
+ textView.setPadding(DP_4, DP_8, DP_4, DP_8);
+// textView.setText(getProcessor().parse(comment));
+ textView.setText(comment);
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
+ textView.setId(android.R.id.text1);
+ textView.setTextSize(14);
+ textView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_blue_grey_900));
+
+ FlexboxLayout.LayoutParams f = new FlexboxLayout.LayoutParams(-1, -2);
+ parent.addView(textView, f);
+ }
+
+ public void submitData(List list) {
+ if (CollectionUtils.isEmpty(list)) {
+ submitList(list);
+ return;
+ }
+
+ if (getItems().isEmpty()) {
+ submitList(list);
+ } else {
+ DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
+ @Override
+ public int getOldListSize() {
+ return getItems().size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return list.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ SDocCommentModel newT = getItems().get(oldItemPosition);
+ SDocCommentModel oldT = list.get(newItemPosition);
+ return newT.id == oldT.id;
+ }
+
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ SDocCommentModel newT = getItems().get(oldItemPosition);
+ SDocCommentModel oldT = list.get(newItemPosition);
+
+ return newT.id == oldT.id
+ && Objects.equals(newT.resolved, oldT.resolved)
+ && Objects.equals(newT.created_at, oldT.created_at)
+ && Objects.equals(newT.updated_at, oldT.updated_at)
+ && TextUtils.equals(newT.avatar_url, oldT.avatar_url)
+ && TextUtils.equals(newT.user_contact_email, oldT.user_contact_email)
+ && TextUtils.equals(newT.user_email, oldT.user_email)
+ && TextUtils.equals(newT.user_name, oldT.user_name)
+ && TextUtils.equals(newT.comment, oldT.comment)
+ && TextUtils.equals(newT.parent_path, oldT.parent_path)
+ && TextUtils.equals(newT.item_name, oldT.item_name);
+ }
+ });
+
+ setItems(list);
+ result.dispatchUpdatesTo(this);
+ }
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java
new file mode 100644
index 000000000..f36926541
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java
@@ -0,0 +1,54 @@
+package com.seafile.seadroid2.ui.sdoc.comments;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.bumptech.glide.Glide;
+import com.seafile.seadroid2.R;
+import com.seafile.seadroid2.databinding.ItemUserAvatarBinding;
+import com.seafile.seadroid2.framework.data.model.user.UserModel;
+import com.seafile.seadroid2.ui.base.adapter.BaseAdapter;
+
+public class SDocCommentUserAdapter extends BaseAdapter {
+ @Override
+ protected void onBindViewHolder(@NonNull SDocCommentUserViewHolder holder, int i, @Nullable UserModel model) {
+
+ if (i == 0) {
+ setMargins(holder.binding.itemUserContainer, 0, 0, 0, 0);
+ }
+
+ if (model == null || TextUtils.isEmpty(model.getAvatarUrl())) {
+ //
+ Glide.with(holder.binding.imageView)
+ .load(R.drawable.default_avatar)
+ .into(holder.binding.imageView);
+ } else {
+ //
+ Glide.with(holder.binding.imageView)
+ .load(model.getAvatarUrl())
+ .into(holder.binding.imageView);
+ }
+
+ }
+
+ @NonNull
+ @Override
+ protected SDocCommentUserViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) {
+ ItemUserAvatarBinding binding = ItemUserAvatarBinding.inflate(LayoutInflater.from(context), viewGroup, false);
+ return new SDocCommentUserViewHolder(binding);
+ }
+
+ public void setMargins(View v, int l, int t, int r, int b) {
+ if (v.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
+ ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
+ p.setMargins(l, t, r, b);
+ v.requestLayout();
+ }
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java
new file mode 100644
index 000000000..2cd81d913
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java
@@ -0,0 +1,17 @@
+package com.seafile.seadroid2.ui.sdoc.comments;
+
+import androidx.annotation.NonNull;
+
+import com.seafile.seadroid2.databinding.ItemUserAvatarBinding;
+import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder;
+
+public class SDocCommentUserViewHolder extends BaseViewHolder {
+
+ public ItemUserAvatarBinding binding;
+
+ public SDocCommentUserViewHolder(@NonNull ItemUserAvatarBinding binding) {
+ super(binding.getRoot());
+
+ this.binding = binding;
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentViewHolder.java
new file mode 100644
index 000000000..e23915268
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentViewHolder.java
@@ -0,0 +1,17 @@
+package com.seafile.seadroid2.ui.sdoc.comments;
+
+import androidx.annotation.NonNull;
+
+import com.seafile.seadroid2.databinding.ItemSdocCommentBinding;
+import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder;
+
+public class SDocCommentViewHolder extends BaseViewHolder {
+
+ public ItemSdocCommentBinding binding;
+
+ public SDocCommentViewHolder(@NonNull ItemSdocCommentBinding binding) {
+ super(binding.getRoot());
+
+ this.binding = binding;
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentsActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentsActivity.java
new file mode 100644
index 000000000..20e40e77d
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentsActivity.java
@@ -0,0 +1,184 @@
+package com.seafile.seadroid2.ui.sdoc.comments;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.widget.Toolbar;
+import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.chad.library.adapter4.BaseQuickAdapter;
+import com.chad.library.adapter4.QuickAdapterHelper;
+import com.seafile.seadroid2.R;
+import com.seafile.seadroid2.databinding.ActivitySdocCommentBinding;
+import com.seafile.seadroid2.databinding.ToolbarActionbarBinding;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentWrapperModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel;
+import com.seafile.seadroid2.ui.base.BaseActivityWithVM;
+import com.seafile.seadroid2.ui.sdoc.SDocViewModel;
+
+public class SDocCommentsActivity extends BaseActivityWithVM {
+ private ActivitySdocCommentBinding binding;
+ private ToolbarActionbarBinding bindingOfToolbar;
+
+ private Toolbar toolbar;
+
+ private SDocCommentAdapter adapter;
+ private SDocCommentUserAdapter userAdapter;
+
+ private SDocPageOptionsModel pageOptionsModel;
+
+ public static void start(Context context, SDocPageOptionsModel pageModel) {
+ Intent starter = new Intent(context, SDocCommentsActivity.class);
+ starter.putExtra("pageOption", pageModel);
+ context.startActivity(starter);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ binding = ActivitySdocCommentBinding.inflate(getLayoutInflater());
+ bindingOfToolbar = ToolbarActionbarBinding.bind(binding.toolbar.getRoot());
+
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
+
+ setContentView(binding.getRoot());
+
+ if (getIntent() == null || !getIntent().hasExtra("pageOption")) {
+ throw new IllegalArgumentException("pageOption is null");
+ }
+
+ pageOptionsModel = getIntent().getParcelableExtra("pageOption");
+
+ initView();
+
+ initViewModel();
+
+ initAdapter();
+
+ refreshData();
+ }
+
+ private void initView() {
+ toolbar = bindingOfToolbar.toolbarActionbar;
+
+ toolbar.setTitle("");
+ setSupportActionBar(toolbar);
+ toolbar.setTitle(pageOptionsModel.docName);
+
+ toolbar.setNavigationOnClickListener(v -> {
+ finish();
+ });
+
+ //refresh listener
+ binding.swipeRefreshLayout.setOnRefreshListener(this::refreshData);
+
+ binding.rv.setLayoutManager(new LinearLayoutManager(this));
+// //
+// LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
+// binding.rvUserList.setLayoutManager(linearLayoutManager);
+// binding.photoView.setOnClickListener(new View.OnClickListener() {
+// @Override
+// public void onClick(View v) {
+// showPickPhotoSheetDialog(true);
+// }
+// });
+//
+// binding.submit.setOnClickListener(new View.OnClickListener() {
+// @Override
+// public void onClick(View v) {
+// submitData();
+// }
+// });
+//
+// binding.richEditText.setOnRichAtListener(new OnRichAtListener() {
+// @Override
+// public void onCall(EditText editText) {
+// showCollaboratorSelector(editText);
+// }
+// });
+ }
+
+ protected void initViewModel() {
+ getViewModel().getRefreshLiveData().observe(this, new Observer() {
+ @Override
+ public void onChanged(Boolean aBoolean) {
+ binding.swipeRefreshLayout.setRefreshing(aBoolean);
+ }
+ });
+
+// getViewModel().getPostCommentLiveData().observe(this, new Observer() {
+// @Override
+// public void onChanged(TableRowCommentModel model) {
+// //remove all
+// binding.richEditText.removeAllViews();
+//
+// refreshData(true);
+// }
+// });
+
+// getViewModel().getUserListLiveData().observe(this, new Observer>() {
+// @Override
+// public void onChanged(List relatedUserModels) {
+// userAdapter.submitList(relatedUserModels);
+// }
+// });
+ getViewModel().getSdocCommentLiveData().observe(this, new Observer() {
+ @Override
+ public void onChanged(SDocCommentWrapperModel sDocCommentWrapperModel) {
+ adapter.setStateViewEnable(true);
+
+ adapter.submitData(sDocCommentWrapperModel.comments);
+ }
+ });
+ }
+
+
+ private void initAdapter() {
+// userAdapter = new SDocCommentUserAdapter();
+// userAdapter.setAnimationEnable(true);
+//
+// userAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
+// @Override
+// public void onClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) {
+// if (CollectionUtils.isEmpty(userAdapter.getItems())) {
+// return;
+// }
+//
+//// showCollaboratorSelector();
+// }
+// });
+// binding.rvUserList.setAdapter(userAdapter);
+//
+// if (CollectionUtils.isEmpty(strategyModel.participants)) {
+// binding.rvUserList.setVisibility(View.GONE);
+// } else {
+// userAdapter.submitList(strategyModel.participants);
+// }
+
+ adapter = new SDocCommentAdapter();
+ adapter.setStateViewLayout(this, R.layout.layout_empty);
+ adapter.setStateViewEnable(false);
+ adapter.setAnimationEnable(true);
+
+ adapter.addOnItemChildClickListener(R.id.comment_more, new BaseQuickAdapter.OnItemChildClickListener() {
+ @Override
+ public void onItemClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) {
+
+ }
+ });
+
+ QuickAdapterHelper helper = new QuickAdapterHelper.Builder(adapter).build();
+ binding.rv.setAdapter(helper.getAdapter());
+ }
+
+ private void refreshData() {
+ getViewModel().getSDocComments(pageOptionsModel);
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryAdapter.java
new file mode 100644
index 000000000..98f3505ef
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryAdapter.java
@@ -0,0 +1,59 @@
+package com.seafile.seadroid2.ui.sdoc.directory;
+
+import static com.seafile.seadroid2.config.Constants.DP.DP_8;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.seafile.seadroid2.databinding.ItemSdocDirectoryBinding;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocModel;
+import com.seafile.seadroid2.ui.base.adapter.BaseAdapter;
+import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder;
+
+public class SDocDirectoryAdapter extends BaseAdapter {
+
+ private final int _paddingStart = DP_8;
+
+ @NonNull
+ @Override
+ protected SDocDirectoryHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) {
+ ItemSdocDirectoryBinding binding = ItemSdocDirectoryBinding.inflate(LayoutInflater.from(context));
+ return new SDocDirectoryHolder(binding);
+ }
+
+ @Override
+ protected void onBindViewHolder(@NonNull SDocDirectoryHolder holder, int i, @Nullable SDocModel sDocModel) {
+ if (sDocModel == null) {
+ return;
+ }
+
+ int padding;
+ if ("header1".equals(sDocModel.type)) {
+ padding = _paddingStart;
+ } else if ("header2".equals(sDocModel.type)) {
+ padding = _paddingStart * 3;
+ } else if ("header3".equals(sDocModel.type)) {
+ padding = _paddingStart * 6;
+ } else {
+ return;
+ }
+
+
+ holder.binding.title.setPadding(padding, 0, 0, 0);
+ holder.binding.title.setText(sDocModel.text);
+ }
+
+
+ public static class SDocDirectoryHolder extends BaseViewHolder {
+ ItemSdocDirectoryBinding binding;
+
+ public SDocDirectoryHolder(@NonNull ItemSdocDirectoryBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryDialog.java
new file mode 100644
index 000000000..16103d29d
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryDialog.java
@@ -0,0 +1,137 @@
+package com.seafile.seadroid2.ui.sdoc.directory;
+
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.blankj.utilcode.util.ToastUtils;
+import com.chad.library.adapter4.BaseQuickAdapter;
+import com.chad.library.adapter4.QuickAdapterHelper;
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
+import com.google.android.material.internal.ViewUtils;
+import com.seafile.seadroid2.R;
+import com.seafile.seadroid2.databinding.DialogSdocDirectoryBinding;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocWrapperModel;
+import com.seafile.seadroid2.ui.sdoc.SDocViewModel;
+
+import java.util.List;
+
+public class SDocDirectoryDialog extends BottomSheetDialogFragment {
+ private SDocViewModel viewModel;
+
+ private SDocPageOptionsModel pageOptionsModel;
+
+ private DialogSdocDirectoryBinding binding;
+ private SDocDirectoryAdapter adapter;
+
+ public static SDocDirectoryDialog newInstance(SDocPageOptionsModel pageOptionsModel) {
+
+ Bundle args = new Bundle();
+ args.putParcelable("pageOption", pageOptionsModel);
+ SDocDirectoryDialog fragment = new SDocDirectoryDialog();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getArguments() == null || !getArguments().containsKey("pageOption")) {
+ throw new IllegalArgumentException("pageOption is null");
+ }
+
+ pageOptionsModel = getArguments().getParcelable("pageOption");
+ viewModel = new ViewModelProvider(this).get(SDocViewModel.class);
+ }
+
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ binding = DialogSdocDirectoryBinding.inflate(inflater, container, false);
+ return binding.getRoot();
+ }
+
+ @SuppressLint("RestrictedApi")
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(requireContext());
+// bottomSheetDialog.setContentView(binding.getRoot());
+//
+// View bottomSheetInternal = bottomSheetDialog.findViewById(R.id.design_bottom_sheet);
+//// BottomSheetBehavior.from(bottomSheetInternal).setPeekHeight(800);
+//
+// View bottomSheetContent = bottomSheetInternal.findViewById(R.id.bottom_drawer_2);
+// ViewUtils.doOnApplyWindowInsets(bottomSheetContent, new ViewUtils.OnApplyWindowInsetsListener() {
+// @Override
+// public WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets, ViewUtils.RelativePadding initialPadding) {
+// // Add the inset in the inner NestedScrollView instead to make the edge-to-edge behavior
+// // consistent - i.e., the extra padding will only show at the bottom of all content, i.e.,
+// // only when you can no longer scroll down to show more content.
+// ViewCompat.setPaddingRelative(bottomSheetContent,
+// initialPadding.start,
+// initialPadding.top,
+// initialPadding.end,
+// initialPadding.bottom + insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom);
+// return insets;
+// }
+// });
+
+ return bottomSheetDialog;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ binding.rv.setLayoutManager(new LinearLayoutManager(requireContext()));
+
+ adapter = new SDocDirectoryAdapter();
+ adapter.setAnimationEnable(true);
+ adapter.setStateViewLayout(requireContext(),R.layout.layout_empty);
+ adapter.setStateViewEnable(false);
+ adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
+ @Override
+ public void onClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) {
+ SDocModel sDocModel = adapter.getItems().get(i);
+ ToastUtils.showLong(sDocModel.text);
+ dismiss();
+ }
+ });
+
+ QuickAdapterHelper helper = new QuickAdapterHelper.Builder(adapter).build();
+ binding.rv.setAdapter(helper.getAdapter());
+
+ initViewModel();
+
+ load();
+ }
+
+ private void initViewModel() {
+ viewModel.getSdocElementLiveData().observe(getViewLifecycleOwner(), new Observer>() {
+ @Override
+ public void onChanged(List sDocModels) {
+ adapter.setStateViewEnable(true);
+ adapter.submitList(sDocModels);
+ }
+ });
+ }
+
+ private void load() {
+ viewModel.getSDocElements(pageOptionsModel);
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java
new file mode 100644
index 000000000..d795dc554
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java
@@ -0,0 +1,715 @@
+package com.seafile.seadroid2.ui.sdoc.profile;
+
+
+import static com.seafile.seadroid2.config.Constants.DP.DP_4;
+
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+import com.blankj.utilcode.util.CollectionUtils;
+import com.blankj.utilcode.util.SizeUtils;
+import com.bumptech.glide.Glide;
+import com.google.android.flexbox.FlexboxLayout;
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
+import com.google.android.material.card.MaterialCardView;
+import com.google.android.material.imageview.ShapeableImageView;
+import com.google.android.material.internal.ViewUtils;
+import com.seafile.seadroid2.R;
+import com.seafile.seadroid2.config.ColumnType;
+import com.seafile.seadroid2.config.GlideLoadConfig;
+import com.seafile.seadroid2.databinding.DialogSdocProfileBinding;
+import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigDataModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.MetadataModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.OptionsTagModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.RecordResultModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocDetailModel;
+import com.seafile.seadroid2.framework.data.model.sdoc.SDocRecordWrapperModel;
+import com.seafile.seadroid2.framework.data.model.user.UserModel;
+import com.seafile.seadroid2.framework.util.SLogs;
+import com.seafile.seadroid2.framework.util.Utils;
+
+import java.lang.reflect.Field;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+
+public class SDocProfileDialog extends BottomSheetDialogFragment {
+
+ private SDocDetailModel docDetailModel;
+ private SDocRecordWrapperModel recordWrapperModel;
+ private ArrayList relatedUsers;
+
+ public static SDocProfileDialog newInstance(SDocDetailModel docDetailModel, SDocRecordWrapperModel recordWrapperModel, List relatedUsers) {
+ Bundle args = new Bundle();
+ args.putParcelable("detailModel", docDetailModel);
+
+ if (!CollectionUtils.isEmpty(relatedUsers)) {
+ args.putParcelable("recordModel", recordWrapperModel);
+ }
+
+ if (!CollectionUtils.isEmpty(relatedUsers)) {
+ args.putParcelableArrayList("relatedUsers", new ArrayList<>(relatedUsers));
+
+ }
+ SDocProfileDialog fragment = new SDocProfileDialog();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ public static SDocProfileDialog newInstance(SDocDetailModel docDetailModel, List relatedUsers) {
+ return newInstance(docDetailModel, null, relatedUsers);
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() == null || !getArguments().containsKey("detailModel")) {
+ throw new IllegalArgumentException("detailModel is null");
+ }
+
+ docDetailModel = getArguments().getParcelable("detailModel");
+ recordWrapperModel = getArguments().getParcelable("recordModel");
+ relatedUsers = getArguments().getParcelableArrayList("relatedUsers");
+
+ if (docDetailModel == null) {
+ throw new IllegalArgumentException("detail is null");
+ }
+
+ initFixedValueIfMetadataNotEnable();
+ }
+
+ DialogSdocProfileBinding profileBinding;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ profileBinding = DialogSdocProfileBinding.inflate(inflater, container, false);
+ return profileBinding.getRoot();
+ }
+
+ @SuppressLint("RestrictedApi")
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(requireContext());
+// View bottomSheetInternal = bottomSheetDialog.findViewById(R.id.design_bottom_sheet);
+// BottomSheetBehavior.from(bottomSheetInternal).setPeekHeight(800);
+
+// View bottomSheetContent = bottomSheetInternal.findViewById(R.id.bottom_drawer_2);
+// ViewUtils.doOnApplyWindowInsets(bottomSheetContent, new ViewUtils.OnApplyWindowInsetsListener() {
+// @Override
+// public WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets, ViewUtils.RelativePadding initialPadding) {
+// // Add the inset in the inner NestedScrollView instead to make the edge-to-edge behavior
+// // consistent - i.e., the extra padding will only show at the bottom of all content, i.e.,
+// // only when you can no longer scroll down to show more content.
+// ViewCompat.setPaddingRelative(bottomSheetContent,
+// initialPadding.start,
+// initialPadding.top,
+// initialPadding.end,
+// initialPadding.bottom + insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom);
+// return insets;
+// }
+// });
+
+ return bottomSheetDialog;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ setData(profileBinding.detailsContainer);
+ }
+
+ private void initFixedValueIfMetadataNotEnable() {
+ if (recordWrapperModel != null) {
+ return;
+ }
+ recordWrapperModel = new SDocRecordWrapperModel();
+
+ RecordResultModel sizeModel = new RecordResultModel();
+ sizeModel._size = docDetailModel.getSize();
+ sizeModel._file_modifier = docDetailModel.getLastModifierEmail();
+ sizeModel._file_mtime = docDetailModel.getLastModified();
+ recordWrapperModel.results = new ArrayList<>();
+ recordWrapperModel.results.add(sizeModel);
+
+ recordWrapperModel.metadata = new ArrayList<>();
+
+ MetadataModel sizeMetadataModel = new MetadataModel();
+ sizeMetadataModel.key = "_size";
+ sizeMetadataModel.name = "_size";
+ sizeMetadataModel.type = ColumnType.NUMBER;
+ recordWrapperModel.metadata.add(sizeMetadataModel);
+
+ MetadataModel modifierMetadataModel = new MetadataModel();
+ modifierMetadataModel.key = "_file_modifier";
+ modifierMetadataModel.name = "_file_modifier";
+ modifierMetadataModel.type = ColumnType.TEXT;
+ recordWrapperModel.metadata.add(modifierMetadataModel);
+
+ MetadataModel mTimeMetadataModel = new MetadataModel();
+ mTimeMetadataModel.key = "_file_mtime";
+ mTimeMetadataModel.name = "_file_mtime";
+ mTimeMetadataModel.type = ColumnType.DATE;
+ recordWrapperModel.metadata.add(mTimeMetadataModel);
+
+ }
+
+ private void setData(LinearLayout parent) {
+ for (MetadataModel metadata : recordWrapperModel.metadata) {
+ if ("_file_modifier".equals(metadata.key)) {
+ metadata.type = "collaborator";
+ metadata.value = CollectionUtils.newArrayList(getValueByKey(metadata.name));
+ } else {
+ metadata.value = getValueByKey(metadata.name);
+ }
+ }
+
+ for (MetadataModel metadata : recordWrapperModel.metadata) {
+ if (metadata.key.startsWith("_")) {
+ if (_fixedField.contains(metadata.key)) {
+ addMetadataView(parent, metadata);
+ }
+ } else {
+ addMetadataView(parent, metadata);
+ }
+ }
+ }
+
+ private void addMetadataView(LinearLayout parent, MetadataModel metadata) {
+ parseViewByType(getContext(), parent, metadata);
+ }
+
+ private final List _fixedField = List.of("_size", "_file_modifier", "_file_mtime", "_description", "_collaborators", "_reviewer", "_status");
+
+ private Object getValueByKey(String key) {
+ RecordResultModel model = recordWrapperModel.results.get(0);
+ try {
+ Field field = RecordResultModel.class.getDeclaredField(key);
+ field.setAccessible(true);
+ return field.get(model);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ SLogs.e(e);
+ return null;
+ }
+ }
+
+ private FlexboxLayout.LayoutParams getFlexParams() {
+ FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ flexLayoutParams.bottomMargin = DP_4;
+ flexLayoutParams.rightMargin = DP_4;
+ return flexLayoutParams;
+ }
+
+ private int getResNameByKey(String key) {
+ switch (key) {
+ case "_description":
+ return R.string.description;
+ case "_file_modifier":
+ return R.string._last_modifier;
+ case "_file_mtime":
+ return R.string._last_modified_time;
+ case "_status":
+ return R.string._file_status;
+ case "_collaborators":
+ return R.string._file_collaborators;
+ case "_size":
+ return R.string._size;
+ case "_reviewer":
+ return R.string._reviewer;
+ case "_in_progress":
+ return R.string._in_progress;
+ case "_in_review":
+ return R.string._in_review;
+ case "_done":
+ return R.string._done;
+ case "_outdated":
+ return R.string._outdated;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ return Resources.ID_NULL;
+ }
+ return 0;
+ }
+
+ public void parseViewByType(Context context, LinearLayout parent, MetadataModel metadata) {
+ final String type = metadata.type;
+ LinearLayout view = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.layout_details_keyview_valuecontainer, null);
+
+ int resStrId = getResNameByKey(metadata.key);
+ if (resStrId != 0) {
+ view.findViewById(R.id.text_title).setText(resStrId);
+ } else {
+ view.findViewById(R.id.text_title).setText(metadata.name);
+ }
+
+ view.findViewById(R.id.text_icon).setImageResource(getIconByColumnType(metadata.type));
+
+ LinearLayout.LayoutParams ll = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ ll.topMargin = SizeUtils.dp2px(8);
+ view.setLayoutParams(ll);
+ parent.addView(view);
+
+ if (!TextUtils.equals(ColumnType.RATE, type) && metadata.value == null) {
+
+ View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null);
+ ltr.findViewById(R.id.text_view).setText(R.string.empty);
+
+ view.findViewById(R.id.flex_box).addView(ltr, getFlexParams());
+ return;
+ }
+
+ if (TextUtils.equals(ColumnType.TEXT, type)) {
+ parseText(view, metadata);
+ } else if (TextUtils.equals(ColumnType.LONG_TEXT, type)) {
+ parseLongText(view, metadata);
+ } else if (TextUtils.equals(ColumnType.NUMBER, type)) {
+ parseNumber(view, metadata);
+ } else if (TextUtils.equals(ColumnType.DATE, type)) {
+ parseDate(view, metadata);
+ } else if (TextUtils.equals(ColumnType.DURATION, type)) {
+ parseDuration(view, metadata);
+ } else if (TextUtils.equals(ColumnType.COLLABORATOR, type)) {
+ parseCollaborator(view, metadata);
+ } else if (TextUtils.equals(ColumnType.SINGLE_SELECT, type)) {
+ parseSingleSelect(view, metadata);
+ } else if (TextUtils.equals(ColumnType.MULTIPLE_SELECT, type)) {
+ parseMultiSelect(view, metadata);
+ } else if (TextUtils.equals(ColumnType.EMAIL, type)) {
+ parseText(view, metadata);
+ } else if (TextUtils.equals(ColumnType.URL, type)) {
+ parseText(view, metadata);
+ } else if (TextUtils.equals(ColumnType.RATE, type)) {
+ parseRate(view, metadata);
+ } else if (TextUtils.equals(ColumnType.GEOLOCATION, type)) {
+// parseGeoLocation(view, model);
+ }
+// else if (TextUtils.equals(ColumnType.IMAGE, type)) {
+// parseImage(view, model);
+// } else if (TextUtils.equals(ColumnType.FILE, type)) {
+// parseFile(view, model);
+// } else if (TextUtils.equals(ColumnType.CHECKBOX, type)) {
+// parseCheckbox(view, model);
+// }else if (TextUtils.equals(ColumnType.LINK, type)) {
+// parseLink(view, model);
+// } else if (TextUtils.equals(ColumnType.DIGITAL_SIGN, type)) {
+// parseDigitalSign(view, workFlowModel, model);
+// }
+ }
+
+ private int getIconByColumnType(String type) {
+ switch (type) {
+ case ColumnType.TEXT:
+ return R.drawable.ic_single_line_text;
+ case ColumnType.COLLABORATOR:
+ return R.drawable.ic_user_collaborator;
+ case ColumnType.IMAGE:
+ return R.drawable.ic_picture;
+ case ColumnType.FILE:
+ return R.drawable.ic_file_alt_solid;
+ case ColumnType.DATE:
+ return R.drawable.ic_calendar_alt_solid;
+ case ColumnType.SINGLE_SELECT:
+ return R.drawable.ic_single_election;
+ case ColumnType.DURATION:
+ return R.drawable.ic_duration;
+ case ColumnType.MULTIPLE_SELECT:
+ return R.drawable.ic_multiple_selection;
+ case ColumnType.CHECKBOX:
+ return R.drawable.ic_check_square_solid;
+ case ColumnType.GEOLOCATION:
+ return R.drawable.ic_location;
+ case ColumnType.EMAIL:
+ return R.drawable.ic_email;
+ case ColumnType.LONG_TEXT:
+ return R.drawable.ic_long_text;
+ case ColumnType.NUMBER:
+ return R.drawable.ic_number;
+ case ColumnType.RATE:
+ return R.drawable.ic_star2;
+ case ColumnType.URL:
+ return R.drawable.ic_url;
+ case ColumnType.LINK:
+ return R.drawable.ic_links;
+ }
+
+ return R.drawable.ic_single_line_text;
+ }
+
+ private void parseText(LinearLayout view, MetadataModel model) {
+ if (model.value instanceof String) {
+ View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null);
+ ltr.findViewById(R.id.text_view).setText(model.value.toString());
+
+ view.findViewById(R.id.flex_box).addView(ltr, getFlexParams());
+ }
+ }
+
+ private void parseLongText(LinearLayout view, MetadataModel model) {
+ if (model.value instanceof String) {
+ View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null);
+ ltr.findViewById(R.id.text_view).setText(model.value.toString());
+
+ view.findViewById(R.id.flex_box).addView(ltr, getFlexParams());
+ }
+ }
+
+
+ private void parseDuration(LinearLayout view, MetadataModel model) {
+ if (model.value instanceof String) {
+ View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null);
+ ltr.findViewById(R.id.text_view).setText(model.value.toString());
+
+ view.findViewById(R.id.flex_box).addView(ltr, getFlexParams());
+ }
+ }
+
+ private void parseNumber(LinearLayout view, MetadataModel model) {
+ if (model.value instanceof Number number) {
+ View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null);
+
+ ltr.findViewById(R.id.text_view).setText(Utils.readableFileSize(number.intValue()));
+
+ view.findViewById(R.id.flex_box).addView(ltr, getFlexParams());
+ }
+ }
+
+ private void parseDate(LinearLayout view, MetadataModel model) {
+ if (model.value instanceof OffsetDateTime date) {
+ View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null);
+
+ String temp = date.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).replace("T", " ");
+ ltr.findViewById(R.id.text_view).setText(temp);
+
+ view.findViewById(R.id.flex_box).addView(ltr, getFlexParams());
+ }
+ }
+
+// private void parseGeoLocation(LinearLayout view, MetadataModel model) {
+// if (model.value instanceof LinkedTreeMap) {
+// LinkedTreeMap treeMap = (LinkedTreeMap) model.value;
+// ColumnDataModel columnDataModel = model.column_data;
+//
+// String geo_format = columnDataModel.geo_format;
+//
+// String content = null;
+// if (TextUtils.equals("lng_lat", geo_format)) {
+// String lat = treeMap.get("lat").toString();
+// String lng = treeMap.get("lng").toString();
+// content = lat + "," + lng;
+// } else if (TextUtils.equals("geolocation", geo_format)) {
+// String province = treeMap.get("province").toString();
+// String city = treeMap.get("city").toString();
+// String dis = treeMap.get("district").toString();
+// String detail = treeMap.get("detail").toString();
+// content = province + city + dis + detail;
+// } else if (TextUtils.equals("country_region", geo_format)) {
+// content = treeMap.get("country_region").toString();
+// } else if (TextUtils.equals("province", geo_format)) {
+// content = treeMap.get("province").toString();
+// } else if (TextUtils.equals("province_city", geo_format)) {
+// String province = treeMap.get("province").toString();
+// String city = treeMap.get("city").toString();
+// content = province + city;
+// } else if (TextUtils.equals("province_city_district", geo_format)) {
+// String province = treeMap.get("province").toString();
+// String city = treeMap.get("city").toString();
+// String dis = treeMap.get("district").toString();
+// content = province + city + dis;
+// }
+//
+// view.findViewById(R.id.text_view).setText(content);
+// }
+// }
+
+ //container
+ private void parseCollaborator(LinearLayout view, MetadataModel model) {
+ if (model.value instanceof ArrayList) {
+ ArrayList arrayList = (ArrayList) model.value;
+ for (String userEmail : arrayList) {
+ UserModel userModel = getRelatedUserByEmail(userEmail);
+ if (userModel == null) {
+ continue;
+ }
+
+ View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_avatar_username_round, null);
+ TextView user_name_text_view = ltr.findViewById(R.id.user_name);
+ ShapeableImageView imageView = ltr.findViewById(R.id.user_avatar);
+ Glide.with(imageView)
+ .load(userModel.getAvatarUrl())
+ .apply(GlideLoadConfig.getOptions())
+ .into(imageView);
+
+ user_name_text_view.setText(userModel.getName());
+ view.findViewById(R.id.flex_box).addView(ltr, getFlexParams());
+ }
+ }
+ }
+
+ private UserModel getRelatedUserByEmail(String email) {
+ if (relatedUsers == null) {
+ return null;
+ }
+
+ Optional op = relatedUsers.stream().filter(f -> f.getEmail().equals(email)).findFirst();
+ return op.orElse(null);
+ }
+
+ private void parseSingleSelect(LinearLayout view, MetadataModel model) {
+ if (model.configData == null || CollectionUtils.isEmpty(model.configData)) {
+ return;
+ }
+
+ MetadataConfigDataModel configDataModel = model.configData.get(0);
+ if (configDataModel.options == null || CollectionUtils.isEmpty(configDataModel.options)) {
+ return;
+ }
+
+ if (model.value instanceof String && !TextUtils.isEmpty(model.value.toString())) {
+ String value = (String) model.value;
+
+ OptionsTagModel option = configDataModel.options.stream().filter(f -> f.id.equals(value)).findFirst().get();
+ View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_detail_text_round, null);
+ TextView textView = ltr.findViewById(R.id.text);
+ MaterialCardView cardView = ltr.findViewById(R.id.card_view);
+
+ int resStrId = getResNameByKey(option.id);
+ if (resStrId != 0) {
+ textView.setText(resStrId);
+ } else {
+ textView.setText(option.name);
+ }
+
+
+// if (!TextUtils.isEmpty(option.borderColor)) {
+// }
+
+ if (!TextUtils.isEmpty(option.textColor)) {
+ textView.setTextColor(Color.parseColor(option.textColor));
+ }
+
+ if (!TextUtils.isEmpty(option.color)) {
+ cardView.setCardBackgroundColor(Color.parseColor(option.color));
+ }
+
+ view.findViewById(R.id.flex_box).addView(ltr, getFlexParams());
+ }
+ }
+
+ private void parseMultiSelect(LinearLayout view, MetadataModel model) {
+ if (model.configData == null || CollectionUtils.isEmpty(model.configData)) {
+ return;
+ }
+
+ MetadataConfigDataModel configDataModel = model.configData.get(0);
+ if (configDataModel.options == null || CollectionUtils.isEmpty(configDataModel.options)) {
+ return;
+ }
+
+ if (model.value instanceof ArrayList) {
+ ArrayList arrayList = (ArrayList) model.value;
+ for (String key : arrayList) {
+ OptionsTagModel option = configDataModel.options.stream().filter(f -> f.id.equals(key)).findFirst().get();
+ View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_detail_text_round, null);
+ TextView textView = ltr.findViewById(R.id.text);
+ MaterialCardView cardView = ltr.findViewById(R.id.card_view);
+
+ int resStrId = getResNameByKey(option.id);
+ if (resStrId != 0) {
+ textView.setText(resStrId);
+ } else {
+ textView.setText(option.name);
+ }
+
+
+// if (!TextUtils.isEmpty(option.borderColor)) {
+// }
+
+ if (!TextUtils.isEmpty(option.textColor)) {
+ textView.setTextColor(Color.parseColor(option.textColor));
+ }
+
+ if (!TextUtils.isEmpty(option.color)) {
+ cardView.setCardBackgroundColor(Color.parseColor(option.color));
+ }
+
+ view.findViewById(R.id.flex_box).addView(ltr, getFlexParams());
+ }
+ }
+ }
+
+
+// private void parseImage(LinearLayout view, MetadataModel model) {
+// if (model.value instanceof ArrayList) {
+// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box);
+//
+// int wh = SizeUtils.dp2px(24);
+// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(wh, wh);
+// flexLayoutParams.bottomMargin = DP_4;
+// flexLayoutParams.rightMargin = DP_4;
+//
+// ArrayList imgList = (ArrayList) model.value;
+// for (int i = 0; i < imgList.size(); i++) {
+// String url = imgList.get(i);
+// ImageView imageView = new ImageView(flexboxLayout.getContext());
+// Glide.with(imageView)
+// .load(GlideLoadConfig.getGlideUrl(url))
+// .apply(GlideLoadConfig.getOptions())
+// .into(imageView);
+// flexboxLayout.addView(imageView, flexLayoutParams);
+// }
+// }
+// }
+
+// private void parseFile(LinearLayout view, MetadataModel model) {
+// if (model.value instanceof ArrayList) {
+// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box);
+//
+// int wh = SizeUtils.dp2px(24);
+// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(wh, wh);
+// flexLayoutParams.bottomMargin = DP_4;
+// flexLayoutParams.rightMargin = DP_4;
+//
+// ArrayList> fileList = (ArrayList>) model.value;
+// for (LinkedTreeMap treeMap : fileList) {
+// ImageView imageView = new ImageView(flexboxLayout.getContext());
+// String url = treeMap.get("url").toString();
+//
+// int rid = MimeTypes.getFileIconFromExtension(url);
+// imageView.setImageResource(rid);
+// flexboxLayout.addView(imageView, flexLayoutParams);
+// }
+// }
+// }
+
+ private void parseRate(LinearLayout view, MetadataModel model) {
+
+ if (model.configData == null || CollectionUtils.isEmpty(model.configData)) {
+ return;
+ }
+
+ MetadataConfigDataModel configDataModel = model.configData.get(0);
+
+ int wh = SizeUtils.dp2px(16);
+ FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(wh, wh);
+ flexLayoutParams.rightMargin = DP_4;
+
+ int selectCount = 0;
+ if (model.value != null) {
+ selectCount = ((Number) model.value).intValue();
+ }
+
+ for (int i = 0; i < configDataModel.rate_max_number; i++) {
+ ImageView ltr = new ImageView(view.getContext());
+
+ int t;
+ if (i < selectCount) {
+ if (TextUtils.isEmpty(configDataModel.rate_style_color)) {
+ t = ContextCompat.getColor(requireContext(), R.color.grey);
+ } else {
+ t = Color.parseColor(configDataModel.rate_style_color);
+ }
+ } else {
+ t = ContextCompat.getColor(requireContext(), R.color.light_gray);
+ }
+
+ ColorStateList stateList = ColorStateList.valueOf(t);
+
+ ltr.setImageTintList(stateList);
+ ltr.setImageResource(R.drawable.ic_star2);
+
+ view.findViewById(R.id.flex_box).addView(ltr, flexLayoutParams);
+ }
+ }
+
+ private void getRateColor() {
+
+ }
+//
+// private void parseCheckbox(LinearLayout view, MetadataModel model) {
+// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box);
+// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+// flexLayoutParams.bottomMargin = DP_4;
+// flexLayoutParams.rightMargin = DP_4;
+// flexLayoutParams.height = SizeUtils.dp2px(30);
+//
+// AppCompatCheckBox checkBox = new AppCompatCheckBox(view.getContext());
+// checkBox.setText("");
+// checkBox.setClickable(false);
+// if (model.value instanceof Boolean) {
+// checkBox.setChecked((Boolean) model.value);
+// }
+// flexboxLayout.addView(checkBox, flexLayoutParams);
+// }
+
+// private void parseLink(LinearLayout view, MetadataModel model) {
+//
+// if (model.value instanceof ArrayList) {
+//
+// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box);
+// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+// flexLayoutParams.bottomMargin = DP_4;
+// flexLayoutParams.rightMargin = DP_4;
+//
+// ArrayList> arrayList = (ArrayList>) model.value;
+// if (!CollectionUtils.isEmpty(arrayList)) {
+// LinkedTreeMap hashMap = arrayList.get(0);
+// View ltr = LayoutInflater.from(flexboxLayout.getContext()).inflate(R.layout.layout_text_round, null);
+// TextView textView = ltr.findViewById(R.id.text);
+// textView.setMaxLines(1);
+// textView.setEllipsize(TextUtils.TruncateAt.END);
+// textView.setText(hashMap.get("display_value"));
+// flexboxLayout.addView(ltr, flexLayoutParams);
+//
+// //
+// flexboxLayout.addView(TaskViews.getInstance().getSingleLineTextView(context, "..."));
+// }
+// }
+// }
+//
+// private void parseDigitalSign(LinearLayout view, WorkFlowSimplifyModel workFlowModel, MetadataModel model) {
+// if (model.value instanceof LinkedTreeMap) {
+// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box);
+// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+// flexLayoutParams.bottomMargin = DP_4;
+// flexLayoutParams.rightMargin = DP_4;
+//
+// LinkedTreeMap treeMap = (LinkedTreeMap) model.value;
+//
+// ///digital-signs/2023-05/chaohui.wang%40seafile.com-1684141281136.png
+// String url = treeMap.get("sign_image_url").toString();
+//
+// //https://dev.seatable.cn/thumbnail/workspace/246/asset/cf317040-a299-4311-a565-27d8b3dde319/digital-signs/2023-05/chaohui.wang%40seafile.com-1684141281136.png?size=256
+// url = IO.getSingleton().getHostUrl() + "thumbnail/workspace/" + workFlowModel.id + "/asset/" + workFlowModel.dtable_uuid + url;
+//
+// ImageView imageView = Views.TableActivityViews.getImageViewWithWhAndUrl(flexboxLayout.getContext(), 24, url);
+// flexboxLayout.addView(imageView, flexLayoutParams);
+// }
+// }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java b/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java
index aa2f65de7..e6e9d330d 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java
@@ -9,7 +9,6 @@
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -51,7 +50,7 @@
import com.seafile.seadroid2.ui.main.MainActivity;
import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity;
import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity;
-import com.seafile.seadroid2.ui.webview.SeaWebViewActivity;
+import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity;
import com.seafile.seadroid2.view.TipsViews;
import java.io.File;
@@ -376,7 +375,7 @@ public void accept(RepoModel repoModel) throws Exception {
private void open(RepoModel repoModel, SearchModel searchedFile, String fileName, String filePath) {
if (fileName.endsWith(Constants.Format.DOT_SDOC)) {
- SeaWebViewActivity.openSdoc(this, repoModel.repo_name, repoModel.repo_id, filePath);
+ SDocWebViewActivity.openSdoc(this, repoModel.repo_name, repoModel.repo_id, filePath);
} else if (Utils.isViewableImage(fileName) && !repoModel.encrypted) {
// Encrypted repo does not support gallery,
// because pic thumbnail under encrypted repo was not supported at the server side
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/settings/TabSettingsFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/TabSettingsFragment.java
index 972821b80..4d8ec45f7 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/settings/TabSettingsFragment.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/TabSettingsFragment.java
@@ -9,6 +9,7 @@
import android.os.Bundle;
import android.text.Spanned;
import android.text.TextUtils;
+import android.view.View;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
@@ -70,6 +71,7 @@
import com.seafile.seadroid2.ui.main.MainActivity;
import com.seafile.seadroid2.ui.selector.ObjSelectorActivity;
import com.seafile.seadroid2.ui.webview.SeaWebViewActivity;
+import com.seafile.seadroid2.widget.prefs.ButtonPreference;
import java.util.ArrayList;
import java.util.List;
@@ -176,8 +178,6 @@ private void initPref() {
initAccountPref();
- initClientEncryptPref();
-
initSignOutPref();
initAlbumBackupPref();
@@ -206,19 +206,15 @@ private void initAccountPref() {
gestureSwitch = findPreference(getString(R.string.pref_key_gesture_lock));
}
- private void initClientEncryptPref() {
- Preference clientEncPref = findPreference(getString(R.string.pref_key_security_client_encrypt));
- if (clientEncPref == null) {
- return;
- }
-
- ServerInfo serverInfo = SupportAccountManager.getInstance().getServerInfo(currentAccount);
- // Client side encryption for encrypted Library
- clientEncPref.setVisible(serverInfo.canLocalDecrypt());
- }
-
private void initSignOutPref() {
//sign out
+// ButtonPreference buttonPreference = findPreference(getString(R.string.pref_key_sign_out));
+// buttonPreference.getButton().setOnClickListener(new View.OnClickListener() {
+// @Override
+// public void onClick(View v) {
+// onPreferenceSignOutClicked();
+// }
+// });
findPreference(getString(R.string.pref_key_sign_out)).setOnPreferenceClickListener(preference -> {
onPreferenceSignOutClicked();
return true;
@@ -437,13 +433,6 @@ public void onChanged(Boolean aBoolean) {
}
});
- Settings.CLIENT_ENCRYPT_SWITCH.observe(getViewLifecycleOwner(), new Observer() {
- @Override
- public void onChanged(Boolean aBoolean) {
- SLogs.e("client encrypt switch:" + aBoolean);
- }
- });
-
//////////////////
/// album backup
//////////////////
@@ -715,7 +704,7 @@ private void updateAlbumBackupSelectedRepoSummary() {
Account camAccount = CameraUploadManager.getInstance().getCameraAccount();
RepoConfig repoConfig = AlbumBackupSharePreferenceHelper.readRepoConfig();
if (camAccount != null && repoConfig != null) {
- mAlbumBackupRepo.setSummary(camAccount.getSignature() + "/" + repoConfig.getRepoName());
+ mAlbumBackupRepo.setSummary(repoConfig.getRepoName());
} else {
mAlbumBackupRepo.setSummary(null);
}
@@ -743,7 +732,7 @@ private void updateFolderBackupSelectedRepoAndFolderSummary() {
RepoConfig repoConfig = FolderBackupSharePreferenceHelper.readRepoConfig();
if (repoConfig != null && !TextUtils.isEmpty(repoConfig.getRepoName())) {
- mFolderBackupSelectRepo.setSummary(repoConfig.getEmail() + "/" + repoConfig.getRepoName());
+ mFolderBackupSelectRepo.setSummary(repoConfig.getRepoName());
} else {
mFolderBackupSelectRepo.setSummary(getString(R.string.folder_backup_select_repo_hint));
}
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java
index d514d3f03..4b799c2f3 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java
@@ -26,24 +26,24 @@
import com.seafile.seadroid2.SeafException;
import com.seafile.seadroid2.account.Account;
import com.seafile.seadroid2.account.SupportAccountManager;
+import com.seafile.seadroid2.bottomsheetmenu.BottomSheetHelper;
import com.seafile.seadroid2.bottomsheetmenu.BottomSheetMenuFragment;
import com.seafile.seadroid2.bottomsheetmenu.OnMenuClickListener;
import com.seafile.seadroid2.config.Constants;
+import com.seafile.seadroid2.databinding.LayoutFrameSwipeRvBinding;
+import com.seafile.seadroid2.framework.data.db.entities.StarredModel;
+import com.seafile.seadroid2.framework.data.model.ResultModel;
import com.seafile.seadroid2.framework.datastore.DataManager;
+import com.seafile.seadroid2.framework.util.Utils;
import com.seafile.seadroid2.ui.WidgetUtils;
import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM;
-import com.seafile.seadroid2.bottomsheetmenu.BottomSheetHelper;
-import com.seafile.seadroid2.databinding.LayoutFrameSwipeRvBinding;
-import com.seafile.seadroid2.framework.data.model.ResultModel;
-import com.seafile.seadroid2.framework.data.db.entities.StarredModel;
import com.seafile.seadroid2.ui.file.FileActivity;
import com.seafile.seadroid2.ui.main.MainActivity;
import com.seafile.seadroid2.ui.main.MainViewModel;
import com.seafile.seadroid2.ui.markdown.MarkdownActivity;
import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity;
import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity;
-import com.seafile.seadroid2.ui.webview.SeaWebViewActivity;
-import com.seafile.seadroid2.framework.util.Utils;
+import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity;
import com.seafile.seadroid2.view.TipsViews;
import java.io.File;
@@ -245,7 +245,7 @@ private void open(StarredModel model) {
} else if (model.obj_name.endsWith(Constants.Format.DOT_SDOC)) {
- SeaWebViewActivity.openSdoc(getContext(), model.repo_name, model.repo_id, model.path);
+ SDocWebViewActivity.openSdoc(getContext(), model.repo_name, model.repo_id, model.path);
} else if (Utils.isVideoFile(model.obj_name)) {
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java
index e09bd07b1..bd991a90b 100644
--- a/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java
+++ b/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java
@@ -5,14 +5,11 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.text.TextUtils;
+import android.view.MenuItem;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import androidx.activity.EdgeToEdge;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
@@ -21,10 +18,11 @@
import androidx.webkit.WebViewFeature;
import com.blankj.utilcode.util.ActivityUtils;
+import com.blankj.utilcode.util.ToastUtils;
+import com.google.android.material.navigation.NavigationBarView;
import com.seafile.seadroid2.R;
import com.seafile.seadroid2.account.Account;
import com.seafile.seadroid2.account.SupportAccountManager;
-import com.seafile.seadroid2.config.Constants;
import com.seafile.seadroid2.databinding.ActivitySeaWebviewBinding;
import com.seafile.seadroid2.databinding.ActivitySeaWebviewProBinding;
import com.seafile.seadroid2.databinding.ToolbarActionbarProgressBarBinding;
@@ -33,8 +31,6 @@
import com.seafile.seadroid2.view.webview.PreloadWebView;
import com.seafile.seadroid2.view.webview.SeaWebView;
-import java.io.Serializable;
-
public class SeaWebViewActivity extends BaseActivity {
private ActivitySeaWebviewBinding binding;
private ToolbarActionbarProgressBarBinding toolBinding;
@@ -43,15 +39,6 @@ public class SeaWebViewActivity extends BaseActivity {
private String targetUrl;
- public static void openSdoc(Context context, String repoName, String repoID, String path) {
- Intent intent = new Intent(context, SeaWebViewActivity.class);
- intent.putExtra("previewType", WebViewPreviewType.SDOC.name());
- intent.putExtra("repoName", repoName);
- intent.putExtra("repoID", repoID);
- intent.putExtra("filePath", path);
- ActivityUtils.startActivity(intent);
- }
-
public static void openUrl(Context context, String url) {
Intent intent = new Intent(context, SeaWebViewActivity.class);
intent.putExtra("previewType", WebViewPreviewType.URL.name());
diff --git a/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java b/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java
new file mode 100644
index 000000000..c4d2e2efa
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java
@@ -0,0 +1,43 @@
+package com.seafile.seadroid2.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.core.view.ViewCompat;
+
+import com.blankj.utilcode.util.BarUtils;
+import com.blankj.utilcode.util.SizeUtils;
+import com.seafile.seadroid2.R;
+import com.seafile.seadroid2.framework.util.SLogs;
+
+public class HideBottomBehavior extends CoordinatorLayout.Behavior {
+
+ public HideBottomBehavior() {
+ }
+
+ public HideBottomBehavior(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull LinearLayout child, View dependency) {
+ return dependency.getId() == R.id.appbar;
+ }
+
+ private boolean isInit = true;
+
+ @Override
+ public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull LinearLayout child, @NonNull View dependency) {
+ if (isInit) {
+ isInit = false;
+ } else {
+ float y = dependency.getY();
+ child.setTranslationY(-(y));
+ }
+ return true;
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/view/KeyViews.java b/app/src/main/java/com/seafile/seadroid2/view/KeyViews.java
new file mode 100644
index 000000000..37a2648fb
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/view/KeyViews.java
@@ -0,0 +1,220 @@
+package com.seafile.seadroid2.view;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.Gravity;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.blankj.utilcode.util.NumberUtils;
+import com.blankj.utilcode.util.SizeUtils;
+import com.google.android.flexbox.FlexWrap;
+import com.google.android.flexbox.FlexboxLayout;
+import com.seafile.seadroid2.R;
+import com.seafile.seadroid2.config.Constants;
+import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigDataModel;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+
+public class KeyViews {
+ public int DP_2 = Constants.DP.DP_2;
+ public int DP_4 = Constants.DP.DP_4;
+ public int DP_8 = Constants.DP.DP_8;
+ public int DP_16 = Constants.DP.DP_16;
+ public int DP_32 = Constants.DP.DP_32;
+
+ public static int DEFAULT_VIEW_WIDTH = SizeUtils.dp2px(150);
+ public static int DEFAULT_VIEW_HEIGHT = SizeUtils.dp2px(30);
+ public static int DEFAULT_IMAGE_VIEW_WH = SizeUtils.dp2px(96);
+
+ //
+ private static volatile KeyViews mSingleton = null;
+
+ public static KeyViews getInstance() {
+ if (mSingleton == null) {
+ synchronized (KeyViews.class) {
+ if (mSingleton == null) {
+ mSingleton = new KeyViews();
+ }
+ }
+ }
+ return mSingleton;
+ }
+
+ public void buildEditableText(Context context, boolean editable, LinearLayout parentContainer, String defaultText) {
+
+ }
+
+ private Pair getFlexContainer(Context context, LinearLayout parentContainer, String key) {
+ FlexboxLayout flexboxContainer = parentContainer.findViewWithTag(key);
+ boolean isAdded = flexboxContainer != null;
+ if (flexboxContainer != null) {
+ flexboxContainer.removeAllViews();
+ } else {
+ flexboxContainer = new FlexboxLayout(context);
+ LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(-1, -2);
+ llp.topMargin = DP_8;
+ llp.bottomMargin = DP_16;
+ flexboxContainer.setLayoutParams(llp);
+ flexboxContainer.setTag(key);
+ flexboxContainer.setFlexWrap(FlexWrap.WRAP);
+ }
+ return new Pair<>(isAdded, flexboxContainer);
+ }
+
+
+ public TextView getSingleLineTextView(Context context, String string) {
+ TextView textView = new TextView(context);
+ textView.setTextSize(14);
+ textView.setTextColor(context.getColor(R.color.light_grey));
+ textView.setMaxLines(1);
+ textView.setEllipsize(TextUtils.TruncateAt.END);
+ textView.setText(string);
+ textView.setGravity(Gravity.CENTER_VERTICAL);
+ LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(DEFAULT_VIEW_WIDTH, DEFAULT_VIEW_HEIGHT);
+ textView.setLayoutParams(llp);
+ textView.setPadding(0, DP_2, DP_2, DP_2);
+ return textView;
+ }
+
+
+ public String getNormalFormatNumber(String t, MetadataConfigDataModel columnDataModel) {
+ return getFormatNumber(t, columnDataModel, false);
+ }
+
+ public String getNoDotCharNumber(String t, String format) {
+ if (TextUtils.isEmpty(format)) {
+ return t;
+ }
+
+ //set default value
+ if (!"number".equals(format)) {
+ return t;
+ }
+ //1.234567890111E12
+
+ if (TextUtils.isEmpty(t)) {
+ return t;
+ }
+
+ //Scientific notation
+ if (t.contains(".") && t.toLowerCase(Locale.getDefault()).contains("e")) {
+ BigDecimal bd = new BigDecimal(t);
+ return bd.toPlainString();
+ } else if (t.contains(".")) {
+ int decimalIndex = t.indexOf(".");
+ if (decimalIndex >= 0) {
+ String[] ts = t.split("\\.");
+ String decimalPart = ts[1];
+ double decimalValue = Double.parseDouble("0." + decimalPart);
+ if (decimalValue == 0) {
+ return ts[0];
+ } else {
+ return t;
+ }
+ }
+ }
+
+
+ return t;
+ }
+
+ /**
+ * @param isPercentTypeMultiplyBy100 true: if type is percent, multiply by 100
+ */
+ public String getFormatNumber(String t, MetadataConfigDataModel columnDataModel, boolean isPercentTypeMultiplyBy100) {
+ if (TextUtils.isEmpty(t)) {
+ return t;
+ }
+
+ if (columnDataModel == null) {
+ return t;
+ }
+
+ //set default value
+ if ("number".equals(columnDataModel.format)) {
+ return getNoDotCharNumber(t, columnDataModel.format);
+ }
+
+ if ("percent".equals(columnDataModel.format)) {
+ if (t.contains("%")) {
+ t = t.replace("%", "");
+ }
+ double d = Double.parseDouble(t);
+ if (isPercentTypeMultiplyBy100) {
+ d = d * 100;
+ }
+
+ String r;
+ if (columnDataModel.enable_precision) {
+ r = NumberUtils.format(d, columnDataModel.precision);
+ } else {
+ r = String.valueOf(d);
+ }
+
+ return r + "%";
+ } else if ("yuan".equals(columnDataModel.format)) {
+ if (t.contains("¥")) {
+ t = t.replace("¥", "");
+ }
+ double d = Double.parseDouble(t);
+
+ String r;
+ if (columnDataModel.enable_precision) {
+ r = NumberUtils.format(d, columnDataModel.precision);
+ } else {
+ r = String.valueOf(d);
+ }
+ return "¥" + r;
+ } else if ("dollar".equals(columnDataModel.format)) {
+ if (t.contains("$")) {
+ t = t.replace("$", "");
+ }
+
+ double d = Double.parseDouble(t);
+
+ String r;
+ if (columnDataModel.enable_precision) {
+ r = NumberUtils.format(d, columnDataModel.precision);
+ } else {
+ r = String.valueOf(d);
+ }
+
+ return "$" + r;
+ } else if ("euro".equals(columnDataModel.format)) {
+ if (t.contains("€")) {
+ t = t.replace("€", "");
+ }
+ double d = Double.parseDouble(t);
+
+ String r;
+ if (columnDataModel.enable_precision) {
+ r = NumberUtils.format(d, columnDataModel.precision);
+ } else {
+ r = String.valueOf(d);
+ }
+ return "€" + r;
+ } else if ("custom_currency".equals(columnDataModel.format)) {
+ if (t.contains(columnDataModel.currency_symbol)) {
+ t = t.replace(columnDataModel.currency_symbol, "");
+ }
+
+ double d = Double.parseDouble(t);
+ String r;
+ if (columnDataModel.enable_precision) {
+ r = NumberUtils.format(d, columnDataModel.precision);
+ } else {
+ r = String.valueOf(d);
+ }
+
+ if ("before".equals(columnDataModel.currency_symbol_position)) {
+ return columnDataModel.currency_symbol + r;
+ } else {
+ return r + columnDataModel.currency_symbol;
+ }
+ }
+ return t;
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/view/MaxHeightScrollView.java b/app/src/main/java/com/seafile/seadroid2/view/MaxHeightScrollView.java
new file mode 100644
index 000000000..47cfffba5
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/view/MaxHeightScrollView.java
@@ -0,0 +1,57 @@
+package com.seafile.seadroid2.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.widget.ScrollView;
+
+import com.blankj.utilcode.util.SizeUtils;
+import com.seafile.seadroid2.R;
+
+
+public class MaxHeightScrollView extends ScrollView {
+ private int maxHeight = -1;
+
+ public MaxHeightScrollView(Context context) {
+ super(context);
+ }
+
+ public MaxHeightScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (!isInEditMode()) {
+ init(context, attrs);
+ }
+ }
+
+ public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ if (!isInEditMode()) {
+ init(context, attrs);
+ }
+ }
+
+ public void setMaxHeight(int maxHeight) {
+ this.maxHeight = maxHeight;
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int heightSpec = heightMeasureSpec;
+ if (maxHeight > 0) {
+ heightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
+ }
+ super.onMeasure(widthMeasureSpec, heightSpec);
+ }
+
+ private void init(Context context, AttributeSet attrs) {
+ if (attrs != null) {
+ TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
+ int DEFAULT_HEIGHT = SizeUtils.dp2px(200);
+ maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_max_height, DEFAULT_HEIGHT);
+
+ styledAttrs.recycle();
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java b/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java
new file mode 100644
index 000000000..1215cfe65
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java
@@ -0,0 +1,545 @@
+package com.seafile.seadroid2.view;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import android.webkit.WebView;
+import android.widget.OverScroller;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.NestedScrollingChild3;
+import androidx.core.view.NestedScrollingChildHelper;
+import androidx.core.view.ViewCompat;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * WebView compatible with CoordinatorLayout by snachmsm
+ * The implementation based on NestedScrollView of design library androidx v1.0.1
+ */
+public class NestedWebView extends WebView implements NestedScrollingChild3 {
+
+ private static final String TAG = "NestedWebView";
+ private static final int INVALID_POINTER = -1;
+
+ private final int[] mScrollOffset = new int[2];
+ private final int[] mScrollConsumed = new int[2];
+
+ private int mLastMotionY;
+ private NestedScrollingChildHelper mChildHelper;
+ private boolean mIsBeingDragged = false;
+ private VelocityTracker mVelocityTracker;
+ private int mTouchSlop;
+ private int mActivePointerId = INVALID_POINTER;
+ private int mNestedYOffset;
+ private OverScroller mScroller;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
+ private int mLastScrollerY;
+
+ public NestedWebView(Context context) {
+ this(context, null);
+ }
+
+ public NestedWebView(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.webViewStyle);
+ }
+
+ public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setOverScrollMode(WebView.OVER_SCROLL_NEVER);
+ initScrollView();
+ mChildHelper = new NestedScrollingChildHelper(this);
+ setNestedScrollingEnabled(true);
+ }
+
+ private void initScrollView() {
+ mScroller = new OverScroller(getContext());
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { // most common
+ return true;
+ }
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE:
+ final int activePointerId = mActivePointerId;
+ if (activePointerId == INVALID_POINTER) {
+ break;
+ }
+
+ final int pointerIndex = ev.findPointerIndex(activePointerId);
+ if (pointerIndex == -1) {
+ Log.e(TAG, "Invalid pointerId=" + activePointerId
+ + " in onInterceptTouchEvent");
+ break;
+ }
+
+ final int y = (int) ev.getY(pointerIndex);
+ final int yDiff = Math.abs(y - mLastMotionY);
+ if (yDiff > mTouchSlop
+ && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) {
+ mIsBeingDragged = true;
+ mLastMotionY = y;
+ initVelocityTrackerIfNotExists();
+ mVelocityTracker.addMovement(ev);
+ mNestedYOffset = 0;
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ break;
+ case MotionEvent.ACTION_DOWN:
+ mLastMotionY = (int) ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+
+ mScroller.computeScrollOffset();
+ mIsBeingDragged = !mScroller.isFinished();
+
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mIsBeingDragged = false;
+ mActivePointerId = INVALID_POINTER;
+ recycleVelocityTracker();
+ if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ stopNestedScroll();
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
+ }
+
+ return mIsBeingDragged;
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ initVelocityTrackerIfNotExists();
+
+ MotionEvent vtev = MotionEvent.obtain(ev);
+
+ final int actionMasked = ev.getActionMasked();
+
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ mNestedYOffset = 0;
+ }
+ vtev.offsetLocation(0, mNestedYOffset);
+
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ if ((mIsBeingDragged = !mScroller.isFinished())) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+
+ if (!mScroller.isFinished()) {
+ abortAnimatedScroll();
+ }
+
+ mLastMotionY = (int) ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (activePointerIndex == -1) {
+ Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
+ break;
+ }
+
+ final int y = (int) ev.getY(activePointerIndex);
+ int deltaY = mLastMotionY - y;
+ if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,
+ ViewCompat.TYPE_TOUCH)) {
+ deltaY -= mScrollConsumed[1];
+ mNestedYOffset += mScrollOffset[1];
+ }
+ if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ mIsBeingDragged = true;
+ if (deltaY > 0) {
+ deltaY -= mTouchSlop;
+ } else {
+ deltaY += mTouchSlop;
+ }
+ }
+ if (mIsBeingDragged) {
+ mLastMotionY = y - mScrollOffset[1];
+
+ final int oldY = getScrollY();
+ final int range = getScrollRange();
+
+ // Calling overScrollByCompat will call onOverScrolled, which
+ // calls onScrollChanged if applicable.
+ if (overScrollByCompat(0, deltaY, 0, oldY, 0, range, 0,
+ 0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) {
+ mVelocityTracker.clear();
+ }
+
+ final int scrolledDeltaY = getScrollY() - oldY;
+ final int unconsumedY = deltaY - scrolledDeltaY;
+
+ mScrollConsumed[1] = 0;
+
+ dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
+ ViewCompat.TYPE_TOUCH, mScrollConsumed);
+
+ mLastMotionY -= mScrollOffset[1];
+ mNestedYOffset += mScrollOffset[1];
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
+ if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+ if (!dispatchNestedPreFling(0, -initialVelocity)) {
+ dispatchNestedFling(0, -initialVelocity, true);
+ fling(-initialVelocity);
+ }
+ } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
+ getScrollRange())) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsBeingDragged) {
+ if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
+ getScrollRange())) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ }
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ final int index = ev.getActionIndex();
+ mLastMotionY = (int) ev.getY(index);
+ mActivePointerId = ev.getPointerId(index);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
+ break;
+ }
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(vtev);
+ }
+ vtev.recycle();
+ return super.onTouchEvent(ev);
+ }
+
+ private void abortAnimatedScroll() {
+ mScroller.abortAnimation();
+ stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
+ }
+
+ private void endDrag() {
+ mIsBeingDragged = false;
+
+ recycleVelocityTracker();
+ stopNestedScroll();
+ }
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionY = (int) ev.getY(newPointerIndex);
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ private void fling(int velocityY) {
+ int height = getHeight();
+ mScroller.fling(getScrollX(), getScrollY(), // start
+ 0, velocityY, // velocities
+ 0, 0, // x
+ Integer.MIN_VALUE, Integer.MAX_VALUE, // y
+ 0, height / 2);
+ runAnimatedScroll(true);
+ }
+
+ private void runAnimatedScroll(boolean participateInNestedScrolling) {
+ if (participateInNestedScrolling) {
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+ } else {
+ stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
+ }
+ mLastScrollerY = getScrollY();
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (disallowIntercept) {
+ recycleVelocityTracker();
+ }
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+
+ private void initOrResetVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ }
+
+ private void initVelocityTrackerIfNotExists() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ }
+
+ private void recycleVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ @Override
+ protected boolean overScrollBy(int deltaX, int deltaY,
+ int scrollX, int scrollY,
+ int scrollRangeX, int scrollRangeY,
+ int maxOverScrollX, int maxOverScrollY,
+ boolean isTouchEvent) {
+ // this is causing double scroll call (doubled speed), but this WebView isn't overscrollable
+ // all overscrolls are passed to appbar, so commenting this out during drag
+ if (!mIsBeingDragged)
+ overScrollByCompat(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
+ maxOverScrollX, maxOverScrollY, isTouchEvent);
+ // without this call webview won't scroll to top when url change or when user pick input
+ // (webview should move a bit making input still in viewport when "adjustResize")
+ return true;
+ }
+
+ int getScrollRange() {
+ //Using scroll range of webview instead of childs as NestedScrollView does.
+ return computeVerticalScrollRange();
+ }
+
+ @Override
+ public boolean isNestedScrollingEnabled() {
+ return mChildHelper.isNestedScrollingEnabled();
+ }
+
+ @Override
+ public void setNestedScrollingEnabled(boolean enabled) {
+ mChildHelper.setNestedScrollingEnabled(enabled);
+ }
+
+ @Override
+ public boolean startNestedScroll(int axes, int type) {
+ return mChildHelper.startNestedScroll(axes, type);
+ }
+
+ @Override
+ public boolean startNestedScroll(int axes) {
+ return startNestedScroll(axes, ViewCompat.TYPE_TOUCH);
+ }
+
+ @Override
+ public void stopNestedScroll(int type) {
+ mChildHelper.stopNestedScroll(type);
+ }
+
+ @Override
+ public void stopNestedScroll() {
+ stopNestedScroll(ViewCompat.TYPE_TOUCH);
+ }
+
+ @Override
+ public boolean hasNestedScrollingParent(int type) {
+ return mChildHelper.hasNestedScrollingParent(type);
+ }
+
+ @Override
+ public boolean hasNestedScrollingParent() {
+ return hasNestedScrollingParent(ViewCompat.TYPE_TOUCH);
+ }
+
+ @Override
+ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
+ int[] offsetInWindow) {
+ return dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+ offsetInWindow, ViewCompat.TYPE_TOUCH);
+ }
+
+ @Override
+ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
+ int[] offsetInWindow, int type) {
+ return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+ offsetInWindow, type);
+ }
+
+ @Override
+ public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
+ @Nullable int[] offsetInWindow, int type, @NonNull int[] consumed) {
+ mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+ offsetInWindow, type, consumed);
+ }
+
+ @Override
+ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
+ return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH);
+ }
+
+
+ @Override
+ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {
+ return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
+ }
+
+ @Override
+ public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
+ return mChildHelper.dispatchNestedFling(velocityX, velocityY, false);
+ }
+
+ @Override
+ public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
+ return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
+ }
+
+ @Override
+ public int getNestedScrollAxes() {
+ return ViewCompat.SCROLL_AXIS_VERTICAL;
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.isFinished()) {
+ return;
+ }
+
+ mScroller.computeScrollOffset();
+ final int y = mScroller.getCurrY();
+ int unconsumed = y - mLastScrollerY;
+ mLastScrollerY = y;
+
+ // Nested Scrolling Pre Pass
+ mScrollConsumed[1] = 0;
+ dispatchNestedPreScroll(0, unconsumed, mScrollConsumed, null,
+ ViewCompat.TYPE_NON_TOUCH);
+ unconsumed -= mScrollConsumed[1];
+
+
+ if (unconsumed != 0) {
+ // Internal Scroll
+ final int oldScrollY = getScrollY();
+ overScrollByCompat(0, unconsumed, getScrollX(), oldScrollY, 0, getScrollRange(),
+ 0, 0, false);
+ final int scrolledByMe = getScrollY() - oldScrollY;
+ unconsumed -= scrolledByMe;
+
+ // Nested Scrolling Post Pass
+ mScrollConsumed[1] = 0;
+ dispatchNestedScroll(0, 0, 0, unconsumed, mScrollOffset,
+ ViewCompat.TYPE_NON_TOUCH, mScrollConsumed);
+ unconsumed -= mScrollConsumed[1];
+ }
+
+ if (unconsumed != 0) {
+ abortAnimatedScroll();
+ }
+
+ if (!mScroller.isFinished()) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ }
+
+ // copied from NestedScrollView exacly as it looks, leaving overscroll related code, maybe future use
+ private boolean overScrollByCompat(int deltaX, int deltaY,
+ int scrollX, int scrollY,
+ int scrollRangeX, int scrollRangeY,
+ int maxOverScrollX, int maxOverScrollY,
+ boolean isTouchEvent) {
+ final int overScrollMode = getOverScrollMode();
+ final boolean canScrollHorizontal =
+ computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+ final boolean canScrollVertical =
+ computeVerticalScrollRange() > computeVerticalScrollExtent();
+ final boolean overScrollHorizontal = overScrollMode == View.OVER_SCROLL_ALWAYS
+ || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
+ final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS
+ || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
+
+ int newScrollX = scrollX + deltaX;
+ if (!overScrollHorizontal) {
+ maxOverScrollX = 0;
+ }
+
+ int newScrollY = scrollY + deltaY;
+ if (!overScrollVertical) {
+ maxOverScrollY = 0;
+ }
+
+ // Clamp values if at the limits and record
+ final int left = -maxOverScrollX;
+ final int right = maxOverScrollX + scrollRangeX;
+ final int top = -maxOverScrollY;
+ final int bottom = maxOverScrollY + scrollRangeY;
+
+ boolean clampedX = false;
+ if (newScrollX > right) {
+ newScrollX = right;
+ clampedX = true;
+ } else if (newScrollX < left) {
+ newScrollX = left;
+ clampedX = true;
+ }
+
+ boolean clampedY = false;
+ if (newScrollY > bottom) {
+ newScrollY = bottom;
+ clampedY = true;
+ } else if (newScrollY < top) {
+ newScrollY = top;
+ clampedY = true;
+ }
+
+ if (clampedY && !hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {
+ mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange());
+ }
+
+ onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
+
+ return clampedX || clampedY;
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeletableEditText.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeletableEditText.java
new file mode 100755
index 000000000..795dba8de
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeletableEditText.java
@@ -0,0 +1,80 @@
+/*
+Copyright 2017 yangchong211(github.com/yangchong211)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package com.seafile.seadroid2.view.rich_edittext;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import androidx.appcompat.widget.AppCompatEditText;
+
+/**
+ *
+ * @author 杨充
+ * blog : https://github.com/yangchong211
+ * time : 2016/3/31
+ * desc : 自定义EditText
+ * revise: 主要用途是处理软键盘回删按钮backSpace时回调OnKeyListener
+ *
+ */
+public class DeletableEditText extends AppCompatEditText {
+
+ private DeleteInputConnection inputConnection;
+
+ public DeletableEditText(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ public DeletableEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public DeletableEditText(Context context) {
+ super(context);
+ init();
+ }
+
+
+ private void init(){
+ inputConnection = new DeleteInputConnection(null,true);
+ }
+
+ /**
+ * 当输入法和EditText建立连接的时候会通过这个方法返回一个InputConnection。
+ * 我们需要代理这个方法的父类方法生成的InputConnection并返回我们自己的代理类。
+ * @param outAttrs attrs
+ * @return
+ */
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ //inputConnection = new DeleteInputConnection(super.onCreateInputConnection(outAttrs), true);
+ inputConnection.setTarget(super.onCreateInputConnection(outAttrs));
+ return inputConnection;
+ }
+
+ /**
+ * 设置格键删除监听事件,主要是解决少部分手机,使用搜狗输入法无法响应当内容为空时的删除逻辑
+ * @param listener listener
+ */
+ public void setBackSpaceListener(DeleteInputConnection.BackspaceListener listener){
+ inputConnection.setBackspaceListener(listener);
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeleteInputConnection.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeleteInputConnection.java
new file mode 100644
index 000000000..2916e6466
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeleteInputConnection.java
@@ -0,0 +1,117 @@
+/*
+Copyright 2017 yangchong211(github.com/yangchong211)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package com.seafile.seadroid2.view.rich_edittext;
+
+import android.view.KeyEvent;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+
+import com.seafile.seadroid2.framework.util.SLogs;
+
+/**
+ *
+ * @author 杨充
+ * blog : https://github.com/yangchong211
+ * time : 2019/07/18
+ * desc : 自定义InputConnectionWrapper
+ * revise:
+ *
+ */
+public class DeleteInputConnection extends InputConnectionWrapper {
+
+ private BackspaceListener mBackspaceListener;
+
+ public interface BackspaceListener {
+ /**
+ * @return true 代表消费了这个事件
+ */
+ boolean onBackspace();
+ }
+
+ public void setBackspaceListener(BackspaceListener backspaceListener) {
+ this.mBackspaceListener = backspaceListener;
+ }
+
+
+ public DeleteInputConnection(InputConnection target, boolean mutable) {
+ super(target, mutable);
+ }
+
+ /**
+ * 提交文本
+ * 输入法输入了字符,包括表情,字母、文字、数字和符号等内容,会回调该方法
+ *
+ * @param text text
+ * @param newCursorPosition 新索引位置
+ * @return
+ */
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ return super.commitText(text, newCursorPosition);
+ }
+
+ /**
+ * 按键事件
+ * 在commitText方法中,
+ * 如果执行父类的 commitText(即super.commitText(text, newCursorPosition))那么表示不拦截,
+ * 如果返回false则表示拦截
+ *
+ * 当有按键输入时,该方法会被回调。比如点击退格键时,搜狗输入法应该就是通过调用该方法,
+ * 发送keyEvent的,但谷歌输入法却不会调用该方法,而是调用下面的deleteSurroundingText()方法。
+ * 网上说少数低端手机,无法响应退格键删除功能,后期找华为p9手机,安装搜狗输入法测试
+ *
+ * @param event event事件
+ * @return
+ */
+ @Override
+ public boolean sendKeyEvent(KeyEvent event) {
+ SLogs.d("DeletableEditText---sendKeyEvent--");
+ if (event.getKeyCode() == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (mBackspaceListener != null && mBackspaceListener.onBackspace()) {
+ return true;
+ }
+ }
+ return super.sendKeyEvent(event);
+ }
+
+ /**
+ * 删除操作
+ * 有文本删除操作时(剪切,点击退格键),会触发该方法
+ *
+ * @param beforeLength beforeLength
+ * @param afterLength afterLength
+ * @return
+ */
+ @Override
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ SLogs.d("DeletableEditText---deleteSurroundingText--" + beforeLength + "----" + afterLength);
+ if (beforeLength == 1 && afterLength == 0) {
+ return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
+ && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
+ }
+ return super.deleteSurroundingText(beforeLength, afterLength);
+ }
+
+ /**
+ * 结 束组合文本输入的时候,回调该方法
+ *
+ * @return
+ */
+ @Override
+ public boolean finishComposingText() {
+ return super.finishComposingText();
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichAtListener.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichAtListener.java
new file mode 100644
index 000000000..874aedd3d
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichAtListener.java
@@ -0,0 +1,23 @@
+/*
+Copyright 2017 yangchong211(github.com/yangchong211)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package com.seafile.seadroid2.view.rich_edittext;
+
+import android.widget.EditText;
+
+public interface OnRichAtListener {
+
+ void onCall(EditText editText);
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichImageClickListener.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichImageClickListener.java
new file mode 100644
index 000000000..a2016f8ea
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichImageClickListener.java
@@ -0,0 +1,23 @@
+/*
+Copyright 2017 yangchong211(github.com/yangchong211)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package com.seafile.seadroid2.view.rich_edittext;
+
+import android.view.View;
+
+public interface OnRichImageClickListener {
+
+ void onClick(View view, String imagePath);
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/RichEditText.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/RichEditText.java
new file mode 100644
index 000000000..c5d3f5412
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/RichEditText.java
@@ -0,0 +1,371 @@
+package com.seafile.seadroid2.view.rich_edittext;
+
+import android.content.Context;
+import android.net.Uri;
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.blankj.utilcode.util.SizeUtils;
+import com.bumptech.glide.Glide;
+import com.seafile.seadroid2.R;
+import com.seafile.seadroid2.config.Constants;
+import com.seafile.seadroid2.databinding.LayoutUploadFileBinding;
+import com.seafile.seadroid2.view.MaxHeightScrollView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RichEditText extends MaxHeightScrollView {
+ public int DP_2 = Constants.DP.DP_2;
+ public int DP_4 = Constants.DP.DP_4;
+ public int DP_8 = Constants.DP.DP_8;
+ public int DP_16 = Constants.DP.DP_16;
+ public int DP_32 = Constants.DP.DP_32;
+
+
+ private static final int VIEW_TAG_VALUE_IMAGE_URI = 0x2000002;
+ private static final int VIEW_TAG_VALUE_IMAGE_URL = 0x2000003;
+
+ private static final String VIEW_TAG_KV_INPUT = "input";
+ private static final String VIEW_TAG_KV_IMAGE = "image";
+
+ private LinearLayout container;
+ private LayoutInflater inflater;
+ private OnKeyListener keyListener;
+ private OnRichAtListener onRichAtListener;
+
+ private OnClickListener onCloseClickListener;
+
+ private OnFocusChangeListener focusListener;
+
+ private OnRichImageClickListener onRichImageStatusChangeListener;
+
+ private EditText lastFocusEdit;
+
+ private int removingImageIndex = 0;
+
+ public RichEditText(Context context) {
+ super(context);
+
+ init(context);
+ }
+
+ public RichEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ init(context);
+ }
+
+ public RichEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ }
+
+ private void init(Context context) {
+ inflater = LayoutInflater.from(context);
+
+ initLayoutView(context);
+
+ initListener();
+
+ initFirstEditText();
+ }
+
+ private void initLayoutView(Context context) {
+ container = new LinearLayout(context);
+ container.setOrientation(LinearLayout.VERTICAL);
+
+ LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ addView(container, layoutParams);
+ }
+
+ private void initFirstEditText() {
+ LinearLayout.LayoutParams firstEditParam = new LinearLayout.LayoutParams(-1, -2);
+ EditText firstEdit = buildEditText();
+
+ container.addView(firstEdit, firstEditParam);
+ lastFocusEdit = firstEdit;
+ }
+
+ private void initListener() {
+ keyListener = (v, keyCode, event) -> {
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
+ EditText edit = (EditText) v;
+ onBackspacePress(edit);
+ }
+ return false;
+ };
+
+ //
+ onCloseClickListener = v -> {
+ if (v.getId() == R.id.remove) {
+ FrameLayout parentView = (FrameLayout) v.getParent();
+ remove(parentView);
+ }
+ };
+
+ //
+ focusListener = (v, hasFocus) -> {
+ if (hasFocus) {
+ lastFocusEdit = (EditText) v;
+ }
+ };
+ }
+
+
+ private void onBackspacePress(EditText editText) {
+ if (editText == null) {
+ return;
+ }
+
+ int startSelection = editText.getSelectionStart();
+ if (startSelection != 0) {
+ return;
+ }
+
+ int indexOfChild = container.indexOfChild(editText);
+ if (indexOfChild <= 0) {
+ return;
+ }
+
+ View preChildView = container.getChildAt(indexOfChild - 1);
+ if (null == preChildView) {
+ return;
+ }
+
+ if (preChildView instanceof FrameLayout) {
+ remove(preChildView);
+ } else if (preChildView instanceof EditText) {
+ String str1 = editText.getText().toString();
+
+ EditText preEditText = (EditText) preChildView;
+ String str2 = preEditText.getText().toString();
+
+ container.removeView(editText);
+
+ preEditText.setText(String.format("%s%s", str2, str1));
+ preEditText.requestFocus();
+ preEditText.setSelection(str2.length(), str2.length());
+
+ lastFocusEdit = preEditText;
+ }
+ }
+
+ public void remove(View view) {
+ removingImageIndex = container.indexOfChild(view);
+ container.removeView(view);
+ mergeEditText();
+ }
+
+ public void setOnRichImageStatusChangeListener(OnRichImageClickListener l) {
+ this.onRichImageStatusChangeListener = l;
+ }
+
+
+ public void setOnRichAtListener(OnRichAtListener onRichAtListener) {
+ this.onRichAtListener = onRichAtListener;
+ }
+
+ public void insertImage(Uri uri) {
+ if (null == uri) {
+ return;
+ }
+
+ int lastEditIndex = container.indexOfChild(lastFocusEdit);
+ if (lastEditIndex + 1 < container.getChildCount()) {
+ View v = container.getChildAt(lastEditIndex + 1);
+ if (v instanceof FrameLayout) {
+ addImageViewAtIndex(lastEditIndex + 1, uri);
+ addEditTextAtIndex(lastEditIndex + 2, "");
+ } else {
+ addImageViewAtIndex(lastEditIndex + 1, uri);
+ }
+ } else {
+ addImageViewAtIndex(-1, uri);
+ addEditTextAtIndex(-1, "");
+ }
+ }
+
+ private void addEditTextAtIndex(final int index, CharSequence editStr) {
+ EditText editText = buildEditText();
+ editText.setText(editStr);
+
+ if (index == -1) {
+ container.addView(editText);
+ } else {
+ container.addView(editText, index);
+ }
+
+ lastFocusEdit = editText;
+ lastFocusEdit.requestFocus();
+ lastFocusEdit.setSelection(editStr.length(), editStr.length());
+ }
+
+ private void addImageViewAtIndex(final int index, final Uri uri) {
+ LayoutUploadFileBinding uploadFileBinding = LayoutUploadFileBinding.inflate(inflater);
+ uploadFileBinding.getRoot().setTag(VIEW_TAG_KV_IMAGE);
+ uploadFileBinding.getRoot().setTag(VIEW_TAG_VALUE_IMAGE_URI, uri.toString());
+ uploadFileBinding.remove.setOnClickListener(onCloseClickListener);
+ uploadFileBinding.uploadImage.setOnClickListener(v -> {
+ if (onRichImageStatusChangeListener != null) {
+ onRichImageStatusChangeListener.onClick(uploadFileBinding.uploadImage, uri.toString());
+ }
+ });
+
+
+ LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(SizeUtils.dp2px(96), SizeUtils.dp2px(96));
+ llp.topMargin = DP_2;
+ llp.bottomMargin = DP_2;
+ uploadFileBinding.getRoot().setLayoutParams(llp);
+
+ Glide.with(this)
+ .load(uri)
+ .centerCrop()
+ .into(uploadFileBinding.uploadImage);
+
+ if (index == -1) {
+ container.addView(uploadFileBinding.getRoot());
+ } else {
+ container.addView(uploadFileBinding.getRoot(), index);
+ }
+ }
+
+ private void mergeEditText() {
+ try {
+ View preView = container.getChildAt(removingImageIndex - 1);
+ View nextView = container.getChildAt(removingImageIndex);
+ if (preView instanceof EditText && nextView instanceof EditText) {
+ EditText preEdit = (EditText) preView;
+
+ EditText nextEdit = (EditText) nextView;
+ String str1 = preEdit.getText().toString();
+ String str2 = nextEdit.getText().toString();
+ String mergeText = "";
+ if (str2.length() > 0) {
+ mergeText = str1 + "\n" + str2;
+ } else {
+ mergeText = str1;
+ }
+
+ container.removeView(nextEdit);
+ preEdit.setText(mergeText);
+ //设置光标的定位
+ preEdit.requestFocus();
+ preEdit.setSelection(str1.length(), str1.length());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void updateUploadState(String uri, String url) {
+ int c = container.getChildCount();
+ if (c == 0) {
+ return;
+ }
+ View frame = null;
+ for (int i = 0; i < c; i++) {
+ String key = container.getChildAt(i).getTag().toString();
+ if (!VIEW_TAG_KV_IMAGE.equals(key)) {
+ continue;
+ }
+ String uriKey = container.getChildAt(i).getTag(VIEW_TAG_VALUE_IMAGE_URI).toString();
+ if (!uriKey.equals(uri)) {
+ continue;
+ }
+ frame = container.getChildAt(i);
+ break;
+ }
+
+ if (null == frame) {
+ return;
+ }
+ frame.setTag(VIEW_TAG_VALUE_IMAGE_URL, url);
+ frame.findViewById(R.id.upload_progress_bar).setVisibility(View.GONE);
+ frame.findViewById(R.id.remove).setVisibility(View.VISIBLE);
+ }
+
+
+ private EditText buildEditText() {
+ EditText editText = new DeletableEditText(getContext());
+ LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ editText.setLayoutParams(layoutParams);
+ editText.setTextSize(16);
+ editText.setCursorVisible(true);
+ editText.setBackground(null);
+ editText.setOnKeyListener(keyListener);
+ editText.setOnFocusChangeListener(focusListener);
+ editText.setPadding(0, DP_4, 0, DP_4);
+ editText.setTag(VIEW_TAG_KV_INPUT);
+ editText.setFilters(new InputFilter[]{new InputFilter() {
+ @Override
+ public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+ if (TextUtils.equals("@", source)) {
+ if (onRichAtListener != null) {
+ onRichAtListener.onCall(editText);
+ }
+ }
+ return null;
+ }
+ }});
+ return editText;
+ }
+
+ public List buildRichEditData() {
+ List dataList = new ArrayList<>();
+ int num = container.getChildCount();
+ for (int index = 0; index < num; index++) {
+ View itemView = container.getChildAt(index);
+ RichContentModel richContentModel = new RichContentModel();
+ if (itemView instanceof EditText) {
+ EditText item = (EditText) itemView;
+ richContentModel.content = item.getText().toString();
+ richContentModel.type = 0;
+ } else if (itemView instanceof FrameLayout) {
+ Object obj = itemView.getTag(VIEW_TAG_VALUE_IMAGE_URL);
+ if (null == obj) {
+ return null;
+ }
+
+ richContentModel.content = obj.toString();
+ richContentModel.type = 1;
+ }
+ if (!TextUtils.isEmpty(richContentModel.content)) {
+ dataList.add(richContentModel);
+ }
+ }
+ return dataList;
+ }
+
+ public void removeAllViews() {
+ if (container != null) {
+ container.removeAllViews();
+
+ initFirstEditText();
+ }
+ }
+
+ public static class RichContentModel{
+ public RichContentModel() {
+ }
+
+ public RichContentModel(int type, String content) {
+ this.type = type;
+ this.content = content;
+ }
+
+ /**
+ * content type, 0 is text, 1 is image
+ */
+ public int type = 0;
+ public String content;
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/PreloadWebView.java b/app/src/main/java/com/seafile/seadroid2/view/webview/PreloadWebView.java
index 24c8885df..cfef3de47 100644
--- a/app/src/main/java/com/seafile/seadroid2/view/webview/PreloadWebView.java
+++ b/app/src/main/java/com/seafile/seadroid2/view/webview/PreloadWebView.java
@@ -5,6 +5,7 @@
import android.content.MutableContextWrapper;
import android.os.Looper;
+import com.seafile.seadroid2.R;
import com.seafile.seadroid2.SeadroidApplication;
import java.util.Stack;
@@ -35,7 +36,9 @@ public void preload() {
}
private SeaWebView buildWebView() {
- return new SeaWebView(new MutableContextWrapper(SeadroidApplication.getAppContext()));
+ SeaWebView webView = new SeaWebView(new MutableContextWrapper(SeadroidApplication.getAppContext()));
+ webView.setId(R.id.webview);
+ return webView;
}
/**
diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java
index 7d37b0781..cc877c532 100644
--- a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java
+++ b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java
@@ -12,8 +12,9 @@
import com.seafile.seadroid2.account.Account;
import com.seafile.seadroid2.account.SupportAccountManager;
+import com.seafile.seadroid2.view.NestedWebView;
-public class SeaWebView extends WebView {
+public class SeaWebView extends NestedWebView {
public static final String PATH_ACCOUNT_LOGIN = "accounts/login/";
public static String URL_LOGIN = null;
@@ -34,10 +35,10 @@ public SeaWebView(@NonNull Context context, @Nullable AttributeSet attrs, int de
init();
}
- public SeaWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- init();
- }
+// public SeaWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+// super(context, attrs, defStyleAttr, defStyleRes);
+// init();
+// }
@SuppressLint("SetJavaScriptEnabled")
private void init() {
diff --git a/app/src/main/java/com/seafile/seadroid2/widget/prefs/ButtonPreference.java b/app/src/main/java/com/seafile/seadroid2/widget/prefs/ButtonPreference.java
new file mode 100644
index 000000000..40a73c3e3
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/widget/prefs/ButtonPreference.java
@@ -0,0 +1,57 @@
+package com.seafile.seadroid2.widget.prefs;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.google.android.material.button.MaterialButton;
+import com.seafile.seadroid2.R;
+
+public class ButtonPreference extends Preference {
+ public ButtonPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ public ButtonPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ public ButtonPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public ButtonPreference(@NonNull Context context) {
+ super(context);
+ init();
+ }
+
+ private void init() {
+ setLayoutResource(R.layout.layout_logout_view);
+ }
+
+ public MaterialButton getButton() {
+ return button;
+ }
+
+ MaterialButton button;
+
+ @Override
+ protected void onClick() {
+ super.onClick();
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ button = (MaterialButton) holder.findViewById(android.R.id.title);
+ button.setText(getTitle());
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/widget/prefs/CardPreferenceCategory.java b/app/src/main/java/com/seafile/seadroid2/widget/prefs/CardPreferenceCategory.java
new file mode 100644
index 000000000..c6b18af94
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/widget/prefs/CardPreferenceCategory.java
@@ -0,0 +1,49 @@
+package com.seafile.seadroid2.widget.prefs;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceViewHolder;
+
+import com.seafile.seadroid2.R;
+
+public class CardPreferenceCategory extends PreferenceCategory {
+ public CardPreferenceCategory(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ public CardPreferenceCategory(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ public CardPreferenceCategory(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public CardPreferenceCategory(@NonNull Context context) {
+ super(context);
+ init();
+ }
+
+ private void init() {
+ setLayoutResource(R.layout.preference_cardbox);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ TextView titleView = (TextView) holder.findViewById(android.R.id.title);
+ TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
+ titleView.setText(getTitle());
+ summaryView.setText(getSummary());
+ }
+
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/widget/TipTextPreference.java b/app/src/main/java/com/seafile/seadroid2/widget/prefs/TipTextPreference.java
similarity index 93%
rename from app/src/main/java/com/seafile/seadroid2/widget/TipTextPreference.java
rename to app/src/main/java/com/seafile/seadroid2/widget/prefs/TipTextPreference.java
index 1cce26c8f..106a38b10 100644
--- a/app/src/main/java/com/seafile/seadroid2/widget/TipTextPreference.java
+++ b/app/src/main/java/com/seafile/seadroid2/widget/prefs/TipTextPreference.java
@@ -1,15 +1,13 @@
-package com.seafile.seadroid2.widget;
+package com.seafile.seadroid2.widget.prefs;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
-import com.blankj.utilcode.util.ToastUtils;
import com.seafile.seadroid2.R;
public class TipTextPreference extends Preference {
diff --git a/app/src/main/res/drawable-xhdpi/tip_no_items.png b/app/src/main/res/drawable-xhdpi/tip_no_items.png
new file mode 100644
index 0000000000000000000000000000000000000000..3ad016f578447db4352f1a9eb52787d4b19c00e8
GIT binary patch
literal 4221
zcmXX}c{r5a8y*QGTgW!qV=|JANeJ1-PK-4>V^?+!$y$+hEF<3-*_9^yk|j&BWi%1V
zzB96A&t87-_w{?O>pj=~+|T{o=RD{9^~M?9)nd8;xc~xzm~^y}#vl+C_rD*Q8el4o
zcv^uZ(n#M_14w6PW)>G0S65dzHa7P5_Li5IXJ=<8{tF6)GBq{zE5HB&$N&uR000aDJU%|YzP=8W{)+!H00{(u0XM)LC{0gKkByD}?FGaD
z0BpCmwg4YM2?&7ruj&7IWn~3m0L@=#00ZFfNq+wP2`GU^fC2FQe=#~by0f#hy}kX{
z1mOR5+}HpB&;?8c3=Ux67y!AjumH3Jw*lM$U;?aw2T%n9sP^^s0cZa5<;&j{=mjuP
zB9qAg_^UiPH~{tnoPnXv&dz|#Tw7ZMJpWpqoSXpWOG`_?fBy!$j*gBN%AZ97Z{QPX
zeAfi@yP1iehV{zv{*pM~8iH$czBp#6WOtucuD2=s{$=*Ad-+FAd1}X|r#xNcG+K{h
zE0)T_F2in^N<6V_!1Xdf|J+slU$A
zYeh+!{sZ%L%3Izxr4uGT*Oz(&axZ<hO&>#FULp7Ntya$1)C+>yi|B+H1>NGR%*N!p^qhJN8xCnM@ICD2Op#oCka{^uc1WAx
z|FfmqVV!1>=Al9LRbAd)ft%V8wdC|7BdBnf=~?vPi|3v?A&Ivk@tLJf#hQ@n>tSEq
z)`=4pgeTeI0%v)Sib0c2QUplo#0Q?f68o+hSh-GL4(4;yIIl^=1?5`nLx%B0=Uq#m
zLz9eRb_D-H$n6bCDr4?DTLH!fm%h*9Xo7NmBcf-H|3{`9+McJx%rtax=BkJ~N09p*
z;_8~FGotNPhPCxvx!RAoll!_W8hP+8pZW3f7-xCqiBv8Q7O?iQc9AS%y4Bv}@OE|)
zLi61H>|e;;H_=(OBdR>aahUQ4A6#uss2W5eU~*3ra(nO|32}w#c~Lec$<~`w<5g3B
z-wTrR7MDTmsBBRG!
zk*Q}F^brdNS}i;pHHzLuLcOHTS2liYmAi%(DI$Ae!M@|Nom&5JqK?hVCqf+L3vwkL7kyW9({4(J=c7FGMBWgLbf#%6j#0tq3e
zDQ0E<92W6R4=fG5y)GQSMZ4}kdN|0NkvMaDN-gWPkQz>Yqaf{8=Rjd4*_#a}>JpnlHvP_}$#ldrSP&sQ
zF{h{OXxl$tv*+mr{@08=0lhrGKX;Z6Jd>c}_V0faW>FfDMrk)MwbCecz{PAQ{nG32
zIgC0Lt9Wzk$V*^M!}S0jF{;onQ0yqh+_c_uFs%-Ti{7aeQHg~`;n5Ke4898jr2De+
zha)-$s%;Y&Wp6>Xf?HqSwICK>4d+|SsrzKfFl$s<_FF(V0C6zy&=BSrcbG+1tn|@T
zvHPy%Fhd7dlA}+;FR$!yo_-$aQoQsns_>F6x!J?d{V|Puo5-I_ph3bXcNN2ABZV3|
zydYXyE%0rTDWVuHTV%qxyPHEp|XGl{r}&&wMuTLQkauhSQ2hC$zqd
zd{kDUj{x<6(EjQ8P96QI{RjtgX>lZqs`DZ!yCE*s{p@+d^tqJii-lCiSadpVrFJdB
z4Me<%HuZL%7l*1(C=brvrjlo8=a$rDm9nqR)B+V%;3aNAIdFwj0zVZ`nDbr~nlGeTmkCVUd?sr_gKDWaOc+BUvPqK!ex8rGIbl?_qo6-;>mdm;E9
z=dzjj+!&q8aV$wn#1Ilo8ffTk;fNq`MVjA1-#k%&T7zdyI^*hS`VmE|Y3h4kWmObP
zrwvW&9Q_&OTG()~5|-y__G^y?WNe2{D=^Yghr=SX4Y1r5%3&93oS7{WcX-e3yha=2
zLSBYmKQF<_UQ@icvqDj0jr^kU_APyi4Eq%sg(iK;ZV(QW{>n%1dd5M23t~%?5L8+~
z$Sa1#!N63V&TWf)l%poYj1eC$dgbBkP%C?sfzeIYD~}zXzlWmJHF4PL$8x%VvdEJQ
zrv470GEol%q0dDPn!7H!524D#LmGz^k`yzwGIj&TlEG4*{%}}~d*)$6s{1g?&U@EJ
z;(lL$@#X`|M$AgZcai&oP&yS%V{iiMwWZdWXy{mN08bLWKKy`eUqVl$MW^T6h&pyU
zgZj?0M+W+B43L83Rqi%2nc9gr*VEUoY5sbk6(&v9nV_UDL4D
zgC3+q3tUF5-=$fiEQ#%~A6rZ={dG5O~{4O6Qh2P%=3#PAtzBWMf-tK*=b$pGby`0C+
zz)BQau_6}0VlzC`5GuG>F!#O43>`j#a`n2srE=Dp&7&Y2V6*t-9^M(!fJ$p~s+
zprIgQb}ZkZM#Csr<_N2bY`|ZV;iJq;E1}0USefk%4n-5_Bf{UViLGhw?m?G%k}Yud
zJ@fGc@yY(STA5CtR#LzW70}Q$eF$A-QniVe<)P^skFa1fVKG#6!sQYjb>3`^%?yfv
zt(c)pz)AD_uT`RUk&_!t!%NHZjX>P&mS_77v_!0Jme89`F^gOOj=6KKuqyePqwIrE
zShjs^XNgYNJXVu-U$5a|dK)PX*nfZ(N(?XA{EkU?u=WX)sp^TvIg>v18B-0T&>u_U
zUTxuvB5Ooo`zNSinK@j#xc#XcsST5UxaEmX6+@v&RQM_eQXzG)IXeQAmKc9NS9BUR
zUsJK_9jUL8hY4XA#j@poUbtmRDz^*fwICwD9TWmDSipoct*WTPvL(LIE+fg4~2)gYvb#tjbq+zL^fBz#t_ECG92?=Vw-x3
zGoaJzsJn#;q2ca&L*756W0xMtA1vo%US6
zX$$zrFE7uxWZB`b6!-5nBuQQ&xZ?T3B)hN6Q5ndPFNa|fiMQ^@)iS7yc2_G7!pZzj
zlp+`IYALKbA;v>0-r#)FFXYMkv!xkfO;^GH!D-3J_Fk
zZ}vm^8h=$MWdzcz$IGP$cSj3dz7Q&XL!XL<v^0V|HDO^#bF(|=@IDo}O3f#gx7(x5_Jc&j1FMX=#jQ?}(@P_wUm|4pP#HpX{TL1O=L`kB5Xu%2^*~5YrxyC+{tZc>M*loORAXb66ywr=Tod*1VuXT)zw@r
zD|+GFkkqK??(h4A?BQ!|JNEjfN0h|_p4Lv|;pv-N99&`KtyiuF#?dBkajqISKgnhu
z4Sw1DL*2F3s#_Nj`r)63z%JXI99ztCA+z#>#;GCU=7cQySv!h$)?AL%SuJdspJn;v
zm80E3b>>33rL6>ey;XEmg;(vYowb6?5ZMh6sjIE84xH3=K1sRZo-Rnq|EFS2I2HA+
zMN#$2gXyttSQ-VolHIOkzE;N}+;34Lu{-TzS=SpNglsCuzO$AKGuGwtx_mY^DBjrt
bMAKQ@ZZFbbi*Ez?hXv_q+(nkFIXw9vww-pr
literal 0
HcmV?d00001
diff --git a/app/src/main/res/drawable/baseline_delete_20.xml b/app/src/main/res/drawable/baseline_delete_20.xml
new file mode 100644
index 000000000..08172bb13
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_delete_20.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_calendar_alt_solid.xml b/app/src/main/res/drawable/ic_calendar_alt_solid.xml
new file mode 100644
index 000000000..028421067
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calendar_alt_solid.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_check_square_solid.xml b/app/src/main/res/drawable/ic_check_square_solid.xml
new file mode 100644
index 000000000..e251c6c4c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_check_square_solid.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_duration.xml b/app/src/main/res/drawable/ic_duration.xml
new file mode 100644
index 000000000..12972fb5c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_duration.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_email.xml b/app/src/main/res/drawable/ic_email.xml
new file mode 100644
index 000000000..79c91cb4d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_email.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_alt_solid.xml b/app/src/main/res/drawable/ic_file_alt_solid.xml
new file mode 100644
index 000000000..ed3ad1697
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_alt_solid.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_links.xml b/app/src/main/res/drawable/ic_links.xml
new file mode 100644
index 000000000..43fc4c7bd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_links.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml
new file mode 100644
index 000000000..8195fd482
--- /dev/null
+++ b/app/src/main/res/drawable/ic_location.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_long_text.xml b/app/src/main/res/drawable/ic_long_text.xml
new file mode 100644
index 000000000..ad2942619
--- /dev/null
+++ b/app/src/main/res/drawable/ic_long_text.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_multiple_selection.xml b/app/src/main/res/drawable/ic_multiple_selection.xml
new file mode 100644
index 000000000..6a5d53834
--- /dev/null
+++ b/app/src/main/res/drawable/ic_multiple_selection.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_number.xml b/app/src/main/res/drawable/ic_number.xml
new file mode 100644
index 000000000..f6b7e21eb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_number.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_picture.xml b/app/src/main/res/drawable/ic_picture.xml
new file mode 100644
index 000000000..3d16723db
--- /dev/null
+++ b/app/src/main/res/drawable/ic_picture.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_single_election.xml b/app/src/main/res/drawable/ic_single_election.xml
new file mode 100644
index 000000000..72f3182c6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_single_election.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_single_line_text.xml b/app/src/main/res/drawable/ic_single_line_text.xml
new file mode 100644
index 000000000..95dfa614f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_single_line_text.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_star2.xml b/app/src/main/res/drawable/ic_star2.xml
new file mode 100644
index 000000000..3077dd5bc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_star2.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_url.xml b/app/src/main/res/drawable/ic_url.xml
new file mode 100644
index 000000000..e9745ae41
--- /dev/null
+++ b/app/src/main/res/drawable/ic_url.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_user_collaborator.xml b/app/src/main/res/drawable/ic_user_collaborator.xml
new file mode 100644
index 000000000..eb65d12d1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_user_collaborator.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/indicator_drawable_2.xml b/app/src/main/res/drawable/indicator_drawable_2.xml
deleted file mode 100644
index 5df9eb433..000000000
--- a/app/src/main/res/drawable/indicator_drawable_2.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/selector_password_toggle.xml b/app/src/main/res/drawable/selector_password_toggle.xml
new file mode 100644
index 000000000..1148b17a1
--- /dev/null
+++ b/app/src/main/res/drawable/selector_password_toggle.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/indicator_drawable.xml b/app/src/main/res/drawable/shape_solid_ff_radius_8.xml
similarity index 64%
rename from app/src/main/res/drawable/indicator_drawable.xml
rename to app/src/main/res/drawable/shape_solid_ff_radius_8.xml
index fa55bc439..f5b590238 100644
--- a/app/src/main/res/drawable/indicator_drawable.xml
+++ b/app/src/main/res/drawable/shape_solid_ff_radius_8.xml
@@ -1,6 +1,5 @@
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_solid_grey100_radius_8.xml b/app/src/main/res/drawable/shape_solid_grey100_radius_8.xml
new file mode 100644
index 000000000..073a68784
--- /dev/null
+++ b/app/src/main/res/drawable/shape_solid_grey100_radius_8.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_stroke1_radius8_solid_grey.xml b/app/src/main/res/drawable/shape_stroke1_radius8_solid_grey.xml
new file mode 100644
index 000000000..2213cd04b
--- /dev/null
+++ b/app/src/main/res/drawable/shape_stroke1_radius8_solid_grey.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_stroke1_radius8_solid_white.xml b/app/src/main/res/drawable/shape_stroke1_radius8_solid_white.xml
new file mode 100644
index 000000000..c3c79cb42
--- /dev/null
+++ b/app/src/main/res/drawable/shape_stroke1_radius8_solid_white.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/account_detail.xml b/app/src/main/res/layout/account_detail.xml
index cff6027f0..bc1a2183a 100644
--- a/app/src/main/res/layout/account_detail.xml
+++ b/app/src/main/res/layout/account_detail.xml
@@ -7,34 +7,31 @@
-
+ android:paddingHorizontal="@dimen/app_padding_horizontal">
-
-
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/app_widget_top_margin_small"
+ android:hint="@string/email_hint"
+ app:endIconDrawable="@drawable/clear_text"
+ app:endIconMode="clear_text">
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+ android:layout_marginBottom="@dimen/et_margin_bottom"
+ android:importantForAutofill="auto"
+ android:inputType="textPassword" />
+
+
-
-
+ android:layout_marginTop="@dimen/app_widget_top_margin_large"
+ android:layout_marginEnd="@dimen/app_padding_horizontal" />
diff --git a/app/src/main/res/layout/account_list_footer.xml b/app/src/main/res/layout/account_list_footer.xml
index 9f11c3d19..01ff7433c 100644
--- a/app/src/main/res/layout/account_list_footer.xml
+++ b/app/src/main/res/layout/account_list_footer.xml
@@ -14,5 +14,6 @@
android:layout_marginHorizontal="16dp"
android:text="@string/add_account"
android:textAlignment="center"
+ android:textColor="@color/white"
android:textSize="@dimen/long_btn_txt_size" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_sdoc_comment.xml b/app/src/main/res/layout/activity_sdoc_comment.xml
new file mode 100644
index 000000000..5a4010868
--- /dev/null
+++ b/app/src/main/res/layout/activity_sdoc_comment.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_sea_webview.xml b/app/src/main/res/layout/activity_sea_webview.xml
index 4110d1950..2df67fa36 100644
--- a/app/src/main/res/layout/activity_sea_webview.xml
+++ b/app/src/main/res/layout/activity_sea_webview.xml
@@ -1,17 +1,38 @@
-
+ android:layout_height="match_parent">
+
+
-
+
-
+
+
+
+
+
-
\ No newline at end of file
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ android:fitsSystemWindows="true"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_sea_webview_pro.xml b/app/src/main/res/layout/activity_sea_webview_pro.xml
index 522a79a81..6820b1abd 100644
--- a/app/src/main/res/layout/activity_sea_webview_pro.xml
+++ b/app/src/main/res/layout/activity_sea_webview_pro.xml
@@ -12,15 +12,16 @@
android:fitsSystemWindows="true">
+ app:layout_scrollFlags="scroll|enterAlways"
+ app:toolbarId="@+id/toolbar_actionbar">
+ layout="@layout/toolbar_actionbar_progress_bar"
+ app:layout_collapseMode="parallax" />
@@ -29,27 +30,61 @@
android:id="@+id/nsv"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:fillViewport="false"
+ android:fillViewport="true"
android:fitsSystemWindows="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_sdoc_directory.xml b/app/src/main/res/layout/dialog_sdoc_directory.xml
new file mode 100644
index 000000000..52f52f3ab
--- /dev/null
+++ b/app/src/main/res/layout/dialog_sdoc_directory.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_sdoc_profile.xml b/app/src/main/res/layout/dialog_sdoc_profile.xml
new file mode 100644
index 000000000..1d72c36c2
--- /dev/null
+++ b/app/src/main/res/layout/dialog_sdoc_profile.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_ssl_confirm.xml b/app/src/main/res/layout/dialog_ssl_confirm.xml
index 326eac4b0..70c75583d 100644
--- a/app/src/main/res/layout/dialog_ssl_confirm.xml
+++ b/app/src/main/res/layout/dialog_ssl_confirm.xml
@@ -1,122 +1,125 @@
-
-
-
+ android:layout_height="match_parent">
+
+
-
-
-
-
+ android:layout_margin="16dp"
+ android:orientation="vertical">
+ android:orientation="vertical"
+ android:textColor="@color/black">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:text="@string/period_of_validity"
+ android:textColor="@color/black"
+ android:textSize="@dimen/dialog_msg_title_txt_size"
+ android:textStyle="bold" />
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_dirent.xml b/app/src/main/res/layout/item_dirent.xml
index fbaa61f7c..909e9dc60 100644
--- a/app/src/main/res/layout/item_dirent.xml
+++ b/app/src/main/res/layout/item_dirent.xml
@@ -102,15 +102,12 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-
+ app:dividerInsetStart="@dimen/rv_item_icon_width"
+ app:layout_constraintBottom_toBottomOf="parent" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_repo.xml b/app/src/main/res/layout/item_repo.xml
index 8b93c2c08..2e43f96c0 100644
--- a/app/src/main/res/layout/item_repo.xml
+++ b/app/src/main/res/layout/item_repo.xml
@@ -72,4 +72,13 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_sdoc_comment.xml b/app/src/main/res/layout/item_sdoc_comment.xml
new file mode 100644
index 000000000..ee802319c
--- /dev/null
+++ b/app/src/main/res/layout/item_sdoc_comment.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_sdoc_directory.xml b/app/src/main/res/layout/item_sdoc_directory.xml
new file mode 100644
index 000000000..814086dc7
--- /dev/null
+++ b/app/src/main/res/layout/item_sdoc_directory.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_starred.xml b/app/src/main/res/layout/item_starred.xml
index d1e20d7ba..59a802eed 100644
--- a/app/src/main/res/layout/item_starred.xml
+++ b/app/src/main/res/layout/item_starred.xml
@@ -15,7 +15,7 @@
android:layout_centerVertical="true"
android:layout_marginVertical="4dp"
android:contentDescription="@null"
- android:padding="8dp"
+ android:padding="12dp"
android:scaleType="centerCrop"
android:src="@drawable/baseline_repo_24"
app:layout_constraintBottom_toBottomOf="parent"
diff --git a/app/src/main/res/layout/item_user_avatar.xml b/app/src/main/res/layout/item_user_avatar.xml
new file mode 100644
index 000000000..42d1799e7
--- /dev/null
+++ b/app/src/main/res/layout/item_user_avatar.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_avatar_username_round.xml b/app/src/main/res/layout/layout_avatar_username_round.xml
new file mode 100644
index 000000000..d1383ab2a
--- /dev/null
+++ b/app/src/main/res/layout/layout_avatar_username_round.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_detail_text_round.xml b/app/src/main/res/layout/layout_detail_text_round.xml
new file mode 100644
index 000000000..2acc09fa4
--- /dev/null
+++ b/app/src/main/res/layout/layout_detail_text_round.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_details_keyview_valuecontainer.xml b/app/src/main/res/layout/layout_details_keyview_valuecontainer.xml
new file mode 100644
index 000000000..ef1c101e0
--- /dev/null
+++ b/app/src/main/res/layout/layout_details_keyview_valuecontainer.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_empty.xml b/app/src/main/res/layout/layout_empty.xml
new file mode 100644
index 000000000..dce427ed0
--- /dev/null
+++ b/app/src/main/res/layout/layout_empty.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_image.xml b/app/src/main/res/layout/layout_image.xml
new file mode 100644
index 000000000..3ae1608ab
--- /dev/null
+++ b/app/src/main/res/layout/layout_image.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_logout_view.xml b/app/src/main/res/layout/layout_logout_view.xml
new file mode 100644
index 000000000..23df0393a
--- /dev/null
+++ b/app/src/main/res/layout/layout_logout_view.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_textview.xml b/app/src/main/res/layout/layout_textview.xml
new file mode 100644
index 000000000..435dacf15
--- /dev/null
+++ b/app/src/main/res/layout/layout_textview.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_upload_file.xml b/app/src/main/res/layout/layout_upload_file.xml
new file mode 100644
index 000000000..8f818e769
--- /dev/null
+++ b/app/src/main/res/layout/layout_upload_file.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/preference_cardbox.xml b/app/src/main/res/layout/preference_cardbox.xml
new file mode 100644
index 000000000..2e8c4d8bb
--- /dev/null
+++ b/app/src/main/res/layout/preference_cardbox.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_dialog_share_password.xml b/app/src/main/res/layout/view_dialog_share_password.xml
index e02402b60..7cd2ad5fc 100644
--- a/app/src/main/res/layout/view_dialog_share_password.xml
+++ b/app/src/main/res/layout/view_dialog_share_password.xml
@@ -9,7 +9,7 @@
android:layout_marginHorizontal="4dp"
android:orientation="vertical">
-
-
-
@@ -85,8 +87,8 @@
app:showAsAction="never" />
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 5ca3c8e25..7c6a333f9 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -480,7 +480,6 @@
파일 업로드 중...
파일 다운로드 중...
업로드를 끝냈습니다
- %1$d%% %2$s
- 파일 %1$d개 업로드 중(%2$d%%)
@@ -628,4 +627,6 @@
최근 수정
오름 차순
폴더 먼저
+
+
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
index 902354cdc..f0ddbbce5 100644
--- a/app/src/main/res/values-night/styles.xml
+++ b/app/src/main/res/values-night/styles.xml
@@ -33,7 +33,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+