New user info: viewModel, model, repository

split up code
use data binding
add groups

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2018-10-17 07:21:03 +02:00
parent 468899522e
commit 22d6979f7e
No known key found for this signature in database
GPG Key ID: 0E00D4D47D0C5AF7
26 changed files with 1055 additions and 333 deletions

View File

@ -191,4 +191,4 @@
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>
</component>

View File

@ -244,6 +244,10 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
dataBinding {
enabled = true
}
}
dependencies {
@ -278,6 +282,8 @@ dependencies {
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15'
implementation 'com.github.tobiaskaminsky:qrcodescanner:0.1.2.2' // 'com.github.blikoon:QRCodeScanner:0.1.2'
implementation 'com.google.android:flexbox:1.1.0'
implementation group: 'android.arch.lifecycle', name: 'extensions', version: '1.1.1'
implementation 'org.parceler:parceler-api:1.1.12'
kapt 'org.parceler:parceler:1.1.12'
implementation('com.github.bumptech.glide:glide:3.7.0') {
@ -314,6 +320,7 @@ dependencies {
testImplementation 'org.json:json:20190722'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0"
testImplementation "androidx.arch.core:core-testing:2.0.1"
testImplementation 'org.mockito:mockito-core:2.24.0'
// dependencies for instrumented tests
// JUnit4 Rules
@ -332,6 +339,7 @@ dependencies {
//androidTestImplementation "com.android.support:support-annotations:${supportLibraryVersion}"
androidTestImplementation 'tools.fastlane:screengrab:1.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
// jacocoAnt "org.jacoco:org.jacoco.ant:${jacocoVersion}"
// jacocoAgent "org.jacoco:org.jacoco.agent:${jacocoVersion}"

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 16 16" width="16" version="1.1"><path d="m8 1c-3.86 0-7 3.15-7 7s3.15 7 7 7c3.86 0 7-3.15 7-7 0-3.86-3.15-7-7-7zm0 1.75c2.91 0 5.25 2.34 5.25 5.25 0 1.42-0.56 2.7-1.47 3.644l-3.78-3.644z"/></svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@ -5,4 +5,5 @@ NC_TEST_SERVER_PASSWORD=test
android.enableJetifier=true
android.useAndroidX=true
android.debug.obsoleteApi=true
android.databinding.enableV2=true

View File

@ -119,7 +119,9 @@
<meta-data android:name="android.app.searchable"
android:resource="@xml/users_and_groups_searchable"/>
</activity>
<activity android:name=".ui.activity.ManageAccountsActivity" />
<activity
android:name=".ui.activity.ManageAccountsActivity"
android:exported="true" />
<activity android:name=".ui.activity.UserInfoActivity" />
<activity android:name=".ui.activity.NotificationsActivity"/>
<activity android:name=".ui.activity.ParticipateActivity" />

View File

@ -48,8 +48,11 @@ import com.owncloud.android.ui.activities.data.activities.RemoteActivitiesReposi
import com.owncloud.android.ui.activities.data.files.FilesRepository;
import com.owncloud.android.ui.activities.data.files.FilesServiceApiImpl;
import com.owncloud.android.ui.activities.data.files.RemoteFilesRepository;
import com.owncloud.android.ui.viewModel.UserInfoViewModel;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.inject.Singleton;
@ -146,4 +149,15 @@ class AppModule {
Handler uiHandler = new Handler();
return new AsyncRunnerImpl(uiHandler, 4);
}
@Provides
public Executor executor() {
return Executors.newCachedThreadPool();
}
@Provides
public UserInfoViewModel userInfoViewModel() {
return new UserInfoViewModel();
}
}

View File

@ -35,8 +35,10 @@ import android.os.RemoteException;
import android.provider.MediaStore;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.owncloud.android.MainApp;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.lib.common.Quota;
import com.owncloud.android.lib.common.network.WebdavEntry;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
@ -1083,7 +1085,7 @@ public class FileDataStorageManager {
/**
* Checks the existance of an stored {@link OCShare} matching the given remote id (not to be confused with
* Checks the existence of an stored {@link OCShare} matching the given remote id (not to be confused with
* the local id) in the current account.
*
* @param remoteId Remote of the share in the server.
@ -1095,7 +1097,7 @@ public class FileDataStorageManager {
/**
* Checks the existance of an stored {@link OCShare} in the current account
* Checks the existence of an stored {@link OCShare} in the current account
* matching a given column and a value for that column
*
* @param key Name of the column to match.
@ -2273,4 +2275,113 @@ public class FileDataStorageManager {
}
}
private boolean userInfoExists(String accountName) {
Cursor c = getUserInfoCursorForAccount(accountName);
boolean exists = false;
if (c != null) {
exists = c.moveToFirst();
c.close();
}
return exists;
}
private Cursor getUserInfoCursorForAccount(String accountName) {
Cursor c = null;
if (getContentResolver() != null) {
c = getContentResolver()
.query(ProviderTableMeta.CONTENT_URI_USERINFO,
null,
ProviderTableMeta.USERINFO_ACCOUNT + "=? ",
new String[]{accountName}, null);
} else {
try {
c = getContentProviderClient().query(
ProviderTableMeta.CONTENT_URI_USERINFO,
null,
ProviderTableMeta.USERINFO_ACCOUNT + "=? ",
new String[]{accountName}, null);
} catch (RemoteException e) {
Log_OC.e(TAG, "Couldn't determine userinfo existence, assuming non existence: " + e.getMessage(), e);
}
}
return c;
}
public UserInfo saveUserInfo(UserInfo userInfo) {
// Prepare userInfo data
ContentValues cv = new ContentValues();
cv.put(ProviderTableMeta.USERINFO_ACCOUNT, userInfo.getAccount());
cv.put(ProviderTableMeta.USERINFO_DISPLAYNAME, userInfo.getDisplayName());
cv.put(ProviderTableMeta.USERINFO_EMAIL, userInfo.getEmail());
cv.put(ProviderTableMeta.USERINFO_PHONE, userInfo.getPhone());
cv.put(ProviderTableMeta.USERINFO_ADDRESS, userInfo.getAddress());
cv.put(ProviderTableMeta.USERINFO_WEBSITE, userInfo.getWebsite());
cv.put(ProviderTableMeta.USERINFO_TWITTER, userInfo.getTwitter());
cv.put(ProviderTableMeta.USERINFO_GROUPS, TextUtils.join(",", userInfo.getGroups()));
cv.put(ProviderTableMeta.USERINFO_QUOTA, new Gson().toJson(userInfo.getQuota()));
if (userInfoExists(account.name)) {
if (getContentResolver() != null) {
getContentResolver().update(ProviderTableMeta.CONTENT_URI_USERINFO, cv,
ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME + "=?",
new String[]{account.name});
} else {
try {
getContentProviderClient().update(ProviderTableMeta.CONTENT_URI_USERINFO,
cv, ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME + "=?",
new String[]{account.name});
} catch (RemoteException e) {
Log_OC.e(TAG, FAILED_TO_INSERT_MSG + e.getMessage(), e);
}
}
} else {
if (getContentResolver() != null) {
getContentResolver().insert(ProviderTableMeta.CONTENT_URI_USERINFO, cv);
} else {
try {
getContentProviderClient().insert(ProviderTableMeta.CONTENT_URI_USERINFO, cv);
} catch (RemoteException e) {
Log_OC.e(TAG, FAILED_TO_INSERT_MSG + e.getMessage(), e);
}
}
}
return userInfo;
}
@NonNull
public UserInfo getUserInfo(String accountName) {
UserInfo userInfo;
Cursor c = getUserInfoCursorForAccount(accountName);
if (c.moveToFirst()) {
userInfo = createUserInfoInstance(c);
} else {
userInfo = new UserInfo();
}
c.close();
return userInfo;
}
private UserInfo createUserInfoInstance(Cursor c) {
UserInfo userInfo = new UserInfo();
if (c != null) {
userInfo.setDisplayName(c.getString(c.getColumnIndex(ProviderTableMeta.USERINFO_DISPLAYNAME)));
userInfo.setEmail(c.getString(c.getColumnIndex(ProviderTableMeta.USERINFO_EMAIL)));
userInfo.setPhone(c.getString(c.getColumnIndex(ProviderTableMeta.USERINFO_PHONE)));
userInfo.setAddress(c.getString(c.getColumnIndex(ProviderTableMeta.USERINFO_ADDRESS)));
userInfo.setWebsite(c.getString(c.getColumnIndex(ProviderTableMeta.USERINFO_WEBSITE)));
userInfo.setTwitter(c.getString(c.getColumnIndex(ProviderTableMeta.USERINFO_TWITTER)));
String groups = c.getString(c.getColumnIndex(ProviderTableMeta.USERINFO_GROUPS));
userInfo.setGroups(new ArrayList<>(Arrays.asList(groups.split(","))));
String quotaJson = c.getString(c.getColumnIndex(ProviderTableMeta.USERINFO_QUOTA));
userInfo.setQuota(new Gson().fromJson(quotaJson, Quota.class));
}
return userInfo;
}
}

View File

@ -0,0 +1,102 @@
package com.owncloud.android.datamodel;
import android.text.TextUtils;
import com.owncloud.android.lib.common.Quota;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import lombok.Getter;
import lombok.Setter;
public class UserInfo {
public String account = "";
public Boolean enabled;
public String displayName;
public String email;
public String phone;
public String address;
public String website;
public String twitter;
@Getter @Setter public ArrayList<String> groups;
public Quota quota;
@NonNull
public String getAccount() {
return account;
}
public void setAccount(@NonNull String account) {
this.account = account;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getWebsite() {
return website;
}
public void setWebsite(String website) {
this.website = website;
}
public String getTwitter() {
return twitter;
}
public void setTwitter(String twitter) {
this.twitter = twitter;
}
public Quota getQuota() {
return quota;
}
public void setQuota(Quota quota) {
this.quota = quota;
}
public boolean isEmpty() {
return TextUtils.isEmpty(phone) && TextUtils.isEmpty(email) && TextUtils.isEmpty(address) &&
TextUtils.isEmpty(twitter) && TextUtils.isEmpty(website) && (groups == null || groups.size() == 0);
}
}

View File

@ -31,7 +31,7 @@ import com.owncloud.android.MainApp;
*/
public class ProviderMeta {
public static final String DB_NAME = "filelist";
public static final int DB_VERSION = 49;
public static final int DB_VERSION = 50;
private ProviderMeta() {
// No instance
@ -47,6 +47,7 @@ public class ProviderMeta {
public static final String ARBITRARY_DATA_TABLE_NAME = "arbitrary_data";
public static final String VIRTUAL_TABLE_NAME = "virtual";
public static final String FILESYSTEM_TABLE_NAME = "filesystem";
public static final String USERINFO_TABLE_NAME = "userinfo";
private static final String CONTENT_PREFIX = "content://";
@ -71,6 +72,7 @@ public class ProviderMeta {
public static final Uri CONTENT_URI_VIRTUAL = Uri.parse(CONTENT_PREFIX + MainApp.getAuthority() + "/virtual");
public static final Uri CONTENT_URI_FILESYSTEM = Uri.parse(CONTENT_PREFIX
+ MainApp.getAuthority() + "/filesystem");
public static final Uri CONTENT_URI_USERINFO = Uri.parse(CONTENT_PREFIX + MainApp.getAuthority() + "/userinfo");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file";
@ -238,7 +240,6 @@ public class ProviderMeta {
public static final String ARBITRARY_DATA_KEY = "key";
public static final String ARBITRARY_DATA_VALUE = "value";
// Columns of virtual
public static final String VIRTUAL_TYPE = "type";
public static final String VIRTUAL_OCFILE_ID = "ocfile_id";
@ -252,6 +253,17 @@ public class ProviderMeta {
public static final String FILESYSTEM_SYNCED_FOLDER_ID = "syncedfolder_id";
public static final String FILESYSTEM_CRC32 = "crc32";
// Columns of userinfo table
public static final String USERINFO_ACCOUNT = "account";
public static final String USERINFO_DISPLAYNAME = "displayname";
public static final String USERINFO_EMAIL = "email";
public static final String USERINFO_PHONE = "phone";
public static final String USERINFO_ADDRESS = "address";
public static final String USERINFO_WEBSITE = "website";
public static final String USERINFO_TWITTER = "twitter";
public static final String USERINFO_GROUPS = "groups";
public static final String USERINFO_QUOTA = "quota";
private ProviderTableMeta() {
// No instance
}

View File

@ -77,6 +77,7 @@ public class FileContentProvider extends ContentProvider {
private static final int ARBITRARY_DATA = 9;
private static final int VIRTUAL = 10;
private static final int FILESYSTEM = 11;
private static final int USERINFO = 12;
private static final String TAG = FileContentProvider.class.getSimpleName();
// todo avoid string concatenation and use string formatting instead later.
private static final String ERROR = "ERROR ";
@ -153,6 +154,9 @@ public class FileContentProvider extends ContentProvider {
case FILESYSTEM:
count = db.delete(ProviderTableMeta.FILESYSTEM_TABLE_NAME, where, whereArgs);
break;
case USERINFO:
count = db.delete(ProviderTableMeta.USERINFO_TABLE_NAME, where, whereArgs);
break;
default:
throw new IllegalArgumentException(String.format(Locale.US, "Unknown uri: %s", uri.toString()));
}
@ -370,6 +374,16 @@ public class FileContentProvider extends ContentProvider {
throw new SQLException("ERROR " + uri);
}
return insertedFilesystemUri;
case USERINFO:
Uri insertedUserInfoUri;
long insertedUserInfoId = db.insert(ProviderTableMeta.USERINFO_TABLE_NAME, null, values);
if (insertedUserInfoId > 0) {
insertedUserInfoUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_USERINFO,
insertedUserInfoId);
} else {
throw new SQLException("ERROR " + uri);
}
return insertedUserInfoUri;
default:
throw new IllegalArgumentException("Unknown uri id: " + uri);
}
@ -432,6 +446,7 @@ public class FileContentProvider extends ContentProvider {
mUriMatcher.addURI(authority, "arbitrary_data", ARBITRARY_DATA);
mUriMatcher.addURI(authority, "virtual", VIRTUAL);
mUriMatcher.addURI(authority, "filesystem", FILESYSTEM);
mUriMatcher.addURI(authority, "userinfo", USERINFO);
return true;
}
@ -531,6 +546,12 @@ public class FileContentProvider extends ContentProvider {
sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1));
}
break;
case USERINFO:
sqlQuery.setTables(ProviderTableMeta.USERINFO_TABLE_NAME);
if (uri.getPathSegments().size() > SINGLE_PATH_SEGMENT) {
sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1));
}
break;
default:
throw new IllegalArgumentException("Unknown uri id: " + uri);
}
@ -559,12 +580,15 @@ public class FileContentProvider extends ContentProvider {
case VIRTUAL:
order = ProviderTableMeta.VIRTUAL_TYPE;
break;
default: // Files
order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER;
break;
case FILESYSTEM:
order = ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH;
break;
case USERINFO:
order = ProviderTableMeta.USERINFO_ACCOUNT;
break;
default: // Files
order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER;
break;
}
} else {
order = sortOrder;
@ -632,6 +656,8 @@ public class FileContentProvider extends ContentProvider {
return db.update(ProviderTableMeta.ARBITRARY_DATA_TABLE_NAME, values, selection, selectionArgs);
case FILESYSTEM:
return db.update(ProviderTableMeta.FILESYSTEM_TABLE_NAME, values, selection, selectionArgs);
case USERINFO:
return db.update(ProviderTableMeta.USERINFO_TABLE_NAME, values, selection, selectionArgs);
default:
return db.update(ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs);
}
@ -862,6 +888,22 @@ public class FileContentProvider extends ContentProvider {
);
}
private void createUserInfoTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + ProviderTableMeta.USERINFO_TABLE_NAME + "("
+ ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
+ ProviderTableMeta.USERINFO_ACCOUNT + TEXT
+ ProviderTableMeta.USERINFO_DISPLAYNAME + TEXT
+ ProviderTableMeta.USERINFO_EMAIL + TEXT
+ ProviderTableMeta.USERINFO_PHONE + TEXT
+ ProviderTableMeta.USERINFO_ADDRESS + TEXT
+ ProviderTableMeta.USERINFO_WEBSITE + TEXT
+ ProviderTableMeta.USERINFO_TWITTER + TEXT
+ ProviderTableMeta.USERINFO_GROUPS + TEXT
+ ProviderTableMeta.USERINFO_QUOTA + " TEXT); "
);
}
/**
* Version 10 of database does not modify its scheme. It coincides with the upgrade of the
* ownCloud account names structure to include in it the path to the server instance. Updating
@ -992,6 +1034,7 @@ public class FileContentProvider extends ContentProvider {
case ARBITRARY_DATA:
case VIRTUAL:
case FILESYSTEM:
case USERINFO:
String callingPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
return callingPackage == null || !callingPackage.equals(mContext.getPackageName());
@ -1037,6 +1080,9 @@ public class FileContentProvider extends ContentProvider {
// Create filesystem table
createFileSystemTable(db);
// Create userInfo table
createUserInfoTable(db);
}
@Override
@ -2004,6 +2050,23 @@ public class FileContentProvider extends ContentProvider {
if (!upgraded) {
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
}
if (oldVersion < 50 && newVersion >= 50) {
Log_OC.i(SQL, "Entering in the #50 add userinfo table");
db.beginTransaction();
try {
createUserInfoTable(db);
upgraded = true;
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (!upgraded) {
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
}
}
@Override

View File

@ -0,0 +1,13 @@
package com.owncloud.android.repository;
import android.accounts.Account;
import com.owncloud.android.MainApp;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
class Invoker {
public RemoteOperationResult invoke(Account account, RemoteOperation remoteOperation) {
return remoteOperation.execute(account, MainApp.getAppContext());
}
}

View File

@ -0,0 +1,84 @@
package com.owncloud.android.repository;
import android.accounts.Account;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
import java.util.concurrent.Executor;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
public class UserInfoRepository {
private final Executor executor;
private FileDataStorageManager storageManager;
MutableLiveData<com.owncloud.android.datamodel.UserInfo> test = new MutableLiveData<>();
public UserInfoRepository(Executor executor, FileDataStorageManager storageManager) {
this.executor = executor;
this.storageManager = storageManager;
}
public LiveData<com.owncloud.android.datamodel.UserInfo> getUserInfo(Account account) {
refreshUserInfo(account);
return test;
}
private void refreshUserInfo(Account account) {
executor.execute(() -> {
// TODO load from server only if force refresh or older than 5min
com.owncloud.android.datamodel.UserInfo oldUserInfo = storageManager.getUserInfo(account.name);
if (!oldUserInfo.isEmpty()) {
test.postValue(oldUserInfo);
}
Log_OC.d(this, "Start refresh user info");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
RemoteOperation getRemoteUserInfoOperation = new GetUserInfoRemoteOperation();
// TODO how to get client better
Invoker invoker = new Invoker();
RemoteOperationResult result = invoker.invoke(account, getRemoteUserInfoOperation);
if (result.isSuccess() && result.getData() != null) {
Log_OC.d(this, "new refresh user info");
com.owncloud.android.datamodel.UserInfo userInfo = parseUserInfo((UserInfo) result.getData().get(0),
account);
test.postValue(userInfo);
storageManager.saveUserInfo(userInfo);
}
// TODO error handling if fetch fails
});
}
private com.owncloud.android.datamodel.UserInfo parseUserInfo(UserInfo remoteUserInfo, Account account) {
com.owncloud.android.datamodel.UserInfo userInfo = new com.owncloud.android.datamodel.UserInfo();
userInfo.account = account.name;
userInfo.displayName = remoteUserInfo.displayName;
userInfo.email = remoteUserInfo.email;
userInfo.phone = remoteUserInfo.phone;
userInfo.address = remoteUserInfo.address;
userInfo.website = remoteUserInfo.website;
userInfo.twitter = remoteUserInfo.twitter;
userInfo.groups = remoteUserInfo.groups;
userInfo.quota = remoteUserInfo.quota;
return userInfo;
}
}

View File

@ -89,6 +89,7 @@ import com.owncloud.android.ui.events.DummyDrawerEvent;
import com.owncloud.android.ui.events.SearchEvent;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.ui.trashbin.TrashbinActivity;
import com.owncloud.android.ui.viewModel.UserInfoViewModel;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.DrawerMenuUtil;
import com.owncloud.android.utils.FilesSyncHelper;
@ -316,7 +317,7 @@ public abstract class DrawerActivity extends ToolbarActivity
*/
private void setupQuotaElement() {
mQuotaView = (LinearLayout) findQuotaViewById(R.id.drawer_quota);
mQuotaProgressBar = (ProgressBar) findQuotaViewById(R.id.drawer_quota_ProgressBar);
mQuotaProgressBar = (ProgressBar) findQuotaViewById(R.id.drawer_quota_progressBar);
mQuotaTextPercentage = (TextView) findQuotaViewById(R.id.drawer_quota_percentage);
mQuotaTextLink = (TextView) findQuotaViewById(R.id.drawer_quota_link);
ThemeUtils.colorProgressBar(mQuotaProgressBar, ThemeUtils.primaryColor(this));
@ -477,7 +478,7 @@ public abstract class DrawerActivity extends ToolbarActivity
case R.id.nav_logout:
mCheckedMenuItem = -1;
menuItem.setChecked(false);
UserInfoActivity.openAccountRemovalConfirmationDialog(getAccount(), getSupportFragmentManager(), true);
UserInfoViewModel.openAccountRemovalConfirmationDialog(getAccount(), getSupportFragmentManager(), true);
break;
case R.id.nav_recently_added:
handleSearchEvents(new SearchEvent("%", SearchRemoteOperation.SearchType.CONTENT_TYPE_SEARCH,
@ -985,9 +986,7 @@ public abstract class DrawerActivity extends ToolbarActivity
final Quota quota = userInfo.getQuota();
if (quota != null) {
final long used = quota.getUsed();
final long total = quota.getTotal();
final int relative = (int) Math.ceil(quota.getRelative());
final long quotaValue = quota.getQuota();
runOnUiThread(new Runnable() {
@ -1000,7 +999,10 @@ public abstract class DrawerActivity extends ToolbarActivity
* it is available and calculated (> 0) or
* in case of legacy servers (==QUOTA_LIMIT_INFO_NOT_AVAILABLE)
*/
setQuotaInformation(used, total, relative, quotaValue);
DisplayUtils.setQuotaInformation(mQuotaProgressBar, mQuotaTextPercentage,
quota, DrawerActivity.this);
updateQuotaLink();
showQuota(true);
} else {
/*
* quotaValue < 0 means special cases like

View File

@ -26,18 +26,11 @@
package com.owncloud.android.ui.activity;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@ -52,40 +45,32 @@ import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.google.gson.Gson;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.PushConfigurationState;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.UserInfo;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
import com.owncloud.android.ui.events.TokenPushEvent;
import com.owncloud.android.repository.UserInfoRepository;
import com.owncloud.android.ui.adapter.UserInfoAdapter;
import com.owncloud.android.ui.components.UserInfoDetailsItem;
import com.owncloud.android.ui.viewModel.UserInfoViewModel;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.PushUtils;
import com.owncloud.android.utils.ThemeUtils;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.parceler.Parcels;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import javax.inject.Inject;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindString;
import butterknife.BindView;
@ -97,33 +82,52 @@ import butterknife.Unbinder;
*/
public class UserInfoActivity extends FileActivity implements Injectable {
public static final String KEY_ACCOUNT = "ACCOUNT";
public static final String KEY_DIRECT_REMOVE = "DIRECT_REMOVE";
private static final String TAG = UserInfoActivity.class.getSimpleName();
private static final String KEY_USER_DATA = "USER_DATA";
private static final String KEY_DIRECT_REMOVE = "DIRECT_REMOVE";
private static final int KEY_DELETE_CODE = 101;
public static final int KEY_DELETE_CODE = 101;
@BindView(R.id.empty_list_view) protected LinearLayout emptyContentContainer;
@BindView(R.id.empty_list_view_text) protected TextView emptyContentMessage;
@BindView(R.id.empty_list_view_headline) protected TextView emptyContentHeadline;
@BindView(R.id.empty_list_icon) protected ImageView emptyContentIcon;
@BindView(R.id.user_info_view) protected LinearLayout userInfoView;
@BindView(R.id.user_icon) protected ImageView avatar;
@BindView(R.id.userinfo_username) protected TextView userName;
@BindView(R.id.userinfo_username_full) protected TextView fullName;
@BindView(R.id.user_info_list) protected RecyclerView mUserInfoList;
@BindView(R.id.empty_list_progress) protected ProgressBar multiListProgressBar;
@BindView(R.id.empty_list_view)
protected LinearLayout emptyContentContainer;
@BindView(R.id.empty_list_view_text)
protected TextView emptyContentMessage;
@BindView(R.id.empty_list_view_headline)
protected TextView emptyContentHeadline;
@BindView(R.id.empty_list_icon)
protected ImageView emptyContentIcon;
@BindView(R.id.user_info_view)
protected LinearLayout userInfoView;
@BindView(R.id.user_icon)
protected ImageView avatar;
@BindView(R.id.userinfo_username)
protected TextView userName;
@BindView(R.id.userinfo_username_full)
protected TextView fullName;
@BindView(R.id.user_info_list)
protected RecyclerView mUserInfoList;
@BindView(R.id.empty_list_progress)
protected ProgressBar multiListProgressBar;
@BindView(R.id.userinfo_quota)
protected LinearLayout quotaView;
@BindView(R.id.userinfo_quota_progressBar)
protected ProgressBar quotaProgressBar;
@BindView(R.id.userinfo_quota_percentage)
protected TextView quotaPercentage;
@BindView(R.id.quota_icon)
protected ImageView quotaIcon;
@BindString(R.string.user_information_retrieval_error) protected String sorryMessage;
@BindString(R.string.user_information_retrieval_error)
protected String sorryMessage;
@Inject AppPreferences preferences;
private float mCurrentAccountAvatarRadiusDimension;
private Unbinder unbinder;
private UserInfo userInfo;
private Account account;
private UserInfoAdapter adapter;
private @ColorRes int primaryColor;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -131,11 +135,20 @@ public class UserInfoActivity extends FileActivity implements Injectable {
super.onCreate(savedInstanceState);
Bundle bundle = getIntent().getExtras();
if (bundle == null) {
throw new NullPointerException("Bundle may not be null");
}
account = Parcels.unwrap(bundle.getParcelable(KEY_ACCOUNT));
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_USER_DATA)) {
userInfo = Parcels.unwrap(savedInstanceState.getParcelable(KEY_USER_DATA));
}
primaryColor = ThemeUtils.primaryColor(account, true, this);
UserInfoRepository userInfoRepository = new UserInfoRepository(Executors.newCachedThreadPool(),
new FileDataStorageManager(account,
getContentResolver()));
UserInfoViewModel viewModel = ViewModelProviders.of(this).get(UserInfoViewModel.class);
viewModel.init(account, userInfoRepository, getUserAccountManager());
mCurrentAccountAvatarRadiusDimension = getResources().getDimension(R.dimen.nav_drawer_header_avatar_radius);
@ -146,19 +159,15 @@ public class UserInfoActivity extends FileActivity implements Injectable {
onAccountSet(false);
boolean useBackgroundImage = URLUtil.isValidUrl(
getStorageManager().getCapability(account.name).getServerBackground());
getStorageManager().getCapability(account.name).getServerBackground());
setupToolbar(useBackgroundImage);
updateActionBarTitleAndHomeButtonByString("");
mUserInfoList.setAdapter(new UserInfoAdapter(null, ThemeUtils.primaryColor(getAccount(), true, this)));
adapter = new UserInfoAdapter(null);
mUserInfoList.setAdapter(adapter);
if (userInfo != null) {
populateUserInfoUi(userInfo);
} else {
setMultiListLoadingMessage();
fetchAndSetData();
}
viewModel.getUserInfo().observe(this, this::populateUserInfoUi);
setHeaderImage();
}
@ -179,7 +188,7 @@ public class UserInfoActivity extends FileActivity implements Injectable {
onBackPressed();
break;
case R.id.delete_account:
openAccountRemovalConfirmationDialog(account, getSupportFragmentManager(), false);
UserInfoViewModel.openAccountRemovalConfirmationDialog(account, getSupportFragmentManager(), false);
break;
default:
retval = super.onOptionsItemSelected(item);
@ -193,24 +202,11 @@ public class UserInfoActivity extends FileActivity implements Injectable {
unbinder.unbind();
}
private void setMultiListLoadingMessage() {
if (emptyContentContainer != null) {
emptyContentHeadline.setText(R.string.file_list_loading);
emptyContentMessage.setText("");
emptyContentIcon.setVisibility(View.GONE);
emptyContentMessage.setVisibility(View.GONE);
multiListProgressBar.getIndeterminateDrawable().setColorFilter(ThemeUtils.primaryColor(this),
PorterDuff.Mode.SRC_IN);
multiListProgressBar.setVisibility(View.VISIBLE);
}
}
private void setErrorMessageForMultiList(String headline, String message, @DrawableRes int errorResource) {
private void setErrorMessageForMultiList(String headline, String message) {
if (emptyContentContainer != null && emptyContentMessage != null) {
emptyContentHeadline.setText(headline);
emptyContentMessage.setText(message);
emptyContentIcon.setImageResource(errorResource);
emptyContentIcon.setImageResource(R.drawable.ic_user);
multiListProgressBar.setVisibility(View.GONE);
emptyContentIcon.setVisibility(View.VISIBLE);
@ -218,6 +214,7 @@ public class UserInfoActivity extends FileActivity implements Injectable {
}
}
// todo move to viewModel
private void setHeaderImage() {
if (getStorageManager().getCapability(account.name).getServerBackground() != null) {
ViewGroup appBar = findViewById(R.id.appbar);
@ -241,19 +238,19 @@ public class UserInfoActivity extends FileActivity implements Injectable {
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
Drawable[] drawables = {new ColorDrawable(primaryColor),
getResources().getDrawable(R.drawable.background)};
getResources().getDrawable(R.drawable.background)};
LayerDrawable layerDrawable = new LayerDrawable(drawables);
backgroundImageView.setImageDrawable(layerDrawable);
}
};
Glide.with(this)
.load(background)
.centerCrop()
.placeholder(R.drawable.background)
.error(R.drawable.background)
.crossFade()
.into(target);
.load(background)
.centerCrop()
.placeholder(R.drawable.background)
.error(R.drawable.background)
.crossFade()
.into(target);
} else {
// plain color
backgroundImageView.setImageDrawable(new ColorDrawable(primaryColor));
@ -267,24 +264,18 @@ public class UserInfoActivity extends FileActivity implements Injectable {
avatar.setTag(account.name);
DisplayUtils.setAvatar(account, this, mCurrentAccountAvatarRadiusDimension, getResources(), avatar, this);
int tint = ThemeUtils.primaryColor(account, true, this);
if (!TextUtils.isEmpty(userInfo.getDisplayName())) {
fullName.setText(userInfo.getDisplayName());
}
if (userInfo.getPhone() == null && userInfo.getEmail() == null && userInfo.getAddress() == null
&& userInfo.getTwitter() == null && userInfo.getWebsite() == null) {
if (userInfo == null || userInfo.isEmpty()) {
setErrorMessageForMultiList(getString(R.string.userinfo_no_info_headline),
getString(R.string.userinfo_no_info_text), R.drawable.ic_user);
getString(R.string.userinfo_no_info_text));
} else {
if (!TextUtils.isEmpty(userInfo.getDisplayName())) {
fullName.setText(userInfo.getDisplayName());
}
emptyContentContainer.setVisibility(View.GONE);
userInfoView.setVisibility(View.VISIBLE);
if (mUserInfoList.getAdapter() instanceof UserInfoAdapter) {
mUserInfoList.setAdapter(new UserInfoAdapter(createUserInfoDetails(userInfo), tint));
}
adapter.setData(createUserInfoDetails(userInfo));
}
}
@ -295,9 +286,22 @@ public class UserInfoActivity extends FileActivity implements Injectable {
addToListIfNeeded(result, R.drawable.ic_email, userInfo.getEmail(), R.string.user_info_email);
addToListIfNeeded(result, R.drawable.ic_map_marker, userInfo.getAddress(), R.string.user_info_address);
addToListIfNeeded(result, R.drawable.ic_web, DisplayUtils.beautifyURL(userInfo.getWebsite()),
R.string.user_info_website);
R.string.user_info_website);
addToListIfNeeded(result, R.drawable.ic_twitter, DisplayUtils.beautifyTwitterHandle(userInfo.getTwitter()),
R.string.user_info_twitter);
R.string.user_info_twitter);
addToListIfNeeded(result, R.drawable.ic_group, DisplayUtils.beautifyGroups(userInfo.getGroups()),
R.string.user_info_groups);
long quotaValue = userInfo.getQuota().getQuota();
if (quotaValue > 0 || quotaValue == GetUserInfoRemoteOperation.SPACE_UNLIMITED
|| quotaValue == GetUserInfoRemoteOperation.QUOTA_LIMIT_INFO_NOT_AVAILABLE) {
DisplayUtils.setQuotaInformation(quotaProgressBar, quotaPercentage, userInfo.getQuota(), this);
ThemeUtils.tintDrawable(quotaIcon.getDrawable(), primaryColor);
quotaView.setVisibility(View.VISIBLE);
} else {
quotaView.setVisibility(View.GONE);
}
return result;
}
@ -305,206 +309,7 @@ public class UserInfoActivity extends FileActivity implements Injectable {
private void addToListIfNeeded(List<UserInfoDetailsItem> info, @DrawableRes int icon, String text,
@StringRes int contentDescriptionInt) {
if (!TextUtils.isEmpty(text)) {
info.add(new UserInfoDetailsItem(icon, text, getResources().getString(contentDescriptionInt)));
}
}
public static void openAccountRemovalConfirmationDialog(Account account, FragmentManager fragmentManager,
boolean removeDirectly) {
UserInfoActivity.AccountRemovalConfirmationDialog dialog =
UserInfoActivity.AccountRemovalConfirmationDialog.newInstance(account, removeDirectly);
dialog.show(fragmentManager, "dialog");
}
public static class AccountRemovalConfirmationDialog extends DialogFragment {
private Account account;
public static UserInfoActivity.AccountRemovalConfirmationDialog newInstance(Account account,
boolean removeDirectly) {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_ACCOUNT, account);
bundle.putBoolean(KEY_DIRECT_REMOVE, removeDirectly);
UserInfoActivity.AccountRemovalConfirmationDialog dialog = new
UserInfoActivity.AccountRemovalConfirmationDialog();
dialog.setArguments(bundle);
return dialog;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
account = getArguments().getParcelable(KEY_ACCOUNT);
}
@Override
public void onStart() {
super.onStart();
int color = ThemeUtils.primaryAccentColor(getActivity());
AlertDialog alertDialog = (AlertDialog) getDialog();
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(color);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final boolean removeDirectly = getArguments().getBoolean(KEY_DIRECT_REMOVE);
return new AlertDialog.Builder(getActivity(), R.style.Theme_ownCloud_Dialog)
.setTitle(R.string.delete_account)
.setMessage(getResources().getString(R.string.delete_account_warning, account.name))
.setIcon(R.drawable.ic_warning)
.setPositiveButton(R.string.common_ok,
(dialogInterface, i) -> {
// remove contact backup job
ContactsPreferenceActivity.cancelContactBackupJobForAccount(getActivity(), account);
ContentResolver contentResolver = getActivity().getContentResolver();
// disable daily backup
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
contentResolver);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
"false");
String arbitraryDataPushString;
if (!TextUtils.isEmpty(arbitraryDataPushString = arbitraryDataProvider.getValue(
account, PushUtils.KEY_PUSH)) &&
!TextUtils.isEmpty(getResources().getString(R.string.push_server_url))) {
Gson gson = new Gson();
PushConfigurationState pushArbitraryData = gson.fromJson(arbitraryDataPushString,
PushConfigurationState.class);
pushArbitraryData.setShouldBeDeleted(true);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PushUtils.KEY_PUSH,
gson.toJson(pushArbitraryData));
EventBus.getDefault().post(new TokenPushEvent());
}
if (getActivity() != null && !removeDirectly) {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_ACCOUNT, Parcels.wrap(account));
Intent intent = new Intent();
intent.putExtras(bundle);
getActivity().setResult(KEY_DELETE_CODE, intent);
getActivity().finish();
} else {
AccountManager am = (AccountManager) getActivity()
.getSystemService(ACCOUNT_SERVICE);
am.removeAccount(account, null, null);
Intent start = new Intent(getActivity(), FileDisplayActivity.class);
start.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(start);
}
})
.setNegativeButton(R.string.common_cancel, null)
.create();
}
}
private void fetchAndSetData() {
Thread t = new Thread(() -> {
RemoteOperation getRemoteUserInfoOperation = new GetUserInfoRemoteOperation();
RemoteOperationResult result = getRemoteUserInfoOperation.execute(account, this);
if (result.isSuccess() && result.getData() != null) {
userInfo = (UserInfo) result.getData().get(0);
runOnUiThread(() -> populateUserInfoUi(userInfo));
} else {
// show error
runOnUiThread(() -> setErrorMessageForMultiList(sorryMessage, result.getLogMessage(),
R.drawable.ic_list_empty_error));
Log_OC.d(TAG, result.getLogMessage());
}
});
t.start();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (userInfo != null) {
outState.putParcelable(KEY_USER_DATA, Parcels.wrap(userInfo));
}
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEvent(TokenPushEvent event) {
PushUtils.pushRegistrationToServer(getUserAccountManager(), preferences.getPushToken());
}
protected class UserInfoDetailsItem {
@DrawableRes public int icon;
public String text;
public String iconContentDescription;
public UserInfoDetailsItem(@DrawableRes int icon, String text, String iconContentDescription) {
this.icon = icon;
this.text = text;
this.iconContentDescription = iconContentDescription;
}
}
protected class UserInfoAdapter extends RecyclerView.Adapter<UserInfoAdapter.ViewHolder> {
protected List<UserInfoDetailsItem> mDisplayList;
@ColorInt protected int mTintColor;
public class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.icon) protected ImageView icon;
@BindView(R.id.text) protected TextView text;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
public UserInfoAdapter(List<UserInfoDetailsItem> displayList, @ColorInt int tintColor) {
mDisplayList = displayList == null ? new LinkedList<>() : displayList;
mTintColor = tintColor;
}
public void setData(List<UserInfoDetailsItem> displayList) {
mDisplayList = displayList == null ? new LinkedList<>() : displayList;
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.user_info_details_table_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
UserInfoDetailsItem item = mDisplayList.get(position);
holder.icon.setImageResource(item.icon);
holder.text.setText(item.text);
holder.icon.setContentDescription(item.iconContentDescription);
DrawableCompat.setTint(holder.icon.getDrawable(), mTintColor);
}
@Override
public int getItemCount() {
return mDisplayList.size();
info.add(new UserInfoDetailsItem(icon, text, getResources().getString(contentDescriptionInt), primaryColor));
}
}
}

View File

@ -0,0 +1,69 @@
package com.owncloud.android.ui.adapter;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.owncloud.android.databinding.UserInfoDetailsTableItemBinding;
import com.owncloud.android.ui.components.UserInfoDetailsItem;
import java.util.LinkedList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.databinding.BindingAdapter;
import androidx.recyclerview.widget.RecyclerView;
public class UserInfoAdapter extends RecyclerView.Adapter<UserInfoAdapter.ViewHolder> {
private List<UserInfoDetailsItem> displayList;
public UserInfoAdapter(List<UserInfoDetailsItem> displayList) {
this.displayList = displayList == null ? new LinkedList<>() : displayList;
}
public void setData(List<UserInfoDetailsItem> displayList) {
this.displayList = displayList == null ? new LinkedList<>() : displayList;
notifyDataSetChanged();
}
@NonNull
@Override
public UserInfoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
UserInfoDetailsTableItemBinding binding = UserInfoDetailsTableItemBinding.inflate(layoutInflater, parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
UserInfoDetailsItem item = displayList.get(position);
holder.bind(item);
}
@Override
public int getItemCount() {
return displayList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private final UserInfoDetailsTableItemBinding binding;
public ViewHolder(UserInfoDetailsTableItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(UserInfoDetailsItem item) {
binding.setUserInfoDetailsItem(item);
binding.executePendingBindings();
}
}
@BindingAdapter({"android:src"})
public static void setImageViewResource(ImageView imageView, int resource) {
imageView.setImageResource(resource);
}
}

View File

@ -0,0 +1,34 @@
package com.owncloud.android.ui.components;
import androidx.annotation.DrawableRes;
public class UserInfoDetailsItem {
@DrawableRes
private int icon;
private String text;
private String iconContentDescription;
private int tintColor;
public UserInfoDetailsItem(@DrawableRes int icon, String text, String iconContentDescription, int tintColor) {
this.icon = icon;
this.text = text;
this.iconContentDescription = iconContentDescription;
this.tintColor = tintColor;
}
public int getIcon() {
return icon;
}
public String getText() {
return text;
}
public String getIconContentDescription() {
return iconContentDescription;
}
public int getTintColor() {
return tintColor;
}
}

View File

@ -0,0 +1,126 @@
package com.owncloud.android.ui.dialog;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.PushConfigurationState;
import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.activity.UserInfoActivity;
import com.owncloud.android.ui.events.TokenPushEvent;
import com.owncloud.android.utils.PushUtils;
import com.owncloud.android.utils.ThemeUtils;
import org.greenrobot.eventbus.EventBus;
import org.parceler.Parcels;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import static android.content.Context.ACCOUNT_SERVICE;
public class AccountRemovalConfirmationDialog extends DialogFragment {
private Account account;
public static AccountRemovalConfirmationDialog newInstance(Account account, boolean removeDirectly) {
Bundle bundle = new Bundle();
bundle.putParcelable(UserInfoActivity.KEY_ACCOUNT, account);
bundle.putBoolean(UserInfoActivity.KEY_DIRECT_REMOVE, removeDirectly);
AccountRemovalConfirmationDialog dialog = new AccountRemovalConfirmationDialog();
dialog.setArguments(bundle);
return dialog;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
account = getArguments().getParcelable(UserInfoActivity.KEY_ACCOUNT);
}
@Override
public void onStart() {
super.onStart();
int color = ThemeUtils.primaryAccentColor(getActivity());
AlertDialog alertDialog = (AlertDialog) getDialog();
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(color);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final boolean removeDirectly = getArguments().getBoolean(UserInfoActivity.KEY_DIRECT_REMOVE);
return new AlertDialog.Builder(getActivity(), R.style.Theme_ownCloud_Dialog)
.setTitle(R.string.delete_account)
.setMessage(getResources().getString(R.string.delete_account_warning, account.name))
.setIcon(R.drawable.ic_warning)
.setPositiveButton(R.string.common_ok,
(dialogInterface, i) -> {
// remove contact backup job
ContactsPreferenceActivity.cancelContactBackupJobForAccount(getActivity(), account);
ContentResolver contentResolver = getActivity().getContentResolver();
// disable daily backup
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
contentResolver);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
"false");
String arbitraryDataPushString;
if (!TextUtils.isEmpty(arbitraryDataPushString = arbitraryDataProvider.getValue(
account, PushUtils.KEY_PUSH)) &&
!TextUtils.isEmpty(getResources().getString(R.string.push_server_url))) {
Gson gson = new Gson();
PushConfigurationState pushArbitraryData = gson.fromJson(arbitraryDataPushString,
PushConfigurationState.class);
pushArbitraryData.setShouldBeDeleted(true);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PushUtils.KEY_PUSH,
gson.toJson(pushArbitraryData));
EventBus.getDefault().post(new TokenPushEvent());
}
if (getActivity() != null && !removeDirectly) {
Bundle bundle = new Bundle();
bundle.putParcelable(UserInfoActivity.KEY_ACCOUNT, Parcels.wrap(account));
Intent intent = new Intent();
intent.putExtras(bundle);
getActivity().setResult(UserInfoActivity.KEY_DELETE_CODE, intent);
getActivity().finish();
} else {
AccountManager am = (AccountManager) getActivity().getSystemService(ACCOUNT_SERVICE);
if (am != null) {
am.removeAccount(account, null, null);
}
Intent start = new Intent(getActivity(), FileDisplayActivity.class);
start.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(start);
}
})
.setNegativeButton(R.string.common_cancel, null)
.create();
}
}

View File

@ -0,0 +1,51 @@
package com.owncloud.android.ui.viewModel;
import android.accounts.Account;
import com.nextcloud.client.account.UserAccountManager;
import com.owncloud.android.datamodel.UserInfo;
import com.owncloud.android.repository.UserInfoRepository;
import com.owncloud.android.ui.dialog.AccountRemovalConfirmationDialog;
import com.owncloud.android.ui.events.TokenPushEvent;
import com.owncloud.android.utils.PushUtils;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
public class UserInfoViewModel extends ViewModel {
private Account account;
private LiveData<UserInfo> userInfo;
private UserAccountManager userAccountManager;
public void init(Account account, UserInfoRepository userInfoRepository, UserAccountManager userAccountManager) {
if (this.account != null) {
return;
}
userInfo = userInfoRepository.getUserInfo(account);
this.userAccountManager = userAccountManager;
// TODO add here setHeader, registerPush, etc.
}
public LiveData<UserInfo> getUserInfo() {
return userInfo;
}
public static void openAccountRemovalConfirmationDialog(Account account, FragmentManager fragmentManager,
boolean removeDirectly) {
AccountRemovalConfirmationDialog.newInstance(account, removeDirectly).show(fragmentManager, "dialog");
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEvent(TokenPushEvent event) {
PushUtils.pushRegistrationToServer(userAccountManager, ""); // todo token?
}
}

View File

@ -0,0 +1,33 @@
package com.owncloud.android.utils;
public class Converters {
// @TypeConverter
// public static ArrayList<String> fromString(String value) {
// Type listType = new TypeToken<ArrayList<String>>() {
// }.getType();
//
// return new Gson().fromJson(value, listType);
// }
//
// @TypeConverter
// public static String fromArrayList(ArrayList<String> list) {
// Gson gson = new Gson();
//
// return gson.toJson(list);
// }
//
// @TypeConverter
// public static Quota quotaFromString(String value) {
// Type listType = new TypeToken<Quota>() {
// }.getType();
//
// return new Gson().fromJson(value, listType);
// }
//
// @TypeConverter
// public static String quotaToString(Quota quota) {
// Gson gson = new Gson();
//
// return gson.toJson(quota);
// }
}

View File

@ -44,6 +44,8 @@ import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.bumptech.glide.GenericRequestBuilder;
import com.bumptech.glide.Glide;
@ -61,7 +63,9 @@ import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.Quota;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
import com.owncloud.android.ui.TextDrawable;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.events.SearchEvent;
@ -83,6 +87,7 @@ import java.math.BigDecimal;
import java.net.IDN;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
@ -239,6 +244,9 @@ public final class DisplayUtils {
}
}
public static String beautifyGroups(ArrayList<String> groups) {
return TextUtils.join(", ", groups);
}
/**
* Converts an internationalized domain name (IDN) in an URL to and from ASCII/Unicode.
*
@ -723,4 +731,30 @@ public final class DisplayUtils {
DisplayUtils.showSnackMessage(activity, error);
}
}
/**
* configured the quota to be displayed
*
* @param quotaProgressBar progress bar
* @param quotaTextPercentage text underneath progress bar
* @param quota quota to use
*/
static public void setQuotaInformation(ProgressBar quotaProgressBar, TextView quotaTextPercentage,
Quota quota, Activity activity) {
final long used = quota.getUsed();
final long total = quota.getTotal();
final int relative = (int) Math.ceil(quota.getRelative());
if (GetUserInfoRemoteOperation.SPACE_UNLIMITED == quota.getQuota()) {
quotaTextPercentage.setText(String.format(activity.getString(R.string.drawer_quota_unlimited),
bytesToHumanReadable(used)));
} else {
quotaTextPercentage.setText(String.format(activity.getString(R.string.drawer_quota),
bytesToHumanReadable(used), bytesToHumanReadable(total)));
}
quotaProgressBar.setProgress(relative);
ThemeUtils.colorProgressBar(quotaProgressBar, getRelativeInfoColor(activity, relative));
}
}

