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
This commit is contained in:
@@ -110,6 +110,7 @@ android_library {
|
||||
"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 @@ android_library {
|
||||
"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",
|
||||
|
||||
@@ -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>
|
||||
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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.android.systemui.lowlightclock.LowLightClockController;
|
||||
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 @@ import dagger.Provides;
|
||||
LogModule.class,
|
||||
MediaProjectionModule.class,
|
||||
PeopleHubModule.class,
|
||||
PeopleModule.class,
|
||||
PluginModule.class,
|
||||
PrivacyModule.class,
|
||||
QsFrameTranslateModule.class,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -19,144 +19,52 @@ package com.android.systemui.people;
|
||||
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 @@ public class PeopleSpaceActivity extends Activity {
|
||||
/** 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.graphics.Rect;
|
||||
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 @@ class PeopleStoryIconFactory implements AutoCloseable {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ import androidx.core.math.MathUtils;
|
||||
|
||||
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 @@ public class PeopleTileViewHelper {
|
||||
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 @@ public class PeopleTileViewHelper {
|
||||
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 @@ public class PeopleTileViewHelper {
|
||||
}
|
||||
|
||||
/** 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 @@ public class PeopleTileViewHelper {
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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?,
|
||||
)
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user