Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH
* SPDX-FileCopyrightText: 2025 TSI-mc <surinder.kumar@t-systems.com>
* SPDX-FileCopyrightText: 2026 Daniele Verducci <daniele.verducci@ichibi.eu
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/
package com.owncloud.android.ui.activity;

import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.URLUtil;
import android.widget.ImageView;

Expand All @@ -36,25 +34,28 @@
import com.nextcloud.utils.GlideHelper;
import com.nextcloud.utils.extensions.BundleExtensionsKt;
import com.owncloud.android.R;
import com.owncloud.android.databinding.UserInfoDetailsTableItemBinding;
import com.owncloud.android.databinding.UserInfoLayoutBinding;
import com.owncloud.android.lib.common.OwnCloudClientFactory;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
import com.owncloud.android.ui.adapter.UserInfoAdapter;
import com.owncloud.android.ui.dialog.AccountRemovalDialog;
import com.owncloud.android.ui.events.TokenPushEvent;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.PushUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;

import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

Expand All @@ -66,7 +67,7 @@
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.RecyclerView;
import kotlin.Unit;

/**
* This Activity presents the user information.
Expand Down Expand Up @@ -124,8 +125,6 @@ public void onCreate(Bundle savedInstanceState) {
viewThemeUtils.files.themeActionBar(this, actionBar);
}

binding.userinfoList.setAdapter(new UserInfoAdapter(null, viewThemeUtils));

if (userInfo != null) {
populateUserInfoUi(userInfo);
} else {
Expand Down Expand Up @@ -274,17 +273,18 @@ private void populateUserInfoUi(UserInfo userInfo) {
binding.loadingContent.setVisibility(View.VISIBLE);
binding.emptyList.emptyListView.setVisibility(View.GONE);

if (binding.userinfoList.getAdapter() instanceof UserInfoAdapter) {
binding.userinfoList.setAdapter(new UserInfoAdapter(createUserInfoDetails(userInfo), viewThemeUtils));
}
Map<Integer, LinkedList<UserInfoAdapter.UserInfoDetailsItem>> list = new HashMap<>();
list.put(UserInfoAdapter.SECTION_USERINFO, createUserInfoDetails(userInfo));
list.put(UserInfoAdapter.SECTION_GROUPS, createGroupInfoDetails(userInfo));
binding.userinfoList.setAdapter(new UserInfoAdapter(this, list, viewThemeUtils));

binding.loadingContent.setVisibility(View.GONE);
binding.userinfoList.setVisibility(View.VISIBLE);
}
}

private List<UserInfoDetailsItem> createUserInfoDetails(UserInfo userInfo) {
List<UserInfoDetailsItem> result = new LinkedList<>();
private LinkedList<UserInfoAdapter.UserInfoDetailsItem> createUserInfoDetails(UserInfo userInfo) {
LinkedList<UserInfoAdapter.UserInfoDetailsItem> result = new LinkedList<>();

addToListIfNeeded(result, R.drawable.ic_phone, userInfo.getPhone(), R.string.user_info_phone);
addToListIfNeeded(result, R.drawable.ic_email, userInfo.getEmail(), R.string.user_info_email);
Expand All @@ -297,10 +297,23 @@ private List<UserInfoDetailsItem> createUserInfoDetails(UserInfo userInfo) {
return result;
}

private void addToListIfNeeded(List<UserInfoDetailsItem> info, @DrawableRes int icon, String text,
private LinkedList<UserInfoAdapter.UserInfoDetailsItem> createGroupInfoDetails(UserInfo userInfo) {
LinkedList<UserInfoAdapter.UserInfoDetailsItem> result = new LinkedList<>();

if (userInfo.getGroups() != null) {
final ArrayList<String> sortedGroups = new ArrayList<>(userInfo.getGroups());
Collections.sort(sortedGroups);
addToListIfNeeded(result, R.drawable.ic_group, String.join(", ", sortedGroups),
R.string.user_info_groups);
}

return result;
}

private void addToListIfNeeded(List<UserInfoAdapter.UserInfoDetailsItem> info, @DrawableRes int icon, String text,
@StringRes int contentDescriptionInt) {
if (!TextUtils.isEmpty(text)) {
info.add(new UserInfoDetailsItem(icon, text, getResources().getString(contentDescriptionInt)));
info.add(new UserInfoAdapter.UserInfoDetailsItem(icon, text, getResources().getString(contentDescriptionInt)));
}
}

Expand Down Expand Up @@ -358,66 +371,4 @@ public void onMessageEvent(TokenPushEvent event) {
PushUtils.pushRegistrationToServer(getUserAccountManager(), preferences.getPushToken());
}


protected static class UserInfoDetailsItem {
@DrawableRes public int icon;
public String text;
public String iconContentDescription;

public UserInfoDetailsItem(@DrawableRes int icon, String text, String iconContentDescription) {
this.icon = icon;
this.text = text;
this.iconContentDescription = iconContentDescription;
}
}

protected static class UserInfoAdapter extends RecyclerView.Adapter<UserInfoAdapter.ViewHolder> {
protected List<UserInfoDetailsItem> mDisplayList;
protected ViewThemeUtils viewThemeUtils;

public static class ViewHolder extends RecyclerView.ViewHolder {
protected UserInfoDetailsTableItemBinding binding;

public ViewHolder(UserInfoDetailsTableItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}

public UserInfoAdapter(List<UserInfoDetailsItem> displayList, ViewThemeUtils viewThemeUtils) {
mDisplayList = displayList == null ? new LinkedList<>() : displayList;
this.viewThemeUtils = viewThemeUtils;
}

@SuppressLint("NotifyDataSetChanged")
public void setData(List<UserInfoDetailsItem> displayList) {
mDisplayList = displayList == null ? new LinkedList<>() : displayList;
notifyDataSetChanged();
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(
UserInfoDetailsTableItemBinding.inflate(
LayoutInflater.from(parent.getContext()),
parent,
false)
);
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
UserInfoDetailsItem item = mDisplayList.get(position);
holder.binding.icon.setImageResource(item.icon);
holder.binding.text.setText(item.text);
holder.binding.icon.setContentDescription(item.iconContentDescription);
viewThemeUtils.platform.colorImageView(holder.binding.icon);
}

@Override
public int getItemCount() {
return mDisplayList.size();
}
}
}
128 changes: 128 additions & 0 deletions app/src/main/java/com/owncloud/android/ui/adapter/UserInfoAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Daniele Verducci <daniele.verducci@ichibi.eu>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.owncloud.android.ui.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.DrawableRes
import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.owncloud.android.R
import com.owncloud.android.databinding.UserInfoDetailsTableItemBinding
import com.owncloud.android.databinding.UserInfoDetailsTableItemTitleBinding
import com.owncloud.android.utils.theme.ViewThemeUtils

class UserInfoAdapter(val context: Context, val displayList: Map<Int, List<UserInfoDetailsItem>>, val viewThemeUtils: ViewThemeUtils) :
SectionedRecyclerViewAdapter<SectionedViewHolder>() {
companion object {
const val SECTION_USERINFO = 0
const val SECTION_GROUPS = 1
}

data class UserInfoDetailsItem(
@field:DrawableRes @param:DrawableRes var icon: Int,
var text: String?,
var iconContentDescription: String?
)

class HeaderSectionedViewHolder(var binding: UserInfoDetailsTableItemTitleBinding) :
SectionedViewHolder(binding.getRoot())

class UserInfoSectionedViewHolder(var binding: UserInfoDetailsTableItemBinding) :
SectionedViewHolder(binding.getRoot())

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionedViewHolder = when (viewType) {
VIEW_TYPE_HEADER -> HeaderSectionedViewHolder(
UserInfoDetailsTableItemTitleBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
else -> UserInfoSectionedViewHolder(
UserInfoDetailsTableItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
)
}

private enum class ItemPosition {
SINGLE, FIRST, MIDDLE, LAST;

fun backgroundRes(): Int = when (this) {
SINGLE -> R.drawable.rounded_corners_listitem_single_background
FIRST -> R.drawable.rounded_corners_listitem_first_background
LAST -> R.drawable.rounded_corners_listitem_last_background
MIDDLE -> R.drawable.rounded_corners_listitem_center_background
}
}

private fun resolvePosition(position: Int, count: Int): ItemPosition {
val isFirst = (position == 0)
val isLast = (position == count - 1)

return when {
isFirst && isLast -> ItemPosition.SINGLE
isFirst -> ItemPosition.FIRST
isLast -> ItemPosition.LAST
else -> ItemPosition.MIDDLE
}
}

override fun onBindViewHolder(
holder: SectionedViewHolder,
section: Int,
relativePosition: Int,
absolutePosition: Int
) {
val count = getItemCount(section)
val positionType = resolvePosition(relativePosition, count)
val uiHolder = holder as UserInfoSectionedViewHolder
val item = displayList[section]?.get(relativePosition)

// Set background
uiHolder.binding.root.setBackgroundResource(
positionType.backgroundRes()
)

// Populate views
uiHolder.binding.icon.setImageResource(item?.icon ?: R.drawable.ic_user_outline)
uiHolder.binding.text.text = item?.text
uiHolder.binding.icon.contentDescription = item?.iconContentDescription
viewThemeUtils.platform.colorImageView(holder.binding.icon, ColorRole.PRIMARY)
}

override fun getSectionCount(): Int = 2

override fun getItemCount(section: Int): Int = when (section) {
SECTION_GROUPS -> displayList[SECTION_GROUPS]?.size ?: 0
SECTION_USERINFO -> displayList[SECTION_USERINFO]?.size ?: 0
else -> 0
}

override fun onBindHeaderViewHolder(
holder: SectionedViewHolder?,
section: Int,
expanded: Boolean
) {
val title = when (section) {
SECTION_GROUPS -> context.getString(R.string.user_info_groups)
SECTION_USERINFO -> context.getString(R.string.user_info_profile)
else -> ""
}
val titleHolder = holder as HeaderSectionedViewHolder
titleHolder.binding.root.text = title
viewThemeUtils.platform.colorTextView(titleHolder.binding.root, ColorRole.PRIMARY)
}

override fun onBindFooterViewHolder(p0: SectionedViewHolder?, p1: Int) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud - Android Client
~
~ SPDX-FileCopyrightText: 2026 Daniele Verducci <daniele.verducci@ichibi.eu>
~ SPDX-License-Identifier: AGPL-3.0-or-later
-->

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="-2dp" android:left="0dp" android:right="0dp" android:bottom="0dp">
<shape android:shape="rectangle">
<stroke android:width="1dp" android:color="@color/grey_200"/>
</shape>
</item>
</layer-list>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud - Android Client
~
~ SPDX-FileCopyrightText: 2026 Daniele Verducci <daniele.verducci@ichibi.eu>
~ SPDX-License-Identifier: AGPL-3.0-or-later
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="@color/bg_default"/>
<stroke android:width="1dp" android:color="@color/grey_200" />
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/>
</shape>
</item>
</layer-list>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud - Android Client
~
~ SPDX-FileCopyrightText: 2026 Daniele Verducci <daniele.verducci@ichibi.eu>
~ SPDX-License-Identifier: AGPL-3.0-or-later
-->

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="-2dp" android:left="0dp" android:right="0dp" android:bottom="0dp">
<shape>
<solid android:color="@color/bg_default"/>
<stroke android:width="1dp" android:color="@color/grey_200" />
<corners android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp"/>
</shape>
</item>
</layer-list>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud - Android Client
~
~ SPDX-FileCopyrightText: 2026 Daniele Verducci <daniele.verducci@ichibi.eu>
~ SPDX-License-Identifier: AGPL-3.0-or-later
-->

<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/bg_default"/>
<stroke android:width="1dp" android:color="@color/grey_200" />
<corners android:radius="4dp"/>
</shape>
Loading