Browse Source

Add user profile, allow to edit it, if server supports it

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
tobiasKaminsky 4 years ago
parent
commit
4f0923ba0d
44 changed files with 2334 additions and 93 deletions
  1. 5 0
      app/build.gradle
  2. 1 0
      app/src/main/AndroidManifest.xml
  3. 23 2
      app/src/main/java/com/nextcloud/talk/api/NcApi.java
  4. 18 3
      app/src/main/java/com/nextcloud/talk/components/filebrowser/adapters/items/BrowserFileItem.java
  5. 37 53
      app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.java
  6. 59 0
      app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserForAvatarController.java
  7. 80 0
      app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserForSharingController.java
  8. 2 1
      app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
  9. 800 0
      app/src/main/java/com/nextcloud/talk/controllers/ProfileController.java
  10. 16 28
      app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java
  11. 1 0
      app/src/main/java/com/nextcloud/talk/dagger/modules/ContextModule.java
  12. 2 0
      app/src/main/java/com/nextcloud/talk/interfaces/SelectionInterface.kt
  13. 33 0
      app/src/main/java/com/nextcloud/talk/models/database/User.java
  14. 6 1
      app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.java
  15. 36 0
      app/src/main/java/com/nextcloud/talk/models/json/capabilities/ProvisioningCapability.java
  16. 47 0
      app/src/main/java/com/nextcloud/talk/models/json/converters/ScopeConverter.java
  17. 38 0
      app/src/main/java/com/nextcloud/talk/models/json/userprofile/Scope.java
  18. 73 1
      app/src/main/java/com/nextcloud/talk/models/json/userprofile/UserProfileData.java
  19. 39 0
      app/src/main/java/com/nextcloud/talk/models/json/userprofile/UserProfileFieldsOCS.java
  20. 36 0
      app/src/main/java/com/nextcloud/talk/models/json/userprofile/UserProfileFieldsOverall.java
  21. 70 0
      app/src/main/java/com/nextcloud/talk/ui/dialog/ScopeDialog.kt
  22. 10 3
      app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
  23. 80 0
      app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java
  24. 9 0
      app/src/main/res/drawable/ic_contacts.xml
  25. 23 0
      app/src/main/res/drawable/ic_email.xml
  26. 9 0
      app/src/main/res/drawable/ic_link.xml
  27. 5 0
      app/src/main/res/drawable/ic_list_empty_error.xml
  28. 23 0
      app/src/main/res/drawable/ic_map_marker.xml
  29. 9 0
      app/src/main/res/drawable/ic_password.xml
  30. 23 0
      app/src/main/res/drawable/ic_phone.xml
  31. 23 0
      app/src/main/res/drawable/ic_twitter.xml
  32. 25 0
      app/src/main/res/drawable/ic_user.xml
  33. 23 0
      app/src/main/res/drawable/ic_web.xml
  34. 5 0
      app/src/main/res/drawable/round_corner.xml
  35. 25 0
      app/src/main/res/drawable/trashbin.xml
  36. 12 0
      app/src/main/res/drawable/upload.xml
  37. 219 0
      app/src/main/res/layout/controller_profile.xml
  38. 1 0
      app/src/main/res/layout/controller_settings.xml
  39. 184 0
      app/src/main/res/layout/dialog_scope.xml
  40. 64 0
      app/src/main/res/layout/empty_list.xml
  41. 70 0
      app/src/main/res/layout/user_info_details_table_item.xml
  42. 28 0
      app/src/main/res/menu/menu_profile.xml
  43. 12 0
      app/src/main/res/values/dimens.xml
  44. 30 1
      app/src/main/res/values/strings.xml

+ 5 - 0
app/build.gradle

@@ -62,6 +62,7 @@ android {
         lintOptions {
             disable 'InvalidPackage'
             disable 'MissingTranslation'
+            disable 'VectorPath'
         }
 
         javaCompileOptions {
@@ -240,6 +241,10 @@ dependencies {
 
     implementation 'com.google.code.gson:gson:2.8.6'
 
+    //implementation 'com.github.dhaval2404:imagepicker:1.8'
+    implementation 'com.github.tobiaskaminsky:ImagePicker:extraFile-SNAPSHOT'
+    implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0'
+
     testImplementation 'junit:junit:4.13'
     testImplementation 'org.mockito:mockito-core:3.0.0'
     testImplementation 'org.powermock:powermock-core:2.0.2'

+ 1 - 0
app/src/main/AndroidManifest.xml

@@ -79,6 +79,7 @@
         android:networkSecurityConfig="@xml/network_security_config"
         android:supportsRtl="true"
         android:theme="@style/AppTheme"
+        android:requestLegacyExternalStorage="true"
         tools:ignore="UnusedAttribute"
         tools:replace="label, icon, theme, name, allowBackup">
 

+ 23 - 2
app/src/main/java/com/nextcloud/talk/api/NcApi.java

@@ -20,8 +20,6 @@
  */
 package com.nextcloud.talk.api;
 
-import androidx.annotation.Nullable;
-
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
 import com.nextcloud.talk.models.json.chat.ChatOverall;
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage;
@@ -37,14 +35,18 @@ import com.nextcloud.talk.models.json.push.PushRegistrationOverall;
 import com.nextcloud.talk.models.json.search.ContactsByNumberOverall;
 import com.nextcloud.talk.models.json.signaling.SignalingOverall;
 import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
+import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall;
 import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
 
 import java.util.List;
 import java.util.Map;
 
+import androidx.annotation.Nullable;
 import io.reactivex.Observable;
+import okhttp3.MultipartBody;
 import okhttp3.RequestBody;
 import okhttp3.ResponseBody;
+import retrofit2.Call;
 import retrofit2.Response;
 import retrofit2.http.Body;
 import retrofit2.http.DELETE;
@@ -53,8 +55,10 @@ import retrofit2.http.FieldMap;
 import retrofit2.http.FormUrlEncoded;
 import retrofit2.http.GET;
 import retrofit2.http.Header;
+import retrofit2.http.Multipart;
 import retrofit2.http.POST;
 import retrofit2.http.PUT;
+import retrofit2.http.Part;
 import retrofit2.http.Query;
 import retrofit2.http.QueryMap;
 import retrofit2.http.Url;
@@ -373,4 +377,21 @@ public interface NcApi {
     @DELETE
     Observable<ChatOverallSingleMessage> deleteChatMessage(@Header("Authorization") String authorization,
                                                            @Url String url);
+
+    @DELETE
+    Observable<GenericOverall> deleteAvatar(@Header("Authorization") String authorization, @Url String url);
+
+    @Multipart
+    @POST
+    Observable<GenericOverall> uploadAvatar(@Header("Authorization") String authorization,
+                                            @Url String url,
+                                            @Part MultipartBody.Part attachment);
+
+    @GET
+    Observable<UserProfileFieldsOverall> getEditableUserProfileFields(@Header("Authorization") String authorization,
+                                                                      @Url String url);
+
+    @GET
+    Call<ResponseBody> downloadResizedImage(@Header("Authorization") String authorization,
+                                            @Url String url);
 }

+ 18 - 3
app/src/main/java/com/nextcloud/talk/components/filebrowser/adapters/items/BrowserFileItem.java

@@ -43,15 +43,20 @@ import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.DateUtils;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.DrawableUtils;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import autodagger.AutoInjector;
+import butterknife.BindView;
+import butterknife.ButterKnife;
 import eu.davidea.flexibleadapter.FlexibleAdapter;
 import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
 import eu.davidea.flexibleadapter.items.IFilterable;
 import eu.davidea.flexibleadapter.items.IFlexible;
 import eu.davidea.viewholders.FlexibleViewHolder;
 
-import javax.inject.Inject;
-import java.util.List;
-
 @AutoInjector(NextcloudTalkApplication.class)
 public class BrowserFileItem extends AbstractFlexibleItem<BrowserFileItem.ViewHolder> implements IFilterable<String> {
     @Inject
@@ -125,6 +130,16 @@ public class BrowserFileItem extends AbstractFlexibleItem<BrowserFileItem.ViewHo
             holder.fileFavoriteImageView.setVisibility(View.GONE);
         }
 
+        if (selectionInterface.shouldOnlySelectOneImageFile()) {
+            if (browserFile.isFile && browserFile.mimeType.startsWith("image/")) {
+                holder.selectFileCheckbox.setVisibility(View.VISIBLE);
+            } else {
+                holder.selectFileCheckbox.setVisibility(View.GONE);
+            }
+        } else {
+            holder.selectFileCheckbox.setVisibility(View.VISIBLE);
+        }
+
         holder.fileIconImageView.getHierarchy().setPlaceholderImage(context.getDrawable(DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType(browserFile.getMimeType())));
 
         if (browserFile.isHasPreview()) {

+ 37 - 53
app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.java

@@ -23,15 +23,13 @@ package com.nextcloud.talk.components.filebrowser.controllers;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Bundle;
-import android.view.*;
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.work.Data;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.WorkManager;
-import autodagger.AutoInjector;
-import butterknife.BindView;
-import butterknife.OnClick;
+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 com.google.android.material.bottomnavigation.BottomNavigationItemView;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
@@ -43,27 +41,39 @@ import com.nextcloud.talk.components.filebrowser.operations.DavListing;
 import com.nextcloud.talk.components.filebrowser.operations.ListingAbstractClass;
 import com.nextcloud.talk.controllers.base.BaseController;
 import com.nextcloud.talk.interfaces.SelectionInterface;
-import com.nextcloud.talk.jobs.ShareOperationWorker;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.database.user.UserUtils;
+
+import org.jetbrains.annotations.NotNull;
+import org.parceler.Parcel;
+import org.parceler.Parcels;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import autodagger.AutoInjector;
+import butterknife.BindView;
+import butterknife.OnClick;
 import eu.davidea.fastscroller.FastScroller;
 import eu.davidea.flexibleadapter.FlexibleAdapter;
 import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
 import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
 import eu.davidea.flexibleadapter.items.IFlexible;
 import okhttp3.OkHttpClient;
-import org.parceler.Parcel;
-import org.parceler.Parcels;
-
-import javax.inject.Inject;
-import java.io.File;
-import java.util.*;
 
 @AutoInjector(NextcloudTalkApplication.class)
-public class BrowserController extends BaseController implements ListingInterface,
+public abstract class BrowserController extends BaseController implements ListingInterface,
         FlexibleAdapter.OnItemClickListener, SelectionInterface {
-    private final Set<String> selectedPaths;
+    protected final Set<String> selectedPaths;
     @Inject
     UserUtils userUtils;
     @BindView(R.id.recycler_view)
@@ -88,8 +98,7 @@ public class BrowserController extends BaseController implements ListingInterfac
     private ListingAbstractClass listingAbstractClass;
     private BrowserType browserType;
     private String currentPath;
-    private UserEntity activeUser;
-    private String roomToken;
+    protected UserEntity activeUser;
 
     public BrowserController(Bundle args) {
         super(args);
@@ -97,7 +106,6 @@ public class BrowserController extends BaseController implements ListingInterfac
         NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
         browserType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_BROWSER_TYPE()));
         activeUser = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY()));
-        roomToken = args.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN());
 
         currentPath = "/";
         if (BrowserType.DAV_BROWSER.equals(browserType)) {
@@ -109,6 +117,7 @@ public class BrowserController extends BaseController implements ListingInterfac
         selectedPaths = Collections.synchronizedSet(new TreeSet<>());
     }
 
+    @NotNull
     @Override
     protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
         return inflater.inflate(R.layout.controller_browser, container, false);
@@ -125,38 +134,10 @@ public class BrowserController extends BaseController implements ListingInterfac
         prepareViews();
     }
 