View File

@ -0,0 +1,9 @@
<vector android:height="24dp"
android:viewportHeight="16"
android:viewportWidth="16"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#FF000000"
android:pathData="m8,1c-3.86,0 -7,3.15 -7,7s3.15,7 7,7c3.86,0 7,-3.15 7,-7 0,-3.86 -3.15,-7 -7,-7zM8,2.75c2.91,0 5.25,2.34 5.25,5.25 0,1.42 -0.56,2.7 -1.47,3.644l-3.78,-3.644z" />
</vector>

View File

@ -54,7 +54,7 @@
/>
<ProgressBar
android:id="@+id/drawer_quota_ProgressBar"
android:id="@+id/drawer_quota_progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -18,34 +18,45 @@
You should have received a copy of the GNU Affero General Public
License along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/standard_margin"
android:layout_marginEnd="@dimen/standard_half_margin"
android:layout_marginLeft="@dimen/standard_icon_list_horizontal_margin"
android:layout_marginRight="@dimen/standard_half_margin"
android:layout_marginStart="@dimen/standard_icon_list_horizontal_margin"
android:layout_marginTop="@dimen/standard_margin"
android:contentDescription="@string/account_icon"
tools:src="@drawable/ic_phone" />
<data>
<TextView
android:id="@+id/text"
<variable
name="UserInfoDetailsItem"
type="com.owncloud.android.ui.components.UserInfoDetailsItem"/>
</data>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_margin="@dimen/standard_margin"
android:layout_toEndOf="@id/icon"
android:layout_toRightOf="@id/icon"
android:maxLines="3"
android:textAppearance="?android:attr/textAppearanceListItem"
tools:text="+49 123 456 789 12" />
android:layout_height="wrap_content">
</RelativeLayout>
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/standard_icon_list_horizontal_margin"
android:layout_marginLeft="@dimen/standard_icon_list_horizontal_margin"
android:layout_marginTop="@dimen/standard_margin"
android:layout_marginEnd="@dimen/standard_half_margin"
android:layout_marginRight="@dimen/standard_half_margin"
android:layout_marginBottom="@dimen/standard_margin"
android:contentDescription="@string/account_icon"
android:src="@{UserInfoDetailsItem.icon}"
android:tint="@{UserInfoDetailsItem.tintColor}"
tools:src="@drawable/ic_phone"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_margin"
android:text="@{UserInfoDetailsItem.text}"
android:contentDescription="@{UserInfoDetailsItem.iconContentDescription"
android:maxLines="3"
android:textAppearance="?android:attr/textAppearanceListItem"
tools:text="+49 123 456 789 12"/>
</LinearLayout>
</layout>

