talk-android/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.java
Andy Scherzinger 7b64fbd7e0
spotbugs: checks the size of a collection against zero rather than using isEmpty()
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2022-02-17 17:23:46 +01:00

881 lines
35 KiB
Java

/*
* Nextcloud Talk application
*
* @author Tobias Kaminsky
* Copyright (C) 2021 Tobias Kaminsky <tobias.kaminsky@nextcloud.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.controllers;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
import com.github.dhaval2404.imagepicker.ImagePicker;
import com.nextcloud.talk.R;
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.components.filebrowser.controllers.BrowserController;
import com.nextcloud.talk.components.filebrowser.controllers.BrowserForAvatarController;
import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.models.database.CapabilitiesUtil;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.generic.GenericOverall;
import com.nextcloud.talk.models.json.userprofile.Scope;
import com.nextcloud.talk.models.json.userprofile.UserProfileData;
import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall;
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
import com.nextcloud.talk.ui.dialog.ScopeDialog;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils;
import org.parceler.Parcels;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import autodagger.AutoInjector;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
@AutoInjector(NextcloudTalkApplication.class)
public class ProfileController extends BaseController {
private static final String TAG = ProfileController.class.getSimpleName();
@Inject
NcApi ncApi;
@Inject
UserUtils userUtils;
private UserEntity currentUser;
private boolean edit = false;
private RecyclerView recyclerView;
private UserInfoAdapter adapter;
private UserProfileData userInfo;
private ArrayList<String> editableFields = new ArrayList<>();
public ProfileController() {
super();
}
@NonNull
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_profile, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_profile, menu);
}
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.findItem(R.id.edit).setVisible(editableFields.size() > 0);
if (edit) {
menu.findItem(R.id.edit).setTitle(R.string.save);
} else {
menu.findItem(R.id.edit).setTitle(R.string.edit);
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.edit) {
if (edit) {
save();
}
edit = !edit;
if (edit) {
item.setTitle(R.string.save);
getActivity().findViewById(R.id.emptyList).setVisibility(View.GONE);
getActivity().findViewById(R.id.userinfo_list).setVisibility(View.VISIBLE);
if (CapabilitiesUtil.isAvatarEndpointAvailable(currentUser)) {
// TODO later avatar can also be checked via user fields, for now it is in Talk capability
getActivity().findViewById(R.id.avatar_buttons).setVisibility(View.VISIBLE);
}
ncApi.getEditableUserProfileFields(
ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()),
ApiUtils.getUrlForUserFields(currentUser.getBaseUrl()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<UserProfileFieldsOverall>() {
@Override
public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
// unused atm
}
@Override
public void onNext(@io.reactivex.annotations.NonNull UserProfileFieldsOverall userProfileFieldsOverall) {
editableFields = userProfileFieldsOverall.getOcs().getData();
adapter.notifyDataSetChanged();
}
@Override
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
Log.e(TAG, "Error loading editable user profile from server", e);
edit = false;
}
@Override
public void onComplete() {
// unused atm
}
});
} else {
item.setTitle(R.string.edit);
getActivity().findViewById(R.id.avatar_buttons).setVisibility(View.INVISIBLE);
if (adapter.filteredDisplayList.isEmpty()) {
getActivity().findViewById(R.id.emptyList).setVisibility(View.VISIBLE);
getActivity().findViewById(R.id.userinfo_list).setVisibility(View.GONE);
}
}
adapter.notifyDataSetChanged();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
recyclerView = getActivity().findViewById(R.id.userinfo_list);
adapter = new UserInfoAdapter(null, getActivity().getResources().getColor(R.color.colorPrimary), this);
recyclerView.setAdapter(adapter);
recyclerView.setItemViewCacheSize(20);
currentUser = userUtils.getCurrentUser();
String credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
getActivity().findViewById(R.id.avatar_upload).setOnClickListener(v -> sendSelectLocalFileIntent());
getActivity().findViewById(R.id.avatar_choose).setOnClickListener(v ->
showBrowserScreen(BrowserController.BrowserType.DAV_BROWSER));
getActivity().findViewById(R.id.avatar_delete).setOnClickListener(v ->
ncApi.deleteAvatar(credentials, ApiUtils.getUrlForTempAvatar(currentUser.getBaseUrl()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<GenericOverall>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
// unused atm
}
@Override
public void onNext(@NonNull GenericOverall genericOverall) {
DisplayUtils.loadAvatarImage(
currentUser,
getActivity().findViewById(R.id.avatar_image),
true);
}
@Override
public void onError(@NonNull Throwable e) {
Toast.makeText(getApplicationContext(), "Error", Toast.LENGTH_LONG).show();
}
@Override
public void onComplete() {
// unused atm
}
}));
ViewCompat.setTransitionName(getActivity().findViewById(R.id.avatar_image), "userAvatar.transitionTag");
ncApi.getUserProfile(credentials, ApiUtils.getUrlForUserProfile(currentUser.getBaseUrl()))
.retry(3)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<UserProfileOverall>() {
@Override
public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
// unused atm
}
@Override
public void onNext(@io.reactivex.annotations.NonNull UserProfileOverall userProfileOverall) {
userInfo = userProfileOverall.getOcs().getData();
showUserProfile();
}
@Override
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
setErrorMessageForMultiList(
getActivity().getString(R.string.userinfo_no_info_headline),
getActivity().getString(R.string.userinfo_error_text),
R.drawable.ic_list_empty_error);
}
@Override
public void onComplete() {
// unused atm
}
});
}
@Override
protected String getTitle() {
return getResources().getString(R.string.nc_profile_personal_info_title);
}
private void showUserProfile() {
if (getActivity() == null) {
return;
}
if (currentUser.getBaseUrl() != null) {
((TextView) getActivity()
.findViewById(R.id.userinfo_baseurl))
.setText(Uri.parse(currentUser.getBaseUrl()).getHost());
}
DisplayUtils.loadAvatarImage(currentUser, getActivity().findViewById(R.id.avatar_image), false);
if (!TextUtils.isEmpty(userInfo.getDisplayName())) {
((TextView) getActivity().findViewById(R.id.userinfo_fullName)).setText(userInfo.getDisplayName());
}
getActivity().findViewById(R.id.loading_content).setVisibility(View.VISIBLE);
adapter.setData(createUserInfoDetails(userInfo));
if (TextUtils.isEmpty(userInfo.getDisplayName()) &&
TextUtils.isEmpty(userInfo.getPhone()) &&
TextUtils.isEmpty(userInfo.getEmail()) &&
TextUtils.isEmpty(userInfo.getAddress()) &&
TextUtils.isEmpty(userInfo.getTwitter()) &&
TextUtils.isEmpty(userInfo.getWebsite())) {
getActivity().findViewById(R.id.userinfo_list).setVisibility(View.GONE);
getActivity().findViewById(R.id.loading_content).setVisibility(View.GONE);
getActivity().findViewById(R.id.emptyList).setVisibility(View.VISIBLE);
setErrorMessageForMultiList(
getActivity().getString(R.string.userinfo_no_info_headline),
getActivity().getString(R.string.userinfo_no_info_text), R.drawable.ic_user);
} else {
getActivity().findViewById(R.id.emptyList).setVisibility(View.GONE);
getActivity().findViewById(R.id.loading_content).setVisibility(View.GONE);
getActivity().findViewById(R.id.userinfo_list).setVisibility(View.VISIBLE);
}
// show edit button
if (CapabilitiesUtil.canEditScopes(currentUser)) {
ncApi.getEditableUserProfileFields(ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()),
ApiUtils.getUrlForUserFields(currentUser.getBaseUrl()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<UserProfileFieldsOverall>() {
@Override
public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
// unused atm
}
@Override
public void onNext(@io.reactivex.annotations.NonNull UserProfileFieldsOverall userProfileFieldsOverall) {
editableFields = userProfileFieldsOverall.getOcs().getData();
getActivity().invalidateOptionsMenu();
adapter.notifyDataSetChanged();
}
@Override
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
Log.e(TAG, "Error loading editable user profile from server", e);
edit = false;
}
@Override
public void onComplete() {
// unused atm
}
});
}
}
private void setErrorMessageForMultiList(String headline, String message, @DrawableRes int errorResource) {
if (getActivity() == null) {
return;
}
((TextView) getActivity().findViewById(R.id.empty_list_view_headline)).setText(headline);
((TextView) getActivity().findViewById(R.id.empty_list_view_text)).setText(message);
((ImageView) getActivity().findViewById(R.id.empty_list_icon)).setImageResource(errorResource);
getActivity().findViewById(R.id.empty_list_icon).setVisibility(View.VISIBLE);
getActivity().findViewById(R.id.empty_list_view_text).setVisibility(View.VISIBLE);
getActivity().findViewById(R.id.userinfo_list).setVisibility(View.GONE);
getActivity().findViewById(R.id.loading_content).setVisibility(View.GONE);
}
private List<UserInfoDetailsItem> createUserInfoDetails(UserProfileData userInfo) {
List<UserInfoDetailsItem> result = new LinkedList<>();
addToList(result,
R.drawable.ic_user,
userInfo.getDisplayName(),
R.string.user_info_displayname,
Field.DISPLAYNAME,
userInfo.getDisplayNameScope());
addToList(result,
R.drawable.ic_phone,
userInfo.getPhone(),
R.string.user_info_phone,
Field.PHONE,
userInfo.getPhoneScope());
addToList(result, R.drawable.ic_email, userInfo.getEmail(), R.string.user_info_email, Field.EMAIL, userInfo.getEmailScope());
addToList(result,
R.drawable.ic_map_marker,
userInfo.getAddress(),
R.string.user_info_address,
Field.ADDRESS,
userInfo.getAddressScope());
addToList(result,
R.drawable.ic_web,
DisplayUtils.beautifyURL(userInfo.getWebsite()),
R.string.user_info_website,
Field.WEBSITE,
userInfo.getWebsiteScope());
addToList(
result,
R.drawable.ic_twitter,
DisplayUtils.beautifyTwitterHandle(userInfo.getTwitter()),
R.string.user_info_twitter,
Field.TWITTER,
userInfo.getTwitterScope());
return result;
}
private void addToList(List<UserInfoDetailsItem> info,
@DrawableRes int icon,
String text,
@StringRes int contentDescriptionInt,
Field field,
Scope scope) {
info.add(new UserInfoDetailsItem(icon, text, getResources().getString(contentDescriptionInt), field, scope));
}
private void save() {
for (UserInfoDetailsItem item : adapter.displayList) {
// Text
if (!item.text.equals(userInfo.getValueByField(item.field))) {
String credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
ncApi.setUserData(
credentials,
ApiUtils.getUrlForUserData(currentUser.getBaseUrl(), currentUser.getUserId()),
item.field.fieldName,
item.text)
.retry(3)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<GenericOverall>() {
@Override
public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
// unused atm
}
@Override
public void onNext(@io.reactivex.annotations.NonNull GenericOverall userProfileOverall) {
Log.d(TAG, "Successfully saved: " + item.text + " as " + item.field);
if (item.field == Field.DISPLAYNAME) {
((TextView) getActivity().findViewById(R.id.userinfo_fullName)).setText(item.text);
}
}
@Override
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
item.text = userInfo.getValueByField(item.field);
Toast.makeText(getApplicationContext(),
String.format(getResources().getString(R.string.failed_to_save),
item.field),
Toast.LENGTH_LONG).show();
adapter.updateFilteredList();
adapter.notifyDataSetChanged();
Log.e(TAG, "Failed to saved: " + item.text + " as " + item.field, e);
}
@Override
public void onComplete() {
// unused atm
}
});
}
// Scope
if (item.scope != userInfo.getScopeByField(item.field)) {
saveScope(item, userInfo);
}
adapter.updateFilteredList();
}
}
private void sendSelectLocalFileIntent() {
Intent intent = ImagePicker.Companion.with(getActivity())
.galleryOnly()
.crop()
.cropSquare()
.compress(1024)
.maxResultSize(1024, 1024)
.prepareIntent();
startActivityForResult(intent, 1);
}
private void showBrowserScreen(BrowserController.BrowserType browserType) {
Bundle bundle = new Bundle();
bundle.putParcelable(
BundleKeys.INSTANCE.getKEY_BROWSER_TYPE(),
Parcels.wrap(BrowserController.BrowserType.class, browserType));
bundle.putParcelable(
BundleKeys.INSTANCE.getKEY_USER_ENTITY(),
Parcels.wrap(UserEntity.class, currentUser));
bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), "123");
getRouter().pushController(RouterTransaction.with(new BrowserForAvatarController(bundle, this))
.pushChangeHandler(new VerticalChangeHandler())
.popChangeHandler(new VerticalChangeHandler()));
}
public void handleAvatar(String remotePath) {
String uri = currentUser.getBaseUrl() + "/index.php/apps/files/api/v1/thumbnail/512/512/" +
Uri.encode(remotePath, "/");
Call<ResponseBody> downloadCall = ncApi.downloadResizedImage(
ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()),
uri);
downloadCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) {
saveBitmapAndPassToImagePicker(BitmapFactory.decodeStream(response.body().byteStream()));
}
@Override
public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
// unused atm
}
});
}
@SuppressWarnings({"IOI_USE_OF_FILE_STREAM_CONSTRUCTORS"}) // only possible with API26
private void saveBitmapAndPassToImagePicker(Bitmap bitmap) {
File file = null;
try {
file = File.createTempFile("avatar", "png",
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
try (FileOutputStream out = new FileOutputStream(file)) {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
} catch (IOException e) {
Log.e(TAG, "Error compressing bitmap", e);
}
} catch (IOException e) {
Log.e(TAG, "Error creating temporary avatar image", e);
}
if (file == null) {
// TODO exception
return;
}
Intent intent = ImagePicker.Companion.with(getActivity())
.fileOnly()
.crop()
.cropSquare()
.compress(1024)
.maxResultSize(1024, 1024)
.prepareIntent();
intent.putExtra(ImagePicker.EXTRA_FILE, file);
startActivityForResult(intent, 1);
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode == Activity.RESULT_OK) {
uploadAvatar(ImagePicker.Companion.getFile(data));
} else if (resultCode == ImagePicker.RESULT_ERROR) {
Toast.makeText(getActivity(), ImagePicker.Companion.getError(data), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getActivity(), "Task Cancelled", Toast.LENGTH_SHORT).show();
}
}
private void uploadAvatar(File file) {
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
builder.addFormDataPart("files[]", file.getName(), RequestBody.create(MediaType.parse("image/*"), file));
final MultipartBody.Part filePart = MultipartBody.Part.createFormData("files[]", file.getName(),
RequestBody.create(MediaType.parse("image/jpg"), file));
// upload file
ncApi.uploadAvatar(
ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()),
ApiUtils.getUrlForTempAvatar(currentUser.getBaseUrl()),
filePart)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<GenericOverall>() {
@Override
public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
// unused atm
}
@Override
public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) {
DisplayUtils.loadAvatarImage(currentUser, getActivity().findViewById(R.id.avatar_image), true);
}
@Override
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
Toast.makeText(getApplicationContext(), "Error", Toast.LENGTH_LONG).show();
Log.e(TAG, "Error uploading avatar", e);
}
@Override
public void onComplete() {
// unused atm
}
});
}
public void saveScope(UserInfoDetailsItem item, UserProfileData userInfo) {
String credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
ncApi.setUserData(
credentials,
ApiUtils.getUrlForUserData(currentUser.getBaseUrl(), currentUser.getUserId()),
item.field.getScopeName(),
item.scope.getName())
.retry(3)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<GenericOverall>() {
@Override
public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
// unused atm
}
@Override
public void onNext(@io.reactivex.annotations.NonNull GenericOverall userProfileOverall) {
Log.d(TAG, "Successfully saved: " + item.scope + " as " + item.field);
}
@Override
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
item.scope = userInfo.getScopeByField(item.field);
Log.e(TAG, "Failed to saved: " + item.scope + " as " + item.field, e);
}
@Override
public void onComplete() {
// unused atm
}
});
}
protected static class UserInfoDetailsItem {
@DrawableRes
public int icon;
public String text;
public String hint;
private Field field;
private Scope scope;
public UserInfoDetailsItem(@DrawableRes int icon, String text, String hint, Field field, Scope scope) {
this.icon = icon;
this.text = text;
this.hint = hint;
this.field = field;
this.scope = scope;
}
}
public static class UserInfoAdapter extends RecyclerView.Adapter<UserInfoAdapter.ViewHolder> {
protected List<UserInfoDetailsItem> displayList;
protected List<UserInfoDetailsItem> filteredDisplayList = new LinkedList<>();
@ColorInt
protected int mTintColor;
private final ProfileController controller;
public static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.user_info_detail_container)
protected View container;
@BindView(R.id.icon)
protected ImageView icon;
@BindView(R.id.user_info_edit_text)
protected EditText text;
@BindView(R.id.scope)
protected ImageView scope;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
public UserInfoAdapter(List<UserInfoDetailsItem> displayList,
@ColorInt int tintColor,
ProfileController controller) {
this.displayList = displayList == null ? new LinkedList<>() : displayList;
mTintColor = tintColor;
this.controller = controller;
}
public void setData(List<UserInfoDetailsItem> displayList) {
this.displayList = displayList == null ? new LinkedList<>() : displayList;
updateFilteredList();
notifyDataSetChanged();
}
public void updateFilteredList() {
filteredDisplayList.clear();
if (displayList != null) {
for (UserInfoDetailsItem item : displayList) {
if (!TextUtils.isEmpty(item.text)) {
filteredDisplayList.add(item);
}
}
}
}
@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;
if (controller.edit) {
item = displayList.get(position);
} else {
item = filteredDisplayList.get(position);
}
if (item.scope == null) {
holder.scope.setVisibility(View.GONE);
} else {
holder.scope.setVisibility(View.VISIBLE);
switch (item.scope) {
case PRIVATE:
case LOCAL:
holder.scope.setImageResource(R.drawable.ic_password);
break;
case FEDERATED:
holder.scope.setImageResource(R.drawable.ic_contacts);
break;
case PUBLISHED:
holder.scope.setImageResource(R.drawable.ic_link);
break;
}
holder.scope.setContentDescription(
controller.getActivity().getResources().getString(
R.string.scope_toggle_description,
item.hint));
}
holder.icon.setImageResource(item.icon);
holder.text.setText(item.text);
holder.text.setHint(item.hint);
holder.text.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// unused atm
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (controller.edit) {
displayList.get(holder.getAdapterPosition()).text = holder.text.getText().toString();
} else {
filteredDisplayList.get(holder.getAdapterPosition()).text = holder.text.getText().toString();
}
}
@Override
public void afterTextChanged(Editable s) {
// unused atm
}
});
holder.icon.setContentDescription(item.hint);
DrawableCompat.setTint(holder.icon.getDrawable(), mTintColor);
if (!TextUtils.isEmpty(item.text) || controller.edit) {
holder.container.setVisibility(View.VISIBLE);
if (controller.getActivity() != null) {
holder.text.setTextColor(ContextCompat.getColor(
controller.getActivity(),
R.color.conversation_item_header)
);
}
if (controller.edit &&
controller.editableFields.contains(item.field.toString().toLowerCase(Locale.ROOT))) {
holder.text.setEnabled(true);
holder.text.setFocusableInTouchMode(true);
holder.text.setEnabled(true);
holder.text.setCursorVisible(true);
holder.text.setBackgroundTintList(ColorStateList.valueOf(mTintColor));
holder.scope.setOnClickListener(v -> new ScopeDialog(
controller.getActivity(),
this,
item.field,
holder.getAdapterPosition()).show());
holder.scope.setAlpha(0.87f); // active - high emphasis
} else {
holder.text.setEnabled(false);
holder.text.setFocusableInTouchMode(false);
holder.text.setEnabled(false);
holder.text.setCursorVisible(false);
holder.text.setBackgroundTintList(ColorStateList.valueOf(Color.TRANSPARENT));
holder.scope.setOnClickListener(null);
holder.scope.setAlpha(0.6f); // inactive - medium emphasis
}
} else {
holder.container.setVisibility(View.GONE);
}
}
@Override
public int getItemCount() {
if (controller.edit) {
return displayList.size();
} else {
return filteredDisplayList.size();
}
}
public void updateScope(int position, Scope scope) {
displayList.get(position).scope = scope;
notifyDataSetChanged();
}
}
public enum Field {
EMAIL("email", "emailScope"),
DISPLAYNAME("displayname", "displaynameScope"),
PHONE("phone", "phoneScope"),
ADDRESS("address", "addressScope"),
WEBSITE("website", "websiteScope"),
TWITTER("twitter", "twitterScope");
private final String fieldName;
private final String scopeName;
Field(String fieldName, String scopeName) {
this.fieldName = fieldName;
this.scopeName = scopeName;
}
public String getFieldName() {
return fieldName;
}
public String getScopeName() {
return scopeName;
}
}
}