-    private void onFileSelectionDone() {
-        synchronized (selectedPaths) {
-            Iterator<String> iterator = selectedPaths.iterator();
-
-            List<String> paths = new ArrayList<>();
-            Data data;
-            OneTimeWorkRequest shareWorker;
-
-            while (iterator.hasNext()) {
-                String path = iterator.next();
-                paths.add(path);
-                iterator.remove();
-                if (paths.size() == 10 || !iterator.hasNext()) {
-                    data = new Data.Builder()
-                            .putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), activeUser.getId())
-                            .putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), roomToken)
-                            .putStringArray(BundleKeys.INSTANCE.getKEY_FILE_PATHS(), paths.toArray(new String[0]))
-                            .build();
-                    shareWorker = new OneTimeWorkRequest.Builder(ShareOperationWorker.class)
-                            .setInputData(data)
-                            .build();
-                    WorkManager.getInstance().enqueue(shareWorker);
-                    paths = new ArrayList<>();
-                }
-            }
-        }
-
-        getRouter().popCurrentController();
-    }
+    abstract void onFileSelectionDone();
 
     @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
         super.onCreateOptionsMenu(menu, inflater);
         inflater.inflate(R.menu.menu_share_files, menu);
         filesSelectionDoneMenuItem = menu.findItem(R.id.files_selection_done);
@@ -315,7 +296,7 @@ public class BrowserController extends BaseController implements ListingInterfac
 
     @SuppressLint("RestrictedApi")
     @Override