View File

@ -22,7 +22,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent">
<include
layout="@layout/toolbar_user_information"/>
@ -32,8 +32,8 @@
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
@ -62,6 +62,55 @@
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/quota_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/standard_icon_list_horizontal_margin"
android:layout_marginLeft="@dimen/standard_icon_list_horizontal_margin"
android:layout_marginTop="@dimen/standard_margin"
android:layout_marginEnd="@dimen/standard_half_margin"
android:layout_marginRight="@dimen/standard_half_margin"
android:layout_marginBottom="@dimen/standard_margin"
android:contentDescription="@string/account_icon"
android:src="@drawable/ic_quota" />
<LinearLayout
android:id="@+id/userinfo_quota"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/white"
android:clickable="false"
android:orientation="vertical"
android:paddingLeft="@dimen/standard_padding"
android:paddingTop="@dimen/standard_half_padding"
android:paddingRight="@dimen/standard_padding"
android:paddingBottom="@dimen/standard_half_padding"
android:visibility="gone">
<ProgressBar
android:id="@+id/userinfo_quota_progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
android:indeterminateOnly="false"
android:text="@string/drawer_quota" />
<TextView
android:id="@+id/userinfo_quota_percentage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="@dimen/alternate_half_padding"
android:text="@string/drawer_quota" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -596,6 +596,7 @@
<string name="user_info_address">Address</string>
<string name="user_info_website">Website</string>
<string name="user_info_twitter">Twitter</string>
<string name="user_info_groups">Groups</string>
<string name="user_information_retrieval_error">Error retrieving user information</string>
@ -826,6 +827,7 @@
<string name="no_pdf_app_available">No App available to handle PDF</string>
<string name="share_via_link_hide_download">Hide download</string>
<string name="unread_comments">Unread comments exist</string>
<string name="user_info_quota">Quota</string>
<string name="richdocuments_failed_to_load_document">Failed to load document!</string>
<string name="create_new_document">Create new document</string>
<string name="create_new_spreadsheet">Create new spreadsheet</string>

