accounts = Lists.newArrayList();
-
- String[] projection = {
- AccountDBHelper.ACCOUNT_COLUMN_SERVER,
- AccountDBHelper.ACCOUNT_COLUMN_EMAIL,
- AccountDBHelper.ACCOUNT_COLUMN_NAME,
- AccountDBHelper.ACCOUNT_COLUMN_TOKEN
- };
-
- Cursor c = database.query(
- AccountDBHelper.ACCOUNT_TABLE_NAME,
- projection,
- null,
- null,
- null, // don't group the rows
- null, // don't filter by row groups
- null // The sort order
- );
-
- c.moveToFirst();
- while (!c.isAfterLast()) {
- Account account = cursorToAccount(c);
- accounts.add(account);
- c.moveToNext();
- }
-
- c.close();
- return accounts;
- }
-
- private Account cursorToAccount(Cursor cursor) {
- return new Account(cursor.getString(0), cursor.getString(1), cursor.getString(2), cursor.getString(3), false);
- }
-
- private ServerInfo getServerInfo(SQLiteDatabase database, String url) {
- String[] projection = {SERVER_INFO_COLUMN_URL, SERVER_INFO_COLUMN_VERSION, SERVER_INFO_COLUMN_FEATURE};
-
- Cursor c = database.query(SERVER_INFO_TABLE_NAME,
- projection,
- "url=?",
- new String[] {url},
- null, // don't group the rows
- null, // don't filter by row groups
- null); // The sort order
-
- if (!c.moveToFirst()) {
- c.close();
- return null;
- }
-
- ServerInfo serverInfo = cursorToServerInfo(c);
-
- c.close();
- return serverInfo;
- }
-
- private ServerInfo cursorToServerInfo(Cursor cursor) {
- String url = cursor.getString(0);
- String version = cursor.getString(1);
- String features = cursor.getString(2);
- ServerInfo serverInfo = new ServerInfo(url, version, features);
- return serverInfo;
- }
-
-}
diff --git a/app/src/main/java/com/seafile/seadroid2/account/AccountInfo.java b/app/src/main/java/com/seafile/seadroid2/account/AccountInfo.java
index 075004fa6..c563f2dde 100644
--- a/app/src/main/java/com/seafile/seadroid2/account/AccountInfo.java
+++ b/app/src/main/java/com/seafile/seadroid2/account/AccountInfo.java
@@ -1,33 +1,42 @@
package com.seafile.seadroid2.account;
import com.seafile.seadroid2.util.Utils;
+
import org.json.JSONException;
import org.json.JSONObject;
/**
* This class used to manage Account information
- *
*/
public class AccountInfo {
- private static final String DEBUG_TAG = "AccountInfo";
-
public static final String SPACE_USAGE_SEPERATOR = " / ";
private long usage;
private long total;
private String email;
private String server;
+ private String avatar_url;
private String name;
+ private String space_usage;// "6.0382327%"
+// private String login_id;
+// private String department;
+// private String contact_email;
+// private String institution;
+// private boolean is_staff;
+// private int file_updates_email_interval;
+// private int collaborate_email_interval;
- private AccountInfo() {}
+ private AccountInfo() {
+ }
- public static AccountInfo fromJson(JSONObject accountInfo, String server) throws JSONException {
+ public static AccountInfo fromJson(JSONObject accountInfo, String server) throws JSONException {
AccountInfo info = new AccountInfo();
info.server = server;
info.usage = accountInfo.getLong("usage");
info.total = accountInfo.getLong("total");
info.email = accountInfo.getString("email");
info.name = accountInfo.optString("name");
-
+ info.avatar_url = accountInfo.optString("avatar_url");
+ info.space_usage = accountInfo.optString("space_usage");
return info;
}
@@ -51,10 +60,22 @@ public String getName() {
return name;
}
+ public String getDisplayName() {
+ String server = Utils.stripSlashes(getServer());
+ return Utils.assembleUserName(name, email, server);
+ }
+
+ public void setServer(String server) {
+ this.server = server;
+ }
+
public String getSpaceUsed() {
String strUsage = Utils.readableFileSize(usage);
String strTotal = Utils.readableFileSize(total);
return strUsage + SPACE_USAGE_SEPERATOR + strTotal;
}
+ public String getAvatarUrl() {
+ return avatar_url;
+ }
}
diff --git a/app/src/main/java/com/seafile/seadroid2/account/AccountManager.java b/app/src/main/java/com/seafile/seadroid2/account/AccountManager.java
deleted file mode 100644
index 8744ec353..000000000
--- a/app/src/main/java/com/seafile/seadroid2/account/AccountManager.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.seafile.seadroid2.account;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import androidx.annotation.Nullable;
-
-import com.google.common.collect.Lists;
-import com.seafile.seadroid2.cameraupload.CameraUploadManager;
-import com.seafile.seadroid2.data.ServerInfo;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 2023-3-28 Convert to a proxy class
- *
- * Account Manager.
- * note the differences between {@link Account} and {@link AccountInfo}
- */
-public class AccountManager {
- WeakReference contextWeakReference = null;
-
- public AccountManager(Context context) {
- contextWeakReference = new WeakReference<>(context);
- }
-
- public List getAccountList() {
- return SupportAccountManager.getInstance().getAccountList();
- }
-
- public List getSignedInAccountList() {
- return SupportAccountManager.getInstance().getSignedInAccountList();
- }
-
- @Nullable
- public Account getCurrentAccount() {
- return SupportAccountManager.getInstance().getCurrentAccount();
- }
-
- public Account getSeafileAccount(android.accounts.Account androidAccount) {
-
- return SupportAccountManager.getInstance().getSeafileAccount(androidAccount);
- }
-
- public void setServerInfo(Account account, ServerInfo serverInfo) {
- SupportAccountManager.getInstance().setServerInfo(account, serverInfo);
- }
-
- /**
- * Return cached ServerInfo
- *
- * @param account
- * @return ServerInfo. Will never be null.
- */
- public ServerInfo getServerInfo(Account account) {
- return SupportAccountManager.getInstance().getServerInfo(account);
- }
-
- /**
- * save current Account info to SharedPreference
- * current means the Account is now in using at the foreground if has multiple accounts
- *
- * @param accountName
- */
- public void saveCurrentAccount(String accountName) {
- SupportAccountManager.getInstance().saveCurrentAccount(accountName);
- }
-
- /**
- * when user sign out, delete authorized information of the current Account instance.
- * If Camera Upload Service is running under the Account, stop the service.
- */
- public void signOutAccount(Account account) {
- SupportAccountManager.getInstance().signOutAccount(account);
- }
-
- /**
- * get all email texts from database in order to auto complete email address
- *
- * @return
- */
- public ArrayList getAccountAutoCompleteTexts() {
- return SupportAccountManager.getInstance().getAccountAutoCompleteTexts();
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/seafile/seadroid2/account/Authenticator.java b/app/src/main/java/com/seafile/seadroid2/account/Authenticator.java
index 1c0605d32..78178f92c 100644
--- a/app/src/main/java/com/seafile/seadroid2/account/Authenticator.java
+++ b/app/src/main/java/com/seafile/seadroid2/account/Authenticator.java
@@ -18,15 +18,12 @@
/*
* Seafile Authenticator.
- *
*/
public class Authenticator extends AbstractAccountAuthenticator {
private String DEBUG_TAG = "SeafileAuthenticator";
private final Context context;
- private com.seafile.seadroid2.account.AccountManager manager;
-
/**
* Type of the auth token used (there is only one type)
*/
@@ -35,49 +32,51 @@ public class Authenticator extends AbstractAccountAuthenticator {
/**
* Key of Server URI in userData
*/
- public final static String KEY_SERVER_URI = "server";
+ public static final String KEY_SERVER_URI = "server";
/**
* Key of email in userData
*/
- public final static String KEY_EMAIL = "email";
+ public static final String KEY_EMAIL = "email";
/**
* Key of name in userData
*/
- public final static String KEY_NAME = "name";
+ public static final String KEY_NAME = "name";
+ /**
+ * Key of avatar_url in userData
+ */
+ public static final String KEY_AVATAR_URL = "avatar_url";
/**
* Key of Server version in userData
*/
- public final static String KEY_SERVER_VERSION = "version";
+ public static final String KEY_SERVER_VERSION = "version";
/**
* Key of Server Feature-list in userData
*/
- public final static String KEY_SERVER_FEATURES = "features";
+ public static final String KEY_SERVER_FEATURES = "features";
/**
* Key of shib_setting in userData
*/
- public final static String KEY_SHIB = "shib";
+ public static final String KEY_SHIB = "shib";
/**
* Two Factor Auth in userData
*/
- public final static String SESSION_KEY = "sessionKey";
+ public static final String SESSION_KEY = "sessionKey";
public Authenticator(Context context) {
super(context);
Log.d(DEBUG_TAG, "SeafileAuthenticator created.");
this.context = context;
- this.manager = new com.seafile.seadroid2.account.AccountManager(context);
}
/**
* We have no properties.
*/
@Override
- public Bundle editProperties(
- AccountAuthenticatorResponse r, String s) {
+ public Bundle editProperties(AccountAuthenticatorResponse r, String s) {
Log.d(DEBUG_TAG, "editProperties");
throw new UnsupportedOperationException();
@@ -90,7 +89,7 @@ public Bundle addAccount(AccountAuthenticatorResponse response,
String[] requiredFeatures,
Bundle options) throws NetworkErrorException {
- Log.d(DEBUG_TAG, "addAccount of type "+accountType);
+ Log.d(DEBUG_TAG, "addAccount of type " + accountType);
if (authTokenType != null && !authTokenType.equals(Authenticator.AUTHTOKEN_TYPE)) {
Bundle result = new Bundle();
@@ -116,7 +115,7 @@ public Bundle confirmCredentials(
Bundle bundle) throws NetworkErrorException {
Log.d(DEBUG_TAG, "confirmCredentials");
- Account a = manager.getSeafileAccount(account);
+ Account a = SupportAccountManager.getInstance().getSeafileAccount(account);
DataManager manager = new DataManager(a);
try {
@@ -185,8 +184,8 @@ public String getAuthTokenLabel(String authTokenType) {
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response,
- android.accounts.Account account,
- String authTokenType, Bundle options) throws NetworkErrorException {
+ android.accounts.Account account,
+ String authTokenType, Bundle options) throws NetworkErrorException {
Log.d(DEBUG_TAG, "updateCredentials");
if (authTokenType != null && !authTokenType.equals(Authenticator.AUTHTOKEN_TYPE)) {
@@ -202,7 +201,7 @@ public Bundle updateCredentials(AccountAuthenticatorResponse response,
intent.putExtra(SeafileAuthenticatorActivity.ARG_ACCOUNT_NAME, account.name); // will be overridden
intent.putExtra(SeafileAuthenticatorActivity.ARG_EDIT_OLD_ACCOUNT_NAME, account.name);
intent.putExtra(SeafileAuthenticatorActivity.ARG_IS_EDITING, true);
- boolean is_shib = manager.getSeafileAccount(account).isShib();
+ boolean is_shib = SupportAccountManager.getInstance().getSeafileAccount(account).isShib();
intent.putExtra(SeafileAuthenticatorActivity.ARG_SHIB, is_shib);
final Bundle bundle = new Bundle();
@@ -212,7 +211,7 @@ public Bundle updateCredentials(AccountAuthenticatorResponse response,
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse r,
- android.accounts.Account account, String[] strings) throws NetworkErrorException {
+ android.accounts.Account account, String[] strings) throws NetworkErrorException {
Log.d(DEBUG_TAG, "hasFeatures");
final Bundle result = new Bundle();
diff --git a/app/src/main/java/com/seafile/seadroid2/account/SupportAccountManager.java b/app/src/main/java/com/seafile/seadroid2/account/SupportAccountManager.java
index 661ef2e5f..8f7be92d8 100644
--- a/app/src/main/java/com/seafile/seadroid2/account/SupportAccountManager.java
+++ b/app/src/main/java/com/seafile/seadroid2/account/SupportAccountManager.java
@@ -1,15 +1,20 @@
package com.seafile.seadroid2.account;
-import android.content.Context;
-import android.content.SharedPreferences;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.os.Bundle;
+import android.os.Handler;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.common.collect.Lists;
import com.seafile.seadroid2.SeadroidApplication;
-import com.seafile.seadroid2.cameraupload.CameraUploadManager;
+import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager;
+import com.seafile.seadroid2.config.Constants;
import com.seafile.seadroid2.data.ServerInfo;
+import com.seafile.seadroid2.io.http.IO;
+import com.seafile.seadroid2.util.sp.SPs;
import java.util.ArrayList;
import java.util.List;
@@ -29,43 +34,35 @@ public static SupportAccountManager getInstance() {
return singleton;
}
- @SuppressWarnings("unused")
- private static String DEBUG_TAG = "AccountManager";
- public static final String SHARED_PREF_NAME = "latest_account";
- public static final String SHARED_PREF_ACCOUNT_NAME = "com.seafile.seadroid.account_name";
+ private final android.accounts.AccountManager accountManager;
- /**
- * used to manage multi Accounts when user switch between different Accounts
- */
- private SharedPreferences actMangeSharedPref;
- private SharedPreferences.Editor editor;
-
- private android.accounts.AccountManager accountManager;
-
- public SupportAccountManager() {
+ private SupportAccountManager() {
accountManager = android.accounts.AccountManager.get(SeadroidApplication.getAppContext());
- // used to manage multi Accounts when user switch between different Accounts
- actMangeSharedPref = SeadroidApplication.getAppContext().getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
- editor = actMangeSharedPref.edit();
-
- // migrate old accounts
- AccountDBHelper.migrateAccounts(SeadroidApplication.getAppContext());
}
+ @NonNull
public List getAccountList() {
List list = new ArrayList();
- android.accounts.Account[] availableAccounts = accountManager.getAccountsByType(Account.ACCOUNT_TYPE);
+ String currentAccountName = getCurrentAccountName();
+
+ android.accounts.Account[] availableAccounts = accountManager.getAccountsByType(Constants.Account.ACCOUNT_TYPE);
for (android.accounts.Account availableAccount : availableAccounts) {
Account a = getSeafileAccount(availableAccount);
+ if (!TextUtils.isEmpty(currentAccountName) && a.getSignature().equals(currentAccountName)) {
+ a.is_selected = true;
+ }
+
list.add(a);
}
+
return list;
}
+ @NonNull
public List getSignedInAccountList() {
- List list = new ArrayList();
- android.accounts.Account[] availableAccounts = accountManager.getAccountsByType(Account.ACCOUNT_TYPE);
+ List list = new ArrayList<>();
+ android.accounts.Account[] availableAccounts = accountManager.getAccountsByType(Constants.Account.ACCOUNT_TYPE);
for (android.accounts.Account availableAccount : availableAccounts) {
Account a = getSeafileAccount(availableAccount);
if (a.hasValidToken())
@@ -74,11 +71,23 @@ public List getSignedInAccountList() {
return list;
}
+ /**
+ * save current Account info to SharedPreference
+ * current means the Account is now in using at the foreground if has multiple accounts
+ */
+ public void saveCurrentAccount(String accountName) {
+ SPs.put(Constants.SP.ACCOUNT_CURRENT, accountName);
+
+ //IMPORTANT
+ //reset IO Singleton
+ IO.resetSingleton();
+ }
+
@Nullable
public Account getCurrentAccount() {
- String name = actMangeSharedPref.getString(SHARED_PREF_ACCOUNT_NAME, null);
+ String name = SPs.getString(Constants.SP.ACCOUNT_CURRENT);
- if (name != null) {
+ if (!TextUtils.isEmpty(name)) {
List list = getAccountList();
for (Account a : list) {
if (a.hasValidToken() && a.getSignature().equals(name)) {
@@ -90,14 +99,20 @@ public Account getCurrentAccount() {
return null;
}
+ public String getCurrentAccountName() {
+ return SPs.getString(Constants.SP.ACCOUNT_CURRENT);
+ }
+
+ @NonNull
public Account getSeafileAccount(android.accounts.Account androidAccount) {
String server = accountManager.getUserData(androidAccount, Authenticator.KEY_SERVER_URI);
String email = accountManager.getUserData(androidAccount, Authenticator.KEY_EMAIL);
String name = accountManager.getUserData(androidAccount, Authenticator.KEY_NAME);
+ String avatarUrl = accountManager.getUserData(androidAccount, Authenticator.KEY_AVATAR_URL);
boolean is_shib = accountManager.getUserData(androidAccount, Authenticator.KEY_SHIB) != null;
String token = accountManager.peekAuthToken(androidAccount, Authenticator.AUTHTOKEN_TYPE);
String session_key = accountManager.getUserData(androidAccount, Authenticator.SESSION_KEY);
- return new Account(name, server, email, token, is_shib, session_key);
+ return new Account(name, server, email, avatarUrl, token, is_shib, session_key);
}
public void setServerInfo(Account account, ServerInfo serverInfo) {
@@ -106,31 +121,40 @@ public void setServerInfo(Account account, ServerInfo serverInfo) {
accountManager.setUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_FEATURES, serverInfo.getFeatures());
}
+ public void setUserData(final android.accounts.Account account, final String key, final String value) {
+ accountManager.setUserData(account, key, value);
+ }
+
+ public boolean addAccountExplicitly(android.accounts.Account account, String password, Bundle userdata) {
+ return accountManager.addAccountExplicitly(account, password, userdata);
+ }
+
+ public AccountManagerFuture removeAccount(final android.accounts.Account account,
+ AccountManagerCallback callback, Handler handler) {
+ return accountManager.removeAccount(account, callback, handler);
+ }
+
+ public void setAuthToken(android.accounts.Account account, final String authTokenType, final String authToken) {
+ accountManager.setAuthToken(account, authTokenType, authToken);
+ }
+
+ public String getUserData(final android.accounts.Account account, final String key) {
+ return accountManager.getUserData(account, key);
+ }
+
/**
* Return cached ServerInfo
*
- * @param account
* @return ServerInfo. Will never be null.
*/
+ @NonNull
public ServerInfo getServerInfo(Account account) {
String server = accountManager.getUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_URI);
String version = accountManager.getUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_VERSION);
String features = accountManager.getUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_FEATURES);
- ServerInfo info = new ServerInfo(server, version, features);
- return info;
+ return new ServerInfo(server, version, features);
}
- /**
- * save current Account info to SharedPreference
- * current means the Account is now in using at the foreground if has multiple accounts
- *
- * @param accountName
- */
- public void saveCurrentAccount(String accountName) {
-
- editor.putString(SHARED_PREF_ACCOUNT_NAME, accountName);
- editor.commit();
- }
/**
* when user sign out, delete authorized information of the current Account instance.
@@ -141,10 +165,11 @@ public void signOutAccount(Account account) {
return;
}
- CameraUploadManager cameraManager = new CameraUploadManager(SeadroidApplication.getAppContext());
+ saveCurrentAccount(null);
- accountManager.invalidateAuthToken(Account.ACCOUNT_TYPE, account.getToken());
+ accountManager.invalidateAuthToken(Constants.Account.ACCOUNT_TYPE, account.getToken());
+ CameraUploadManager cameraManager = new CameraUploadManager();
// disable camera upload if on this account
Account camAccount = cameraManager.getCameraAccount();
if (camAccount != null && camAccount.equals(account)) {
@@ -152,22 +177,4 @@ public void signOutAccount(Account account) {
}
}
- /**
- * get all email texts from database in order to auto complete email address
- *
- * @return
- */
- public ArrayList getAccountAutoCompleteTexts() {
- ArrayList autoCompleteTexts = Lists.newArrayList();
-
- List accounts = getAccountList();
-
- if (accounts == null) return null;
- for (Account act : accounts) {
- if (!autoCompleteTexts.contains(act.getEmail()))
- autoCompleteTexts.add(act.getEmail());
- }
- return autoCompleteTexts;
- }
-
}
diff --git a/app/src/main/java/com/seafile/seadroid2/account/SupportDataManager.java b/app/src/main/java/com/seafile/seadroid2/account/SupportDataManager.java
new file mode 100644
index 000000000..0e2b2608d
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/account/SupportDataManager.java
@@ -0,0 +1,28 @@
+package com.seafile.seadroid2.account;
+
+import com.seafile.seadroid2.data.DataManager;
+
+public class SupportDataManager {
+ private DataManager dataManager;
+
+ private static volatile SupportDataManager singleton = null;
+
+ public static SupportDataManager getInstance() {
+ if (singleton == null) {
+ synchronized (SupportDataManager.class) {
+ if (singleton == null) {
+ singleton = new SupportDataManager();
+ }
+ }
+ }
+ return singleton;
+ }
+
+ public SupportDataManager() {
+ dataManager = new DataManager(SupportAccountManager.getInstance().getCurrentAccount());
+ }
+
+ public DataManager getDataManager() {
+ return dataManager;
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/avatar/AuthImageDownloader.java b/app/src/main/java/com/seafile/seadroid2/avatar/AuthImageDownloader.java
deleted file mode 100644
index 6f4991a96..000000000
--- a/app/src/main/java/com/seafile/seadroid2/avatar/AuthImageDownloader.java
+++ /dev/null
@@ -1,51 +0,0 @@
-
-package com.seafile.seadroid2.avatar;
-
-import android.content.Context;
-
-import com.github.kevinsawicki.http.HttpRequest;
-import com.nostra13.universalimageloader.core.assist.FlushedInputStream;
-import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
-import com.seafile.seadroid2.account.Account;
-import com.seafile.seadroid2.ssl.SSLTrustManager;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-
-import javax.net.ssl.HttpsURLConnection;
-
-public class AuthImageDownloader extends BaseImageDownloader {
- public static final String TAG = AuthImageDownloader.class.getName();
-
- public AuthImageDownloader(Context context, int connectTimeout,
- int readTimeout) {
- super(context, connectTimeout, readTimeout);
- }
-
- @Override
- protected InputStream getStreamFromNetwork(String imageUri, Object extra)
- throws IOException {
- HttpRequest req = HttpRequest.get(imageUri, null, false)
- .readTimeout(readTimeout)
- .connectTimeout(connectTimeout)
- .followRedirects(true)
- .header("Authorization", "Token " + ((Account) extra).token);
-
- HttpURLConnection conn = req.getConnection();
-
- if (conn instanceof HttpsURLConnection) {
- // Tell HttpRequest to trust all hosts, and then the user will get a dialog
- // where he needs to confirm the SSL certificate for the account,
- // and the accepted certificate will be stored, so he is not prompted to accept later on.
- // This is handled by SSLTrustManager and CertsManager
- req.trustAllHosts();
- HttpsURLConnection sconn = (HttpsURLConnection) conn;
- sconn.setSSLSocketFactory(SSLTrustManager.instance().getSSLSocketFactory((Account) extra));
- }
-
- return new FlushedInputStream(new BufferedInputStream(
- req.stream()));
- }
-}
diff --git a/app/src/main/java/com/seafile/seadroid2/avatar/Avatar.java b/app/src/main/java/com/seafile/seadroid2/avatar/Avatar.java
deleted file mode 100644
index 582f19526..000000000
--- a/app/src/main/java/com/seafile/seadroid2/avatar/Avatar.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.seafile.seadroid2.avatar;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
-
-import android.util.Log;
-
-public class Avatar {
- private static final String DEBUG_TAG = "Avatar";
-
- private String signature; // Account Signature
- private String url;
- private long mtime;
- // private boolean is_default;
-
- static Avatar fromJson(JSONObject obj) {
- Avatar avatar = new Avatar();
- try {
- avatar.url = obj.getString("url");
- avatar.mtime = obj.getLong("mtime");
- // avatar.is_default = obj.getBoolean("is_default");
-
- return avatar;
- } catch (JSONException e) {
- Log.d(DEBUG_TAG, e.getMessage());
- return null;
- }
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(url, mtime);
- }
-
- public String getUrl() {
- return url;
- }
-
- public void setUrl(String url) {
- this.url = url;
- }
-
- public long getMtime() {
- return mtime;
- }
-
- public void setMtime(long mtime) {
- this.mtime = mtime;
- }
-
- /*public boolean isIs_default() {
- return is_default;
- }
-
- public void setIs_default(boolean is_default) {
- this.is_default = is_default;
- }*/
-
- public String getSignature() {
- return signature;
- }
-
- public void setSignature(String signature) {
- this.signature = signature;
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("signature", signature)
- .add("url", url)
- .add("mtime", mtime)
- /*.add("is_default", is_default)*/
- .toString();
- }
-}
diff --git a/app/src/main/java/com/seafile/seadroid2/avatar/AvatarDBHelper.java b/app/src/main/java/com/seafile/seadroid2/avatar/AvatarDBHelper.java
deleted file mode 100644
index e0ba35d84..000000000
--- a/app/src/main/java/com/seafile/seadroid2/avatar/AvatarDBHelper.java
+++ /dev/null
@@ -1,182 +0,0 @@
-package com.seafile.seadroid2.avatar;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-
-import com.google.common.collect.Lists;
-import com.seafile.seadroid2.SeadroidApplication;
-import com.seafile.seadroid2.account.Account;
-
-public class AvatarDBHelper extends SQLiteOpenHelper {
- private static final String DEBUG_TAG = "AvatarDBHelper";
-
- public static final int DATABASE_VERSION = 1;
- public static final String DATABASE_NAME = "avatar.db";
- private static final String AVATAR_TABLE_NAME = "Avatar";
-
- private static final String AVATAR_COLUMN_ID = "id";
- private static final String AVATAR_COLUMN_SIGNATURE = "signature";
- private static final String AVATAR_COLUMN_URL = "url";
- private static final String AVATAR_COLUMN_MTIME = "mtime";
- /*private static final String AVATAR_COLUMN_IS_DEFAULT = "is_default";*/
-
- private static final String SQL_CREATE_AVATAR_TABLE =
- "CREATE TABLE " + AVATAR_TABLE_NAME + " ("
- + AVATAR_COLUMN_ID + " INTEGER PRIMARY KEY, "
- + AVATAR_COLUMN_SIGNATURE + " TEXT NOT NULL, "
- + AVATAR_COLUMN_URL + " TEXT NOT NULL, "
- + AVATAR_COLUMN_MTIME + " INTEGER NOT NULL);";
-
- private static final String[] projection = {
- //AVATAR_COLUMN_ID,
- AVATAR_COLUMN_SIGNATURE,
- AVATAR_COLUMN_URL,
- AVATAR_COLUMN_MTIME
- /*AVATAR_COLUMN_IS_DEFAULT*/
- };
-
- public static final String [] hasAvatarProjection = {
- AVATAR_COLUMN_SIGNATURE,
- AVATAR_COLUMN_URL,
- AVATAR_COLUMN_MTIME
- };
-
- private static AvatarDBHelper dbHelper = null;
- private SQLiteDatabase database = null;
-
- public static synchronized AvatarDBHelper getAvatarDbHelper() {
- if (dbHelper != null)
- return dbHelper;
- dbHelper = new AvatarDBHelper(SeadroidApplication.getAppContext());
- dbHelper.database = dbHelper.getWritableDatabase();
- return dbHelper;
- }
-
- private AvatarDBHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- public boolean hasAvatar(Account account) {
-
- if (account == null)
- return false;
- if (account.getSignature() == null || account.getSignature().isEmpty())
- return false;
-
- String selection = AVATAR_COLUMN_SIGNATURE + "=?";
-
- Cursor cursor = database.query(
- AVATAR_TABLE_NAME,
- hasAvatarProjection,
- selection,
- new String[]{account.getSignature()},
- null,
- null,
- null
- );
-
- boolean hasAvatar = false;
-
- if (cursor.moveToFirst())
- hasAvatar = true;
-
- cursor.close();
- return hasAvatar;
-
- }
-
- public List getAvatarList() {
- Cursor cursor = database.query(
- AVATAR_TABLE_NAME,
- projection,
- null,
- null,
- null, // don't group the rows
- null, // don't filter by row groups
- null // The sort order
- );
-
- List avatars = new ArrayList();
-
- if (!cursor.moveToFirst())
- return avatars;
-
- do {
- Avatar avatar = new Avatar();
- avatar.setSignature(cursor.getString(0));
- avatar.setUrl(cursor.getString(1));
- avatar.setMtime(cursor.getInt(2));
- /*avatar.setIs_default(cursor.getInt(3) == 1);*/
- avatars.add(avatar);
- } while (cursor.moveToNext());
-
- cursor.close();
- return avatars;
- }
-
- public void saveAvatars(List avatars) {
-
- List validAvatars = Lists.newArrayList();
-
- // query database in case insert duplicate rows
- for (Avatar avatar : avatars) {
- if (!isRowDuplicate(avatar)) {
- validAvatars.add(avatar);
- }
- }
-
- for (Avatar avatar : validAvatars) {
- ContentValues values = new ContentValues();
- values.put(AVATAR_COLUMN_SIGNATURE, avatar.getSignature());
- values.put(AVATAR_COLUMN_URL, avatar.getUrl());
- values.put(AVATAR_COLUMN_MTIME, avatar.getMtime());
- /*values.put(AVATAR_COLUMN_IS_DEFAULT, (avatar.isIs_default() ? 1 : 0));*/
- database.insert(AVATAR_TABLE_NAME, null, values);
- }
- }
-
- // avoid duplicate inserting request
- private boolean isRowDuplicate(Avatar avatar) {
-
- long count = DatabaseUtils.queryNumEntries(
- database,
- AVATAR_TABLE_NAME,
- AVATAR_COLUMN_SIGNATURE + "=? and " +
- AVATAR_COLUMN_URL + "=?",
- new String[]{avatar.getSignature(), avatar.getUrl()});
- return count > 0;
-
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- createAvatarTable(db);
- }
-
- private void createAvatarTable(SQLiteDatabase db) {
- db.execSQL(SQL_CREATE_AVATAR_TABLE);
- db.execSQL("CREATE INDEX account_signature_index ON " + AVATAR_TABLE_NAME
- + " (" + AVATAR_COLUMN_SIGNATURE + ");");
- db.execSQL("CREATE INDEX avatar_url_index ON " + AVATAR_TABLE_NAME
- + " (" + AVATAR_COLUMN_URL + ");");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL("DROP TABLE IF EXISTS " + AVATAR_TABLE_NAME + ";");
- onCreate(db);
- }
-
- @Override
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- onUpgrade(db, oldVersion, newVersion);
- }
-
-}
diff --git a/app/src/main/java/com/seafile/seadroid2/avatar/AvatarManager.java b/app/src/main/java/com/seafile/seadroid2/avatar/AvatarManager.java
deleted file mode 100644
index 32c7077e3..000000000
--- a/app/src/main/java/com/seafile/seadroid2/avatar/AvatarManager.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.seafile.seadroid2.avatar;
-
-import java.util.*;
-
-import com.seafile.seadroid2.SeadroidApplication;
-import com.seafile.seadroid2.account.AccountManager;
-import org.json.JSONObject;
-
-import com.google.common.collect.Lists;
-import com.seafile.seadroid2.account.Account;
-import com.seafile.seadroid2.util.Utils;
-
-/**
- * load, cache, update avatars
- *
- */
-public class AvatarManager {
- private static final String DEBUG_TAG = "AvatarManager";
-
- private final AvatarDBHelper dbHelper = AvatarDBHelper.getAvatarDbHelper();
- private List avatars;
- private AccountManager accountMgr;
-
- public AvatarManager() {
- this.avatars = Lists.newArrayList();
- this.accountMgr = new AccountManager(SeadroidApplication.getAppContext());
- }
-
- /**
- * get accounts who don`t have avatars yet
- *
- * @return ArrayList
- */
- public ArrayList getAccountsWithoutAvatars() {
- List accounts = accountMgr.getAccountList();
-
- if (accounts == null) return null;
-
- ArrayList accountsWithoutAvatar = Lists.newArrayList();
-
- for (Account account : accounts) {
- if (!hasAvatar(account)) {
- accountsWithoutAvatar.add(account);
- }
- }
-
- return accountsWithoutAvatar;
- }
-
- private boolean hasAvatar(Account account) {
- return dbHelper.hasAvatar(account);
- }
-
- public boolean isNeedToLoadNewAvatars() {
- ArrayList accounts = getAccountsWithoutAvatars();
- if (accounts == null || accounts.size() ==0) return false;
- else
- return true;
- }
-
- public List getAvatarList() {
- return dbHelper.getAvatarList();
- }
-
- public void saveAvatarList(List avatars) {
- dbHelper.saveAvatars(avatars);
- }
-
- public Avatar parseAvatar(String json) {
- if (json == null) return null;
-
- JSONObject obj = Utils.parseJsonObject(json);
- if (obj == null)
- return null;
- Avatar avatar = Avatar.fromJson(obj);
- if (avatar == null)
- return null;
-
- return avatar;
- }
-
-
-
-
-}
diff --git a/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenu.java b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenu.java
new file mode 100644
index 000000000..eea46dd88
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenu.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.bottomsheetmenu;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.SubMenu;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+class ActionMenu implements android.view.Menu {
+ private static final int[] sCategoryToOrder = new int[]{
+ 1, /* No category */
+ 4, /* CONTAINER */
+ 5, /* SYSTEM */
+ 3, /* SECONDARY */
+ 2, /* ALTERNATIVE */
+ 0, /* SELECTED_ALTERNATIVE */
+ };
+ /**
+ * This is the part of an order integer that the user can provide.
+ */
+ private static final int USER_MASK = 0x0000ffff;
+ /**
+ * Bit shift of the user portion of the order integer.
+ */
+ private static final int USER_SHIFT = 0;
+ /**
+ * This is the part of an order integer that supplies the category of the item.
+ */
+ private static final int CATEGORY_MASK = 0xffff0000;
+ /**
+ * Bit shift of the category portion of the order integer.
+ */
+ private static final int CATEGORY_SHIFT = 16;
+ private Context mContext;
+ private boolean mIsQwerty;
+ private ArrayList mItems;
+
+ ActionMenu(Context context) {
+ mContext = context;
+ mItems = new ArrayList<>();
+ }
+
+ private static int findInsertIndex(ArrayList items, int ordering) {
+ for (int i = items.size() - 1; i >= 0; i--) {
+ ActionMenuItem item = items.get(i);
+ if (item.getOrder() <= ordering) {
+ return i + 1;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the ordering across all items. This will grab the category from
+ * the upper bits, find out how to order the category with respect to other
+ * categories, and combine it with the lower bits.
+ *
+ * @param categoryOrder The category order for a particular item (if it has
+ * not been or/add with a category, the default category is
+ * assumed).
+ * @return An ordering integer that can be used to order this item across
+ * all the items (even from other categories).
+ */
+ private static int getOrdering(int categoryOrder) {
+ final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
+
+ if (index < 0 || index >= sCategoryToOrder.length) {
+ throw new IllegalArgumentException("order does not contain a valid category.");
+ }
+
+ return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public MenuItem add(CharSequence title) {
+ return add(0, 0, 0, title);
+ }
+
+ public MenuItem add(int titleRes) {
+ return add(0, 0, 0, titleRes);
+ }
+
+ public MenuItem add(int groupId, int itemId, int order, int titleRes) {
+ return add(groupId, itemId, order, mContext.getResources().getString(titleRes));
+ }
+
+ public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
+ ActionMenuItem item = new ActionMenuItem(getContext(),
+ groupId, itemId, 0, order, title);
+ mItems.add(findInsertIndex(mItems, getOrdering(order)), item);
+ return item;
+ }
+
+ MenuItem add(ActionMenuItem item) {
+ mItems.add(findInsertIndex(mItems, getOrdering(item.getOrder())), item);
+ return item;
+ }
+
+ public int addIntentOptions(int groupId, int itemId, int order,
+ ComponentName caller, Intent[] specifics, Intent intent, int flags,
+ MenuItem[] outSpecificItems) {
+ PackageManager pm = mContext.getPackageManager();
+ final List lri =
+ pm.queryIntentActivityOptions(caller, specifics, intent, 0);
+ final int N = lri != null ? lri.size() : 0;
+
+ if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
+ removeGroup(groupId);
+ }
+
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo ri = lri.get(i);
+ Intent rintent = new Intent(
+ ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
+ rintent.setComponent(new ComponentName(
+ ri.activityInfo.applicationInfo.packageName,
+ ri.activityInfo.name));
+ final MenuItem item = add(groupId, itemId, order, ri.loadLabel(pm))
+ .setIcon(ri.loadIcon(pm))
+ .setIntent(rintent);
+ if (outSpecificItems != null && ri.specificIndex >= 0) {
+ outSpecificItems[ri.specificIndex] = item;
+ }
+ }
+
+ return N;
+ }
+
+ public SubMenu addSubMenu(CharSequence title) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public SubMenu addSubMenu(int titleRes) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public SubMenu addSubMenu(int groupId, int itemId, int order,
+ CharSequence title) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public void clear() {
+ mItems.clear();
+ }
+
+ public void close() {
+ }
+
+ private int findItemIndex(int id) {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+ for (int i = 0; i < itemCount; i++) {
+ if (items.get(i).getItemId() == id) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public MenuItem findItem(int id) {
+ final int index = findItemIndex(id);
+ if (index < 0) {
+ return null;
+ }
+
+ return mItems.get(index);
+ }
+
+ public MenuItem getItem(int index) {
+ return mItems.get(index);
+ }
+
+ public boolean hasVisibleItems() {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ if (items.get(i).isVisible()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private ActionMenuItem findItemWithShortcut(int keyCode, KeyEvent event) {
+ // TODO Make this smarter.
+ final boolean qwerty = mIsQwerty;
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ final char shortcut = qwerty ? item.getAlphabeticShortcut() :
+ item.getNumericShortcut();
+ if (keyCode == shortcut) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ public boolean isShortcutKey(int keyCode, KeyEvent event) {
+ return findItemWithShortcut(keyCode, event) != null;
+ }
+
+ public boolean performIdentifierAction(int id, int flags) {
+ final int index = findItemIndex(id);
+ if (index < 0) {
+ return false;
+ }
+
+ return mItems.get(index).invoke();
+ }
+
+ public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
+ ActionMenuItem item = findItemWithShortcut(keyCode, event);
+ if (item == null) {
+ return false;
+ }
+
+ return item.invoke();
+ }
+
+ public void removeGroup(int groupId) {
+ final ArrayList items = mItems;
+ int itemCount = items.size();
+ int i = 0;
+ while (i < itemCount) {
+ if (items.get(i).getGroupId() == groupId) {
+ items.remove(i);
+ itemCount--;
+ } else {
+ i++;
+ }
+ }
+ }
+
+ public void removeItem(int id) {
+ final int index = findItemIndex(id);
+ if (index < 0) {
+ return;
+ }
+
+ mItems.remove(index);
+ }
+
+ public void setGroupCheckable(int group, boolean checkable,
+ boolean exclusive) {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ if (item.getGroupId() == group) {
+ item.setCheckable(checkable);
+ item.setExclusiveCheckable(exclusive);
+ }
+ }
+ }
+
+ public void setGroupEnabled(int group, boolean enabled) {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ if (item.getGroupId() == group) {
+ item.setEnabled(enabled);
+ }
+ }
+ }
+
+ public void setGroupVisible(int group, boolean visible) {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ if (item.getGroupId() == group) {
+ item.setVisible(visible);
+ }
+ }
+ }
+
+ public void setQwertyMode(boolean isQwerty) {
+ mIsQwerty = isQwerty;
+ }
+
+ public int size() {
+ return mItems.size();
+ }
+
+ ActionMenu clone(int size) {
+ ActionMenu out = new ActionMenu(getContext());
+ out.mItems = new ArrayList<>(this.mItems.subList(0, size));
+ return out;
+ }
+
+ void removeInvisible() {
+ Iterator iter = mItems.iterator();
+ while (iter.hasNext()) {
+ ActionMenuItem item = iter.next();
+ if (!item.isVisible()) iter.remove();
+ }
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenuItem.java b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenuItem.java
new file mode 100644
index 000000000..47f28f68c
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenuItem.java
@@ -0,0 +1,342 @@
+package com.seafile.seadroid2.bottomsheetmenu;
+
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+
+import androidx.annotation.DrawableRes;
+import androidx.core.content.ContextCompat;
+
+
+class ActionMenuItem implements MenuItem {
+
+
+ private static final int NO_ICON = 0;
+ private static final int CHECKABLE = 0x00000001;
+ private static final int CHECKED = 0x00000002;
+ private static final int EXCLUSIVE = 0x00000004;
+ private static final int HIDDEN = 0x00000008;
+ private static final int ENABLED = 0x00000010;
+ private final int mId;
+ private final int mGroup;
+ private final int mCategoryOrder;
+ private final int mOrdering;
+ private CharSequence mTitle;
+ private CharSequence mTitleCondensed;
+ private Intent mIntent;
+ private char mShortcutNumericChar;
+ private char mShortcutAlphabeticChar;
+ private Drawable mIconDrawable;
+ private int mIconResId = NO_ICON;
+ private Context mContext;
+ private OnMenuItemClickListener mClickListener;
+ private int mFlags = ENABLED;
+
+ public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering,
+ CharSequence title) {
+ mContext = context;
+ mId = id;
+ mGroup = group;
+ mCategoryOrder = categoryOrder;
+ mOrdering = ordering;
+ mTitle = title;
+ }
+
+ public char getAlphabeticShortcut() {
+ return mShortcutAlphabeticChar;
+ }
+
+ public int getGroupId() {
+ return mGroup;
+ }
+
+ public Drawable getIcon() {
+ return mIconDrawable;
+ }
+
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ public int getItemId() {
+ return mId;
+ }
+
+ public ContextMenu.ContextMenuInfo getMenuInfo() {
+ return null;
+ }
+
+ public char getNumericShortcut() {
+ return mShortcutNumericChar;
+ }
+
+ public int getOrder() {
+ return mOrdering;
+ }
+
+ public SubMenu getSubMenu() {
+ return null;
+ }
+
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ public CharSequence getTitleCondensed() {
+ return mTitleCondensed != null ? mTitleCondensed : mTitle;
+ }
+
+ public boolean hasSubMenu() {
+ return false;
+ }
+
+ public boolean isCheckable() {
+ return (mFlags & CHECKABLE) != 0;
+ }
+
+ public boolean isChecked() {
+ return (mFlags & CHECKED) != 0;
+ }
+
+ public boolean isEnabled() {
+ return (mFlags & ENABLED) != 0;
+ }
+
+ public boolean isVisible() {
+ return (mFlags & HIDDEN) == 0;
+ }
+
+ public MenuItem setAlphabeticShortcut(char alphaChar) {
+ mShortcutAlphabeticChar = alphaChar;
+ return this;
+ }
+
+ public MenuItem setCheckable(boolean checkable) {
+ mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
+ return this;
+ }
+
+ public ActionMenuItem setExclusiveCheckable(boolean exclusive) {
+ mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+ return this;
+ }
+
+ public MenuItem setChecked(boolean checked) {
+ mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
+ return this;
+ }
+
+ public MenuItem setEnabled(boolean enabled) {
+ mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0);
+ return this;
+ }
+
+ public MenuItem setIcon(Drawable icon) {
+ mIconDrawable = icon;
+ mIconResId = NO_ICON;
+ return this;
+ }
+
+ public MenuItem setIcon(@DrawableRes int iconRes) {
+ mIconResId = iconRes;
+ if (iconRes != 0) {
+ mIconDrawable = ContextCompat.getDrawable(mContext, iconRes);
+ }
+ return this;
+ }
+
+ public MenuItem setIntent(Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ public MenuItem setNumericShortcut(char numericChar) {
+ mShortcutNumericChar = numericChar;
+ return this;
+ }
+
+ public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
+ mClickListener = menuItemClickListener;
+ return this;
+ }
+
+ public MenuItem setShortcut(char numericChar, char alphaChar) {
+ mShortcutNumericChar = numericChar;
+ mShortcutAlphabeticChar = alphaChar;
+ return this;
+ }
+
+ public MenuItem setTitle(CharSequence title) {
+ mTitle = title;
+ return this;
+ }
+
+ public MenuItem setTitle(int title) {
+ mTitle = mContext.getResources().getString(title);
+ return this;
+ }
+
+ public MenuItem setTitleCondensed(CharSequence title) {
+ mTitleCondensed = title;
+ return this;
+ }
+
+ public MenuItem setVisible(boolean visible) {
+ mFlags = (mFlags & ~HIDDEN) | (visible ? 0 : HIDDEN);
+ return this;
+ }
+
+ public boolean invoke() {
+ if (mClickListener != null && mClickListener.onMenuItemClick(this)) {
+ return true;
+ }
+
+ if (mIntent != null) {
+ mContext.startActivity(mIntent);
+ return true;
+ }
+
+ return false;
+ }
+
+ public void setShowAsAction(int show) {
+ // Do nothing. ActionMenuItems always show as action buttons.
+ }
+
+ public MenuItem setActionView(View actionView) {
+ throw new UnsupportedOperationException();
+ }
+
+ public View getActionView() {
+ return null;
+ }
+
+ @Override
+ public MenuItem setActionProvider(android.view.ActionProvider actionProvider) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public android.view.ActionProvider getActionProvider() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public MenuItem setActionView(int resId) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public MenuItem setShowAsActionFlags(int actionEnum) {
+ setShowAsAction(actionEnum);
+ return this;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean isActionViewExpanded() {
+ return false;
+ }
+
+ @Override
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public MenuItem setContentDescription(CharSequence contentDescription) {
+ return this;
+ }
+
+ @Override
+ public CharSequence getContentDescription() {
+ return null;
+ }
+
+ @Override
+ public MenuItem setTooltipText(CharSequence tooltipText) {
+ return this;
+ }
+
+ @Override
+ public CharSequence getTooltipText() {
+ return null;
+ }
+
+ @Override
+ public MenuItem setShortcut(char numericChar, char alphaChar, int numericModifiers, int alphaModifiers) {
+ return this;
+ }
+
+ @Override
+ public MenuItem setNumericShortcut(char numericChar, int numericModifiers) {
+ return this;
+ }
+
+ @Override
+ public int getNumericModifiers() {
+ return 0;
+ }
+
+ @Override
+ public MenuItem setAlphabeticShortcut(char alphaChar, int alphaModifiers) {
+ return this;
+ }
+
+ @Override
+ public int getAlphabeticModifiers() {
+ return 0;
+ }
+
+ @Override
+ public MenuItem setIconTintList(ColorStateList tint) {
+ return this;
+ }
+
+ @Override
+ public ColorStateList getIconTintList() {
+ return null;
+ }
+
+ @Override
+ public MenuItem setIconTintMode(PorterDuff.Mode tintMode) {
+ return this;
+ }
+
+ @Override
+ public PorterDuff.Mode getIconTintMode() {
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetHelper.java b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetHelper.java
new file mode 100644
index 000000000..77ca716dd
--- /dev/null
+++ b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetHelper.java
@@ -0,0 +1,39 @@
+package com.seafile.seadroid2.bottomsheetmenu;
+
+import android.view.MenuItem;
+
+import androidx.fragment.app.FragmentActivity;
+
+import java.util.List;
+
+public class BottomSheetHelper {
+
+ public static BottomSheetMenuFragment.Builder buildSheet(FragmentActivity activity, int menuSheetId, OnMenuClickListener onMenuClickListener) {
+ return new BottomSheetMenuFragment.Builder(activity)
+ .setMenuSheetId(menuSheetId)
+ .setColumnCount(1)
+ .setCancelable(true)
+ .setOnMenuClickListener(onMenuClickListener);
+ }
+
+ public static void showSheet(FragmentActivity activity, int menuSheetId, OnMenuClickListener onMenuClickListener) {
+ new BottomSheetMenuFragment.Builder(activity)
+ .setMenuSheetId(menuSheetId)
+ .setColumnCount(1)
+ .setCancelable(true)
+ .setOnMenuClickListener(onMenuClickListener)
+ .show(activity.getSupportFragmentManager());
+ }
+
+ public static void showSheet(FragmentActivity activity, List