-    public void toggleBrowserItemSelection(String path) {
+    public void toggleBrowserItemSelection(@NonNull String path) {
         if (selectedPaths.contains(path) || shouldPathBeSelectedDueToParent(path)) {
             checkAndRemoveAnySelectedParents(path);
         } else {
@@ -327,10 +308,13 @@ public class BrowserController extends BaseController implements ListingInterfac
     }
 
     @Override
-    public boolean isPathSelected(String path) {
+    public boolean isPathSelected(@NonNull String path) {
         return (selectedPaths.contains(path) || shouldPathBeSelectedDueToParent(path));
     }
 
+    @Override
+    abstract public boolean shouldOnlySelectOneImageFile();
+
     @Parcel
     public enum BrowserType {
         FILE_BROWSER,

+ 59 - 0
app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserForAvatarController.java

@@ -0,0 +1,59 @@
+/*
+ * 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.components.filebrowser.controllers;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.nextcloud.talk.controllers.ProfileController;
+
+import androidx.annotation.Nullable;
+
+public class BrowserForAvatarController extends BrowserController {
+    private ProfileController controller;
+
+    public BrowserForAvatarController(Bundle args) {
+        super(args);
+    }
+
+    public BrowserForAvatarController(Bundle args, ProfileController controller) {
+        super(args);
+
+        this.controller = controller;
+    }
+
+    @Override
+    void onFileSelectionDone() {
+        controller.handleAvatar(selectedPaths.iterator().next());
+
+        getRouter().popCurrentController();
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    @Override
+    public boolean shouldOnlySelectOneImageFile() {
+        return true;
+    }
+}

+ 80 - 0
app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserForSharingController.java

@@ -0,0 +1,80 @@
+/*
+ * 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.components.filebrowser.controllers;
+
+import android.os.Bundle;
+
+import com.nextcloud.talk.jobs.ShareOperationWorker;
+import com.nextcloud.talk.utils.bundle.BundleKeys;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import androidx.work.Data;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
+
+public class BrowserForSharingController extends BrowserController {
+    private final String roomToken;
+
+    public BrowserForSharingController(Bundle args) {
+        super(args);
+
+        roomToken = args.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN());
+    }
+
+    @Override
+    void onFileSelectionDone() {
+        synchronized (selectedPaths) {
+            Iterator<String> iterator = selectedPaths.iterator();
+
+            List<String> paths = new ArrayList<>();
+            Data data;
+            OneTimeWorkRequest shareWorker;
+
+            while (iterator.hasNext()) {
+                String path = iterator.next();
+                paths.add(path);
+                iterator.remove();
+                if (paths.size() == 10 || !iterator.hasNext()) {
+                    data = new Data.Builder()
+                            .putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), activeUser.getId())
+                            .putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), roomToken)
+                            .putStringArray(BundleKeys.INSTANCE.getKEY_FILE_PATHS(), paths.toArray(new String[0]))
+                            .build();
+                    shareWorker = new OneTimeWorkRequest.Builder(ShareOperationWorker.class)
+                            .setInputData(data)
+                            .build();
+                    WorkManager.getInstance().enqueue(shareWorker);
+                    paths = new ArrayList<>();
+                }
+            }
+        }
+
+        getRouter().popCurrentController();
+    }
+
+    @Override
+    public boolean shouldOnlySelectOneImageFile() {
+        return false;
+    }
+}

+ 2 - 1
app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt

@@ -72,6 +72,7 @@ import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
 import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
+import com.nextcloud.talk.components.filebrowser.controllers.BrowserForSharingController
 import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.events.UserMentionClickEvent
 import com.nextcloud.talk.events.WebSocketCommunicationEvent
@@ -667,7 +668,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
         bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType))
         bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserEntity>(conversationUser))
         bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
-        router.pushController(RouterTransaction.with(BrowserController(bundle))
+        router.pushController(RouterTransaction.with(BrowserForSharingController(bundle))
                 .pushChangeHandler(VerticalChangeHandler())
                 .popChangeHandler(VerticalChangeHandler()))
     }

+ 800 - 0
app/src/main/java/com/nextcloud/talk/controllers/ProfileController.java

@@ -0,0 +1,800 @@
+/*
+ * 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.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.jetbrains.annotations.NotNull;
+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.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 {
+    @Inject
+    NcApi ncApi;
+
+    @Inject
+    UserUtils userUtils;
+    private UserEntity currentUser;
+    private boolean edit = false;
+    private RecyclerView recyclerView;
+    private UserProfileData userInfo;
+    private ArrayList<String> editableFields = new ArrayList<>();
+
+    public ProfileController() {
+        super();
+    }
+
+    @NotNull
+    protected View inflateView(@NotNull LayoutInflater inflater, @NotNull 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);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.edit:
+                if (edit) {
+                    save();
+                }
+
+                edit = !edit;
+
+                if (edit) {
+                    item.setTitle(R.string.save);
+
+                    if (currentUser.isAvatarEndpointAvailable()) {
+                        // 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(@NotNull Disposable d) {
+                                }
+
+                                @Override
+                                public void onNext(@NotNull UserProfileFieldsOverall userProfileFieldsOverall) {
+                                    editableFields = userProfileFieldsOverall.getOcs().getData();
+                                    recyclerView.getAdapter().notifyDataSetChanged();
+                                }
+
+                                @Override
+                                public void onError(@NotNull Throwable e) {
+                                    edit = false;
+                                }
+
+                                @Override
+                                public void onComplete() {
+
+                                }
+                            });
+                } else {
+                    item.setTitle(R.string.edit);
+                    getActivity().findViewById(R.id.avatar_buttons).setVisibility(View.INVISIBLE);
+                }
+
+                recyclerView.getAdapter().notifyDataSetChanged();
+
+                return true;
+
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    @Override
+    protected void onAttach(@NonNull View view) {
+        super.onAttach(view);
+
+        recyclerView = getActivity().findViewById(R.id.userinfo_list);
+        recyclerView.setAdapter(new UserInfoAdapter(null,
+                getActivity().getResources().getColor(R.color.colorPrimary),
+                this));
+        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(@NotNull Disposable d) {
+                            }
+
+                            @Override
+                            public void onNext(@NotNull GenericOverall genericOverall) {
+                                DisplayUtils.loadAvatarImage(currentUser,
+                                        getActivity().findViewById(R.id.avatar_image));
+                            }
+
+                            @Override
+                            public void onError(@NotNull Throwable e) {
+                                Toast.makeText(getApplicationContext(), "Error", Toast.LENGTH_LONG).show();
+                            }
+
+                            @Override
+                            public void onComplete() {
+
+                            }
+                        }));
+
+        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(@NotNull Disposable d) {
+                    }
+
+                    @Override
+                    public void onNext(@NotNull UserProfileOverall userProfileOverall) {
+                        userInfo = userProfileOverall.getOcs().getData();
+                        showUserProfile();
+                    }
+
+                    @Override
+                    public void onError(@NotNull 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() {
+
+                    }
+                });
+    }
+
+    private void showUserProfile() {
+        if (getActivity() == null) {
+            return;
+        }
+        ((TextView) getActivity()
+                .findViewById(R.id.userinfo_baseurl))
+                .setText(Uri.parse(currentUser.getBaseUrl()).getHost());
+
+        DisplayUtils.loadAvatarImage(currentUser, getActivity().findViewById(R.id.avatar_image));
+
+        if (!TextUtils.isEmpty(userInfo.getDisplayName())) {
+            ((TextView) getActivity().findViewById(R.id.userinfo_fullName)).setText(userInfo.getDisplayName());
+        }
+
+        if (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.loading_content).setVisibility(View.VISIBLE);
+            getActivity().findViewById(R.id.emptyList).setVisibility(View.GONE);
+
+            RecyclerView recyclerView = getActivity().findViewById(R.id.userinfo_list);
+            if (recyclerView.getAdapter() instanceof UserInfoAdapter) {
+                UserInfoAdapter adapter = ((UserInfoAdapter) recyclerView.getAdapter());
+                adapter.setData(createUserInfoDetails(userInfo));
+            }
+
+            getActivity().findViewById(R.id.loading_content).setVisibility(View.GONE);
+            getActivity().findViewById(R.id.userinfo_list).setVisibility(View.VISIBLE);
+        }
+
+        // show edit button
+        if (currentUser.canEditScopes()) {
+            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(@NotNull Disposable d) {
+                        }
+
+                        @Override
+                        public void onNext(@NotNull UserProfileFieldsOverall userProfileFieldsOverall) {
+                            editableFields = userProfileFieldsOverall.getOcs().getData();
+
+                            getActivity().invalidateOptionsMenu();
+                            recyclerView.getAdapter().notifyDataSetChanged();
+                        }
+
+                        @Override
+                        public void onError(@NotNull Throwable e) {
+                            edit = false;
+                        }
+
+                        @Override
+                        public void onComplete() {
+
+                        }
+                    });
+        }
+    }
+
+    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() {
+        UserInfoAdapter adapter = (UserInfoAdapter) recyclerView.getAdapter();
+
+        for (UserInfoDetailsItem item : adapter.mDisplayList) {
+            // 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(@NotNull Disposable d) {
+                            }
+
+                            @Override
+                            public void onNext(@NotNull GenericOverall userProfileOverall) {
+                                Log.d("ProfileController", "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(@NotNull Throwable e) {
+                                item.text = userInfo.getValueByField(item.field);
+                                Log.e("ProfileController", "Failed to saved: " + item.text + " as " + item.field);
+                            }
+
+                            @Override
+                            public void onComplete() {
+
+                            }
+                        });
+            }
+
+            // Scope
+            if (item.scope != userInfo.getScopeByField(item.field)) {
+                saveScope(item, userInfo);
+            }
+        }
+    }
+
+    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) {
+
+            }
+        });
+    }
+
+    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) {
+                e.printStackTrace();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        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(@NotNull Disposable d) {
+                    }
+
+                    @Override
+                    public void onNext(@NotNull GenericOverall genericOverall) {
+                        DisplayUtils.loadAvatarImage(currentUser, getActivity().findViewById(R.id.avatar_image));
+                    }
+
+                    @Override
+                    public void onError(@NotNull Throwable e) {
+                        Toast.makeText(getApplicationContext(), "Error", Toast.LENGTH_LONG).show();
+                    }
+
+                    @Override
+                    public void onComplete() {
+
+                    }
+                });
+    }
+
+    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(@NotNull Disposable d) {
+                    }
+
+                    @Override
+                    public void onNext(@NotNull GenericOverall userProfileOverall) {
+                        Log.d("ProfileController", "Successfully saved: " + item.scope + " as " + item.field);
+                    }
+
+                    @Override
+                    public void onError(@NotNull Throwable e) {
+                        item.scope = userInfo.getScopeByField(item.field);
+                        Log.e("ProfileController", "Failed to saved: " + item.scope + " as " + item.field);
+                    }
+
+                    @Override
+                    public void onComplete() {
+
+                    }
+                });
+    }
+
+    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> mDisplayList;
+        @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) {
+            mDisplayList = displayList == null ? new LinkedList<>() : displayList;
+            mTintColor = tintColor;
+            this.controller = controller;
+        }
+
+        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);
+
+            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.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) {
+
+                }
+
+                @Override
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
+                    mDisplayList.get(holder.getAdapterPosition()).text = holder.text.getText().toString();
+                }
+
+                @Override
+                public void afterTextChanged(Editable s) {
+
+                }
+            });
+
+            holder.icon.setContentDescription(item.hint);
+            DrawableCompat.setTint(holder.icon.getDrawable(), mTintColor);
+
+            if (!TextUtils.isEmpty(item.text) || controller.edit) {
+                holder.container.setVisibility(View.VISIBLE);
+                holder.text.setTextColor(Color.BLACK);
+
+                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(1.0f);
+                } 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.4f);
+                }
+            } else {
+                holder.container.setVisibility(View.GONE);
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return mDisplayList.size();
+        }
+
+        public void updateScope(int position, Scope scope) {
+            mDisplayList.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;
+        }
+    }
+}

+ 16 - 28
app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java

@@ -46,26 +46,16 @@ import android.widget.Button;
 import android.widget.Checkable;
 import android.widget.EditText;
 import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
 import android.widget.TextView;
 import android.widget.Toast;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.view.ViewCompat;
-import androidx.emoji.widget.EmojiTextView;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.WorkManager;
-
 import com.bluelinelabs.conductor.Controller;
 import com.bluelinelabs.conductor.RouterTransaction;
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
 import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
 import com.bluelinelabs.logansquare.LoganSquare;
-import com.facebook.drawee.backends.pipeline.Fresco;
-import com.facebook.drawee.interfaces.DraweeController;
 import com.facebook.drawee.view.SimpleDraweeView;
-import com.google.android.material.snackbar.Snackbar;
 import com.google.android.material.textfield.TextInputLayout;
 import com.nextcloud.talk.BuildConfig;
 import com.nextcloud.talk.R;
@@ -110,6 +100,13 @@ import java.util.Objects;
 
 import javax.inject.Inject;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.view.ViewCompat;
+import androidx.emoji.widget.EmojiTextView;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
 import autodagger.AutoInjector;
 import butterknife.BindView;
 import butterknife.OnClick;
@@ -139,6 +136,8 @@ public class SettingsController extends BaseController {
     MaterialStandardPreference sourceCodeButton;
     @BindView(R.id.settings_version)
     MaterialStandardPreference versionInfo;
+    @BindView(R.id.avatarContainer)
+    RelativeLayout avatarContainer;
     @BindView(R.id.avatar_image)
     SimpleDraweeView avatarImageView;
     @BindView(R.id.display_name_text)
@@ -563,7 +562,7 @@ public class SettingsController extends BaseController {
                 displayNameTextView.setText(currentUser.getDisplayName());
             }
 
-            loadAvatarImage();
+            DisplayUtils.loadAvatarImage(currentUser, avatarImageView);
 
             profileQueryDisposable = ncApi.getUserProfile(credentials,
                     ApiUtils.getUrlForUserProfile(currentUser.getBaseUrl()))
@@ -661,23 +660,12 @@ public class SettingsController extends BaseController {
                 messageView.setVisibility(View.GONE);
             }
         }
-    }
-
-    private void loadAvatarImage() {
-        String avatarId;
-        if (!TextUtils.isEmpty(currentUser.getUserId())) {
-            avatarId = currentUser.getUserId();
-        } else {
-            avatarId = currentUser.getUsername();
-        }
 
-        DraweeController draweeController = Fresco.newDraweeControllerBuilder()
-                .setOldController(avatarImageView.getController())
-                .setAutoPlayAnimations(true)
-                .setImageRequest(DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(currentUser.getBaseUrl(),
-                        avatarId, R.dimen.avatar_size_big), null))
-                .build();
-        avatarImageView.setController(draweeController);
+        avatarContainer.setOnClickListener(v ->
+                getRouter()
+                        .pushController((RouterTransaction.with(new ProfileController())
+                                .pushChangeHandler(new HorizontalChangeHandler())
+                                .popChangeHandler(new HorizontalChangeHandler()))));
     }
 
     @Override

+ 1 - 0
app/src/main/java/com/nextcloud/talk/dagger/modules/ContextModule.java

@@ -21,6 +21,7 @@
 package com.nextcloud.talk.dagger.modules;
 
 import android.content.Context;
+
 import androidx.annotation.NonNull;
 import dagger.Module;
 import dagger.Provides;

+ 2 - 0
app/src/main/java/com/nextcloud/talk/interfaces/SelectionInterface.kt

@@ -24,4 +24,6 @@ interface SelectionInterface {
     fun toggleBrowserItemSelection(path: String)
 
     fun isPathSelected(path: String): Boolean
+
+    fun shouldOnlySelectOneImageFile(): Boolean
 }

+ 33 - 0
app/src/main/java/com/nextcloud/talk/models/database/User.java

@@ -222,4 +222,37 @@ public interface User extends Parcelable, Persistable, Serializable {
         }
         return "";
     }
+
+    // TODO later avatar can also be checked via user fields, for now it is in Talk capability
+    default boolean isAvatarEndpointAvailable() {
+        if (getCapabilities() != null) {
+            Capabilities capabilities;
+            try {
+                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                return (capabilities != null &&
+                        capabilities.getSpreedCapability() != null &&
+                        capabilities.getSpreedCapability().getFeatures() != null &&
+                        capabilities.getSpreedCapability().getFeatures().contains("temp-user-avatar-api"));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return false;
+    }
+
+    default boolean canEditScopes() {
+        if (getCapabilities() != null) {
+            Capabilities capabilities;
+            try {
+                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                return (capabilities != null &&
+                        capabilities.getProvisioningCapability() != null &&
+                        capabilities.getProvisioningCapability().getAccountPropertyScopesVersion() != null &&
+                        capabilities.getProvisioningCapability().getAccountPropertyScopesVersion() > 1);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return false;
+    }
 }

+ 6 - 1
app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.java

@@ -22,12 +22,14 @@ package com.nextcloud.talk.models.json.capabilities;
 
 import com.bluelinelabs.logansquare.annotation.JsonField;
 import com.bluelinelabs.logansquare.annotation.JsonObject;
-import lombok.Data;
+
 import org.parceler.Parcel;
 
 import java.util.HashMap;
 import java.util.List;
 
+import lombok.Data;
+
 @Parcel
 @Data
 @JsonObject
@@ -43,4 +45,7 @@ public class Capabilities {
 
     @JsonField(name = "external")
     HashMap<String, List<String>> externalCapability;
+
+    @JsonField(name = "provisioning_api")
+    ProvisioningCapability provisioningCapability;
 }

+ 36 - 0
app/src/main/java/com/nextcloud/talk/models/json/capabilities/ProvisioningCapability.java

@@ -0,0 +1,36 @@
+/*
+ * 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.models.json.capabilities;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+
+import org.parceler.Parcel;
+
+import lombok.Data;
+
+@Parcel
+@Data
+@JsonObject
+public class ProvisioningCapability {
+    @JsonField(name = "AccountPropertyScopesVersion")
+    Integer accountPropertyScopesVersion;
+}

+ 47 - 0
app/src/main/java/com/nextcloud/talk/models/json/converters/ScopeConverter.java

@@ -0,0 +1,47 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.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.models.json.converters;
+
+import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter;
+import com.nextcloud.talk.models.json.userprofile.Scope;
+
+public class ScopeConverter extends StringBasedTypeConverter<Scope> {
+    @Override
+    public Scope getFromString(String string) {
+        switch (string) {
+            case "v2-private":
+                return Scope.PRIVATE;
+            case "v2-local":
+                return Scope.LOCAL;
+            case "v2-federated":
+                return Scope.FEDERATED;
+            case "v2-published":
+                return Scope.PUBLISHED;
+            default:
+                return Scope.PRIVATE;
+        }
+    }
+
+    @Override
+    public String convertToString(Scope scope) {
+        return scope.getName();
+    }
+}

+ 38 - 0
app/src/main/java/com/nextcloud/talk/models/json/userprofile/Scope.java

@@ -0,0 +1,38 @@
+/*
+ * 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.models.json.userprofile;
+
+public enum Scope {
+    PRIVATE("v2-private"),
+    LOCAL("v2-local"),
+    FEDERATED("v2-federated"),
+    PUBLISHED("v2-published");
+
+    private final String name;
+
+    Scope(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 73 - 1
app/src/main/java/com/nextcloud/talk/models/json/userprofile/UserProfileData.java

@@ -22,9 +22,13 @@ package com.nextcloud.talk.models.json.userprofile;
 
 import com.bluelinelabs.logansquare.annotation.JsonField;
 import com.bluelinelabs.logansquare.annotation.JsonObject;
-import lombok.Data;
+import com.nextcloud.talk.controllers.ProfileController;
+import com.nextcloud.talk.models.json.converters.ScopeConverter;
+
 import org.parceler.Parcel;
 
+import lombok.Data;
+
 @Parcel
 @Data
 @JsonObject()
@@ -32,6 +36,9 @@ public class UserProfileData {
     @JsonField(name = "display-name")
     String displayName;
 
+    @JsonField(name = "displaynameScope", typeConverter = ScopeConverter.class)
+    Scope displayNameScope;
+
     @JsonField(name = "displayname")
     String displayNameAlt;
 
@@ -40,4 +47,69 @@ public class UserProfileData {
 
     @JsonField(name = "phone")
     String phone;
+
+    @JsonField(name = "phoneScope", typeConverter = ScopeConverter.class)
+    Scope phoneScope;
+
+    @JsonField(name = "email")
+    String email;
+
+    @JsonField(name = "emailScope", typeConverter = ScopeConverter.class)
+    Scope emailScope;
+
+    @JsonField(name = "address")
+    String address;
+
+    @JsonField(name = "addressScope", typeConverter = ScopeConverter.class)
+    Scope addressScope;
+
+    @JsonField(name = "twitter")
+    String twitter;
+
+    @JsonField(name = "twitterScope", typeConverter = ScopeConverter.class)
+    Scope twitterScope;
+
+    @JsonField(name = "website")
+    String website;
+
+    @JsonField(name = "websiteScope", typeConverter = ScopeConverter.class)
+    Scope websiteScope;
+
+    public String getValueByField(ProfileController.Field field) {
+        switch (field) {
+            case EMAIL:
+                return email;
+            case DISPLAYNAME:
+                return displayName;
+            case PHONE:
+                return phone;
+            case ADDRESS:
+                return address;
+            case WEBSITE:
+                return website;
+            case TWITTER:
+                return twitter;
+            default:
+                return "";
+        }
+    }
+
+    public Scope getScopeByField(ProfileController.Field field) {
+        switch (field) {
+            case EMAIL:
+                return emailScope;
+            case DISPLAYNAME:
+                return displayNameScope;
+            case PHONE:
+                return phoneScope;
+            case ADDRESS:
+                return addressScope;
+            case WEBSITE:
+                return websiteScope;
+            case TWITTER:
+                return twitterScope;
+            default:
+                return null;
+        }
+    }
 }

+ 39 - 0
app/src/main/java/com/nextcloud/talk/models/json/userprofile/UserProfileFieldsOCS.java

@@ -0,0 +1,39 @@
+/*
+ *
+ *   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.models.json.userprofile;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+import com.nextcloud.talk.models.json.generic.GenericOCS;
+
+import org.parceler.Parcel;
+
+import java.util.ArrayList;
+
+import lombok.Data;
+
+@Parcel
+@Data
+@JsonObject
+public class UserProfileFieldsOCS extends GenericOCS {
+    @JsonField(name = "data")
+    ArrayList<String> data;
+}

+ 36 - 0
app/src/main/java/com/nextcloud/talk/models/json/userprofile/UserProfileFieldsOverall.java

@@ -0,0 +1,36 @@
+/*
+ *
+ *   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.models.json.userprofile;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+
+import org.parceler.Parcel;
+
+import lombok.Data;
+
+@Parcel
+@Data
+@JsonObject
+public class UserProfileFieldsOverall {
+    @JsonField(name = "ocs")
+    UserProfileFieldsOCS ocs;
+}

+ 70 - 0
app/src/main/java/com/nextcloud/talk/ui/dialog/ScopeDialog.kt

@@ -0,0 +1,70 @@
+/*
+ * 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.ui.dialog
+
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.nextcloud.talk.R
+import com.nextcloud.talk.controllers.ProfileController
+import com.nextcloud.talk.models.json.userprofile.Scope
+
+
+class ScopeDialog(con: Context,
+                  private val userInfoAdapter: ProfileController.UserInfoAdapter,
+                  private val field: ProfileController.Field,
+                  private val position: Int) :
+        BottomSheetDialog(con) {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val view = layoutInflater.inflate(R.layout.dialog_scope, null)
+        setContentView(view)
+
+        window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+
+        if (field == ProfileController.Field.DISPLAYNAME || field == ProfileController.Field.EMAIL) {
+            findViewById<LinearLayout>(R.id.scope_private)?.visibility = View.GONE
+        }
+
+        findViewById<LinearLayout>(R.id.scope_private)?.setOnClickListener {
+            userInfoAdapter.updateScope(position, Scope.PRIVATE)
+            dismiss()
+        }
+
+        findViewById<LinearLayout>(R.id.scope_local)?.setOnClickListener {
+            userInfoAdapter.updateScope(position, Scope.LOCAL)
+            dismiss()
+        }
+
+        findViewById<LinearLayout>(R.id.scope_federated)?.setOnClickListener {
+            userInfoAdapter.updateScope(position, Scope.FEDERATED)
+            dismiss()
+        }
+
+        findViewById<LinearLayout>(R.id.scope_published)?.setOnClickListener {
+            userInfoAdapter.updateScope(position, Scope.PUBLISHED)
+            dismiss()
+        }
+    }
+}

+ 10 - 3
app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java

@@ -22,9 +22,6 @@ package com.nextcloud.talk.utils;
 import android.net.Uri;
 import android.text.TextUtils;
 
-import androidx.annotation.DimenRes;
-import androidx.annotation.Nullable;
-
 import com.nextcloud.talk.BuildConfig;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
@@ -33,6 +30,8 @@ import com.nextcloud.talk.models.RetrofitBucket;
 import java.util.HashMap;
 import java.util.Map;
 
+import androidx.annotation.DimenRes;
+import androidx.annotation.Nullable;
 import okhttp3.Credentials;
 
 public class ApiUtils {
@@ -303,4 +302,12 @@ public class ApiUtils {
     public static String getUrlForMessageDeletion(String baseUrl, String token, String messageId) {
         return baseUrl + ocsApiVersion + spreedApiVersion + "/chat/" + token + "/" + messageId;
     }
+
+    public static String getUrlForTempAvatar(String baseUrl) {
+        return baseUrl + ocsApiVersion + "/apps/spreed/temp-user-avatar";
+    }
+
+    public static String getUrlForUserFields(String baseUrl) {
+        return baseUrl + ocsApiVersion + "/cloud/user/fields";
+    }
 }

+ 80 - 0
app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java

@@ -78,6 +78,7 @@ import com.facebook.common.references.CloseableReference;
 import com.facebook.datasource.DataSource;
 import com.facebook.drawee.backends.pipeline.Fresco;
 import com.facebook.drawee.controller.ControllerListener;
+import com.facebook.drawee.interfaces.DraweeController;
 import com.facebook.drawee.view.SimpleDraweeView;
 import com.facebook.imagepipeline.common.RotationOptions;
 import com.facebook.imagepipeline.core.ImagePipeline;
@@ -107,6 +108,17 @@ import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import androidx.annotation.ColorInt;
+import androidx.annotation.ColorRes;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.XmlRes;
+import androidx.appcompat.widget.AppCompatDrawableManager;
+import androidx.core.content.ContextCompat;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.emoji.text.EmojiCompat;
+
 public class DisplayUtils {
 
     private static final String TAG = "DisplayUtils";
@@ -114,6 +126,10 @@ public class DisplayUtils {
     private static final int INDEX_LUMINATION = 2;
     private static final double MAX_LIGHTNESS = 0.92;
 
+    private static final String TWITTER_HANDLE_PREFIX = "@";
+    private static final String HTTP_PROTOCOL = "http://";
+    private static final String HTTPS_PROTOCOL = "https://";
+
     public static void setClickableString(String string, String url, TextView textView) {
         SpannableString spannableString = new SpannableString(string);
         spannableString.setSpan(new ClickableSpan() {
@@ -471,4 +487,68 @@ public class DisplayUtils {
         editText.setTextSize(16);
         editText.setHintTextColor(context.getResources().getColor(R.color.fontSecondaryAppbar));
     }
+
+    /**
+     * beautifies a given URL by removing any http/https protocol prefix.
+     *
+     * @param url to be beautified url
+     * @return beautified url
+     */
+    public static String beautifyURL(@Nullable String url) {
+        if (TextUtils.isEmpty(url)) {
+            return "";
+        }
+
+        if (url.length() >= 7 && HTTP_PROTOCOL.equalsIgnoreCase(url.substring(0, 7))) {
+            return url.substring(HTTP_PROTOCOL.length()).trim();
+        }
+
+        if (url.length() >= 8 && HTTPS_PROTOCOL.equalsIgnoreCase(url.substring(0, 8))) {
+            return url.substring(HTTPS_PROTOCOL.length()).trim();
+        }
+
+        return url.trim();
+    }
+
+    /**
+     * beautifies a given twitter handle by prefixing it with an @ in case it is missing.
+     *
+     * @param handle to be beautified twitter handle
+     * @return beautified twitter handle
+     */
+    public static String beautifyTwitterHandle(@Nullable String handle) {
+        if (handle != null) {
+            String trimmedHandle = handle.trim();
+
+            if (TextUtils.isEmpty(trimmedHandle)) {
+                return "";
+            }
+
+            if (trimmedHandle.startsWith(TWITTER_HANDLE_PREFIX)) {
+                return trimmedHandle;
+            } else {
+                return TWITTER_HANDLE_PREFIX + trimmedHandle;
+            }
+        } else {
+            return "";
+        }
+    }
+    
+    public static void loadAvatarImage(UserEntity user, SimpleDraweeView avatarImageView) {
+        String avatarId;
+        if (!TextUtils.isEmpty(user.getUserId())) {
+            avatarId = user.getUserId();
+        } else {
+            avatarId = user.getUsername();
+        }
+
+        DraweeController draweeController = Fresco.newDraweeControllerBuilder()
+                .setOldController(avatarImageView.getController())
+                .setAutoPlayAnimations(true)
+                .setImageRequest(DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(user.getBaseUrl(),
+                        avatarId, R.dimen.avatar_size_big), null))
+                .build();
+        avatarImageView.setController(draweeController);
+    }
 }
+

+ 9 - 0
app/src/main/res/drawable/ic_contacts.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="m9,1c-1.746,0 -3,1.43 -3,2.8 0,1.4 0.1,2.4 0.8,3.5 0.224,0.29 0.485,0.35 0.7,0.6 0.135,0.5 0.24,1 0.1,1.5 -0.436,0.153 -0.85,0.332 -1.27,0.5 -0.51,-0.273 -1.1,-0.5 -1.61,-0.7 -0.07,-0.28 -0.02,-0.487 0.05,-0.75 0.12,-0.125 0.23,-0.18 0.36,-0.3 0.37,-0.45 0.39,-1.21 0.39,-1.75 0,-0.8 -0.72,-1.4 -1.5,-1.4 -0.87,0 -1.5,0.72 -1.5,1.4h-0.02c0,0.7 0.05,1.2 0.4,1.75 0.1,0.15 0.242,0.175 0.35,0.3 0.0674,0.25 0.121,0.5 0.05,0.75 -0.64,0.223 -1.244,0.5 -1.8,0.8 -0.42,0.3 -0.233,0.182 -0.5,1.15 -0.124,0.5 1.3,0.73 2.32,0.81 -0.05,0.275 -0.12,0.64 -0.32,1.34 -0.32,1.25 4.353,1.7 6,1.7 2.43,0 6.313,-0.456 5.98,-1.7 -0.52,-1.94 -0.208,-1.71 -0.98,-2.3 -1.09,-0.654 -2.452,-1.167 -3.6,-1.6 -0.15,-0.557 -0.04,-0.97 0.1,-1.5 0.235,-0.25 0.5,-0.36 0.72,-0.6 0.69,-0.884 0.78,-2.424 0.78,-3.5 0,-1.586 -1.43,-2.8 -3,-2.8z" />
+</vector>

+ 23 - 0
app/src/main/res/drawable/ic_email.xml

@@ -0,0 +1,23 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2018 Google LLC
+
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path android:fillColor="#757575" android:pathData="M20,8L12,13L4,8V6L12,11L20,6M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_link.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="m7.95,0.65c-4.1,0 -7.4,3.3 -7.4,7.4s3.3,7.4 7.4,7.4 7.4,-3.3 7.4,-7.4 -3.3,-7.4 -7.4,-7.4zM8.75,1.55c1.3,0 2.4,0.8 3.5,1.3l1.8,2.5 -0.3,1.1 0.6,0.3v2.4c-0.2,0.7 -0.6,1.3 -0.9,2 -0.2,0.1 0,-0.8 -0.1,-1 0,-0.6 -0.5,-0.6 -0.9,-0.2 -0.4,0.3 -1.4,0.3 -1.5,-0.4 -0.3,-0.8 0,-1.7 0.3,-2.5l-0.6,-0.7 0.2,-1.8 -0.8,-0.9 0.2,-1 -1,-0.6c-0.2,-0.2 -0.6,-0.2 -0.7,-0.4 0.1,0 0.2,-0.1 0.2,-0.1zM6.15,1.65s0.1,0 0.1,0.1c0.4,0.2 -0.1,0.4 -0.2,0.6 -0.5,0.3 0.3,0.7 0.5,1 0.4,-0.1 0.8,-0.7 1.4,-0.5 0.7,-0.2 0.6,0.6 1.1,1 0.1,0.2 0.9,0.8 0.4,0.6 -0.5,-0.4 -1,-0.4 -1.3,0.1 -0.8,0.5 -0.3,-0.9 -0.7,-1.2 -0.6,-0.7 -0.4,0.5 -0.4,0.9 -0.4,0 -1.1,-0.3 -1.5,0.2l0.4,0.6 0.5,-0.7c0,-0.3 0.1,0.2 0.3,0.3 0.1,0.2 0.8,0.7 0.3,0.9 -0.8,0.4 -1.4,1.1 -2.1,1.7 -0.2,0.5 -0.7,0.4 -1,0 -0.7,-0.4 -0.7,0.7 -0.6,1.1l0.6,-0.4v1.1c-0.4,0.4 -0.9,-0.7 -1.3,-0.9v-1.6c0,-0.4 -0.1,-0.9 0,-1.3 0.8,-0.9 1.7,-1.9 2.2,-3h0.8c0.6,0.2 0.3,-0.7 0.5,-0.6zM4.95,9.85c0.1,0 0.2,0 0.3,0.1 0.8,0.1 1.4,0.7 2,1.1 0.5,0.5 1.6,0.3 1.7,1.2 -0.2,0.9 -1.1,1.4 -1.8,1.7 -0.2,0.1 -0.4,0.2 -0.6,0.2 -0.7,0.2 -1,-0.6 -1.2,-1.1 -0.3,-0.7 -1.1,-1.2 -1,-2.1 0,-0.4 0.2,-1 0.6,-1.1z" />
+</vector>

+ 5 - 0
app/src/main/res/drawable/ic_list_empty_error.xml

@@ -0,0 +1,5 @@
+<vector android:alpha="0.5" android:height="24dp"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF969696" android:pathData="M13,13H11V7H13M12,17.3A1.3,1.3 0,0 1,10.7 16A1.3,1.3 0,0 1,12 14.7A1.3,1.3 0,0 1,13.3 16A1.3,1.3 0,0 1,12 17.3M15.73,3H8.27L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3Z"/>
+</vector>

+ 23 - 0
app/src/main/res/drawable/ic_map_marker.xml

@@ -0,0 +1,23 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2018 Google LLC
+
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path android:fillColor="#757575" android:pathData="M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_password.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="m8,1c-2.319,0 -3.967,1.8644 -4,4v2.5h-1.5v7.5h11v-7.5h-1.5v-2.5c0,-2.27 -1.8,-3.9735 -4,-4zM8,3c1.25,0 2,0.963 2,2v2.5h-4v-2.5c0,-1.174 0.747,-2 2,-2z" />
+</vector>

+ 23 - 0
app/src/main/res/drawable/ic_phone.xml

@@ -0,0 +1,23 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2018 Google LLC
+
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path android:fillColor="#757575" android:pathData="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z" />
+</vector>

+ 23 - 0
app/src/main/res/drawable/ic_twitter.xml

@@ -0,0 +1,23 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2018 Google LLC
+
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path android:fillColor="#757575" android:pathData="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16 21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4 16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96 11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16 2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10 2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39 4.69,14.39C4.42,14.39 4.15,14.36 3.89,14.31C4.43,16 6,17.26 7.89,17.29C6.43,18.45 4.58,19.13 2.56,19.13C2.22,19.13 1.88,19.11 1.54,19.07C3.44,20.29 5.7,21 8.12,21C16,21 20.33,14.46 20.33,8.79C20.33,8.6 20.33,8.42 20.32,8.23C21.16,7.63 21.88,6.87 22.46,6Z" />
+</vector>

+ 25 - 0
app/src/main/res/drawable/ic_user.xml

@@ -0,0 +1,25 @@
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2020 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  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/>.
+
+  Icon provided by Android Material Library in Apache License 2.0
+-->
+<vector android:height="24dp" android:tint="#666666"
+    android:viewportHeight="24.0" android:viewportWidth="24.0"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
+</vector>

+ 23 - 0
app/src/main/res/drawable/ic_web.xml

@@ -0,0 +1,23 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2018 Google LLC
+
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path android:fillColor="#757575" android:pathData="M16.36,14C16.44,13.34 16.5,12.68 16.5,12C16.5,11.32 16.44,10.66 16.36,10H19.74C19.9,10.64 20,11.31 20,12C20,12.69 19.9,13.36 19.74,14M14.59,19.56C15.19,18.45 15.65,17.25 15.97,16H18.92C17.96,17.65 16.43,18.93 14.59,19.56M14.34,14H9.66C9.56,13.34 9.5,12.68 9.5,12C9.5,11.32 9.56,10.65 9.66,10H14.34C14.43,10.65 14.5,11.32 14.5,12C14.5,12.68 14.43,13.34 14.34,14M12,19.96C11.17,18.76 10.5,17.43 10.09,16H13.91C13.5,17.43 12.83,18.76 12,19.96M8,8H5.08C6.03,6.34 7.57,5.06 9.4,4.44C8.8,5.55 8.35,6.75 8,8M5.08,16H8C8.35,17.25 8.8,18.45 9.4,19.56C7.57,18.93 6.03,17.65 5.08,16M4.26,14C4.1,13.36 4,12.69 4,12C4,11.31 4.1,10.64 4.26,10H7.64C7.56,10.66 7.5,11.32 7.5,12C7.5,12.68 7.56,13.34 7.64,14M12,4.03C12.83,5.23 13.5,6.57 13.91,8H10.09C10.5,6.57 11.17,5.23 12,4.03M18.92,8H15.97C15.65,6.75 15.19,5.55 14.59,4.44C16.43,5.07 17.96,6.34 18.92,8M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
+</vector>

+ 5 - 0
app/src/main/res/drawable/round_corner.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#ededed" />
+    <corners android:radius="15dp" />
+</shape>

+ 25 - 0
app/src/main/res/drawable/trashbin.xml

@@ -0,0 +1,25 @@
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2020 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  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/>.
+
+  Icon provided by Android Material Library in Apache License 2.0
+-->
+<vector android:height="24dp" android:tint="#757575"
+    android:viewportHeight="24.0" android:viewportWidth="24.0"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
+</vector>

+ 12 - 0
app/src/main/res/drawable/upload.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+
+    <path
+        android:fillColor="#757575"
+        android:strokeWidth="1"
+        android:pathData="M 9,16 V 10 H 5 l 7,-7 7,7 h -4 v 6 H 9 m -4,4 v -2 h 14 v 2 z"/>
+</vector>

+ 219 - 0
app/src/main/res/layout/controller_profile.xml

@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="utf-8"?><!--  Nextcloud Android client application
+
+  Copyright (C) 2017 Andy Scherzinger
+  Copyright (C) 2017 Nextcloud
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  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/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/bg_default"
+    android:orientation="vertical">
+
+    <RelativeLayout
+        android:id="@+id/avatarContainer"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <com.facebook.drawee.view.SimpleDraweeView
+            android:id="@+id/avatar_image"
+            android:layout_width="@dimen/avatar_size_big"
+            android:layout_height="@dimen/avatar_size_big"
+            android:layout_centerHorizontal="true"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="16dp"
+            android:transitionName="userAvatar.transitionTag"
+            app:roundAsCircle="true" />
+
+        <androidx.emoji.widget.EmojiTextView
+            android:id="@+id/userinfo_fullName"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/avatar_image"
+            android:layout_centerHorizontal="true"
+            android:layout_marginTop="@dimen/margin_between_elements"
+            android:ellipsize="end"
+            android:textColor="@color/nc_incoming_text_default"
+            tools:text="John Doe" />
+
+        <TextView
+            android:id="@+id/userinfo_baseurl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/userinfo_fullName"
+            android:layout_centerHorizontal="true"
+            android:layout_margin="4dp"
+            android:ellipsize="end"
+            android:lines="2"
+            android:textColor="@color/nc_incoming_text_default"
+            tools:text="john@nextcloud.com" />
+
+        <LinearLayout
+            android:id="@+id/avatar_buttons"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/userinfo_baseurl"
+            android:layout_centerInParent="true"
+            android:layout_centerHorizontal="true"
+            android:orientation="horizontal"
+            android:visibility="invisible"
+            tools:visibility="visible">
+
+            <ImageButton
+                android:id="@+id/avatar_upload"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_marginLeft="@dimen/standard_half_margin"
+                android:layout_marginRight="@dimen/standard_half_margin"
+                android:tint="@color/black"
+                android:src="@drawable/upload"
+                android:background="@drawable/round_corner"
+                android:contentDescription="@string/upload_new_avatar_from_device" />
+
+            <ImageButton
+                android:id="@+id/avatar_choose"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_marginLeft="@dimen/standard_half_margin"
+                android:layout_marginRight="@dimen/standard_half_margin"
+                android:src="@drawable/ic_mimetype_folder"
+                android:background="@drawable/round_corner"
+                android:tint="@color/colorPrimary"
+                android:contentDescription="@string/choose_avatar_from_cloud" />
+
+            <ImageButton
+                android:id="@+id/avatar_delete"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_marginLeft="@dimen/standard_half_margin"
+                android:layout_marginRight="@dimen/standard_half_margin"
+                android:src="@drawable/trashbin"
+                android:tint="@color/black"
+                android:background="@drawable/round_corner"
+                android:contentDescription="@string/delete_avatar" />
+        </LinearLayout>
+
+    </RelativeLayout>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/userinfo_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:visibility="gone"
+        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+        tools:itemCount="3"
+        tools:listitem="@layout/user_info_details_table_item"
+        tools:visibility="gone" />
+
+    <include
+        android:id="@+id/emptyList"
+        layout="@layout/empty_list" />
+
+    <LinearLayout
+        android:id="@+id/loading_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/iconized_single_line_item_layout_height"
+            android:orientation="horizontal">
+
+            <com.elyeproj.loaderviewlibrary.LoaderTextView
+                android:layout_width="@dimen/iconized_single_line_item_icon_size"
+                android:layout_height="@dimen/iconized_single_line_item_icon_size"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+                app:corners="100" />
+
+            <com.elyeproj.loaderviewlibrary.LoaderTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+                android:layout_marginEnd="@dimen/standard_margin" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/iconized_single_line_item_layout_height"
+            android:orientation="horizontal">
+
+            <com.elyeproj.loaderviewlibrary.LoaderTextView
+                android:layout_width="@dimen/iconized_single_line_item_icon_size"
+                android:layout_height="@dimen/iconized_single_line_item_icon_size"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+                app:corners="100" />
+
+            <com.elyeproj.loaderviewlibrary.LoaderTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+                android:layout_marginEnd="@dimen/standard_margin" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/iconized_single_line_item_layout_height"
+            android:orientation="horizontal">
+
+            <com.elyeproj.loaderviewlibrary.LoaderTextView
+                android:layout_width="@dimen/iconized_single_line_item_icon_size"
+                android:layout_height="@dimen/iconized_single_line_item_icon_size"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+                app:corners="100" />
+
+            <com.elyeproj.loaderviewlibrary.LoaderTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+                android:layout_marginEnd="@dimen/standard_margin" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/iconized_single_line_item_layout_height"
+            android:orientation="horizontal">
+
+            <com.elyeproj.loaderviewlibrary.LoaderTextView
+                android:layout_width="@dimen/iconized_single_line_item_icon_size"
+                android:layout_height="@dimen/iconized_single_line_item_icon_size"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+                app:corners="100" />
+
+            <com.elyeproj.loaderviewlibrary.LoaderTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+                android:layout_marginEnd="@dimen/standard_margin" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>

+ 1 - 0
app/src/main/res/layout/controller_settings.xml

@@ -43,6 +43,7 @@
         android:animateLayoutChanges="true">
 
         <RelativeLayout
+            android:id="@+id/avatarContainer"
             android:layout_width="match_parent"
             android:layout_height="wrap_content">
 

+ 184 - 0
app/src/main/res/layout/dialog_scope.xml

@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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/>.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp">
+
+    <LinearLayout
+        android:id="@+id/scope_private"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/standard_margin"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_gravity="center_vertical"
+            android:contentDescription="@string/lock_symbol"
+            app:srcCompat="@drawable/ic_password" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/standard_half_padding"
+                android:paddingEnd="@dimen/standard_half_padding"
+                android:text="@string/scope_private_title"
+                android:textStyle="bold" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/standard_half_padding"
+                android:paddingEnd="@dimen/standard_half_padding"
+                android:text="@string/scope_private_description" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/scope_local"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/standard_margin"
+        android:layout_marginTop="@dimen/standard_half_margin"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_gravity="center_vertical"
+            android:contentDescription="@string/lock_symbol"
+            app:srcCompat="@drawable/ic_password" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/standard_half_padding"
+                android:paddingEnd="@dimen/standard_half_padding"
+                android:text="@string/scope_local_title"
+                android:textStyle="bold" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/standard_half_padding"
+                android:paddingEnd="@dimen/standard_half_padding"
+                android:text="@string/scope_local_description" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/scope_federated"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/standard_margin"
+        android:layout_marginTop="@dimen/standard_half_margin"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_gravity="center_vertical"
+            android:contentDescription="@string/lock_symbol"
+            app:srcCompat="@drawable/ic_contacts" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/standard_half_padding"
+                android:paddingEnd="@dimen/standard_half_padding"
+                android:text="@string/scope_federated_title"
+                android:textStyle="bold" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/standard_half_padding"
+                android:paddingEnd="@dimen/standard_half_padding"
+                android:text="@string/scope_federated_description" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/scope_published"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/standard_margin"
+        android:layout_marginTop="@dimen/standard_half_margin"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_gravity="center_vertical"
+            android:contentDescription="@string/lock_symbol"
+            app:srcCompat="@drawable/ic_link" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/standard_half_padding"
+                android:paddingEnd="@dimen/standard_half_padding"
+                android:text="@string/scope_published_title"
+                android:textStyle="bold" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/standard_half_padding"
+                android:paddingEnd="@dimen/standard_half_padding"
+                android:text="@string/scope_published_description" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>

+ 64 - 0
app/src/main/res/layout/empty_list.xml

@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Nextcloud Android client application
+
+  Copyright (C) 2016 Andy Scherzinger
+  Copyright (C) 2016 Nextcloud.
+  Copyright (C) 2021 Tobias Kaminsky
+  Copyright (C) 2021 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  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/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/empty_list_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:layout_margin="@dimen/standard_margin"
+    android:gravity="center_vertical|center_horizontal"
+    android:orientation="vertical"
+    android:paddingBottom="@dimen/standard_double_margin">
+
+    <ImageView
+        android:id="@+id/empty_list_icon"
+        android:layout_width="@dimen/empty_list_icon_layout_width"
+        android:layout_height="@dimen/empty_list_icon_layout_height"
+        android:contentDescription="@string/file_list_folder"
+        android:src="@drawable/ic_user"
+        android:visibility="gone" />
+
+    <TextView
+        android:id="@+id/empty_list_view_headline"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:ellipsize="end"
+        android:gravity="center"
+        android:maxLines="2"
+        android:paddingTop="@dimen/standard_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        android:text="@string/file_list_loading"
+        android:textSize="26sp" />
+
+    <TextView
+        android:id="@+id/empty_list_view_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:ellipsize="end"
+        android:gravity="center"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        android:text=""
+        android:visibility="gone" />
+</LinearLayout>

+ 70 - 0
app/src/main/res/layout/user_info_details_table_item.xml

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Nextcloud Android client application
+
+  Copyright (C) 2018 Andy Scherzinger
+  Copyright (C) 2018 Nextcloud
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  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/>.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/user_info_detail_container"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/iconized_single_line_item_layout_height"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/iconized_single_line_item_icon_size"
+        android:layout_height="@dimen/iconized_single_line_item_icon_size"
+        android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+        android:contentDescription="@string/account_icon"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:src="@drawable/ic_phone" />
+
+    <EditText
+        android:id="@+id/user_info_edit_text"
+        android:layout_width="259dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+        android:layout_marginEnd="@dimen/standard_margin"
+        android:autofillHints="none"
+        android:ellipsize="end"
+        android:inputType="text"
+        android:maxLines="1"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/scope"
+        app:layout_constraintStart_toEndOf="@id/icon"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="+49 123 456 789 12"
+        tools:ignore="LabelFor" />
+
+    <ImageView
+        android:id="@+id/scope"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginStart="@dimen/user_info_icon_horizontal_margin"
+        android:layout_marginEnd="@dimen/standard_double_margin"
+        android:contentDescription="@string/scope_toggle"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/user_info_edit_text"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:src="@drawable/ic_link" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 28 - 0
app/src/main/res/menu/menu_profile.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android Talk application
+
+  @author Tobias Kaminsky
+  Copyright (C) 2021 Tobias Kaminsky
+  Copyright (C) 2021 Nextcloud GmbH
+ 
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ 
+  You should have received a copy of the GNU Affero General Public License
+  along with this program. If not, see <https://www.gnu.org/licenses/>.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/edit"
+        android:title="@string/edit"
+        app:showAsAction="ifRoom" />
+</menu>

+ 12 - 0
app/src/main/res/values/dimens.xml

@@ -44,4 +44,16 @@
     <dimen name="large_preview_dimension">80dp</dimen>
     <dimen name="corner_radius">16dp</dimen>
     <dimen name="button_corner_radius">24dp</dimen>
+    <dimen name="standard_margin">16dp</dimen>
+    <dimen name="two_line_primary_text_size">16sp</dimen>
+    <dimen name="drawer_header_subtext">12sp</dimen>
+    <dimen name="iconized_single_line_item_layout_height">56dp</dimen>
+    <dimen name="iconized_single_line_item_icon_size">24dp</dimen>
+    <dimen name="user_info_icon_horizontal_margin">24dp</dimen>
+    <dimen name="standard_double_margin">32dp</dimen>
+    <dimen name="empty_list_icon_layout_width">72dp</dimen>
+    <dimen name="empty_list_icon_layout_height">72dp</dimen>
+    <dimen name="standard_padding">16dp</dimen>
+    <dimen name="standard_half_padding">8dp</dimen>
+    <dimen name="standard_half_margin">8dp</dimen>
 </resources>

+ 30 - 1
app/src/main/res/values/strings.xml

@@ -356,7 +356,36 @@
     <string name="no_phone_book_integration_due_to_permissions">No phone book integration due to missing permissions</string>
     <string name="nc_phone_book_integration_chat_via">Chat via %s</string>
     <string name="nc_phone_book_integration_account_not_found">Account not found</string>
-
+   
+    <string name="avatar">Avatar</string>
+    <string name="account_icon">Account icon</string>
+    <string name="userinfo_no_info_headline">No personal info set</string>
+    <string name="userinfo_no_info_text">Add name, picture and contact details on your profile page.</string>
+    <string name="userinfo_error_text">Failed to retrieve personal user information.</string>
+    <string name="user_info_phone">Phone number</string>
+    <string name="user_info_email">E-mail</string>
+    <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_displayname">Full name</string>
+    <string name="file_list_folder">folder</string>
+    <string name="file_list_loading">Loading…</string>
+    <string name="edit">Edit</string>
+    <string name="save">Save</string>
+    <string name="upload_new_avatar_from_device">Upload new avatar from device</string>
+    <string name="choose_avatar_from_cloud">Choose avatar from cloud</string>
+    <string name="delete_avatar">Delete avatar</string>
+    <string name="scope_private_title">Private</string>
+    <string name="scope_private_description">Don\'t show via public link</string>
+    <string name="lock_symbol">Lock symbol</string>
+    <string name="scope_local_title">Local</string>
+    <string name="scope_local_description">Don\'t synchronize to servers</string>
+    <string name="scope_federated_title">Federated</string>
+    <string name="scope_federated_description">Only synchronize to trusted servers</string>
+    <string name="scope_published_title">Published</string>
+    <string name="scope_published_description">Synchronize to trusted serves and the global and public address book</string>
+    <string name="scope_toggle">Scope toggle</string>
+    
     <!-- App Bar -->
     <string name="appbar_search_in">Search in %s</string>