View File

@ -0,0 +1,86 @@
package com.owncloud.android.repository;
import android.accounts.Account;
import android.content.Context;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.concurrent.Executor;
import androidx.lifecycle.LiveData;
import static java.util.Collections.singletonList;
import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class UserInfoRepositoryTest {
@Mock
private Executor executor;
@Mock
private Context context;
@Mock
private Invoker invoker;
@Mock
private FileDataStorageManager storageManager;
@Test
public void getUserInfo() {
// init
Account accountMock = mock(Account.class);
// when(accountMock.name).thenReturn("accountName");
RemoteOperationResult remoteOperationResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.OK);
UserInfo userInfoMock = new UserInfo();
userInfoMock.displayName = "displayName";
// LiveData userInfoResult = mock(LiveData.class);
// when(userInfoDao.load(any())).thenReturn(userInfoResult);
remoteOperationResult.setData(new ArrayList<>(singletonList(userInfoMock)));
when(invoker.invoke(any(), any())).thenReturn(remoteOperationResult);
UserInfoRepository sut = new UserInfoRepository(executor, storageManager);
// test
LiveData<com.owncloud.android.datamodel.UserInfo> userInfo = sut.getUserInfo(accountMock);
// verify result
assertTrue(userInfo != null);
// verify local dao call
ArgumentCaptor<com.owncloud.android.datamodel.UserInfo> userInfoArgumentCaptor = ArgumentCaptor.forClass(com.owncloud.android.datamodel.UserInfo.class);
// TODO re-enable verify(userInfoDao).save(userInfoArgumentCaptor.capture());
assertThat(userInfoArgumentCaptor.getValue().getDisplayName(), is("displayName"));
// verify(userInfoDao).load("accountName");
// verify remote lib call
ArgumentCaptor<Account> accountArgumentCaptor = ArgumentCaptor.forClass(Account.class);
ArgumentCaptor<RemoteOperation> remoteOperationArgumentCaptor = ArgumentCaptor.forClass(RemoteOperation.class);
verify(invoker.invoke(accountArgumentCaptor.capture(), remoteOperationArgumentCaptor.capture()));
assertSame(accountArgumentCaptor.getValue(), accountMock);
assertThat(remoteOperationArgumentCaptor.getValue(), is(instanceOf(GetUserInfoRemoteOperation.class)));
}
}