Make the PeopleSpaceActivity screenshot testable
This CL makes the PeopleSpaceActivity screenshot testable by extracting
a ViewModel and ViewBinder out of it. See ag/19289788 for the associated
screenshot tests.
Note that I tried to change the code inflating and updating the View as
less as possible, to avoid introducing bugs. Once this CL and the
associated screenshots are submitted, I will go ahead and refactor this
code even more. This CL is meant to be an example of the kind of
refactoring required to make a UI screenshot testable, so I tried to not
make it too big.
Bug: 238993727
Test: atest PeopleSpaceScreenshotTest
Change-Id: Ib792bd5da41c9e8bdab6cba7108a249bab10ebd2
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index fa87de2..ffd6b52 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -110,6 +110,7 @@
"androidx.arch.core_core-runtime",
"androidx.lifecycle_lifecycle-common-java8",
"androidx.lifecycle_lifecycle-extensions",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
@@ -218,6 +219,7 @@
"androidx.arch.core_core-runtime",
"androidx.lifecycle_lifecycle-common-java8",
"androidx.lifecycle_lifecycle-extensions",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/people_space_activity.xml
index 7102375..f45cc7c 100644
--- a/packages/SystemUI/res/layout/people_space_activity.xml
+++ b/packages/SystemUI/res/layout/people_space_activity.xml
@@ -13,103 +13,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<LinearLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/top_level"
+ android:id="@+id/container"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="8dp">
- <TextView
- android:id="@+id/select_conversation_title"
- android:text="@string/select_conversation_title"
- android:gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"/>
-
- <TextView
- android:id="@+id/select_conversation"
- android:text="@string/select_conversation_text"
- android:gravity="center"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="16sp"
- android:paddingVertical="24dp"
- android:paddingHorizontal="48dp"/>
-
- <androidx.core.widget.NestedScrollView
- android:id="@+id/scroll_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:id="@+id/scroll_layout"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/priority"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="35dp">
- <TextView
- android:id="@+id/priority_header"
- android:text="@string/priority_conversations"
- android:layout_width="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
- android:textSize="14sp"
- android:paddingStart="16dp"
- android:layout_height="wrap_content"/>
-
- <LinearLayout
- android:id="@+id/priority_tiles"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:orientation="vertical"
- android:background="@drawable/rounded_bg_full_large_radius"
- android:clipToOutline="true">
- </LinearLayout>
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/recent"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <TextView
- android:id="@+id/recent_header"
- android:gravity="start"
- android:text="@string/recent_conversations"
- android:layout_width="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
- android:textSize="14sp"
- android:paddingStart="16dp"
- android:layout_height="wrap_content"/>
-
- <LinearLayout
- android:id="@+id/recent_tiles"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:orientation="vertical"
- android:background="@drawable/rounded_bg_full_large_radius"
- android:clipToOutline="true">
- </LinearLayout>
- </LinearLayout>
- </LinearLayout>
- </androidx.core.widget.NestedScrollView>
-</LinearLayout>
\ No newline at end of file
+ android:layout_height="match_parent">
+ <!-- The content of people_space_activity_(no|with)_conversations.xml will be added here at
+ runtime depending on the number of conversations to show. -->
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
index 2e9ff07..e929169 100644
--- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
+++ b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
@@ -16,7 +16,7 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/top_level"
+ android:id="@+id/top_level_no_conversations"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp"
diff --git a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
new file mode 100644
index 0000000..2384963
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
@@ -0,0 +1,115 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/top_level_with_conversations"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="8dp">
+ <TextView
+ android:id="@+id/select_conversation_title"
+ android:text="@string/select_conversation_title"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="24sp"/>
+
+ <TextView
+ android:id="@+id/select_conversation"
+ android:text="@string/select_conversation_text"
+ android:gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="16sp"
+ android:paddingVertical="24dp"
+ android:paddingHorizontal="48dp"/>
+
+ <androidx.core.widget.NestedScrollView
+ android:id="@+id/scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/scroll_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/priority"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="35dp">
+ <TextView
+ android:id="@+id/priority_header"
+ android:text="@string/priority_conversations"
+ android:layout_width="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
+ android:textSize="14sp"
+ android:paddingStart="16dp"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:id="@+id/priority_tiles"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:orientation="vertical"
+ android:background="@drawable/rounded_bg_full_large_radius"
+ android:clipToOutline="true">
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/recent"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/recent_header"
+ android:gravity="start"
+ android:text="@string/recent_conversations"
+ android:layout_width="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
+ android:textSize="14sp"
+ android:paddingStart="16dp"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:id="@+id/recent_tiles"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:orientation="vertical"
+ android:background="@drawable/rounded_bg_full_large_radius"
+ android:clipToOutline="true">
+ </LinearLayout>
+ </LinearLayout>
+ </LinearLayout>
+ </androidx.core.widget.NestedScrollView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml
index 2a2c35d..b0599ca 100644
--- a/packages/SystemUI/res/layout/people_space_tile_view.xml
+++ b/packages/SystemUI/res/layout/people_space_tile_view.xml
@@ -37,8 +37,8 @@
<ImageView
android:id="@+id/tile_view_person_icon"
- android:layout_width="52dp"
- android:layout_height="52dp" />
+ android:layout_width="@dimen/avatar_size_for_medium"
+ android:layout_height="@dimen/avatar_size_for_medium" />
<LinearLayout
android:orientation="horizontal"
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 137e288..d5f1f25 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -46,6 +46,7 @@
import com.android.systemui.media.dagger.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
+import com.android.systemui.people.PeopleModule;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.privacy.PrivacyModule;
import com.android.systemui.recents.Recents;
@@ -122,6 +123,7 @@
LogModule.class,
MediaProjectionModule.class,
PeopleHubModule.class,
+ PeopleModule.class,
PluginModule.class,
PrivacyModule.class,
QsFrameTranslateModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt
new file mode 100644
index 0000000..dd35445
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people
+
+import com.android.systemui.people.data.repository.PeopleTileRepository
+import com.android.systemui.people.data.repository.PeopleTileRepositoryImpl
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import com.android.systemui.people.data.repository.PeopleWidgetRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** Dagger module to provide/bind people space dependencies. */
+@Module
+interface PeopleModule {
+ @Binds fun bindTileRepository(impl: PeopleTileRepositoryImpl): PeopleTileRepository
+
+ @Binds fun bindWidgetRepository(impl: PeopleWidgetRepositoryImpl): PeopleWidgetRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 93a3f81..e845aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -19,144 +19,52 @@
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
-import static com.android.systemui.people.PeopleTileViewHelper.getPersonIconBitmap;
-import static com.android.systemui.people.PeopleTileViewHelper.getSizeInDp;
-
-import android.app.Activity;
-import android.app.people.PeopleSpaceTile;
-import android.content.Context;
import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.widget.LinearLayout;
-import com.android.systemui.R;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
-import com.android.systemui.people.widget.PeopleTileKey;
+import androidx.activity.ComponentActivity;
+import androidx.lifecycle.ViewModelProvider;
-import java.util.ArrayList;
-import java.util.List;
+import com.android.systemui.people.ui.view.PeopleViewBinder;
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel;
import javax.inject.Inject;
/** People Tile Widget configuration activity that shows the user their conversation tiles. */
-public class PeopleSpaceActivity extends Activity {
+public class PeopleSpaceActivity extends ComponentActivity {
private static final String TAG = "PeopleSpaceActivity";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
- private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
- private Context mContext;
- private int mAppWidgetId;
+ private final PeopleViewModel.Factory mViewModelFactory;
+ private PeopleViewModel mViewModel;
@Inject
- public PeopleSpaceActivity(PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
+ public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) {
super();
- mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
-
+ mViewModelFactory = viewModelFactory;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mContext = getApplicationContext();
- mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID,
- INVALID_APPWIDGET_ID);
setResult(RESULT_CANCELED);
- }
+ mViewModel = new ViewModelProvider(this, mViewModelFactory).get(PeopleViewModel.class);
- /** Builds the conversation selection activity. */
- private void buildActivity() {
- List<PeopleSpaceTile> priorityTiles = new ArrayList<>();
- List<PeopleSpaceTile> recentTiles = new ArrayList<>();
- try {
- priorityTiles = mPeopleSpaceWidgetManager.getPriorityTiles();
- recentTiles = mPeopleSpaceWidgetManager.getRecentTiles();
- } catch (Exception e) {
- Log.e(TAG, "Couldn't retrieve conversations", e);
- }
+ // Update the widget ID coming from the intent.
+ int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
+ mViewModel.onWidgetIdChanged(widgetId);
- // If no conversations, render activity without conversations
- if (recentTiles.isEmpty() && priorityTiles.isEmpty()) {
- setContentView(R.layout.people_space_activity_no_conversations);
-
- // The Tile preview has colorBackground as its background. Change it so it's different
- // than the activity's background.
- LinearLayout item = findViewById(android.R.id.background);
- GradientDrawable shape = (GradientDrawable) item.getBackground();
- final TypedArray ta = mContext.getTheme().obtainStyledAttributes(
- new int[]{com.android.internal.R.attr.colorSurface});
- shape.setColor(ta.getColor(0, Color.WHITE));
- return;
- }
-
- setContentView(R.layout.people_space_activity);
- setTileViews(R.id.priority, R.id.priority_tiles, priorityTiles);
- setTileViews(R.id.recent, R.id.recent_tiles, recentTiles);
- }
-
- private ViewOutlineProvider mViewOutlineProvider = new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
- mContext.getResources().getDimension(R.dimen.people_space_widget_radius));
- }
- };
-
- /** Sets a {@link PeopleSpaceTileView}s for each conversation. */
- private void setTileViews(int viewId, int tilesId, List<PeopleSpaceTile> tiles) {
- if (tiles.isEmpty()) {
- LinearLayout view = findViewById(viewId);
- view.setVisibility(View.GONE);
- return;
- }
-
- ViewGroup layout = findViewById(tilesId);
- layout.setClipToOutline(true);
- layout.setOutlineProvider(mViewOutlineProvider);
- for (int i = 0; i < tiles.size(); ++i) {
- PeopleSpaceTile tile = tiles.get(i);
- PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext,
- layout, tile.getId(), i == (tiles.size() - 1));
- setTileView(tileView, tile);
- }
- }
-
- /** Sets {@code tileView} with the data in {@code conversation}. */
- private void setTileView(PeopleSpaceTileView tileView, PeopleSpaceTile tile) {
- try {
- if (tile.getUserName() != null) {
- tileView.setName(tile.getUserName().toString());
- }
- tileView.setPersonIcon(getPersonIconBitmap(mContext, tile,
- getSizeInDp(mContext, R.dimen.avatar_size_for_medium,
- mContext.getResources().getDisplayMetrics().density)));
-
- PeopleTileKey key = new PeopleTileKey(tile);
- tileView.setOnClickListener(v -> storeWidgetConfiguration(tile, key));
- } catch (Exception e) {
- Log.e(TAG, "Couldn't retrieve shortcut information", e);
- }
- }
-
- /** Stores the user selected configuration for {@code mAppWidgetId}. */
- private void storeWidgetConfiguration(PeopleSpaceTile tile, PeopleTileKey key) {
- if (PeopleSpaceUtils.DEBUG) {
- if (DEBUG) {
- Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
- + tile.getId() + " for widget ID: "
- + mAppWidgetId);
- }
- }
- mPeopleSpaceWidgetManager.addNewWidget(mAppWidgetId, key);
- finishActivity();
+ ViewGroup view = PeopleViewBinder.create(this);
+ PeopleViewBinder.bind(view, mViewModel, /* lifecycleOwner= */ this,
+ () -> {
+ finishActivity();
+ return null;
+ });
+ setContentView(view);
}
/** Finish activity with a successful widget configuration result. */
@@ -169,19 +77,13 @@
/** Finish activity without choosing a widget. */
public void dismissActivity(View v) {
if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
+ setResult(RESULT_CANCELED);
finish();
}
private void setActivityResult(int result) {
Intent resultValue = new Intent();
- resultValue.putExtra(EXTRA_APPWIDGET_ID, mAppWidgetId);
+ resultValue.putExtra(EXTRA_APPWIDGET_ID, mViewModel.getAppWidgetId().getValue());
setResult(result, resultValue);
}
-
- @Override
- protected void onResume() {
- super.onResume();
- // Refresh tile views to sync new conversations.
- buildActivity();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
index 4ee951f..58e700f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
@@ -28,6 +28,7 @@
import android.graphics.drawable.Drawable;
import android.util.IconDrawableFactory;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
@@ -52,16 +53,15 @@
PeopleStoryIconFactory(Context context, PackageManager pm,
IconDrawableFactory iconDrawableFactory, int iconSizeDp) {
- context.setTheme(android.R.style.Theme_DeviceDefault_DayNight);
- mIconBitmapSize = (int) (iconSizeDp * context.getResources().getDisplayMetrics().density);
- mDensity = context.getResources().getDisplayMetrics().density;
+ mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_DayNight);
+ mIconBitmapSize = (int) (iconSizeDp * mContext.getResources().getDisplayMetrics().density);
+ mDensity = mContext.getResources().getDisplayMetrics().density;
mIconSize = mDensity * iconSizeDp;
mPackageManager = pm;
mIconDrawableFactory = iconDrawableFactory;
- mImportantConversationColor = context.getColor(R.color.important_conversation);
- mAccentColor = Utils.getColorAttr(context,
+ mImportantConversationColor = mContext.getColor(R.color.important_conversation);
+ mAccentColor = Utils.getColorAttr(mContext,
com.android.internal.R.attr.colorAccentPrimaryVariant).getDefaultColor();
- mContext = context;
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 00aa138..be82b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -75,6 +75,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
+import com.android.systemui.people.data.model.PeopleTileModel;
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.people.widget.PeopleTileKey;
@@ -299,7 +300,8 @@
return createLastInteractionRemoteViews();
}
- private static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) {
+ /** Whether the conversation associated with {@code tile} can bypass DND. */
+ public static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) {
if (tile == null) return false;
int notificationPolicyState = tile.getNotificationPolicyState();
@@ -536,7 +538,8 @@
return views;
}
- private static boolean getHasNewStory(PeopleSpaceTile tile) {
+ /** Whether {@code tile} has a new story. */
+ public static boolean getHasNewStory(PeopleSpaceTile tile) {
return tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
c -> c.getActivity() == ACTIVITY_NEW_STORY);
}
@@ -1250,16 +1253,24 @@
}
/** Returns a bitmap with the user icon and package icon. */
- public static Bitmap getPersonIconBitmap(Context context, PeopleSpaceTile tile,
+ public static Bitmap getPersonIconBitmap(Context context, PeopleTileModel tile,
int maxAvatarSize) {
- boolean hasNewStory = getHasNewStory(tile);
- return getPersonIconBitmap(context, tile, maxAvatarSize, hasNewStory);
+ return getPersonIconBitmap(context, maxAvatarSize, tile.getHasNewStory(),
+ tile.getUserIcon(), tile.getKey().getPackageName(), tile.getKey().getUserId(),
+ tile.isImportant(), tile.isDndBlocking());
}
/** Returns a bitmap with the user icon and package icon. */
private static Bitmap getPersonIconBitmap(
Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) {
- Icon icon = tile.getUserIcon();
+ return getPersonIconBitmap(context, maxAvatarSize, hasNewStory, tile.getUserIcon(),
+ tile.getPackageName(), getUserId(tile),
+ tile.isImportantConversation(), isDndBlockingTileData(tile));
+ }
+
+ private static Bitmap getPersonIconBitmap(
+ Context context, int maxAvatarSize, boolean hasNewStory, Icon icon, String packageName,
+ int userId, boolean importantConversation, boolean dndBlockingTileData) {
if (icon == null) {
Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge).mutate();
placeholder.setColorFilter(getDisabledColorFilter());
@@ -1272,10 +1283,10 @@
RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
context.getResources(), icon.getBitmap());
Drawable personDrawable = storyIcon.getPeopleTileDrawable(roundedDrawable,
- tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
+ packageName, userId, importantConversation,
hasNewStory);
- if (isDndBlockingTileData(tile)) {
+ if (dndBlockingTileData) {
personDrawable.setColorFilter(getDisabledColorFilter());
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt b/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt
new file mode 100644
index 0000000..5d8539f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.data.model
+
+import android.graphics.drawable.Icon
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** Models a tile/conversation. */
+data class PeopleTileModel(
+ val key: PeopleTileKey,
+ val username: String,
+ val userIcon: Icon,
+ val hasNewStory: Boolean,
+ val isImportant: Boolean,
+ val isDndBlocking: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt
new file mode 100644
index 0000000..01b43d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.data.repository
+
+import android.app.people.PeopleSpaceTile
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.people.PeopleTileViewHelper
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.people.widget.PeopleTileKey
+import javax.inject.Inject
+
+/** A Repository to fetch the current tiles/conversations. */
+// TODO(b/238993727): Make the tiles API reactive.
+interface PeopleTileRepository {
+ /* The current priority tiles. */
+ fun priorityTiles(): List<PeopleTileModel>
+
+ /* The current recent tiles. */
+ fun recentTiles(): List<PeopleTileModel>
+}
+
+@SysUISingleton
+class PeopleTileRepositoryImpl
+@Inject
+constructor(
+ private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
+) : PeopleTileRepository {
+ override fun priorityTiles(): List<PeopleTileModel> {
+ return peopleSpaceWidgetManager.priorityTiles.map { it.toModel() }
+ }
+
+ override fun recentTiles(): List<PeopleTileModel> {
+ return peopleSpaceWidgetManager.recentTiles.map { it.toModel() }
+ }
+
+ private fun PeopleSpaceTile.toModel(): PeopleTileModel {
+ return PeopleTileModel(
+ PeopleTileKey(this),
+ userName.toString(),
+ userIcon,
+ PeopleTileViewHelper.getHasNewStory(this),
+ isImportantConversation,
+ PeopleTileViewHelper.isDndBlockingTileData(this),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt
new file mode 100644
index 0000000..f2b6cb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.people.widget.PeopleTileKey
+import javax.inject.Inject
+
+interface PeopleWidgetRepository {
+ /**
+ * Bind the widget with ID [widgetId] to the tile keyed by [tileKey].
+ *
+ * If there is already a widget with [widgetId], this existing widget will be reconfigured and
+ * associated to this tile. If there is no widget with [widgetId], a new one will be created.
+ */
+ fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey)
+}
+
+@SysUISingleton
+class PeopleWidgetRepositoryImpl
+@Inject
+constructor(
+ private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
+) : PeopleWidgetRepository {
+ override fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) {
+ peopleSpaceWidgetManager.addNewWidget(widgetId, tileKey)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
new file mode 100644
index 0000000..bc982cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.ui.view
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Outline
+import android.graphics.drawable.GradientDrawable
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewOutlineProvider
+import android.widget.LinearLayout
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.Lifecycle.State.CREATED
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.people.PeopleSpaceTileView
+import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** A ViewBinder for [PeopleViewModel]. */
+object PeopleViewBinder {
+ private const val TAG = "PeopleSpaceViewBinder"
+
+ /**
+ * The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists.
+ */
+ private val ViewOutlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ 0,
+ 0,
+ view.width,
+ view.height,
+ view.context.resources.getDimension(R.dimen.people_space_widget_radius),
+ )
+ }
+ }
+
+ /** Create a [View] that can later be [bound][bind] to a [PeopleViewModel]. */
+ @JvmStatic
+ fun create(context: Context): ViewGroup {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.people_space_activity, /* root= */ null) as ViewGroup
+ }
+
+ /** Bind [view] to [viewModel]. */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: PeopleViewModel,
+ lifecycleOwner: LifecycleOwner,
+ onFinish: () -> Unit,
+ ) {
+ // Call [onFinish] this activity when the ViewModel tells us so.
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(CREATED) {
+ viewModel.isFinished.collect { isFinished ->
+ if (isFinished) {
+ viewModel.clearIsFinished()
+ onFinish()
+ }
+ }
+ }
+ }
+
+ // Start collecting the UI data once the Activity is STARTED.
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ combine(
+ viewModel.priorityTiles,
+ viewModel.recentTiles,
+ ) { priority, recent ->
+ priority to recent
+ }
+ .collect { (priorityTiles, recentTiles) ->
+ if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
+ setConversationsContent(
+ view,
+ priorityTiles,
+ recentTiles,
+ viewModel::onTileClicked,
+ )
+ } else {
+ setNoConversationsContent(view)
+ }
+ }
+ }
+ }
+
+ // Make sure to refresh the tiles/conversations when the Activity is resumed, so that it
+ // updates them when going back to the Activity after leaving it.
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ viewModel.onTileRefreshRequested()
+ }
+ }
+ }
+
+ private fun setNoConversationsContent(view: ViewGroup) {
+ // This should never happen.
+ if (view.childCount > 1) {
+ error("view has ${view.childCount} children, it should have maximum 1")
+ }
+
+ // The static content for no conversations is already shown.
+ if (view.findViewById<View>(R.id.top_level_no_conversations) != null) {
+ return
+ }
+
+ // If we were showing the content with conversations earlier, remove it.
+ if (view.childCount == 1) {
+ view.removeViewAt(0)
+ }
+
+ val context = view.context
+ val noConversationsView =
+ LayoutInflater.from(context)
+ .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
+
+ // The Tile preview has colorBackground as its background. Change it so it's different than
+ // the activity's background.
+ val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
+ val shape = item.background as GradientDrawable
+ val ta =
+ context.theme.obtainStyledAttributes(
+ intArrayOf(com.android.internal.R.attr.colorSurface)
+ )
+ shape.setColor(ta.getColor(0, Color.WHITE))
+ ta.recycle()
+ }
+
+ private fun setConversationsContent(
+ view: ViewGroup,
+ priorityTiles: List<PeopleTileViewModel>,
+ recentTiles: List<PeopleTileViewModel>,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+ ) {
+ // This should never happen.
+ if (view.childCount > 1) {
+ error("view has ${view.childCount} children, it should have maximum 1")
+ }
+
+ // Inflate the content with conversations, if it's not already.
+ if (view.findViewById<View>(R.id.top_level_with_conversations) == null) {
+ // If we were showing the content without conversations earlier, remove it.
+ if (view.childCount == 1) {
+ view.removeViewAt(0)
+ }
+
+ LayoutInflater.from(view.context)
+ .inflate(R.layout.people_space_activity_with_conversations, /* root= */ view)
+ }
+
+ // TODO(b/193782241): Replace the NestedScrollView + 2x LinearLayout from this layout into a
+ // single RecyclerView once this screen is tested by screenshot tests. Introduce a
+ // PeopleSpaceTileViewBinder that will properly create and bind the View associated to a
+ // PeopleSpaceTileViewModel (and remove the PeopleSpaceTileView class).
+ val conversationsView = view.requireViewById<View>(R.id.top_level_with_conversations)
+ setTileViews(
+ conversationsView,
+ R.id.priority,
+ R.id.priority_tiles,
+ priorityTiles,
+ onTileClicked,
+ )
+
+ setTileViews(
+ conversationsView,
+ R.id.recent,
+ R.id.recent_tiles,
+ recentTiles,
+ onTileClicked,
+ )
+ }
+
+ /** Sets a [PeopleSpaceTileView]s for each conversation. */
+ private fun setTileViews(
+ root: View,
+ tilesListId: Int,
+ tilesId: Int,
+ tiles: List<PeopleTileViewModel>,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+ ) {
+ // Remove any previously added tile.
+ // TODO(b/193782241): Once this list is a big RecyclerView, set the current list and use
+ // DiffUtil to do as less addView/removeView as possible.
+ val layout = root.requireViewById<ViewGroup>(tilesId)
+ layout.removeAllViews()
+ layout.outlineProvider = ViewOutlineProvider
+
+ val tilesListView = root.requireViewById<LinearLayout>(tilesListId)
+ if (tiles.isEmpty()) {
+ tilesListView.visibility = View.GONE
+ return
+ }
+ tilesListView.visibility = View.VISIBLE
+
+ // Add each tile.
+ tiles.forEachIndexed { i, tile ->
+ val tileView =
+ PeopleSpaceTileView(root.context, layout, tile.key.shortcutId, i == tiles.size - 1)
+ bindTileView(tileView, tile, onTileClicked)
+ }
+ }
+
+ /** Sets [tileView] with the data in [conversation]. */
+ private fun bindTileView(
+ tileView: PeopleSpaceTileView,
+ tile: PeopleTileViewModel,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+ ) {
+ try {
+ tileView.setName(tile.username)
+ tileView.setPersonIcon(tile.icon)
+ tileView.setOnClickListener { onTileClicked(tile) }
+ } catch (e: Exception) {
+ Log.e(TAG, "Couldn't retrieve shortcut information", e)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt
new file mode 100644
index 0000000..40205ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.ui.viewmodel
+
+import android.graphics.Bitmap
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** Models UI state for a single tile/conversation. */
+data class PeopleTileViewModel(
+ val key: PeopleTileKey,
+ val icon: Bitmap,
+ val username: String?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
new file mode 100644
index 0000000..17de991
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.ui.viewmodel
+
+import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.people.PeopleSpaceUtils
+import com.android.systemui.people.PeopleTileViewHelper
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.data.repository.PeopleTileRepository
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Models UI state for the people space, allowing the user to select which conversation should be
+ * associated to a new or existing Conversation widget.
+ */
+class PeopleViewModel(
+ @Application private val context: Context,
+ private val tileRepository: PeopleTileRepository,
+ private val widgetRepository: PeopleWidgetRepository,
+) : ViewModel() {
+ /**
+ * The list of the priority tiles/conversations.
+ *
+ * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
+ * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
+ */
+ private val _priorityTiles = MutableStateFlow(priorityTiles())
+ val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles
+
+ /**
+ * The list of the priority tiles/conversations.
+ *
+ * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
+ * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
+ */
+ private val _recentTiles = MutableStateFlow(recentTiles())
+ val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles
+
+ /** The ID of the widget currently being edited/added. */
+ private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
+ val appWidgetId: StateFlow<Int> = _appWidgetId
+
+ /** Whether the user journey is complete. */
+ private val _isFinished = MutableStateFlow(false)
+ val isFinished: StateFlow<Boolean> = _isFinished
+
+ /** Refresh the [priorityTiles] and [recentTiles]. */
+ fun onTileRefreshRequested() {
+ _priorityTiles.value = priorityTiles()
+ _recentTiles.value = recentTiles()
+ }
+
+ /** Called when the [appWidgetId] should be changed to [widgetId]. */
+ fun onWidgetIdChanged(widgetId: Int) {
+ _appWidgetId.value = widgetId
+ }
+
+ /** Clear [isFinished], setting it to false. */
+ fun clearIsFinished() {
+ _isFinished.value = false
+ }
+
+ /** Called when a tile is clicked. */
+ fun onTileClicked(tile: PeopleTileViewModel) {
+ if (PeopleSpaceUtils.DEBUG) {
+ Log.d(
+ TAG,
+ "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID: " +
+ _appWidgetId.value
+ )
+ }
+ widgetRepository.setWidgetTile(_appWidgetId.value, tile.key)
+ _isFinished.value = true
+ }
+
+ private fun priorityTiles(): List<PeopleTileViewModel> {
+ return try {
+ tileRepository.priorityTiles().map { it.toViewModel() }
+ } catch (e: Exception) {
+ Log.e(TAG, "Couldn't retrieve priority conversations", e)
+ emptyList()
+ }
+ }
+
+ private fun recentTiles(): List<PeopleTileViewModel> {
+ return try {
+ tileRepository.recentTiles().map { it.toViewModel() }
+ } catch (e: Exception) {
+ Log.e(TAG, "Couldn't retrieve recent conversations", e)
+ emptyList()
+ }
+ }
+
+ private fun PeopleTileModel.toViewModel(): PeopleTileViewModel {
+ val icon =
+ PeopleTileViewHelper.getPersonIconBitmap(
+ context,
+ this,
+ PeopleTileViewHelper.getSizeInDp(
+ context,
+ R.dimen.avatar_size_for_medium,
+ context.resources.displayMetrics.density,
+ )
+ )
+ return PeopleTileViewModel(key, icon, username)
+ }
+
+ /** The Factory that should be used to create a [PeopleViewModel]. */
+ class Factory
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ private val tileRepository: PeopleTileRepository,
+ private val widgetRepository: PeopleWidgetRepository,
+ ) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ check(modelClass == PeopleViewModel::class.java)
+ return PeopleViewModel(context, tileRepository, widgetRepository) as T
+ }
+ }
+
+ companion object {
+ private const val TAG = "PeopleSpaceViewModel"
+ }
+}