diff options
author | Xin Li <delphij@google.com> | 2021-08-14 06:31:01 +0000 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2021-08-14 06:31:01 +0000 |
commit | e595fd650a23892286e18f2216e017ff5092a74e (patch) | |
tree | fa0429fae76936b9bc212aa5ddd323759bc005fc | |
parent | 0aa6eb462b2449c29d1cae10abe122e3367d9f9e (diff) | |
parent | a364928354280dc412543f6b42319a6df367d213 (diff) | |
download | setupdesign-e595fd650a23892286e18f2216e017ff5092a74e.tar.gz |
Merge sc-dev-plus-aosp-without-vendor@7634622temp_sam_202323961
Merged-In: I1ab0e42437ef8c2acdf7e93863c21197b4731e5f
Change-Id: I38da9f192d57688b1b05bb2bf41ce33f87a60807
135 files changed, 7210 insertions, 262 deletions
@@ -33,6 +33,7 @@ android_library { "androidx.legacy_legacy-support-core-ui", "androidx.appcompat_appcompat", "androidx.recyclerview_recyclerview", + "com.google.android.material_material", "setupcompat", "setupdesign-strings", ], diff --git a/exempting_lint_checks.txt b/exempting_lint_checks.txt new file mode 100644 index 0000000..201fd64 --- /dev/null +++ b/exempting_lint_checks.txt @@ -0,0 +1,49 @@ +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/GlifLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SudGlifLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/SetupWizardLayout.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudSetupWizardLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/FillContentLayout.java: CustomViewStyleable: context.obtainStyledAttributes(attrs, R.styleable.SudFillContentLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudHeaderRecyclerView, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/Illustration.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SudIllustration, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java: CustomViewStyleable: context.obtainStyledAttributes(attrs, R.styleable.SudIllustrationVideoView); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/IntrinsicSizeFrameLayout.java: CustomViewStyleable: attrs, R.styleable.SudIntrinsicSizeFrameLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/NavigationBar.java: ResourceType: @StyleableRes int colorBackground = 2; +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/NavigationBar.java: ResourceType: @StyleableRes int colorForeground = 1; +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/StickyHeaderListView.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudStickyHeaderListView, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/StickyHeaderScrollView.java: ObsoleteSdkInt: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/GlifLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SudGlifLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/SetupWizardLayout.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudSetupWizardLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/FillContentLayout.java: CustomViewStyleable: context.obtainStyledAttributes(attrs, R.styleable.SudFillContentLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudHeaderRecyclerView, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/Illustration.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SudIllustration, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java: CustomViewStyleable: context.obtainStyledAttributes(attrs, R.styleable.SudIllustrationVideoView); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/IntrinsicSizeFrameLayout.java: CustomViewStyleable: attrs, R.styleable.SudIntrinsicSizeFrameLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/NavigationBar.java: ResourceType: @StyleableRes int colorBackground = 2; +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/NavigationBar.java: ResourceType: @StyleableRes int colorForeground = 1; +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/StickyHeaderListView.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudStickyHeaderListView, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/StickyHeaderScrollView.java: ObsoleteSdkInt: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/GlifLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SudGlifLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/SetupWizardLayout.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudSetupWizardLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/items/RecyclerItemAdapter.java: NotifyDataSetChanged: notifyDataSetChanged(); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/FillContentLayout.java: CustomViewStyleable: context.obtainStyledAttributes(attrs, R.styleable.SudFillContentLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudHeaderRecyclerView, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java: NotifyDataSetChanged: notifyDataSetChanged(); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/Illustration.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SudIllustration, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java: CustomViewStyleable: context.obtainStyledAttributes(attrs, R.styleable.SudIllustrationVideoView); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/IntrinsicSizeFrameLayout.java: CustomViewStyleable: attrs, R.styleable.SudIntrinsicSizeFrameLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/NavigationBar.java: ResourceType: @StyleableRes int colorBackground = 2; +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/NavigationBar.java: ResourceType: @StyleableRes int colorForeground = 1; +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/StickyHeaderListView.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudStickyHeaderListView, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/StickyHeaderScrollView.java: ObsoleteSdkInt: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/GlifLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SudGlifLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/SetupWizardLayout.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudSetupWizardLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/items/RecyclerItemAdapter.java: NotifyDataSetChanged: notifyDataSetChanged(); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/FillContentLayout.java: CustomViewStyleable: context.obtainStyledAttributes(attrs, R.styleable.SudFillContentLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudHeaderRecyclerView, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java: NotifyDataSetChanged: notifyDataSetChanged(); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/IconUniformityAppImageView.java: AnnotateVersionCheck: private static final boolean ON_L_PLUS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/Illustration.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SudIllustration, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java: CustomViewStyleable: context.obtainStyledAttributes(attrs, R.styleable.SudIllustrationVideoView); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/IntrinsicSizeFrameLayout.java: CustomViewStyleable: attrs, R.styleable.SudIntrinsicSizeFrameLayout, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/NavigationBar.java: ResourceType: @StyleableRes int colorBackground = 2; +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/NavigationBar.java: ResourceType: @StyleableRes int colorForeground = 1; +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/StickyHeaderListView.java: CustomViewStyleable: .obtainStyledAttributes(attrs, R.styleable.SudStickyHeaderListView, defStyleAttr, 0); +third_party/java_src/android_libs/setupdesign/main/src/com/google/android/setupdesign/view/StickyHeaderScrollView.java: ObsoleteSdkInt: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { diff --git a/grandfathered_lint_checks.txt b/grandfathered_lint_checks.txt deleted file mode 100644 index e69de29..0000000 --- a/grandfathered_lint_checks.txt +++ /dev/null diff --git a/lint-baseline.xml b/lint-baseline.xml index dbe190c..c369cea 100644 --- a/lint-baseline.xml +++ b/lint-baseline.xml @@ -8,7 +8,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> <location file="external/setupdesign/main/res/values/styles.xml" - line="229" + line="291" column="15"/> </issue> @@ -19,7 +19,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> <location file="external/setupdesign/main/res/values/styles.xml" - line="230" + line="292" column="15"/> </issue> @@ -30,7 +30,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> <location file="external/setupdesign/main/res/values/styles.xml" - line="239" + line="302" column="15"/> </issue> @@ -41,7 +41,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> <location file="external/setupdesign/main/res/values/styles.xml" - line="240" + line="303" column="15"/> </issue> @@ -52,7 +52,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> <location file="external/setupdesign/main/res/values/styles.xml" - line="460" + line="523" column="15"/> </issue> @@ -63,7 +63,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> <location file="external/setupdesign/main/res/values/styles.xml" - line="556" + line="619" column="15"/> </issue> @@ -78,4 +78,26 @@ column="9"/> </issue> + <issue + id="NewApi" + message="`@android:interpolator/linear_out_slow_in` requires API level 21 (current min is 14)" + errorLine1=" android:interpolator="@android:interpolator/linear_out_slow_in"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="external/setupdesign/main/res/anim/sud_pre_p_activity_close_enter.xml" + line="21" + column="9"/> + </issue> + + <issue + id="NewApi" + message="`@android:interpolator/fast_out_slow_in` requires API level 21 (current min is 14)" + errorLine1=" android:interpolator="@android:interpolator/fast_out_slow_in"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="external/setupdesign/main/res/anim/sud_pre_p_activity_open_exit.xml" + line="22" + column="9"/> + </issue> + </issues> diff --git a/lottie_loading_layout/Android.bp b/lottie_loading_layout/Android.bp new file mode 100644 index 0000000..6d41812 --- /dev/null +++ b/lottie_loading_layout/Android.bp @@ -0,0 +1,31 @@ +// +// Build the setup design - lottie_loading_layout. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "external_setupdesign_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["external_setupdesign_license"], +} + +android_library { + name: "setupdesign-lottie-loading-layout", + manifest: "AndroidManifest.xml", + static_libs: [ + "androidx.annotation_annotation", + "lottie", + "setupcompat", + "setupdesign", + ], + srcs: [ + "src/**/*.java", + ], + resource_dirs: [ + "res", + ], + min_sdk_version: "16", + sdk_version: "current" +} diff --git a/lottie_loading_layout/AndroidManifest.xml b/lottie_loading_layout/AndroidManifest.xml new file mode 100644 index 0000000..3cf1948 --- /dev/null +++ b/lottie_loading_layout/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.setupdesign.lottieloadinglayout"> + + <uses-sdk + android:minSdkVersion="14" + android:targetSdkVersion="30" /> + +</manifest> diff --git a/lottie_loading_layout/res/layout-land-v31/sud_glif_loading_template_content.xml b/lottie_loading_layout/res/layout-land-v31/sud_glif_loading_template_content.xml new file mode 100644 index 0000000..65e20a9 --- /dev/null +++ b/lottie_loading_layout/res/layout-land-v31/sud_glif_loading_template_content.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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:tools="http://schemas.android.com/tools" + android:id="@+id/sud_layout_template_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/sud_landscape_header_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_header_area_weight" + android:orientation="vertical"> + + <ViewStub + android:id="@+id/sud_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <com.google.android.setupdesign.view.BottomScrollView + android:id="@+id/sud_header_scroll_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true" + android:scrollIndicators="?attr/sudScrollIndicators"> + + <include layout="@layout/sud_glif_header" /> + + </com.google.android.setupdesign.view.BottomScrollView> + + </LinearLayout> + + <LinearLayout + android:id="@+id/sud_landscape_content_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_content_area_weight" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/sud_layout_loading_content" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ViewStub + android:id="@+id/sud_loading_layout_lottie_stub" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:inflatedId="@+id/sud_layout_lottie_illustration" + android:layout="@layout/sud_loading_lottie_layout" /> + + <ViewStub + android:id="@+id/sud_loading_layout_illustration_stub" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:inflatedId="@+id/sud_layout_progress_illustration" + android:layout="@layout/sud_loading_illustration_layout" /> + + <FrameLayout + android:id="@+id/sud_layout_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" /> + + </FrameLayout> + + </LinearLayout> + + </LinearLayout> + + <ViewStub + android:id="@+id/suc_layout_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout> diff --git a/lottie_loading_layout/res/layout-v31/sud_glif_loading_template_content.xml b/lottie_loading_layout/res/layout-v31/sud_glif_loading_template_content.xml new file mode 100644 index 0000000..8415324 --- /dev/null +++ b/lottie_loading_layout/res/layout-v31/sud_glif_loading_template_content.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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:tools="http://schemas.android.com/tools" + android:id="@+id/sud_layout_template_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <ViewStub + android:id="@+id/sud_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <!-- Ignore UnusedAttribute: scrollIndicators is new in M. Default to no indicators in older + versions. --> + <com.google.android.setupdesign.view.BottomScrollView + android:id="@+id/sud_header_scroll_view" + android:layout_width="match_parent" + android:layout_height="?attr/sudLoadingHeaderHeight" + android:fillViewport="true" + android:scrollIndicators="?attr/sudScrollIndicators" + tools:ignore="UnusedAttribute"> + + <include layout="@layout/sud_glif_header" /> + + </com.google.android.setupdesign.view.BottomScrollView> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <ViewStub + android:id="@+id/sud_loading_layout_lottie_stub" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:inflatedId="@+id/sud_layout_lottie_illustration" + android:layout="@layout/sud_loading_lottie_layout" /> + + <ViewStub + android:id="@+id/sud_loading_layout_illustration_stub" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:inflatedId="@+id/sud_layout_progress_illustration" + android:layout="@layout/sud_loading_illustration_layout" /> + + <FrameLayout + android:id="@+id/sud_layout_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" /> + + </LinearLayout> + + </LinearLayout> + + </LinearLayout> + + <ViewStub + android:id="@+id/suc_layout_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ No newline at end of file diff --git a/lottie_loading_layout/res/layout-v31/sud_loading_illustration_layout.xml b/lottie_loading_layout/res/layout-v31/sud_loading_illustration_layout.xml new file mode 100644 index 0000000..0f60d02 --- /dev/null +++ b/lottie_loading_layout/res/layout-v31/sud_loading_illustration_layout.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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" + style="@style/SudLoadingContentFrame" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <ProgressBar + android:id="@+id/sud_progress_bar" + android:visibility="gone" + style="@style/SudFourColorIndeterminateProgressBar" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <com.google.android.setupdesign.view.IllustrationVideoView + android:id="@+id/sud_progress_illustration" + android:visibility="gone" + style="@style/SudContentIllustration" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ No newline at end of file diff --git a/lottie_loading_layout/res/layout-v31/sud_loading_lottie_layout.xml b/lottie_loading_layout/res/layout-v31/sud_loading_lottie_layout.xml new file mode 100644 index 0000000..a38b791 --- /dev/null +++ b/lottie_loading_layout/res/layout-v31/sud_loading_lottie_layout.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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:app="http://schemas.android.com/apk/res-auto" + style="@style/SudLoadingContentFrame" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.airbnb.lottie.LottieAnimationView + android:id="@+id/sud_lottie_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:importantForAccessibility="no" + style="@style/SudContentIllustration" + app:lottie_autoPlay="false" + app:lottie_loop="true" /> + +</LinearLayout> diff --git a/lottie_loading_layout/res/layout/sud_glif_loading_template_card.xml b/lottie_loading_layout/res/layout/sud_glif_loading_template_card.xml new file mode 100644 index 0000000..2db095c --- /dev/null +++ b/lottie_loading_layout/res/layout/sud_glif_loading_template_card.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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" + android:id="@+id/suc_layout_status" + style="@style/SudGlifCardBackground" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + android:gravity="center_horizontal" + android:orientation="vertical"> + + <View + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1" + android:visibility="invisible" /> + + <com.google.android.setupdesign.view.IntrinsicSizeFrameLayout + style="@style/SudGlifCardContainer" + android:layout_width="@dimen/sud_glif_card_width" + android:layout_height="wrap_content" + android:height="@dimen/sud_glif_card_height"> + + <include layout="@layout/sud_glif_loading_template_content" /> + + </com.google.android.setupdesign.view.IntrinsicSizeFrameLayout> + + <View + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1" + android:visibility="invisible" /> + +</LinearLayout> diff --git a/lottie_loading_layout/res/layout/sud_glif_loading_template_compat.xml b/lottie_loading_layout/res/layout/sud_glif_loading_template_compat.xml new file mode 100644 index 0000000..86003b9 --- /dev/null +++ b/lottie_loading_layout/res/layout/sud_glif_loading_template_compat.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<com.google.android.setupcompat.view.StatusBarBackgroundLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/suc_layout_status" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include layout="@layout/sud_glif_loading_template_content" /> + +</com.google.android.setupcompat.view.StatusBarBackgroundLayout>
\ No newline at end of file diff --git a/lottie_loading_layout/res/layout/sud_glif_loading_template_content.xml b/lottie_loading_layout/res/layout/sud_glif_loading_template_content.xml new file mode 100644 index 0000000..e70edc1 --- /dev/null +++ b/lottie_loading_layout/res/layout/sud_glif_loading_template_content.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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:tools="http://schemas.android.com/tools" + android:id="@+id/sud_layout_template_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <ViewStub + android:id="@+id/sud_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <!-- Ignore UnusedAttribute: scrollIndicators is new in M. Default to no indicators in older + versions. --> + <com.google.android.setupdesign.view.BottomScrollView + android:id="@+id/sud_scroll_view" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:fillViewport="true" + android:scrollIndicators="?attr/sudScrollIndicators" + tools:ignore="UnusedAttribute"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <include layout="@layout/sud_glif_header" /> + + <ViewStub + android:id="@+id/sud_loading_layout_illustration_stub" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:inflatedId="@+id/sud_layout_progress_illustration" + android:layout="@layout/sud_loading_illustration_layout" /> + + <ViewStub + android:id="@+id/sud_loading_layout_lottie_stub" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:inflatedId="@+id/sud_layout_lottie_illustration" + android:layout="@layout/sud_loading_lottie_layout" /> + + <FrameLayout + android:id="@+id/sud_layout_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" /> + + </LinearLayout> + + </com.google.android.setupdesign.view.BottomScrollView> + + <ViewStub + android:id="@+id/suc_layout_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout> diff --git a/lottie_loading_layout/res/layout/sud_loading_illustration_layout.xml b/lottie_loading_layout/res/layout/sud_loading_illustration_layout.xml new file mode 100644 index 0000000..772598e --- /dev/null +++ b/lottie_loading_layout/res/layout/sud_loading_illustration_layout.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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" + style="@style/SudContentFrame" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <com.google.android.setupdesign.view.FillContentLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ProgressBar + android:id="@+id/sud_progress_bar" + android:visibility="gone" + style="@style/SudFourColorIndeterminateProgressBar" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <com.google.android.setupdesign.view.IllustrationVideoView + android:id="@+id/sud_progress_illustration" + android:visibility="gone" + style="@style/SudContentIllustration" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </com.google.android.setupdesign.view.FillContentLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/lottie_loading_layout/res/layout/sud_loading_lottie_layout.xml b/lottie_loading_layout/res/layout/sud_loading_lottie_layout.xml new file mode 100644 index 0000000..6e00f98 --- /dev/null +++ b/lottie_loading_layout/res/layout/sud_loading_lottie_layout.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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:app="http://schemas.android.com/apk/res-auto" + style="@style/SudContentFrame" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.google.android.setupdesign.view.FillContentLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.airbnb.lottie.LottieAnimationView + android:id="@+id/sud_lottie_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:importantForAccessibility="no" + style="@style/SudContentIllustration" + app:lottie_autoPlay="false" + app:lottie_loop="true" /> + + </com.google.android.setupdesign.view.FillContentLayout> + +</LinearLayout> diff --git a/lottie_loading_layout/res/values-sw600dp/layouts.xml b/lottie_loading_layout/res/values-sw600dp/layouts.xml new file mode 100644 index 0000000..9fe5404 --- /dev/null +++ b/lottie_loading_layout/res/values-sw600dp/layouts.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources xmlns:tools="http://schemas.android.com/tools"> + <item name="sud_glif_loading_template" type="layout" tools:ignore="UnusedResources">@layout/sud_glif_loading_template_card</item> +</resources> diff --git a/lottie_loading_layout/res/values/attrs.xml b/lottie_loading_layout/res/values/attrs.xml new file mode 100644 index 0000000..57fcac7 --- /dev/null +++ b/lottie_loading_layout/res/values/attrs.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources> + <declare-styleable name="SudGlifLoadingLayout"> + <attr name="sudIllustrationType" format="string" /> + <attr name="sudLottieRes" format="reference" /> + <attr name="sudUsePartnerHeavyTheme" /> + </declare-styleable> +</resources>
\ No newline at end of file diff --git a/lottie_loading_layout/res/values/layouts.xml b/lottie_loading_layout/res/values/layouts.xml new file mode 100644 index 0000000..483f09d --- /dev/null +++ b/lottie_loading_layout/res/values/layouts.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources xmlns:tools="http://schemas.android.com/tools"> + <item name="sud_glif_loading_template" type="layout" tools:ignore="UnusedResources">@layout/sud_glif_loading_template_compat</item> +</resources>
\ No newline at end of file diff --git a/lottie_loading_layout/src/com/google/android/setupdesign/GlifLoadingLayout.java b/lottie_loading_layout/src/com/google/android/setupdesign/GlifLoadingLayout.java new file mode 100644 index 0000000..d239e3d --- /dev/null +++ b/lottie_loading_layout/src/com/google/android/setupdesign/GlifLoadingLayout.java @@ -0,0 +1,864 @@ +/* + * Copyright (C) 2021 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.google.android.setupdesign; + +import static com.google.android.setupcompat.partnerconfig.Util.isNightMode; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings.Global; +import android.provider.Settings.SettingNotFoundException; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewStub; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RawRes; +import androidx.annotation.StringDef; +import androidx.annotation.VisibleForTesting; +import com.airbnb.lottie.LottieAnimationView; +import com.airbnb.lottie.LottieDrawable; +import com.airbnb.lottie.LottieProperty; +import com.airbnb.lottie.SimpleColorFilter; +import com.airbnb.lottie.model.KeyPath; +import com.airbnb.lottie.value.LottieValueCallback; +import com.airbnb.lottie.value.SimpleLottieValueCallback; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupcompat.partnerconfig.ResourceEntry; +import com.google.android.setupcompat.template.FooterBarMixin; +import com.google.android.setupcompat.util.BuildCompatUtils; +import com.google.android.setupdesign.lottieloadinglayout.R; +import com.google.android.setupdesign.view.IllustrationVideoView; +import java.io.InputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A GLIF themed layout with a {@link com.airbnb.lottie.LottieAnimationView} to showing lottie + * illustration and a substitute {@link com.google.android.setupdesign.view.IllustrationVideoView} + * to showing mp4 illustration. {@code app:sudIllustrationType} can also be used to specify one of + * the set including "default", "account", "connection", "update", and "final_hold". {@code + * app:sudLottieRes} can assign the json file of Lottie resource. + */ +public class GlifLoadingLayout extends GlifLayout { + + private static final String TAG = "GlifLoadingLayout"; + View inflatedView; + + @VisibleForTesting @IllustrationType String illustrationType = IllustrationType.DEFAULT; + @VisibleForTesting LottieAnimationConfig animationConfig = LottieAnimationConfig.CONFIG_DEFAULT; + + @VisibleForTesting @RawRes int customLottieResource = 0; + + @VisibleForTesting Map<KeyPath, SimpleColorFilter> customizationMap = new HashMap<>(); + + @VisibleForTesting + public List<LottieAnimationFinishListener> animationFinishListeners = new ArrayList<>(); + + public GlifLoadingLayout(Context context) { + this(context, 0, 0); + } + + public GlifLoadingLayout(Context context, int template) { + this(context, template, 0); + } + + public GlifLoadingLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(null, R.attr.sudLayoutTheme); + } + + public GlifLoadingLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, R.attr.sudLayoutTheme); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public GlifLoadingLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + registerMixin(FooterBarMixin.class, new LoadingFooterBarMixin(this, attrs, defStyleAttr)); + + TypedArray a = + getContext() + .obtainStyledAttributes(attrs, R.styleable.SudGlifLoadingLayout, defStyleAttr, 0); + customLottieResource = a.getResourceId(R.styleable.SudGlifLoadingLayout_sudLottieRes, 0); + String illustrationType = a.getString(R.styleable.SudGlifLoadingLayout_sudIllustrationType); + boolean usePartnerHeavyTheme = + a.getBoolean(R.styleable.SudGlifLoadingLayout_sudUsePartnerHeavyTheme, false); + a.recycle(); + + if (customLottieResource != 0) { + inflateLottieView(); + ViewGroup container = findContainer(0); + container.setVisibility(View.VISIBLE); + } else { + if (illustrationType != null) { + setIllustrationType(illustrationType); + } + + if (BuildCompatUtils.isAtLeastS()) { + inflateLottieView(); + } else { + inflateIllustrationStub(); + } + } + + boolean applyPartnerHeavyThemeResource = shouldApplyPartnerResource() && usePartnerHeavyTheme; + if (applyPartnerHeavyThemeResource) { + View view = findManagedViewById(R.id.sud_layout_loading_content); + if (view != null) { + applyPartnerCustomizationContentPaddingTopStyle(view); + } + } + + updateHeaderHeight(); + updateLandscapeMiddleHorizontalSpacing(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + if (inflatedView instanceof LinearLayout) { + updateContentPadding((LinearLayout) inflatedView); + } + } + + public void setIllustrationType(@IllustrationType String type) { + if (customLottieResource != 0) { + throw new IllegalStateException( + "custom illustration already applied, should not set illustration."); + } + + if (!illustrationType.equals(type)) { + illustrationType = type; + customizationMap.clear(); + } + + switch (type) { + case IllustrationType.ACCOUNT: + animationConfig = LottieAnimationConfig.CONFIG_ACCOUNT; + break; + + case IllustrationType.CONNECTION: + animationConfig = LottieAnimationConfig.CONFIG_CONNECTION; + break; + + case IllustrationType.UPDATE: + animationConfig = LottieAnimationConfig.CONFIG_UPDATE; + break; + + case IllustrationType.FINAL_HOLD: + animationConfig = LottieAnimationConfig.CONFIG_FINAL_HOLD; + break; + + default: + animationConfig = LottieAnimationConfig.CONFIG_DEFAULT; + break; + } + + updateAnimationView(); + } + + // TODO: [GlifLoadingLayout] Should add testcase. LottieAnimationView was auto + // generated not able to mock. So we have no idea how to detected is the api pass to + // LottiAnimationView correctly. + public boolean setAnimation(InputStream inputStream, String keyCache) { + LottieAnimationView lottieAnimationView = findLottieAnimationView(); + if (lottieAnimationView != null) { + lottieAnimationView.setAnimation(inputStream, keyCache); + return true; + } else { + return false; + } + } + + public boolean setAnimation(String assetName) { + LottieAnimationView lottieAnimationView = findLottieAnimationView(); + if (lottieAnimationView != null) { + lottieAnimationView.setAnimation(assetName); + return true; + } else { + return false; + } + } + + public boolean setAnimation(@RawRes int rawRes) { + LottieAnimationView lottieAnimationView = findLottieAnimationView(); + if (lottieAnimationView != null) { + lottieAnimationView.setAnimation(rawRes); + return true; + } else { + return false; + } + } + + private void updateAnimationView() { + if (BuildCompatUtils.isAtLeastS()) { + setLottieResource(); + } else { + setIllustrationResource(); + } + } + + /** + * Call this when your activity is done and should be closed. The activity will be finished while + * animation finished. + */ + public void finish(@NonNull Activity activity) { + if (activity == null) { + throw new NullPointerException("activity should not be null"); + } + registerAnimationFinishRunnable(activity::finish, /* allowFinishWithMaximumDuration= */ true); + } + + /** + * Launch a new activity after the animation finished. + * + * @param activity The activity which is GlifLoadingLayout attached to. + * @param intent The intent to start. + * @param options Additional options for how the Activity should be started. See {@link + * android.content.Context#startActivity(Intent, Bundle)} for more details. + * @param finish Finish the activity after startActivity + * @see Activity#startActivity(Intent) + * @see Activity#startActivityForResult + */ + public void startActivity( + @NonNull Activity activity, + @NonNull Intent intent, + @Nullable Bundle options, + boolean finish) { + if (activity == null) { + throw new NullPointerException("activity should not be null"); + } + + if (intent == null) { + throw new NullPointerException("intent should not be null"); + } + + registerAnimationFinishRunnable( + () -> { + if (options == null || Build.VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN) { + activity.startActivity(intent); + } else { + activity.startActivity(intent, options); + } + + if (finish) { + activity.finish(); + } + }, + /* allowFinishWithMaximumDuration= */ true); + } + + /** + * Waiting for the animation finished and launch an activity for which you would like a result + * when it finished. + * + * @param activity The activity which the GlifLoadingLayout attached to. + * @param intent The intent to start. + * @param requestCode If >= 0, this code will be returned in onActivityResult() when the activity + * exits. + * @param options Additional options for how the Activity should be started. + * @param finish Finish the activity after startActivityForResult. The onActivityResult might not + * be called because the activity already finished. + * <p>See {@link android.content.Context#startActivity(Intent, Bundle)} + * Context.startActivity(Intent, Bundle)} for more details. + */ + public void startActivityForResult( + @NonNull Activity activity, + @NonNull Intent intent, + int requestCode, + @Nullable Bundle options, + boolean finish) { + if (activity == null) { + throw new NullPointerException("activity should not be null"); + } + + if (intent == null) { + throw new NullPointerException("intent should not be null"); + } + + registerAnimationFinishRunnable( + () -> { + if (options == null || Build.VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN) { + activity.startActivityForResult(intent, requestCode); + } else { + activity.startActivityForResult(intent, requestCode, options); + } + + if (finish) { + activity.finish(); + } + }, + /* allowFinishWithMaximumDuration= */ true); + } + + private void updateHeaderHeight() { + View headerView = findManagedViewById(R.id.sud_header_scroll_view); + if (headerView != null + && PartnerConfigHelper.get(getContext()) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LOADING_LAYOUT_HEADER_HEIGHT)) { + float configHeaderHeight = + PartnerConfigHelper.get(getContext()) + .getDimension(getContext(), PartnerConfig.CONFIG_LOADING_LAYOUT_HEADER_HEIGHT); + headerView.getLayoutParams().height = (int) configHeaderHeight; + } + } + + private void updateContentPadding(LinearLayout linearLayout) { + int paddingTop = linearLayout.getPaddingTop(); + int paddingLeft = linearLayout.getPaddingLeft(); + int paddingRight = linearLayout.getPaddingRight(); + int paddingBottom = linearLayout.getPaddingBottom(); + + if (PartnerConfigHelper.get(getContext()) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LOADING_LAYOUT_PADDING_TOP)) { + float configPaddingTop = + PartnerConfigHelper.get(getContext()) + .getDimension(getContext(), PartnerConfig.CONFIG_LOADING_LAYOUT_PADDING_TOP); + if (configPaddingTop >= 0) { + paddingTop = (int) configPaddingTop; + } + } + + if (PartnerConfigHelper.get(getContext()) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LOADING_LAYOUT_PADDING_START)) { + float configPaddingLeft = + PartnerConfigHelper.get(getContext()) + .getDimension(getContext(), PartnerConfig.CONFIG_LOADING_LAYOUT_PADDING_START); + if (configPaddingLeft >= 0) { + paddingLeft = (int) configPaddingLeft; + } + } + + if (PartnerConfigHelper.get(getContext()) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LOADING_LAYOUT_PADDING_END)) { + float configPaddingRight = + PartnerConfigHelper.get(getContext()) + .getDimension(getContext(), PartnerConfig.CONFIG_LOADING_LAYOUT_PADDING_END); + if (configPaddingRight >= 0) { + paddingRight = (int) configPaddingRight; + } + } + + if (PartnerConfigHelper.get(getContext()) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LOADING_LAYOUT_PADDING_BOTTOM)) { + float configPaddingBottom = + PartnerConfigHelper.get(getContext()) + .getDimension(getContext(), PartnerConfig.CONFIG_LOADING_LAYOUT_PADDING_BOTTOM); + if (configPaddingBottom >= 0) { + FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class); + if (footerBarMixin == null || footerBarMixin.getButtonContainer() == null) { + paddingBottom = (int) configPaddingBottom; + } else { + paddingBottom = + (int) configPaddingBottom + - (int) + Math.min( + configPaddingBottom, + getButtonContainerHeight(footerBarMixin.getButtonContainer())); + } + } + } + + linearLayout.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); + } + + private static final int getButtonContainerHeight(View view) { + view.measure( + MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(), MeasureSpec.EXACTLY)); + return view.getMeasuredHeight(); + } + + private void inflateLottieView() { + final View lottieLayout = peekLottieLayout(); + if (lottieLayout == null) { + ViewStub viewStub = findManagedViewById(R.id.sud_loading_layout_lottie_stub); + if (viewStub != null) { + inflatedView = viewStub.inflate(); + if (inflatedView instanceof LinearLayout) { + updateContentPadding((LinearLayout) inflatedView); + } + setLottieResource(); + } + } + } + + private void inflateIllustrationStub() { + final View progressLayout = peekProgressIllustrationLayout(); + if (progressLayout == null) { + ViewStub viewStub = findManagedViewById(R.id.sud_loading_layout_illustration_stub); + if (viewStub != null) { + inflatedView = viewStub.inflate(); + if (inflatedView instanceof LinearLayout) { + updateContentPadding((LinearLayout) inflatedView); + } + setIllustrationResource(); + } + } + } + + private void setLottieResource() { + LottieAnimationView lottieView = findViewById(R.id.sud_lottie_view); + if (lottieView == null) { + Log.w(TAG, "Lottie view not found, skip set resource. Wait for layout inflated."); + return; + } + if (customLottieResource != 0) { + InputStream inputRaw = getResources().openRawResource(customLottieResource); + lottieView.setAnimation(inputRaw, null); + lottieView.playAnimation(); + } else { + PartnerConfigHelper partnerConfigHelper = PartnerConfigHelper.get(getContext()); + ResourceEntry resourceEntry = + partnerConfigHelper.getIllustrationResourceEntry( + getContext(), animationConfig.getLottieConfig()); + + if (resourceEntry != null) { + InputStream inputRaw = + resourceEntry.getResources().openRawResource(resourceEntry.getResourceId()); + lottieView.setAnimation(inputRaw, null); + lottieView.playAnimation(); + setLottieLayoutVisibility(View.VISIBLE); + setIllustrationLayoutVisibility(View.GONE); + applyThemeCustomization(); + } else { + setLottieLayoutVisibility(View.GONE); + setIllustrationLayoutVisibility(View.VISIBLE); + inflateIllustrationStub(); + } + } + } + + private void setIllustrationLayoutVisibility(int visibility) { + View illustrationLayout = findViewById(R.id.sud_layout_progress_illustration); + if (illustrationLayout != null) { + illustrationLayout.setVisibility(visibility); + } + } + + private void setLottieLayoutVisibility(int visibility) { + View lottieLayout = findViewById(R.id.sud_layout_lottie_illustration); + if (lottieLayout != null) { + lottieLayout.setVisibility(visibility); + } + } + + @VisibleForTesting + boolean isLottieLayoutVisible() { + View lottieLayout = findViewById(R.id.sud_layout_lottie_illustration); + return lottieLayout != null && lottieLayout.getVisibility() == View.VISIBLE; + } + + private void setIllustrationResource() { + View illustrationLayout = findViewById(R.id.sud_layout_progress_illustration); + if (illustrationLayout == null) { + Log.i(TAG, "Illustration stub not inflated, skip set resource"); + return; + } + + IllustrationVideoView illustrationVideoView = + findManagedViewById(R.id.sud_progress_illustration); + ProgressBar progressBar = findManagedViewById(R.id.sud_progress_bar); + + PartnerConfigHelper partnerConfigHelper = PartnerConfigHelper.get(getContext()); + ResourceEntry resourceEntry = + partnerConfigHelper.getIllustrationResourceEntry( + getContext(), animationConfig.getIllustrationConfig()); + + if (resourceEntry != null) { + progressBar.setVisibility(GONE); + illustrationVideoView.setVisibility(VISIBLE); + illustrationVideoView.setVideoResourceEntry(resourceEntry); + } else { + progressBar.setVisibility(VISIBLE); + illustrationVideoView.setVisibility(GONE); + } + } + + private LottieAnimationView findLottieAnimationView() { + return findViewById(R.id.sud_lottie_view); + } + + private IllustrationVideoView findIllustrationVideoView() { + return findManagedViewById(R.id.sud_progress_illustration); + } + + public void playAnimation() { + LottieAnimationView lottieAnimationView = findLottieAnimationView(); + if (lottieAnimationView != null) { + lottieAnimationView.setRepeatCount(LottieDrawable.INFINITE); + lottieAnimationView.playAnimation(); + } + } + + /** Returns whether the layout is waiting for animation finish or not. */ + public boolean isFinishing() { + LottieAnimationView lottieAnimationView = findLottieAnimationView(); + if (lottieAnimationView != null) { + return !animationFinishListeners.isEmpty() && lottieAnimationView.getRepeatCount() == 0; + } else { + return false; + } + } + + @AnimationType + public int getAnimationType() { + if (findLottieAnimationView() != null && isLottieLayoutVisible()) { + return AnimationType.LOTTIE; + } else if (findIllustrationVideoView() != null) { + return AnimationType.ILLUSTRATION; + } else { + return AnimationType.PROGRESS_BAR; + } + } + + // TODO: Should add testcase with mocked LottieAnimationView. + /** Add an animator listener to {@link LottieAnimationView}. */ + public void addAnimatorListener(Animator.AnimatorListener listener) { + LottieAnimationView animationView = findLottieAnimationView(); + if (animationView != null) { + animationView.addAnimatorListener(listener); + } + } + + /** Remove the listener from {@link LottieAnimationView}. */ + public void removeAnimatorListener(AnimatorListener listener) { + LottieAnimationView animationView = findLottieAnimationView(); + if (animationView != null) { + animationView.removeAnimatorListener(listener); + } + } + + /** Remove all {@link AnimatorListener} from {@link LottieAnimationView}. */ + public void removeAllAnimatorListener() { + LottieAnimationView animationView = findLottieAnimationView(); + if (animationView != null) { + animationView.removeAllAnimatorListeners(); + } + } + + /** Add a value callback with property {@link LottieProperty.COLOR_FILTER}. */ + public void addColorCallback(KeyPath keyPath, LottieValueCallback<ColorFilter> callback) { + LottieAnimationView animationView = findLottieAnimationView(); + if (animationView != null) { + animationView.addValueCallback(keyPath, LottieProperty.COLOR_FILTER, callback); + } + } + + /** Add a simple value callback with property {@link LottieProperty.COLOR_FILTER}. */ + public void addColorCallback(KeyPath keyPath, SimpleLottieValueCallback<ColorFilter> callback) { + LottieAnimationView animationView = findLottieAnimationView(); + if (animationView != null) { + animationView.addValueCallback(keyPath, LottieProperty.COLOR_FILTER, callback); + } + } + + @VisibleForTesting + protected void loadCustomization() { + if (customizationMap.isEmpty()) { + PartnerConfigHelper helper = PartnerConfigHelper.get(getContext()); + List<String> lists = + helper.getStringArray( + getContext(), + isNightMode(getResources().getConfiguration()) + ? animationConfig.getDarkThemeCustomization() + : animationConfig.getLightThemeCustomization()); + for (String item : lists) { + String[] splitItem = item.split(":"); + if (splitItem.length == 2) { + customizationMap.put( + new KeyPath(splitItem[0]), new SimpleColorFilter(Color.parseColor(splitItem[1]))); + } else { + Log.w(TAG, "incorrect format customization, value=" + item); + } + } + } + } + + @VisibleForTesting + protected void applyThemeCustomization() { + LottieAnimationView animationView = findLottieAnimationView(); + if (animationView != null) { + loadCustomization(); + for (KeyPath keyPath : customizationMap.keySet()) { + animationView.addValueCallback( + keyPath, + LottieProperty.COLOR_FILTER, + new LottieValueCallback<>(customizationMap.get(keyPath))); + } + } + } + + @Nullable + private View peekLottieLayout() { + return findViewById(R.id.sud_layout_lottie_illustration); + } + + @Nullable + private View peekProgressIllustrationLayout() { + return findViewById(R.id.sud_layout_progress_illustration); + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.sud_glif_loading_template; + } + return inflateTemplate(inflater, R.style.SudThemeGlif_Light, template); + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.sud_layout_content; + } + return super.findContainer(containerId); + } + + /** The progress config used to maps to different animation */ + public enum LottieAnimationConfig { + CONFIG_DEFAULT( + PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_DEFAULT, + PartnerConfig.CONFIG_LOADING_LOTTIE_DEFAULT, + PartnerConfig.CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_DEFAULT, + PartnerConfig.CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_DEFAULT), + CONFIG_ACCOUNT( + PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_ACCOUNT, + PartnerConfig.CONFIG_LOADING_LOTTIE_ACCOUNT, + PartnerConfig.CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_ACCOUNT, + PartnerConfig.CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_ACCOUNT), + CONFIG_CONNECTION( + PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_CONNECTION, + PartnerConfig.CONFIG_LOADING_LOTTIE_CONNECTION, + PartnerConfig.CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_CONNECTION, + PartnerConfig.CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_CONNECTION), + CONFIG_UPDATE( + PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_UPDATE, + PartnerConfig.CONFIG_LOADING_LOTTIE_UPDATE, + PartnerConfig.CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_UPDATE, + PartnerConfig.CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_UPDATE), + CONFIG_FINAL_HOLD( + PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_FINAL_HOLD, + PartnerConfig.CONFIG_LOADING_LOTTIE_FINAL_HOLD, + PartnerConfig.CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_FINAL_HOLD, + PartnerConfig.CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_FINAL_HOLD); + + private final PartnerConfig illustrationConfig; + private final PartnerConfig lottieConfig; + private final PartnerConfig lightThemeCustomization; + private final PartnerConfig darkThemeCustomization; + + LottieAnimationConfig( + PartnerConfig illustrationConfig, + PartnerConfig lottieConfig, + PartnerConfig lightThemeCustomization, + PartnerConfig darkThemeCustomization) { + if (illustrationConfig.getResourceType() != ResourceType.ILLUSTRATION + || lottieConfig.getResourceType() != ResourceType.ILLUSTRATION) { + throw new IllegalArgumentException( + "Illustration progress only allow illustration resource"); + } + this.illustrationConfig = illustrationConfig; + this.lottieConfig = lottieConfig; + this.lightThemeCustomization = lightThemeCustomization; + this.darkThemeCustomization = darkThemeCustomization; + } + + PartnerConfig getIllustrationConfig() { + return illustrationConfig; + } + + PartnerConfig getLottieConfig() { + return lottieConfig; + } + + PartnerConfig getLightThemeCustomization() { + return lightThemeCustomization; + } + + PartnerConfig getDarkThemeCustomization() { + return darkThemeCustomization; + } + } + + /** + * Register the {@link Runnable} as a callback class that will be perform when animation finished. + */ + public void registerAnimationFinishRunnable(Runnable runnable) { + registerAnimationFinishRunnable(runnable, /* allowFinishWithMaximumDuration= */ false); + } + + /** + * Register the {@link Runnable} as a callback class that will be perform when animation finished. + * {@code allowFinishWithMaximumDuration} to allow the animation finish advanced by {@link + * PartnerConfig#CONFIG_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS} config. The {@code runnable} + * will be performed if the Lottie animation finish played and the duration of Lottie animation + * less than @link PartnerConfig#CONFIG_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS} config. + */ + public void registerAnimationFinishRunnable( + Runnable runnable, boolean allowFinishWithMaximumDuration) { + if (allowFinishWithMaximumDuration) { + int delayMs = + PartnerConfigHelper.get(getContext()) + .getInteger( + getContext(), PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS, 0); + animationFinishListeners.add(new LottieAnimationFinishListener(this, runnable, delayMs)); + } else { + animationFinishListeners.add( + new LottieAnimationFinishListener(this, runnable, /* finishWithMinimumDuration= */ 0L)); + } + } + + /** The listener that to indicate the playing status for lottie animation. */ + @VisibleForTesting + public static class LottieAnimationFinishListener { + + private final Handler handler; + private final Runnable runnable; + private final GlifLoadingLayout glifLoadingLayout; + private final LottieAnimationView lottieAnimationView; + + @VisibleForTesting + AnimatorListener animatorListener = + new AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + // Do nothing. + } + + @Override + public void onAnimationEnd(Animator animation) { + onAnimationFinished(); + } + + @Override + public void onAnimationCancel(Animator animation) { + // Do nothing. + } + + @Override + public void onAnimationRepeat(Animator animation) { + // Do nothing. + } + }; + + @VisibleForTesting + LottieAnimationFinishListener( + GlifLoadingLayout glifLoadingLayout, Runnable runnable, long finishWithMinimumDuration) { + if (runnable == null) { + throw new NullPointerException("Runnable can not be null"); + } + this.glifLoadingLayout = glifLoadingLayout; + this.runnable = runnable; + this.handler = new Handler(Looper.getMainLooper()); + this.lottieAnimationView = glifLoadingLayout.findLottieAnimationView(); + + if (glifLoadingLayout.isLottieLayoutVisible() && !isZeroAnimatorDurationScale()) { + lottieAnimationView.setRepeatCount(0); + lottieAnimationView.addAnimatorListener(animatorListener); + if (finishWithMinimumDuration > 0) { + handler.postDelayed(this::onAnimationFinished, finishWithMinimumDuration); + } + } else { + onAnimationFinished(); + } + } + + @VisibleForTesting + boolean isZeroAnimatorDurationScale() { + try { + if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + return Global.getFloat( + glifLoadingLayout.getContext().getContentResolver(), Global.ANIMATOR_DURATION_SCALE) + == 0f; + } else { + return false; + } + + } catch (SettingNotFoundException e) { + return false; + } + } + + @VisibleForTesting + public void onAnimationFinished() { + handler.removeCallbacks(runnable); + runnable.run(); + if (lottieAnimationView != null) { + lottieAnimationView.removeAnimatorListener(animatorListener); + } + glifLoadingLayout.animationFinishListeners.remove(this); + } + } + + /** Annotates the state for the illustration. */ + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + IllustrationType.ACCOUNT, + IllustrationType.CONNECTION, + IllustrationType.DEFAULT, + IllustrationType.UPDATE, + IllustrationType.FINAL_HOLD + }) + public @interface IllustrationType { + String DEFAULT = "default"; + String ACCOUNT = "account"; + String CONNECTION = "connection"; + String UPDATE = "update"; + String FINAL_HOLD = "final_hold"; + } + + /** Annotates the type for the illustration. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({AnimationType.LOTTIE, AnimationType.ILLUSTRATION, AnimationType.PROGRESS_BAR}) + public @interface AnimationType { + int LOTTIE = 1; + int ILLUSTRATION = 2; + int PROGRESS_BAR = 3; + } +} diff --git a/lottie_loading_layout/src/com/google/android/setupdesign/LoadingFooterBarMixin.java b/lottie_loading_layout/src/com/google/android/setupdesign/LoadingFooterBarMixin.java new file mode 100644 index 0000000..fcc2fff --- /dev/null +++ b/lottie_loading_layout/src/com/google/android/setupdesign/LoadingFooterBarMixin.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 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.google.android.setupdesign; + +import android.util.AttributeSet; +import android.widget.LinearLayout; +import androidx.annotation.Nullable; +import com.google.android.setupcompat.internal.TemplateLayout; +import com.google.android.setupcompat.template.FooterBarMixin; + +/** A {@link Mixin} to get the container of footer bar for usage. */ +public class LoadingFooterBarMixin extends FooterBarMixin { + + /** + * Creates a mixin for managing buttons on the footer. + * + * @param layout The {@link TemplateLayout} containing this mixin. + * @param attrs XML attributes given to the layout. + * @param defStyleAttr The default style attribute as given to the constructor of the layout. + */ + public LoadingFooterBarMixin( + TemplateLayout layout, @Nullable AttributeSet attrs, int defStyleAttr) { + super(layout, attrs, defStyleAttr); + } + + @Override + public LinearLayout getButtonContainer() { + return super.getButtonContainer(); + } +} diff --git a/main/AndroidManifest.xml b/main/AndroidManifest.xml index 245db97..984a73b 100644 --- a/main/AndroidManifest.xml +++ b/main/AndroidManifest.xml @@ -15,7 +15,15 @@ limitations under the License. --> -<manifest package="com.google.android.setupdesign"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.google.android.setupdesign"> <!-- Set in the BUILD or gradle file --> <uses-sdk /> + <!-- after SDK 30, package need to declare its visible packages. --> + <queries tools:node="merge"> + <intent> + <action android:name="com.android.setupwizard.action.PARTNER_CUSTOMIZATION" /> + </intent> + </queries> </manifest> diff --git a/main/res/anim-v31/sud_interpolator.xml b/main/res/anim-v31/sud_interpolator.xml new file mode 100644 index 0000000..bec77f0 --- /dev/null +++ b/main/res/anim-v31/sud_interpolator.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1" /> diff --git a/main/res/anim-v31/sud_slide_back_in.xml b/main/res/anim-v31/sud_slide_back_in.xml new file mode 100644 index 0000000..77d4cd5 --- /dev/null +++ b/main/res/anim-v31/sud_slide_back_in.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@integer/sudTransitionDuration" + android:fromXDelta="-100%" + android:toXDelta="0%" + android:interpolator="@anim/sud_interpolator" /> diff --git a/main/res/anim-v31/sud_slide_back_out.xml b/main/res/anim-v31/sud_slide_back_out.xml new file mode 100644 index 0000000..8ae16d4 --- /dev/null +++ b/main/res/anim-v31/sud_slide_back_out.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@integer/sudTransitionDuration" + android:fromXDelta="0%" + android:toXDelta="100%" + android:interpolator="@anim/sud_interpolator" /> diff --git a/main/res/anim-v31/sud_slide_next_in.xml b/main/res/anim-v31/sud_slide_next_in.xml new file mode 100644 index 0000000..006d9b4 --- /dev/null +++ b/main/res/anim-v31/sud_slide_next_in.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@integer/sudTransitionDuration" + android:fromXDelta="100%" + android:toXDelta="0%" + android:interpolator="@anim/sud_interpolator" /> diff --git a/main/res/anim-v31/sud_slide_next_out.xml b/main/res/anim-v31/sud_slide_next_out.xml new file mode 100644 index 0000000..341bbad --- /dev/null +++ b/main/res/anim-v31/sud_slide_next_out.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@integer/sudTransitionDuration" + android:fromXDelta="0%" + android:toXDelta="-100%" + android:interpolator="@anim/sud_interpolator" /> diff --git a/main/res/anim/sud_pre_p_activity_close_enter.xml b/main/res/anim/sud_pre_p_activity_close_enter.xml new file mode 100644 index 0000000..b8a7654 --- /dev/null +++ b/main/res/anim/sud_pre_p_activity_close_enter.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" android:zAdjustment="normal"> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" + android:interpolator="@android:interpolator/linear_out_slow_in" + android:duration="250"/> +</set> diff --git a/main/res/anim/sud_pre_p_activity_close_exit.xml b/main/res/anim/sud_pre_p_activity_close_exit.xml new file mode 100644 index 0000000..c813937 --- /dev/null +++ b/main/res/anim/sud_pre_p_activity_close_exit.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false" android:zAdjustment="top"> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@android:interpolator/linear" + android:fillEnabled="true" + android:fillBefore="false" android:fillAfter="true" + android:startOffset="100" + android:duration="150"/> + <translate android:fromYDelta="0%" android:toYDelta="8%" + android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/sud_accelerate_quart" + android:duration="250"/> +</set> diff --git a/main/res/anim/sud_pre_p_activity_open_enter.xml b/main/res/anim/sud_pre_p_activity_open_enter.xml new file mode 100644 index 0000000..63acc28 --- /dev/null +++ b/main/res/anim/sud_pre_p_activity_open_enter.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false" + android:zAdjustment="top"> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:interpolator="@interpolator/sud_decelerate_quart" + android:fillEnabled="true" + android:fillBefore="false" android:fillAfter="true" + android:duration="200"/> + <translate android:fromYDelta="8%" android:toYDelta="0" + android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" + android:interpolator="@android:interpolator/decelerate_quint" + android:duration="350"/> +</set> diff --git a/main/res/anim/sud_pre_p_activity_open_exit.xml b/main/res/anim/sud_pre_p_activity_open_exit.xml new file mode 100644 index 0000000..950e32c --- /dev/null +++ b/main/res/anim/sud_pre_p_activity_open_exit.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="normal"> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="217"/> +</set> diff --git a/main/res/anim/sud_stay.xml b/main/res/anim/sud_stay.xml new file mode 100644 index 0000000..285e7af --- /dev/null +++ b/main/res/anim/sud_stay.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2020 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. +--> + +<!-- This is needed to keep the exiting window shown while the entering window fades in --> +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@android:integer/config_longAnimTime" + android:fromAlpha="1.0" + android:interpolator="@android:interpolator/decelerate_quad" + android:toAlpha="1.0" /> diff --git a/main/res/color/sud_switch_track_on.xml b/main/res/color/sud_switch_track_on.xml new file mode 100644 index 0000000..a95a1ee --- /dev/null +++ b/main/res/color/sud_switch_track_on.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> + <item android:state_enabled="false" + android:color="?android:attr/colorForeground" + android:alpha="?android:attr/disabledAlpha" /> + <item android:state_checked="true" + android:color="?android:attr/colorControlActivated" tools:ignore="NewApi" + android:alpha="?android:attr/disabledAlpha" /> + <item android:color="?android:attr/colorForeground" /> +</selector> diff --git a/main/res/drawable-v21/sud_switch_thumb_on.xml b/main/res/drawable-v21/sud_switch_thumb_on.xml new file mode 100644 index 0000000..224f181 --- /dev/null +++ b/main/res/drawable-v21/sud_switch_thumb_on.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:top="@dimen/sud_switch_thumb_margin" + android:left="@dimen/sud_switch_thumb_margin" + android:right="@dimen/sud_switch_thumb_margin" + android:bottom="@dimen/sud_switch_thumb_margin"> + <shape android:shape="oval"> + <size + android:height="@dimen/sud_switch_thumb_size" + android:width="@dimen/sud_switch_thumb_size" /> + <solid android:color="?android:attr/colorControlActivated" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/main/res/drawable/sud_dialog_background_dark.xml b/main/res/drawable/sud_dialog_background_dark.xml new file mode 100644 index 0000000..914cfec --- /dev/null +++ b/main/res/drawable/sud_dialog_background_dark.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<!-- The purpose of this file is to set the radius for datetime dialog which is + copy from abc_dialog_material_background.xml of + Base.V7.Theme.AppCompat.Light.Dialog. --> +<inset xmlns:android="http://schemas.android.com/apk/res/android"> + android:insetLeft="16dp" + android:insetTop="16dp" + android:insetRight="16dp" + android:insetBottom="16dp"> + <shape android:shape="rectangle"> + <corners android:radius="?attr/dialogCornerRadius" /> + <solid android:color="@color/sud_glif_window_bg_dark_color" /> + </shape> +</inset> diff --git a/main/res/drawable/sud_dialog_background_light.xml b/main/res/drawable/sud_dialog_background_light.xml new file mode 100644 index 0000000..0302783 --- /dev/null +++ b/main/res/drawable/sud_dialog_background_light.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<!-- The purpose of this file is to set the radius for datetime dialog which is + copy from abc_dialog_material_background.xml of + Base.V7.Theme.AppCompat.Light.Dialog. --> +<inset xmlns:android="http://schemas.android.com/apk/res/android"> + android:insetLeft="16dp" + android:insetTop="16dp" + android:insetRight="16dp" + android:insetBottom="16dp"> + <shape android:shape="rectangle"> + <corners android:radius="?attr/dialogCornerRadius" /> + <solid android:color="@color/sud_glif_window_bg_light_color" /> + </shape> +</inset> diff --git a/main/res/drawable/sud_scroll_bar_dark.xml b/main/res/drawable/sud_scroll_bar_dark.xml new file mode 100644 index 0000000..fbfce55 --- /dev/null +++ b/main/res/drawable/sud_scroll_bar_dark.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#33FFFFFF" /> + <corners android:radius="2dp" /> +</shape> diff --git a/main/res/drawable/sud_scroll_bar_light.xml b/main/res/drawable/sud_scroll_bar_light.xml new file mode 100644 index 0000000..ad32634 --- /dev/null +++ b/main/res/drawable/sud_scroll_bar_light.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#33000000" /> + <corners android:radius="2dp" /> +</shape> diff --git a/main/res/drawable/sud_switch_thumb_off.xml b/main/res/drawable/sud_switch_thumb_off.xml new file mode 100644 index 0000000..6b4f115 --- /dev/null +++ b/main/res/drawable/sud_switch_thumb_off.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:top="@dimen/sud_switch_thumb_margin" + android:left="@dimen/sud_switch_thumb_margin" + android:right="@dimen/sud_switch_thumb_margin" + android:bottom="@dimen/sud_switch_thumb_margin"> + <shape android:shape="oval"> + <size + android:height="@dimen/sud_switch_thumb_size" + android:width="@dimen/sud_switch_thumb_size" /> + <solid android:color="@color/sud_switch_thumb_off" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/main/res/drawable/sud_switch_thumb_on.xml b/main/res/drawable/sud_switch_thumb_on.xml new file mode 100644 index 0000000..cc1cf06 --- /dev/null +++ b/main/res/drawable/sud_switch_thumb_on.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> + <item + android:top="@dimen/sud_switch_thumb_margin" + android:left="@dimen/sud_switch_thumb_margin" + android:right="@dimen/sud_switch_thumb_margin" + android:bottom="@dimen/sud_switch_thumb_margin"> + <shape android:shape="oval"> + <size + android:height="@dimen/sud_switch_thumb_size" + android:width="@dimen/sud_switch_thumb_size" /> + <solid android:color="?android:attr/colorAccent" tools:ignore="NewApi" /> + </shape> + </item> +</layer-list> diff --git a/main/res/drawable/sud_switch_thumb_selector.xml b/main/res/drawable/sud_switch_thumb_selector.xml new file mode 100644 index 0000000..b30ffb0 --- /dev/null +++ b/main/res/drawable/sud_switch_thumb_selector.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/sud_switch_thumb_on" android:state_checked="true" /> + <item android:drawable="@drawable/sud_switch_thumb_off" android:state_checked="false" /> +</selector>
\ No newline at end of file diff --git a/main/res/drawable/sud_switch_track_off_background.xml b/main/res/drawable/sud_switch_track_off_background.xml new file mode 100644 index 0000000..d15cb9b --- /dev/null +++ b/main/res/drawable/sud_switch_track_off_background.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" + android:width="@dimen/sud_switch_track_width" + android:height="@dimen/sud_switch_track_height"> + <solid android:color="@color/sud_switch_track_off" /> + <corners android:radius="@dimen/sud_switch_track_radius" /> +</shape>
\ No newline at end of file diff --git a/main/res/drawable/sud_switch_track_on_background.xml b/main/res/drawable/sud_switch_track_on_background.xml new file mode 100644 index 0000000..ff40595 --- /dev/null +++ b/main/res/drawable/sud_switch_track_on_background.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" + android:width="@dimen/sud_switch_track_width" + android:height="@dimen/sud_switch_track_height"> + <solid android:color="@color/sud_switch_track_on" /> + <corners android:radius="@dimen/sud_switch_track_radius" /> +</shape>
\ No newline at end of file diff --git a/main/res/drawable/sud_switch_track_selector.xml b/main/res/drawable/sud_switch_track_selector.xml new file mode 100644 index 0000000..11cee5c --- /dev/null +++ b/main/res/drawable/sud_switch_track_selector.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/sud_switch_track_on_background" android:state_checked="true" /> + <item android:drawable="@drawable/sud_switch_track_off_background" android:state_checked="false" /> +</selector>
\ No newline at end of file diff --git a/main/res/interpolator/sud_accelerate_quart.xml b/main/res/interpolator/sud_accelerate_quart.xml new file mode 100644 index 0000000..b8a6ebf --- /dev/null +++ b/main/res/interpolator/sud_accelerate_quart.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2020 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. +--> + +<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:factor="2.0" /> diff --git a/main/res/interpolator/sud_decelerate_quart.xml b/main/res/interpolator/sud_decelerate_quart.xml new file mode 100644 index 0000000..06d7fe8 --- /dev/null +++ b/main/res/interpolator/sud_decelerate_quart.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2020 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. +--> + +<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:factor="2.0" /> diff --git a/main/res/layout-land-v31/sud_glif_blank_template_content.xml b/main/res/layout-land-v31/sud_glif_blank_template_content.xml new file mode 100644 index 0000000..dd4d52d --- /dev/null +++ b/main/res/layout-land-v31/sud_glif_blank_template_content.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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" + android:id="@+id/sud_layout_template_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/sud_landscape_header_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_header_area_weight" + android:orientation="vertical"> + + <ViewStub + android:id="@+id/sud_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/sud_landscape_content_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_content_area_weight" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/sud_layout_content" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + </LinearLayout> + + </LinearLayout> + + <ViewStub + android:id="@+id/suc_layout_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout> diff --git a/main/res/layout-land-v31/sud_glif_list_template_content.xml b/main/res/layout-land-v31/sud_glif_list_template_content.xml new file mode 100644 index 0000000..478ac5f --- /dev/null +++ b/main/res/layout-land-v31/sud_glif_list_template_content.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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" + android:id="@+id/sud_layout_template_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/sud_landscape_header_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_header_area_weight" + android:orientation="vertical"> + + <ViewStub + android:id="@+id/sud_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <com.google.android.setupdesign.view.BottomScrollView + android:id="@+id/sud_header_scroll_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true" + android:scrollIndicators="?attr/sudScrollIndicators"> + + <include layout="@layout/sud_glif_header" /> + + </com.google.android.setupdesign.view.BottomScrollView> + + </LinearLayout> + + <LinearLayout + android:id="@+id/sud_landscape_content_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_content_area_weight" + android:orientation="vertical"> + + <com.google.android.setupdesign.view.StickyHeaderListView + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@android:id/list" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:scrollIndicators="?attr/sudScrollIndicators" /> + + </LinearLayout> + + </LinearLayout> + + <ViewStub + android:id="@+id/suc_layout_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout> diff --git a/main/res/layout-land-v31/sud_glif_preference_recycler_view.xml b/main/res/layout-land-v31/sud_glif_preference_recycler_view.xml new file mode 100644 index 0000000..53fac8f --- /dev/null +++ b/main/res/layout-land-v31/sud_glif_preference_recycler_view.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<com.google.android.setupdesign.view.HeaderRecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/sud_recycler_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:scrollbars="vertical" /> diff --git a/main/res/layout-land-v31/sud_glif_preference_template_content.xml b/main/res/layout-land-v31/sud_glif_preference_template_content.xml new file mode 100644 index 0000000..c03d2fe --- /dev/null +++ b/main/res/layout-land-v31/sud_glif_preference_template_content.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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" + android:id="@+id/sud_layout_template_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/sud_landscape_header_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_header_area_weight" + android:orientation="vertical"> + + <ViewStub + android:id="@+id/sud_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <com.google.android.setupdesign.view.BottomScrollView + android:id="@+id/sud_header_scroll_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true" + android:scrollIndicators="?attr/sudScrollIndicators"> + + <include layout="@layout/sud_glif_header" /> + + </com.google.android.setupdesign.view.BottomScrollView> + + </LinearLayout> + + <LinearLayout + android:id="@+id/sud_landscape_content_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_content_area_weight" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/sud_layout_content" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + </LinearLayout> + + </LinearLayout> + + <ViewStub + android:id="@+id/suc_layout_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout> diff --git a/main/res/layout-land-v31/sud_glif_recycler_template_content.xml b/main/res/layout-land-v31/sud_glif_recycler_template_content.xml new file mode 100644 index 0000000..4840caf --- /dev/null +++ b/main/res/layout-land-v31/sud_glif_recycler_template_content.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/sud_layout_template_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/sud_landscape_header_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_header_area_weight" + android:orientation="vertical"> + + <ViewStub + android:id="@+id/sud_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <com.google.android.setupdesign.view.BottomScrollView + android:id="@+id/sud_header_scroll_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true" + android:scrollIndicators="?attr/sudScrollIndicators"> + + <include layout="@layout/sud_glif_header" /> + + </com.google.android.setupdesign.view.BottomScrollView> + + </LinearLayout> + + <LinearLayout + android:id="@+id/sud_landscape_content_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_content_area_weight" + android:orientation="vertical"> + + <com.google.android.setupdesign.view.HeaderRecyclerView + android:id="@+id/sud_recycler_view" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:scrollbars="vertical" + android:scrollIndicators="?attr/sudScrollIndicators" /> + + </LinearLayout> + + </LinearLayout> + + <ViewStub + android:id="@+id/suc_layout_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout> diff --git a/main/res/layout-land-v31/sud_glif_template_content.xml b/main/res/layout-land-v31/sud_glif_template_content.xml new file mode 100644 index 0000000..d1b6b92 --- /dev/null +++ b/main/res/layout-land-v31/sud_glif_template_content.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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" + android:id="@+id/sud_layout_template_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/sud_landscape_header_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_header_area_weight" + android:orientation="vertical"> + + <ViewStub + android:id="@+id/sud_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <com.google.android.setupdesign.view.BottomScrollView + android:id="@+id/sud_header_scroll_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true" + android:scrollIndicators="?attr/sudScrollIndicators"> + + <include layout="@layout/sud_glif_header" /> + + </com.google.android.setupdesign.view.BottomScrollView> + + </LinearLayout> + + <LinearLayout + android:id="@+id/sud_landscape_content_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="@dimen/sud_glif_land_content_area_weight" + android:orientation="vertical"> + + <com.google.android.setupdesign.view.BottomScrollView + android:id="@+id/sud_scroll_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true" + android:scrollIndicators="?attr/sudScrollIndicators"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/sud_layout_content" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <ViewStub + android:id="@+id/sud_layout_illustration_progress_stub" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inflatedId="@+id/sud_layout_progress_illustration" + android:layout="@layout/sud_progress_illustration_layout" /> + </LinearLayout> + + </com.google.android.setupdesign.view.BottomScrollView> + + </LinearLayout> + + </LinearLayout> + + <ViewStub + android:id="@+id/suc_layout_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout> diff --git a/main/res/layout-v31/sud_glif_preference_template_compact.xml b/main/res/layout-v31/sud_glif_preference_template_compact.xml new file mode 100644 index 0000000..a111e70 --- /dev/null +++ b/main/res/layout-v31/sud_glif_preference_template_compact.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<com.google.android.setupcompat.view.StatusBarBackgroundLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/suc_layout_status" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include layout="@layout/sud_glif_preference_template_content" /> + +</com.google.android.setupcompat.view.StatusBarBackgroundLayout>
\ No newline at end of file diff --git a/main/res/layout-v31/sud_glif_preference_template_content.xml b/main/res/layout-v31/sud_glif_preference_template_content.xml new file mode 100644 index 0000000..c618139 --- /dev/null +++ b/main/res/layout-v31/sud_glif_preference_template_content.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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" + android:id="@+id/sud_layout_template_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <include layout="@layout/sud_glif_blank_template_content" /> + +</LinearLayout> diff --git a/main/res/layout/sud_content_info.xml b/main/res/layout/sud_content_info.xml new file mode 100644 index 0000000..288c3af --- /dev/null +++ b/main/res/layout/sud_content_info.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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:tools="http://schemas.android.com/tools" + android:id="@+id/sud_content_info_container" + style="@style/SudInfoContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:baselineAligned="false" + android:orientation="horizontal"> + + <FrameLayout + android:id="@+id/sud_content_info_icon_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:gravity="start"> + + <ImageView + android:id="@+id/sud_content_info_icon" + android:layout_width="@dimen/sud_content_info_icon_size" + android:layout_height="@dimen/sud_content_info_icon_size" + android:layout_marginEnd="@dimen/sud_content_info_icon_margin_end" + tools:ignore="ContentDescription" /> + + </FrameLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_vertical" + android:orientation="vertical"> + + <com.google.android.setupdesign.view.RichTextView + android:id="@+id/sud_content_info_description" + style="@style/SudInfoDescription" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + android:textAlignment="viewStart" + tools:ignore="UnusedAttribute" /> + + </LinearLayout> + +</LinearLayout> diff --git a/main/res/layout/sud_glif_blank_template_content.xml b/main/res/layout/sud_glif_blank_template_content.xml index 1eaae13..887655d 100644 --- a/main/res/layout/sud_glif_blank_template_content.xml +++ b/main/res/layout/sud_glif_blank_template_content.xml @@ -17,6 +17,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/sud_layout_template_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> diff --git a/main/res/layout/sud_glif_header.xml b/main/res/layout/sud_glif_header.xml index f5a1113..50feeb2 100644 --- a/main/res/layout/sud_glif_header.xml +++ b/main/res/layout/sud_glif_header.xml @@ -22,13 +22,25 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <ImageView - android:id="@+id/sud_layout_icon" - style="?attr/sudGlifIconStyle" - android:layout_width="wrap_content" + <FrameLayout + android:id="@+id/sud_layout_icon_container" + style="@style/SudGlifIconContainer" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:contentDescription="@null" - android:visibility="gone" /> + android:visibility="gone" > + <ImageView + android:id="@+id/sud_layout_icon" + style="?attr/sudGlifIconStyle" + android:layout_marginLeft="0dp" + android:layout_marginRight="0dp" + android:layout_marginTop="0dp" + android:layout_marginBottom="0dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@null" + android:layout_gravity="?attr/sudGlifHeaderGravity" + android:visibility="gone" /> + </FrameLayout> <TextView android:id="@+id/suc_layout_title" @@ -41,10 +53,29 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/sud_glif_progress_bar_margin_vertical" - android:layout_marginLeft="?attr/sudMarginSides" - android:layout_marginRight="?attr/sudMarginSides" + android:layout_marginLeft="?attr/sudMarginStart" + android:layout_marginRight="?attr/sudMarginStart" android:layout_marginTop="@dimen/sud_glif_progress_bar_margin_vertical" android:inflatedId="@+id/sud_layout_progress" android:layout="@layout/sud_progress_bar" /> + <com.google.android.setupdesign.view.RichTextView + android:id="@+id/sud_layout_subtitle" + style="@style/SudGlifDescription" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" /> + + <ProgressBar + android:id="@+id/sud_glif_progress_bar" + style="?android:attr/progressBarStyleHorizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="?attr/sudMarginStart" + android:layout_marginRight="?attr/sudMarginStart" + android:layout_marginBottom="@dimen/sud_progress_bar_margin_vertical" + android:layout_marginTop="@dimen/sud_progress_bar_margin_vertical" + android:visibility="gone" + android:indeterminate="true" /> + </LinearLayout> diff --git a/main/res/layout/sud_glif_list_template_content.xml b/main/res/layout/sud_glif_list_template_content.xml index 1a6b4cd..09c56d2 100644 --- a/main/res/layout/sud_glif_list_template_content.xml +++ b/main/res/layout/sud_glif_list_template_content.xml @@ -18,6 +18,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/sud_layout_template_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> diff --git a/main/res/layout/sud_glif_recycler_template_content.xml b/main/res/layout/sud_glif_recycler_template_content.xml index 9ca640b..c2cccf0 100644 --- a/main/res/layout/sud_glif_recycler_template_content.xml +++ b/main/res/layout/sud_glif_recycler_template_content.xml @@ -19,6 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/sud_layout_template_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> diff --git a/main/res/layout/sud_glif_template_content.xml b/main/res/layout/sud_glif_template_content.xml index a72861e..fa898eb 100644 --- a/main/res/layout/sud_glif_template_content.xml +++ b/main/res/layout/sud_glif_template_content.xml @@ -18,6 +18,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/sud_layout_template_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> @@ -45,6 +46,13 @@ <include layout="@layout/sud_glif_header" /> + <ViewStub + android:id="@+id/sud_layout_illustration_progress_stub" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inflatedId="@+id/sud_layout_progress_illustration" + android:layout="@layout/sud_progress_illustration_layout" /> + <FrameLayout android:id="@+id/sud_layout_content" android:layout_width="match_parent" diff --git a/main/res/layout/sud_progress_illustration_layout.xml b/main/res/layout/sud_progress_illustration_layout.xml new file mode 100644 index 0000000..3b850f4 --- /dev/null +++ b/main/res/layout/sud_progress_illustration_layout.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 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" + style="@style/SudContentFrame" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/sud_layout_description" + android:visibility="invisible" + style="@style/SudDescription.Glif" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <com.google.android.setupdesign.view.FillContentLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ProgressBar + android:id="@+id/sud_progress_bar" + android:visibility="gone" + style="@style/SudFourColorIndeterminateProgressBar" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <com.google.android.setupdesign.view.IllustrationVideoView + android:id="@+id/sud_progress_illustration" + android:visibility="gone" + style="@style/SudContentIllustration" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </com.google.android.setupdesign.view.FillContentLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/main/res/values-land-v31/dimens.xml b/main/res/values-land-v31/dimens.xml new file mode 100644 index 0000000..8b8d10d --- /dev/null +++ b/main/res/values-land-v31/dimens.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources> + + <!-- General --> + <dimen name="sud_glif_footer_padding_start">16dp</dimen> + <!-- Calculated by (sud_glif_margin_end - 4dp internal padding of button) --> + <dimen name="sud_glif_footer_padding_end">28dp</dimen> + <dimen name="sud_glif_margin_start">32dp</dimen> + <dimen name="sud_glif_margin_end">32dp</dimen> + +</resources> diff --git a/main/res/values-land/dimens.xml b/main/res/values-land/dimens.xml index 88a97be..49ed20d 100644 --- a/main/res/values-land/dimens.xml +++ b/main/res/values-land/dimens.xml @@ -17,6 +17,11 @@ <resources> + <!-- General --> + <dimen name="sud_glif_button_min_height">36dp</dimen> + <dimen name="sud_glif_footer_min_height">52dp</dimen> + <dimen name="sud_glif_footer_padding_vertical">0dp</dimen> + <!-- Card layout (for tablets) --> <dimen name="sud_card_title_padding_end">32dp</dimen> <dimen name="sud_card_title_padding_start">56dp</dimen> @@ -33,4 +38,11 @@ <!-- Illustration --> <item name="sud_illustration_aspect_ratio" format="float" type="dimen">0</item> + <!-- TODO: Add testcase for testing padding value in landscape mode --> + <!-- Loading content styles --> + <dimen name="sud_content_loading_frame_padding_top">0dp</dimen> + <dimen name="sud_content_loading_frame_padding_start">0dp</dimen> + <dimen name="sud_content_loading_frame_padding_end">0dp</dimen> + <dimen name="sud_content_loading_frame_padding_bottom">24dp</dimen> + </resources> diff --git a/main/res/values-night-v31/colors.xml b/main/res/values-night-v31/colors.xml new file mode 100644 index 0000000..f929595 --- /dev/null +++ b/main/res/values-night-v31/colors.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources> + <!-- Accent color --> + <color name="sud_dynamic_color_accent_glif_v3">@color/sud_dynamic_color_accent_glif_v3_dark</color> + + + <color name="sud_system_primary_text">@color/sud_system_neutral1_50</color> + <color name="sud_system_secondary_text">@color/sud_system_neutral2_200</color> + <color name="sud_system_tertiary_text_inactive">@color/sud_system_neutral2_400</color> + <color name="sud_system_error_warning">@color/sud_error_warning_default_dark</color> + <color name="sud_system_background_surface">@color/sud_system_neutral1_900</color> + <color name="sud_system_accent_icon_text_button">@color/sud_system_accent1_100</color> + <color name="sud_system_button_surface">@color/sud_system_accent1_100</color> + <color name="sud_system_button_text">@color/sud_system_neutral1_900</color> + <color name="sud_system_dividing_line">@color/sud_system_neutral2_300</color> + +</resources>
\ No newline at end of file diff --git a/main/res/values-night/colors.xml b/main/res/values-night/colors.xml new file mode 100644 index 0000000..1062ea7 --- /dev/null +++ b/main/res/values-night/colors.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources> + + <color name="sud_system_primary_text">@color/sud_primary_default_text_dark</color> + <color name="sud_system_secondary_text">@color/sud_secondary_default_text_dark</color> + <color name="sud_system_tertiary_text_inactive">@color/sud_inactive_default_dark</color> + <color name="sud_system_error_warning">@color/sud_error_warning_default_dark</color> + <color name="sud_system_success_done">@color/success_color_device_default_dark</color> + <color name="sud_system_fallback_accent">@color/fallback_color_device_default_dark</color> + + <color name="sud_portal_pending_progress">@color/sud_portal_pending_progress_dark</color> + + + <color name="sud_uniformity_backdrop_color">#2A2B2E</color> +</resources>
\ No newline at end of file diff --git a/main/res/values-night/styles.xml b/main/res/values-night/styles.xml index ca94c12..d609b4c 100644 --- a/main/res/values-night/styles.xml +++ b/main/res/values-night/styles.xml @@ -22,5 +22,7 @@ <style name="SudThemeGlif.DayNight" parent="SudThemeGlif" /> <style name="SudThemeGlifV2.DayNight" parent="SudThemeGlifV2" /> <style name="SudThemeGlifV3.DayNight" parent="SudThemeGlifV3" /> + <style name="SudDynamicColorThemeGlifV3.DayNight" parent="SudDynamicColorThemeGlifV3" /> + <style name="SudFullDynamicColorThemeGlifV3.DayNight" parent="SudFullDynamicColorThemeGlifV3" /> </resources> diff --git a/main/res/values-sw600dp/dimens.xml b/main/res/values-sw600dp/dimens.xml index bf9e1e9..b7802b1 100644 --- a/main/res/values-sw600dp/dimens.xml +++ b/main/res/values-sw600dp/dimens.xml @@ -18,11 +18,12 @@ <resources> <!-- General --> - <!-- Calculated by (sud_glif_margin_sides - 4dp internal padding of button) --> + <!-- Calculated by (sud_glif_margin_start - 4dp internal padding of button) --> <dimen name="sud_glif_button_margin_end">36dp</dimen> - <!-- Calculated by (sud_glif_margin_sides - sud_glif_button_padding) --> + <!-- Calculated by (sud_glif_margin_start - sud_glif_button_padding) --> <dimen name="sud_glif_button_margin_start">24dp</dimen> - <dimen name="sud_glif_margin_sides">40dp</dimen> + <dimen name="sud_glif_margin_start">40dp</dimen> + <dimen name="sud_glif_margin_end">40dp</dimen> <!-- Decor view (illustration or the header without illustration) --> <dimen name="sud_decor_padding_top">256dp</dimen> diff --git a/main/res/values-v21/styles.xml b/main/res/values-v21/styles.xml index 6a4df1b..c961745 100644 --- a/main/res/values-v21/styles.xml +++ b/main/res/values-v21/styles.xml @@ -54,10 +54,10 @@ <style name="SudItemContainer"> <item name="android:minHeight">?android:attr/listPreferredItemHeight</item> - <item name="android:paddingBottom">@dimen/sud_items_padding_vertical</item> + <item name="android:paddingBottom">@dimen/sud_items_padding_bottom</item> <item name="android:paddingEnd">?android:attr/listPreferredItemPaddingEnd</item> <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item> - <item name="android:paddingTop">@dimen/sud_items_padding_vertical</item> + <item name="android:paddingTop">@dimen/sud_items_padding_top</item> </style> <style name="SudItemTitle"> diff --git a/main/res/values-v27/styles.xml b/main/res/values-v27/styles.xml index d3906b8..c24f538 100644 --- a/main/res/values-v27/styles.xml +++ b/main/res/values-v27/styles.xml @@ -27,5 +27,14 @@ <!-- Ignore NewApi: For some reason lint seems to think this API is new in v28 (b/73514594) --> <item name="android:windowLightNavigationBar" tools:ignore="NewApi">true</item> <item name="sucLightSystemNavBar" tools:ignore="NewApi">?android:attr/windowLightNavigationBar</item> + <item name="sucSystemNavBarDividerColor" tools:ignore="NewApi">?android:attr/navigationBarDividerColor</item> + </style> + + <style name="SudThemeGlifV3" parent="SudBaseThemeGlifV3"> + <item name="android:navigationBarColor">@color/sud_glif_v3_nav_bar_color_dark</item> + <item name="android:navigationBarDividerColor" tools:ignore="NewApi">@color/sud_glif_v3_nav_bar_divider_color_dark</item> + <item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item> + <item name="sucLightSystemNavBar" tools:ignore="NewApi">?android:attr/windowLightNavigationBar</item> + <item name="sucSystemNavBarDividerColor" tools:ignore="NewApi">?android:attr/navigationBarDividerColor</item> </style> </resources> diff --git a/main/res/values-v31/colors.xml b/main/res/values-v31/colors.xml new file mode 100644 index 0000000..91587ee --- /dev/null +++ b/main/res/values-v31/colors.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources> + <!-- Default color for BC --> + + <color name="sud_color_accent_glif_v3_dark">#ff669df6</color> + + <color name="sud_color_accent_glif_v3_light">#ff1a73e8</color> + + <!-- Accent color --> + <color name="sud_dynamic_color_accent_glif_v3">@color/sud_dynamic_color_accent_glif_v3_light</color> + <color name="sud_dynamic_color_accent_glif_v3_dark">@color/sud_system_accent1_100</color> + <color name="sud_dynamic_color_accent_glif_v3_light">@color/sud_system_accent1_600</color> + + + <color name="sud_error_warning_default_dark">#F28B82</color> + + <color name="sud_error_warning_default_light">#EA4335</color> + + + + <color name="sud_system_accent1_100">@android:color/system_accent1_100</color> + + <color name="sud_system_accent1_200">@android:color/system_accent1_200</color> + + <color name="sud_system_accent1_600">@android:color/system_accent1_600</color> + + + + <color name="sud_system_neutral1_0">@android:color/system_neutral1_0</color> + + <color name="sud_system_neutral1_50">@android:color/system_neutral1_50</color> + + <color name="sud_system_neutral1_800">@android:color/system_neutral1_800</color> + + <color name="sud_system_neutral1_900">@android:color/system_neutral1_900</color> + + + + <color name="sud_system_neutral2_50">@android:color/system_neutral1_50</color> + + <color name="sud_system_neutral2_100">@android:color/system_neutral1_100</color> + + <color name="sud_system_neutral2_200">@android:color/system_neutral2_200</color> + + <color name="sud_system_neutral2_300">@android:color/system_accent2_300</color> + + <color name="sud_system_neutral2_400">@android:color/system_neutral2_400</color> + + <color name="sud_system_neutral2_500">@android:color/system_neutral2_500</color> + + <color name="sud_system_neutral2_700">@android:color/system_neutral2_700</color> + + + <color name="sud_system_primary_text">@color/sud_system_neutral1_900</color> + <color name="sud_system_secondary_text">@color/sud_system_neutral2_700</color> + <color name="sud_system_tertiary_text_inactive">@color/sud_system_neutral2_500</color> + <color name="sud_system_error_warning">@color/sud_error_warning_default_light</color> + <color name="sud_system_background_surface">@color/sud_system_neutral1_50</color> + <color name="sud_system_accent_icon_text_button">@color/sud_system_accent1_600</color> + <color name="sud_system_button_surface">@color/sud_system_accent1_100</color> + <color name="sud_system_button_text">@color/sud_system_neutral1_900</color> + <color name="sud_system_dividing_line">@color/sud_system_neutral2_300</color> + +</resources>
\ No newline at end of file diff --git a/main/res/values-v31/config.xml b/main/res/values-v31/config.xml new file mode 100644 index 0000000..7367e33 --- /dev/null +++ b/main/res/values-v31/config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources> + <!-- The duration (in milliseconds) of activity transitions --> + <integer name="sudTransitionDuration">450</integer> + +</resources>
\ No newline at end of file diff --git a/main/res/values-v31/layouts.xml b/main/res/values-v31/layouts.xml new file mode 100644 index 0000000..e6198e8 --- /dev/null +++ b/main/res/values-v31/layouts.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources xmlns:tools="http://schemas.android.com/tools"> + + <item name="sud_glif_preference_template" type="layout">@layout/sud_glif_preference_template_compact</item> + +</resources> diff --git a/main/res/values-v31/styles.xml b/main/res/values-v31/styles.xml new file mode 100644 index 0000000..e1240e4 --- /dev/null +++ b/main/res/values-v31/styles.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2021 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. +--> + +<resources> + <!-- Main theme for dynamic color --> + <style name="SudDynamicColorThemeGlifV3" parent="SudThemeGlifV3"> + <item name="colorAccent">@color/sud_dynamic_color_accent_glif_v3_dark</item> + <item name="android:textColorLink">@color/sud_dynamic_color_accent_glif_v3_dark</item> + <item name="alertDialogTheme">@style/SudDynamicColorAlertDialogThemeCompat</item> + <item name="android:datePickerDialogTheme">@style/SudDynamicColorDateTimePickerDialogTheme</item> + <item name="android:timePickerDialogTheme">@style/SudDynamicColorDateTimePickerDialogTheme</item> + <item name="sucFullDynamicColor">false</item> + </style> + + <style name="SudDynamicColorThemeGlifV3.Light" parent="SudThemeGlifV3.Light"> + <item name="colorAccent">@color/sud_dynamic_color_accent_glif_v3_light</item> + <item name="android:textColorLink">@color/sud_dynamic_color_accent_glif_v3_light</item> + <item name="alertDialogTheme">@style/SudDynamicColorAlertDialogThemeCompat.Light</item> + <item name="android:datePickerDialogTheme">@style/SudDynamicColorDateTimePickerDialogTheme.Light</item> + <item name="android:timePickerDialogTheme">@style/SudDynamicColorDateTimePickerDialogTheme.Light</item> + <item name="sucFullDynamicColor">false</item> + </style> + + <style name="SudFullDynamicColorThemeGlifV3" parent="SudDynamicColorThemeGlifV3"> + <item name="android:windowBackground">@android:color/system_neutral1_900</item> + <item name="android:colorForeground">@android:color/system_neutral1_50</item> + <item name="android:colorForegroundInverse">@android:color/system_neutral1_900</item> + <item name="android:colorBackground">@android:color/system_neutral1_900</item> + <item name="android:colorBackgroundCacheHint">@android:color/system_neutral1_900</item> + <item name="colorBackgroundFloating">@android:color/system_neutral1_900</item> + <item name="android:navigationBarColor">@android:color/system_neutral1_900</item> + + <item name="android:textColorPrimary">@color/sud_system_primary_text</item> + <item name="android:textColorSecondary">@color/sud_system_secondary_text</item> + <item name="android:textColorTertiary">@color/sud_system_tertiary_text_inactive</item> + <item name="android:textColorPrimaryDisableOnly">@color/sud_system_tertiary_text_inactive</item> + <item name="android:textColorPrimaryInverseDisableOnly">@color/sud_system_tertiary_text_inactive</item> + + <item name="colorControlNormal">?android:attr/textColorSecondary</item> + <item name="colorControlHighlight">@color/ripple_material_dark</item> + <item name="colorButtonNormal">@color/button_material_dark</item> + <item name="colorSwitchThumbNormal">@color/switch_thumb_material_dark</item> + + <item name="alertDialogTheme">@style/SudFullDynamicColorAlertDialogThemeCompat</item> + + <item name="sucFullDynamicColor">true</item> + </style> + + <style name="SudFullDynamicColorThemeGlifV3.Light" parent="SudDynamicColorThemeGlifV3.Light"> + <item name="android:windowBackground">@android:color/system_neutral1_50</item> + <item name="android:colorForeground">@android:color/system_neutral1_900</item> + <item name="android:colorForegroundInverse">@android:color/system_neutral1_50</item> + <item name="android:colorBackground">@android:color/system_neutral1_50</item> + <item name="android:colorBackgroundCacheHint">@@android:color/system_neutral1_50</item> + <item name="colorBackgroundFloating">@android:color/system_neutral1_50</item> + <item name="android:navigationBarColor">@android:color/system_neutral1_50</item> + + <item name="android:textColorPrimary">@color/sud_system_primary_text</item> + <item name="android:textColorSecondary">@color/sud_system_secondary_text</item> + <item name="android:textColorTertiary">@color/sud_system_tertiary_text_inactive</item> + <item name="android:textColorPrimaryDisableOnly">@color/sud_system_tertiary_text_inactive</item> + + <item name="colorControlNormal">?android:attr/textColorSecondary</item> + <item name="colorControlHighlight">@color/ripple_material_light</item> + <item name="colorButtonNormal">@color/button_material_light</item> + <item name="colorSwitchThumbNormal">@color/switch_thumb_material_light</item> + + <item name="alertDialogTheme">@style/SudFullDynamicColorAlertDialogThemeCompat.Light</item> + + <item name="sucFullDynamicColor">true</item> + </style> + + <!-- Dynamic color theme for alert dialog --> + <style name="SudDynamicColorAlertDialogThemeCompat" parent="Theme.AppCompat.Dialog.Alert"> + <item name="android:textAllCaps">false</item> + <item name="android:colorBackground">@color/sud_glif_v3_dialog_background_color_dark</item> + <item name="colorAccent">@color/sud_dynamic_color_accent_glif_v3_dark</item> + <item name="dialogCornerRadius">@dimen/sud_glif_device_default_dialog_corner_radius</item> + </style> + + <style name="SudDynamicColorAlertDialogThemeCompat.Light" parent="Theme.AppCompat.Light.Dialog.Alert"> + <item name="android:textAllCaps">false</item> + <item name="android:colorBackground">@color/sud_glif_v3_dialog_background_color_light</item> + <item name="colorAccent">@color/sud_dynamic_color_accent_glif_v3_light</item> + <item name="dialogCornerRadius">@dimen/sud_glif_device_default_dialog_corner_radius</item> + </style> + + <style name="SudFullDynamicColorAlertDialogThemeCompat" parent="SudDynamicColorAlertDialogThemeCompat"> + <item name="android:colorBackground">@color/sud_system_neutral1_900</item> + </style> + <style name="SudFullDynamicColorAlertDialogThemeCompat.Light" parent="SudDynamicColorAlertDialogThemeCompat.Light"> + <item name="android:colorBackground">@color/sud_system_neutral1_50</item> + </style> + + <!-- Dynamic color theme for date time dialog --> + <style name="SudDynamicColorDateTimePickerDialogTheme" parent="SudDateTimePickerDialogTheme"> + <item name="colorAccent">@color/sud_dynamic_color_accent_glif_v3_dark</item> + <item name="dialogCornerRadius">@dimen/sud_glif_device_default_dialog_corner_radius</item> + </style> + + <style name="SudDynamicColorDateTimePickerDialogTheme.Light" parent="SudDateTimePickerDialogTheme.Light"> + <item name="colorAccent">@color/sud_dynamic_color_accent_glif_v3_light</item> + <item name="dialogCornerRadius">@dimen/sud_glif_device_default_dialog_corner_radius</item> + </style> + +</resources> diff --git a/main/res/values/attrs.xml b/main/res/values/attrs.xml index 95c8bb5..c946912 100644 --- a/main/res/values/attrs.xml +++ b/main/res/values/attrs.xml @@ -19,7 +19,8 @@ <!-- Theme attributes --> <attr name="sudLayoutTheme" format="reference" /> - <attr name="sudMarginSides" format="dimension|reference" /> + <attr name="sudMarginStart" format="dimension|reference" /> + <attr name="sudMarginEnd" format="dimension|reference" /> <attr name="sudEditTextBackgroundColor" format="color" /> <attr name="sudButtonHighlightAlpha" format="float" /> @@ -39,7 +40,24 @@ <!-- Push object to the end of its container, not changing its size. --> <flag name="end" value="0x00800005" /> </attr> + + <attr name="sudGlifSubtitleGravity"> + <!-- Push object to the left of its container, not changing its size. --> + <flag name="left" value="0x03" /> + <!-- Push object to the right of its container, not changing its size. --> + <flag name="right" value="0x05" /> + <!-- Place object in the horizontal center of its container, not changing its size. --> + <flag name="center_horizontal" value="0x01" /> + <!-- Grow the horizontal size of the object if needed so it completely fills its container. --> + <flag name="fill_horizontal" value="0x07" /> + <!-- Push object to the beginning of its container, not changing its size. --> + <flag name="start" value="0x00800003" /> + <!-- Push object to the end of its container, not changing its size. --> + <flag name="end" value="0x00800005" /> + </attr> + <attr name="sudGlifIconStyle" format="reference" /> + <attr name="sudGlifIconSize" format="dimension" /> <attr name="sudButtonAllCaps" format="boolean" /> <attr name="sudButtonCornerRadius" format="dimension" /> @@ -83,6 +101,9 @@ <flag name="end" value="0x20" /> </attr> + <!-- Custom the scroll bar indicator --> + <attr name="sudScrollBarThumb" format="reference" /> + <!-- Custom view attributes --> <attr name="sudColorPrimary" format="color" /> <attr name="sudHeader" format="reference" /> @@ -93,6 +114,8 @@ <attr name="sudDividerInsetStartNoIcon" format="dimension|reference" /> <attr name="sudItemDescriptionStyle" format="reference" /> <attr name="sudItemDescriptionTitleStyle" format="reference" /> + <attr name="sudContentFramePaddingTop" format="dimension|reference" /> + <attr name="sudContentFramePaddingBottom" format="dimension|reference" /> <attr name="sudHasStableIds" format="boolean|reference" /> @@ -110,6 +133,7 @@ <declare-styleable name="SudIllustrationVideoView"> <attr name="sudVideo" format="reference" /> + <attr name="sudPauseVideoWhenFinished" format="boolean" /> </declare-styleable> <declare-styleable name="SudGlifLayout"> @@ -128,6 +152,10 @@ <attr name="android:width" /> </declare-styleable> + <attr name="sudContentIllustrationMaxWidth" format="dimension" /> + <attr name="sudContentIllustrationMaxHeight" format="dimension" /> + <attr name="sudContentIllustrationPaddingTop" format="dimension" /> + <attr name="sudContentIllustrationPaddingBottom" format="dimension" /> <declare-styleable name="SudFillContentLayout"> <attr name="android:maxHeight" /> <attr name="android:maxWidth" /> @@ -211,4 +239,24 @@ <attr name="sudCollapsedSummary" format="string" localization="suggested" /> <attr name="sudExpandedSummary" format="string" localization="suggested" /> </declare-styleable> + + <declare-styleable name="SudDescriptionMixin"> + <attr name="sudDescriptionText" format="string" localization="suggested" /> + <attr name="sudDescriptionTextColor" format="reference|color" /> + <attr name="sudGlifDescriptionMarginTop" format="dimension" /> + <attr name="sudGlifDescriptionMarginBottom" format="dimension" /> + </declare-styleable> + + <declare-styleable name="SudProgressBarMixin"> + <attr name="sudUseBottomProgressBar" format="boolean" /> + </declare-styleable> + + <declare-styleable name="SudGlifLoadingFramePadding"> + <attr name="sudLoadingContentFramePaddingTop" format="dimension|reference" /> + <attr name="sudLoadingContentFramePaddingStart" format="dimension|reference" /> + <attr name="sudLoadingContentFramePaddingEnd" format="dimension|reference" /> + <attr name="sudLoadingContentFramePaddingBottom" format="dimension|reference" /> + </declare-styleable> + + <attr name="sudLoadingHeaderHeight" format="dimension|reference" /> </resources> diff --git a/main/res/values/colors.xml b/main/res/values/colors.xml index a386eb5..798abc9 100644 --- a/main/res/values/colors.xml +++ b/main/res/values/colors.xml @@ -18,7 +18,6 @@ <resources> <!-- General colors --> - <color name="sud_color_accent_dark">#ff448aff</color> <color name="sud_color_accent_light">#ff3367d6</color> <color name="sud_color_background_dark">#ff303030</color> @@ -35,25 +34,116 @@ <color name="sud_flat_button_highlight">#1f000000</color> <!-- Navigation bar colors --> - <color name="sud_navbar_bg_dark">#ff21272b</color> <color name="sud_navbar_bg_light">#ffe4e7e9</color> <!-- GLIF colors --> <color name="sud_color_accent_glif_dark">#ff4285f4</color> <color name="sud_color_accent_glif_light">#ff4285f4</color> - <color name="sud_color_accent_glif_v3_light">#ff1a73e8</color> <color name="sud_color_accent_glif_v3_dark">#ff8ab4f8</color> + <color name="sud_color_accent_glif_v3_light">#ff1a73e8</color> <color name="sud_glif_background_color_dark">#ff000000</color> <color name="sud_glif_background_color_light">#ffffffff</color> <color name="sud_glif_edit_text_bg_dark_color">#ff202124</color> <color name="sud_glif_edit_text_bg_light_color">#0a000000</color> <color name="sud_glif_v3_dialog_background_color_dark">#ff3c4043</color> + <color name="sud_glif_v3_dialog_background_color_light">#fff1f3f4</color> + <color name="sud_glif_v3_nav_bar_color_dark">#ff000000</color> <color name="sud_glif_v3_nav_bar_color_light">#ffffffff</color> + <color name="sud_glif_v3_nav_bar_divider_color_dark">#00000000</color> <color name="sud_glif_v3_nav_bar_divider_color_light">#1f000000</color> + <color name="sud_glif_v3_text_color_dark">#ffffffff</color> + <color name="sud_glif_v3_text_color_light">#de000000</color> + <color name="sud_glif_window_bg_dark_color">#ff202124</color> + <color name="sud_glif_window_bg_light_color">#ffffffff</color> <!-- Color for error text --> - <color name="sud_color_error_text_light">#ffd93025</color> <!-- Google red 600 --> - <color name="sud_color_error_text_dark">#fff28b82</color> <!-- Google red 300 --> + + <color name="sud_color_error_text_dark">#fff28b82</color> + + <color name="sud_color_error_text_light">#ffd93025</color> + + + <color name="sud_dynamic_color_accent_glif_v3_dark">@color/sud_color_accent_glif_v3_dark</color> + <color name="sud_dynamic_color_accent_glif_v3_light">@color/sud_color_accent_glif_v3_light</color> + + + + <color name="sud_system_accent1_100">#8DF5E3</color> + + <color name="sud_system_accent1_200">#71D8C7</color> + + <color name="sud_system_accent1_600">#006C5F</color> + + + + <color name="sud_system_neutral1_0">#ffffff</color> + + <color name="sud_system_neutral1_50">#f0f0f0</color> + + <color name="sud_system_neutral1_800">#303030</color> + + <color name="sud_system_neutral1_900">#1b1b1b</color> + + + + <color name="sud_system_neutral2_50">#f0f0f0</color> + + <color name="sud_system_neutral2_100">#e2e2e2</color> + + <color name="sud_system_neutral2_200">#c6c6c6</color> + + <color name="sud_system_neutral2_300">#ababab</color> + + <color name="sud_system_neutral2_400">#909090</color> + + <color name="sud_system_neutral2_500">#757575</color> + + <color name="sud_system_neutral2_700">#464646</color> + + + <color name="success_color_device_default_dark">#5BB974</color> + + <color name="success_color_device_default_light">#1E8E3E</color> + + <color name="fallback_color_device_default_dark">#669DF6</color> + + <color name="fallback_color_device_default_light">#1A73E8</color> + + <color name="sud_portal_pending_progress_light">#fdc69c</color> + + <color name="sud_portal_pending_progress_dark">#b06000</color> + + <color name="sud_primary_default_text_dark">#FFFFFF</color> + + <color name="sud_primary_default_text_light">#202124</color> + + <color name="sud_secondary_default_text_dark">#9AA0A6</color> + + <color name="sud_secondary_default_text_light">#5F6368</color> + + <color name="sud_inactive_default_dark">#5F6368</color> + + <color name="sud_inactive_default_light">#9AA0A6</color> + + <color name="sud_error_warning_default_dark">#EE675C</color> + + <color name="sud_error_warning_default_light">#D93025</color> + + + <color name="sud_system_primary_text">@color/sud_primary_default_text_light</color> + <color name="sud_system_secondary_text">@color/sud_secondary_default_text_light</color> + <color name="sud_system_tertiary_text_inactive">@color/sud_inactive_default_light</color> + <color name="sud_system_error_warning">@color/sud_error_warning_default_light</color> + <color name="sud_system_success_done">@color/success_color_device_default_light</color> + <color name="sud_system_fallback_accent">@color/fallback_color_device_default_light</color> + + <color name="sud_portal_pending_progress">@color/sud_portal_pending_progress_light</color> + + <color name="sud_switch_track_off">#FF757575</color> + <color name="sud_switch_thumb_off">#BFFFFFFF</color> + + + <color name="sud_uniformity_backdrop_color">@android:color/white</color> </resources> diff --git a/main/res/values/dimens.xml b/main/res/values/dimens.xml index 7508f25..64970c8 100644 --- a/main/res/values/dimens.xml +++ b/main/res/values/dimens.xml @@ -21,20 +21,29 @@ <dimen name="sud_layout_margin_sides">40dp</dimen> <dimen name="sud_glif_button_corner_radius">2dp</dimen> - <!-- Calculated by (sud_glif_margin_sides - 4dp internal padding of button) --> + <!-- Calculated by (sud_glif_margin_start - 4dp internal padding of button) --> <dimen name="sud_glif_button_margin_end">20dp</dimen> - <!-- Calculated by (sud_glif_margin_sides - sud_glif_button_padding) --> + <!-- Calculated by (sud_glif_margin_start - sud_glif_button_padding) --> <dimen name="sud_glif_button_margin_start">8dp</dimen> <dimen name="sud_glif_button_padding">16dp</dimen> + <dimen name="sud_glif_button_min_height">48dp</dimen> <!-- Negative of sud_glif_button_padding --> <dimen name="sud_glif_negative_button_padding">-16dp</dimen> <dimen name="sud_glif_footer_padding_vertical">8dp</dimen> + <dimen name="sud_glif_footer_padding_start">8dp</dimen> + <!-- Calculated by (sud_glif_margin_end - 4dp internal padding of button) --> + <dimen name="sud_glif_footer_padding_end">20dp</dimen> <dimen name="sud_glif_footer_min_height">72dp</dimen> - <dimen name="sud_glif_margin_sides">24dp</dimen> - <dimen name="sud_glif_margin_top">56dp</dimen> + <dimen name="sud_glif_margin_start">24dp</dimen> + <dimen name="sud_glif_margin_end">24dp</dimen> + <dimen name="sud_glif_icon_margin_top">56dp</dimen> <dimen name="sud_glif_alert_dialog_corner_radius">8dp</dimen> <dimen name="sud_glif_v3_button_corner_radius">4dp</dimen> + <dimen name="sud_glif_device_default_dialog_corner_radius">28dp</dimen> + <dimen name="sud_glif_land_header_area_weight">1</dimen> + <dimen name="sud_glif_land_content_area_weight">1</dimen> + <dimen name="sud_glif_land_middle_horizontal_spacing">48dp</dimen> <!-- Content styles --> <dimen name="sud_check_box_line_spacing_extra">4sp</dimen> @@ -51,7 +60,11 @@ <dimen name="sud_description_margin_bottom_lists">24dp</dimen> <dimen name="sud_description_line_spacing_extra">4sp</dimen> <dimen name="sud_description_text_size">16sp</dimen> + <!-- This value is the margin bottom difference between DescriptionItem and DescriptionMixin. --> + <dimen name="sud_description_margin_top_extra">1dp</dimen> + <dimen name="sud_description_margin_bottom_extra">12dp</dimen> + <!-- TODO: Remove sud_description_glif_margin_xxx once all apps migrate to sud_glif_description_margin_xxx --> <dimen name="sud_description_glif_margin_top">3dp</dimen> <dimen name="sud_description_glif_margin_bottom_lists">24dp</dimen> @@ -64,6 +77,22 @@ <dimen name="sud_content_illustration_min_width">172dp</dimen> <dimen name="sud_content_illustration_padding_vertical">24dp</dimen> + <!-- Loading content styles --> + <dimen name="sud_content_loading_frame_padding_top">0dp</dimen> + <dimen name="sud_content_loading_frame_padding_start">40dp</dimen> + <dimen name="sud_content_loading_frame_padding_end">40dp</dimen> + <dimen name="sud_content_loading_frame_padding_bottom">144dp</dimen> + + <dimen name="sud_loading_header_height">274dp</dimen> + + <!-- Glif Content info text --> + <dimen name="sud_content_info_text_size">16sp</dimen> + <dimen name="sud_content_info_line_spacing_extra">3sp</dimen> + <dimen name="sud_content_info_icon_size">18dp</dimen> + <dimen name="sud_content_info_icon_margin_end">16dp</dimen> + <dimen name="sud_content_info_padding_top">0dp</dimen> + <dimen name="sud_content_info_padding_bottom">0dp</dimen> + <!-- Margin on the start to offset for margin in the drawable --> <dimen name="sud_radio_button_margin_start">-6dp</dimen> <dimen name="sud_radio_button_margin_top">0dp</dimen> @@ -104,16 +133,26 @@ <dimen name="sud_glif_header_title_margin_top">16dp</dimen> <dimen name="sud_glif_header_title_margin_bottom">2dp</dimen> + <dimen name="sud_header_container_margin_bottom">0dp</dimen> - <dimen name="sud_glif_icon_max_height">32dp</dimen> + <!-- This value leverages sud_description_glif_margin_top --> + <dimen name="sud_glif_description_margin_top">3dp</dimen> + <dimen name="sud_glif_description_margin_bottom">12dp</dimen> + <dimen name="sud_glif_icon_max_height">32dp</dimen> <!-- Illustration --> <item name="sud_illustration_aspect_ratio" format="float" type="dimen">2.22</item> <!-- Items --> <dimen name="sud_items_icon_container_width">48dp</dimen> + <!-- TODO: Remove it when all attributes being used migrated into new one. --> <dimen name="sud_items_padding_vertical">15dp</dimen> <dimen name="sud_items_verbose_padding_vertical">20dp</dimen> + <dimen name="sud_items_title_text_size">16sp</dimen> + <dimen name="sud_items_summary_text_size">14sp</dimen> + <dimen name="sud_items_summary_margin_top">4dp</dimen> + <dimen name="sud_items_padding_top">15dp</dimen> + <dimen name="sud_items_padding_bottom">15dp</dimen> <!-- Ignore UnusedResources: can be used by clients --> <dimen name="sud_items_icon_divider_inset" tools:ignore="UnusedResources">88dp</dimen> @@ -160,4 +199,22 @@ <!-- ExpandableSwithItem --> <dimen name="sud_expand_arrow_drawable_padding">4dp</dimen> + <!-- SwitchBar --> + <!-- Minimum width of switch --> + <dimen name="sud_switch_min_width">52dp</dimen> + <!-- Margin of switch thumb --> + <dimen name="sud_switch_thumb_margin">4dp</dimen> + <!-- Size of switch thumb --> + <dimen name="sud_switch_thumb_size">20dp</dimen> + <!-- Width of switch track --> + <dimen name="sud_switch_track_width">52dp</dimen> + <!-- Height of switch track --> + <dimen name="sud_switch_track_height">28dp</dimen> + <!-- Radius of switch track --> + <dimen name="sud_switch_track_radius">35dp</dimen> + + <!-- IconUniformityAppImageView --> + <!-- Set 0dp since we don't want shadow. --> + <dimen name="sud_icon_uniformity_elevation">0dp</dimen> + </resources> diff --git a/main/res/values/layouts.xml b/main/res/values/layouts.xml index cf37c37..910965f 100644 --- a/main/res/values/layouts.xml +++ b/main/res/values/layouts.xml @@ -53,4 +53,3 @@ <item name="sud_glif_preference_template" type="layout">@layout/sud_glif_blank_template_compact</item> <item name="sud_glif_recycler_template" type="layout">@layout/sud_glif_recycler_template_compact</item> </resources> - diff --git a/main/res/values/styles.xml b/main/res/values/styles.xml index d3d6018..4725735 100644 --- a/main/res/values/styles.xml +++ b/main/res/values/styles.xml @@ -25,8 +25,8 @@ <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> <item name="android:listPreferredItemHeight">@dimen/sud_items_preferred_height</item> - <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/sudMarginSides</item> - <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/sudMarginSides</item> + <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/sudMarginEnd</item> + <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/sudMarginStart</item> <item name="android:navigationBarColor" tools:ignore="NewApi">@android:color/black</item> <item name="android:statusBarColor" tools:ignore="NewApi">@android:color/black</item> <item name="android:textAppearanceListItemSmall" tools:ignore="NewApi">?attr/textAppearanceListItemSmall</item> @@ -37,21 +37,33 @@ <item name="colorAccent">@color/sud_color_accent_dark</item> <item name="sudFillContentLayoutStyle">@style/SudFillContentLayout</item> - <item name="listPreferredItemPaddingLeft">?attr/sudMarginSides</item> - <item name="listPreferredItemPaddingRight">?attr/sudMarginSides</item> + <item name="listPreferredItemPaddingLeft">?attr/sudMarginStart</item> + <item name="listPreferredItemPaddingRight">?attr/sudMarginEnd</item> <item name="sudButtonAllCaps">true</item> <item name="sudButtonFontFamily">sans-serif</item> <item name="sudButtonHighlightAlpha">0.24</item> <item name="sudCardBackground">@drawable/sud_card_bg_dark</item> + <item name="sudContentFramePaddingTop">@dimen/sud_content_frame_padding_top</item> + <item name="sudContentFramePaddingBottom">@dimen/sud_content_frame_padding_bottom</item> + <item name="sudLoadingContentFramePaddingTop">@dimen/sud_content_loading_frame_padding_top</item> + <item name="sudLoadingContentFramePaddingStart">@dimen/sud_content_loading_frame_padding_start</item> + <item name="sudLoadingContentFramePaddingEnd">@dimen/sud_content_loading_frame_padding_end</item> + <item name="sudLoadingContentFramePaddingBottom">@dimen/sud_content_loading_frame_padding_bottom</item> <item name="sudDividerInsetEnd">0dp</item> <item name="sudDividerInsetStart">@dimen/sud_items_icon_divider_inset</item> <item name="sudDividerInsetStartNoIcon">@dimen/sud_items_text_divider_inset</item> <item name="sudItemDescriptionStyle">@style/SudItemContainer.Description</item> <item name="sudItemDescriptionTitleStyle">@style/SudItemTitle</item> <item name="sudListItemIconColor">@color/sud_list_item_icon_color_dark</item> - <item name="sudMarginSides">@dimen/sud_layout_margin_sides</item> + <item name="sudMarginStart">@dimen/sud_layout_margin_sides</item> + <item name="sudMarginEnd">@dimen/sud_layout_margin_sides</item> <item name="sudNavBarTheme">@style/SudNavBarThemeDark</item> <item name="textAppearanceListItemSmall">@style/TextAppearance.SudItemSummary</item> + <item name="sudContentIllustrationMaxWidth">@dimen/sud_content_illustration_max_width</item> + <item name="sudContentIllustrationMaxHeight">@dimen/sud_content_illustration_max_height</item> + <item name="sudContentIllustrationPaddingTop">@dimen/sud_content_illustration_padding_vertical</item> + <item name="sudContentIllustrationPaddingBottom">@dimen/sud_content_illustration_padding_vertical</item> + <item name="sudLoadingHeaderHeight">@dimen/sud_loading_header_height</item> </style> <style name="SudThemeMaterial.Light" parent="Theme.AppCompat.Light.NoActionBar"> @@ -60,8 +72,8 @@ <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> <item name="android:listPreferredItemHeight">@dimen/sud_items_preferred_height</item> - <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/sudMarginSides</item> - <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/sudMarginSides</item> + <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/sudMarginEnd</item> + <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/sudMarginStart</item> <item name="android:navigationBarColor" tools:ignore="NewApi">@android:color/black</item> <item name="android:statusBarColor" tools:ignore="NewApi">@android:color/black</item> <item name="android:textAppearanceListItemSmall" tools:ignore="NewApi">?attr/textAppearanceListItemSmall</item> @@ -72,21 +84,33 @@ <item name="colorAccent">@color/sud_color_accent_light</item> <item name="sudFillContentLayoutStyle">@style/SudFillContentLayout</item> - <item name="listPreferredItemPaddingLeft">?attr/sudMarginSides</item> - <item name="listPreferredItemPaddingRight">?attr/sudMarginSides</item> + <item name="listPreferredItemPaddingLeft">?attr/sudMarginStart</item> + <item name="listPreferredItemPaddingRight">?attr/sudMarginEnd</item> <item name="sudButtonAllCaps">true</item> <item name="sudButtonFontFamily">sans-serif</item> <item name="sudButtonHighlightAlpha">0.24</item> <item name="sudCardBackground">@drawable/sud_card_bg_light</item> + <item name="sudContentFramePaddingTop">@dimen/sud_content_frame_padding_top</item> + <item name="sudContentFramePaddingBottom">@dimen/sud_content_frame_padding_bottom</item> + <item name="sudLoadingContentFramePaddingTop">@dimen/sud_content_loading_frame_padding_top</item> + <item name="sudLoadingContentFramePaddingStart">@dimen/sud_content_loading_frame_padding_start</item> + <item name="sudLoadingContentFramePaddingEnd">@dimen/sud_content_loading_frame_padding_end</item> + <item name="sudLoadingContentFramePaddingBottom">@dimen/sud_content_loading_frame_padding_bottom</item> <item name="sudDividerInsetEnd">0dp</item> <item name="sudDividerInsetStart">@dimen/sud_items_icon_divider_inset</item> <item name="sudDividerInsetStartNoIcon">@dimen/sud_items_text_divider_inset</item> <item name="sudItemDescriptionStyle">@style/SudItemContainer.Description</item> <item name="sudItemDescriptionTitleStyle">@style/SudItemTitle</item> <item name="sudListItemIconColor">@color/sud_list_item_icon_color_light</item> - <item name="sudMarginSides">@dimen/sud_layout_margin_sides</item> + <item name="sudMarginStart">@dimen/sud_layout_margin_sides</item> + <item name="sudMarginEnd">@dimen/sud_layout_margin_sides</item> <item name="sudNavBarTheme">@style/SudNavBarThemeLight</item> <item name="textAppearanceListItemSmall">@style/TextAppearance.SudItemSummary</item> + <item name="sudContentIllustrationMaxWidth">@dimen/sud_content_illustration_max_width</item> + <item name="sudContentIllustrationMaxHeight">@dimen/sud_content_illustration_max_height</item> + <item name="sudContentIllustrationPaddingTop">@dimen/sud_content_illustration_padding_vertical</item> + <item name="sudContentIllustrationPaddingBottom">@dimen/sud_content_illustration_padding_vertical</item> + <item name="sudLoadingHeaderHeight">@dimen/sud_loading_header_height</item> </style> <style name="SudBaseThemeGlif" parent="Theme.AppCompat.NoActionBar"> @@ -95,8 +119,8 @@ <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> <item name="android:listPreferredItemHeight">@dimen/sud_items_preferred_height</item> - <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/sudMarginSides</item> - <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/sudMarginSides</item> + <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/sudMarginEnd</item> + <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/sudMarginStart</item> <item name="android:navigationBarColor" tools:ignore="NewApi">@android:color/black</item> <item name="android:statusBarColor" tools:ignore="NewApi">@android:color/transparent</item> <item name="android:textAppearanceListItemSmall" tools:ignore="NewApi">?attr/textAppearanceListItemSmall</item> @@ -105,27 +129,44 @@ <item name="android:windowDisablePreview">true</item> <item name="android:windowSoftInputMode">adjustResize</item> <item name="android:colorError" tools:targetApi="26">@color/sud_color_error_text_dark</item> + <item name="android:scrollbarThumbVertical">?attr/sudScrollBarThumb</item> <item name="colorAccent">@color/sud_color_accent_glif_dark</item> <item name="colorPrimary">?attr/colorAccent</item> - <item name="listPreferredItemPaddingLeft">?attr/sudMarginSides</item> - <item name="listPreferredItemPaddingRight">?attr/sudMarginSides</item> + <item name="listPreferredItemPaddingLeft">?attr/sudMarginStart</item> + <item name="listPreferredItemPaddingRight">?attr/sudMarginEnd</item> <item name="sudButtonAllCaps">true</item> <item name="sudButtonCornerRadius">@dimen/sud_glif_button_corner_radius</item> <item name="sudButtonFontFamily">sans-serif-medium</item> <item name="sudButtonHighlightAlpha">0.24</item> <item name="sudColorPrimary">?attr/colorPrimary</item> + <item name="sudContentFramePaddingTop">@dimen/sud_content_frame_padding_top</item> + <item name="sudContentFramePaddingBottom">@dimen/sud_content_frame_padding_bottom</item> + <item name="sudLoadingContentFramePaddingTop">@dimen/sud_content_loading_frame_padding_top</item> + <item name="sudLoadingContentFramePaddingStart">@dimen/sud_content_loading_frame_padding_start</item> + <item name="sudLoadingContentFramePaddingEnd">@dimen/sud_content_loading_frame_padding_end</item> + <item name="sudLoadingContentFramePaddingBottom">@dimen/sud_content_loading_frame_padding_bottom</item> <item name="sudFillContentLayoutStyle">@style/SudFillContentLayout</item> <item name="sudDividerInsetEnd">0dp</item> <item name="sudDividerInsetStart">@dimen/sud_items_glif_icon_divider_inset</item> <item name="sudDividerInsetStartNoIcon">@dimen/sud_items_glif_text_divider_inset</item> <item name="sudGlifHeaderGravity">start</item> + <item name="sudGlifSubtitleGravity">start</item> + <item name="sucGlifHeaderMarginTop">@dimen/sud_glif_header_title_margin_top</item> + <item name="sudGlifDescriptionMarginTop">@dimen/sud_glif_description_margin_top</item> + <item name="sucGlifHeaderMarginBottom">@dimen/sud_glif_header_title_margin_bottom</item> + <item name="sudGlifDescriptionMarginBottom">@dimen/sud_glif_description_margin_bottom</item> + <item name="sucHeaderContainerMarginBottom">@dimen/sud_header_container_margin_bottom</item> <item name="sudGlifIconStyle">@style/SudGlifIcon</item> + <item name="sucGlifIconMarginTop">@dimen/sud_glif_icon_margin_top</item> + <item name="sudGlifIconSize">@dimen/sud_glif_icon_max_height</item> <item name="sudItemDescriptionStyle">@style/SudItemContainer.Description.Glif</item> <item name="sudItemDescriptionTitleStyle">@style/SudItemTitle.GlifDescription</item> <item name="sudListItemIconColor">@color/sud_list_item_icon_color_dark</item> - <item name="sudMarginSides">@dimen/sud_glif_margin_sides</item> + <item name="sudMarginStart">@dimen/sud_glif_margin_start</item> + <item name="sudMarginEnd">@dimen/sud_glif_margin_end</item> <item name="sudScrollIndicators">bottom</item> + <item name="sudScrollBarThumb">@drawable/sud_scroll_bar_dark</item> <item name="textAppearanceListItem">@style/TextAppearance.SudGlifItemTitle</item> <item name="textAppearanceListItemSmall">@style/TextAppearance.SudGlifItemSummary</item> <item name="sucFooterBarButtonFontFamily">?attr/sudButtonFontFamily</item> @@ -136,6 +177,16 @@ <item name="sucStatusBarBackground">?android:attr/colorBackground</item> <item name="sucSystemNavBarBackgroundColor">@android:color/black</item> <item name="sucFooterBarPaddingVertical">@dimen/sud_glif_footer_padding_vertical</item> + <item name="sucFooterBarPaddingStart">@dimen/sud_glif_footer_padding_start</item> + <item name="sucFooterBarPaddingEnd">@dimen/sud_glif_footer_padding_end</item> + <item name="sucFooterBarMinHeight">@dimen/sud_glif_footer_min_height</item> + <item name="sucFooterButtonPaddingStart">@dimen/sud_glif_button_padding</item> + <item name="sucFooterButtonPaddingEnd">@dimen/sud_glif_button_padding</item> + <item name="sudContentIllustrationMaxWidth">@dimen/sud_content_illustration_max_width</item> + <item name="sudContentIllustrationMaxHeight">@dimen/sud_content_illustration_max_height</item> + <item name="sudContentIllustrationPaddingTop">@dimen/sud_content_illustration_padding_vertical</item> + <item name="sudContentIllustrationPaddingBottom">@dimen/sud_content_illustration_padding_vertical</item> + <item name="sudLoadingHeaderHeight">@dimen/sud_loading_header_height</item> </style> <style name="SudThemeGlif" parent="SudBaseThemeGlif"/> @@ -145,8 +196,8 @@ <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> <item name="android:listPreferredItemHeight">@dimen/sud_items_preferred_height</item> - <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/sudMarginSides</item> - <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/sudMarginSides</item> + <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/sudMarginEnd</item> + <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/sudMarginStart</item> <item name="android:navigationBarColor" tools:ignore="NewApi">@android:color/black</item> <item name="android:statusBarColor" tools:ignore="NewApi">@android:color/transparent</item> <item name="android:textAppearanceListItemSmall" tools:ignore="NewApi">?attr/textAppearanceListItemSmall</item> @@ -155,27 +206,44 @@ <item name="android:windowDisablePreview">true</item> <item name="android:windowSoftInputMode">adjustResize</item> <item name="android:colorError" tools:targetApi="26">@color/sud_color_error_text_light</item> + <item name="android:scrollbarThumbVertical">?attr/sudScrollBarThumb</item> <item name="colorAccent">@color/sud_color_accent_glif_light</item> <item name="colorPrimary">?attr/colorAccent</item> - <item name="listPreferredItemPaddingLeft">?attr/sudMarginSides</item> - <item name="listPreferredItemPaddingRight">?attr/sudMarginSides</item> + <item name="listPreferredItemPaddingLeft">?attr/sudMarginStart</item> + <item name="listPreferredItemPaddingRight">?attr/sudMarginEnd</item> <item name="sudButtonAllCaps">true</item> <item name="sudButtonCornerRadius">@dimen/sud_glif_button_corner_radius</item> <item name="sudButtonFontFamily">sans-serif-medium</item> <item name="sudButtonHighlightAlpha">0.12</item> <item name="sudColorPrimary">?attr/colorPrimary</item> + <item name="sudContentFramePaddingTop">@dimen/sud_content_frame_padding_top</item> + <item name="sudContentFramePaddingBottom">@dimen/sud_content_frame_padding_bottom</item> + <item name="sudLoadingContentFramePaddingTop">@dimen/sud_content_loading_frame_padding_top</item> + <item name="sudLoadingContentFramePaddingStart">@dimen/sud_content_loading_frame_padding_start</item> + <item name="sudLoadingContentFramePaddingEnd">@dimen/sud_content_loading_frame_padding_end</item> + <item name="sudLoadingContentFramePaddingBottom">@dimen/sud_content_loading_frame_padding_bottom</item> <item name="sudFillContentLayoutStyle">@style/SudFillContentLayout</item> <item name="sudDividerInsetEnd">0dp</item> <item name="sudDividerInsetStart">@dimen/sud_items_glif_icon_divider_inset</item> <item name="sudDividerInsetStartNoIcon">@dimen/sud_items_glif_text_divider_inset</item> <item name="sudGlifHeaderGravity">start</item> + <item name="sudGlifSubtitleGravity">start</item> + <item name="sucGlifHeaderMarginTop">@dimen/sud_glif_header_title_margin_top</item> + <item name="sudGlifDescriptionMarginTop">@dimen/sud_glif_description_margin_top</item> + <item name="sucGlifHeaderMarginBottom">@dimen/sud_glif_header_title_margin_bottom</item> + <item name="sudGlifDescriptionMarginBottom">@dimen/sud_glif_description_margin_bottom</item> + <item name="sucHeaderContainerMarginBottom">@dimen/sud_header_container_margin_bottom</item> <item name="sudGlifIconStyle">@style/SudGlifIcon</item> + <item name="sucGlifIconMarginTop">@dimen/sud_glif_icon_margin_top</item> + <item name="sudGlifIconSize">@dimen/sud_glif_icon_max_height</item> <item name="sudItemDescriptionStyle">@style/SudItemContainer.Description.Glif</item> <item name="sudItemDescriptionTitleStyle">@style/SudItemTitle.GlifDescription</item> <item name="sudListItemIconColor">@color/sud_list_item_icon_color_light</item> - <item name="sudMarginSides">@dimen/sud_glif_margin_sides</item> + <item name="sudMarginStart">@dimen/sud_glif_margin_start</item> + <item name="sudMarginEnd">@dimen/sud_glif_margin_end</item> <item name="sudScrollIndicators">bottom</item> + <item name="sudScrollBarThumb">@drawable/sud_scroll_bar_light</item> <item name="textAppearanceListItem">@style/TextAppearance.SudGlifItemTitle</item> <item name="textAppearanceListItemSmall">@style/TextAppearance.SudGlifItemSummary</item> <item name="sucFooterBarButtonFontFamily">?attr/sudButtonFontFamily</item> @@ -186,6 +254,16 @@ <item name="sucStatusBarBackground">?android:attr/colorBackground</item> <item name="sucSystemNavBarBackgroundColor">@android:color/black</item> <item name="sucFooterBarPaddingVertical">@dimen/sud_glif_footer_padding_vertical</item> + <item name="sucFooterBarPaddingStart">@dimen/sud_glif_footer_padding_start</item> + <item name="sucFooterBarPaddingEnd">@dimen/sud_glif_footer_padding_end</item> + <item name="sucFooterBarMinHeight">@dimen/sud_glif_footer_min_height</item> + <item name="sucFooterButtonPaddingStart">@dimen/sud_glif_button_padding</item> + <item name="sucFooterButtonPaddingEnd">@dimen/sud_glif_button_padding</item> + <item name="sudContentIllustrationMaxWidth">@dimen/sud_content_illustration_max_width</item> + <item name="sudContentIllustrationMaxHeight">@dimen/sud_content_illustration_max_height</item> + <item name="sudContentIllustrationPaddingTop">@dimen/sud_content_illustration_padding_vertical</item> + <item name="sudContentIllustrationPaddingBottom">@dimen/sud_content_illustration_padding_vertical</item> + <item name="sudLoadingHeaderHeight">@dimen/sud_loading_header_height</item> </style> <style name="SudThemeGlif.Light" parent="SudBaseThemeGlif.Light"/> @@ -195,13 +273,15 @@ <item name="sudBackgroundBaseColor">?android:attr/colorBackground</item> <item name="sudBackgroundPatterned">false</item> - <item name="sudDividerInsetEnd">?attr/sudMarginSides</item> - <item name="sudDividerInsetStart">?attr/sudMarginSides</item> - <item name="sudDividerInsetStartNoIcon">?attr/sudMarginSides</item> + <item name="sudDividerInsetEnd">?attr/sudMarginEnd</item> + <item name="sudDividerInsetStart">?attr/sudMarginStart</item> + <item name="sudDividerInsetStartNoIcon">?attr/sudMarginStart</item> <item name="sudGlifHeaderGravity">center_horizontal</item> + <item name="sudGlifSubtitleGravity">center_horizontal</item> <item name="sudScrollIndicators">top|bottom</item> <item name="sudEditTextBackgroundColor">@color/sud_glif_edit_text_bg_dark_color</item> <item name="android:editTextStyle">@style/SudEditText</item> + <item name="alertDialogTheme">@style/SudAlertDialogThemeCompat</item> <item name="android:alertDialogTheme" tools:targetApi="honeycomb">@style/SudAlertDialogTheme</item> <item name="sucLightStatusBar" tools:targetApi="m">?android:attr/windowLightStatusBar</item> </style> @@ -212,18 +292,20 @@ <item name="sudBackgroundBaseColor">?android:attr/colorBackground</item> <item name="sudBackgroundPatterned">false</item> - <item name="sudDividerInsetEnd">?attr/sudMarginSides</item> - <item name="sudDividerInsetStart">?attr/sudMarginSides</item> - <item name="sudDividerInsetStartNoIcon">?attr/sudMarginSides</item> + <item name="sudDividerInsetEnd">?attr/sudMarginEnd</item> + <item name="sudDividerInsetStart">?attr/sudMarginStart</item> + <item name="sudDividerInsetStartNoIcon">?attr/sudMarginStart</item> <item name="sudGlifHeaderGravity">center_horizontal</item> + <item name="sudGlifSubtitleGravity">center_horizontal</item> <item name="sudScrollIndicators">top|bottom</item> <item name="sudEditTextBackgroundColor">@color/sud_glif_edit_text_bg_light_color</item> <item name="android:editTextStyle">@style/SudEditText</item> + <item name="alertDialogTheme">@style/SudAlertDialogThemeCompat.Light</item> <item name="android:alertDialogTheme" tools:targetApi="honeycomb">@style/SudAlertDialogTheme.Light</item> <item name="sucLightStatusBar" tools:targetApi="m">?android:attr/windowLightStatusBar</item> </style> - <style name="SudThemeGlifV3" parent="SudThemeGlifV2"> + <style name="SudBaseThemeGlifV3" parent="SudThemeGlifV2"> <item name="colorAccent">@color/sud_color_accent_glif_v3_dark</item> <item name="colorBackgroundFloating">@color/sud_glif_v3_dialog_background_color_dark</item> <item name="android:datePickerDialogTheme">@style/SudDateTimePickerDialogTheme</item> @@ -233,6 +315,9 @@ <item name="sudButtonCornerRadius">@dimen/sud_glif_v3_button_corner_radius</item> <item name="sudButtonFontFamily">@string/sudFontSecondaryMedium</item> </style> + <style name="SudThemeGlifV3" parent="SudBaseThemeGlifV3" /> + <style name="SudDynamicColorThemeGlifV3" parent="SudThemeGlifV3" /> + <style name="SudFullDynamicColorThemeGlifV3" parent="SudDynamicColorThemeGlifV3" /> <style name="SudBaseThemeGlifV3.Light" parent="SudThemeGlifV2.Light"> <item name="colorAccent">@color/sud_color_accent_glif_v3_light</item> @@ -244,6 +329,8 @@ <item name="sudButtonFontFamily">@string/sudFontSecondaryMedium</item> </style> <style name="SudThemeGlifV3.Light" parent="SudBaseThemeGlifV3.Light" /> + <style name="SudDynamicColorThemeGlifV3.Light" parent="SudThemeGlifV3.Light" /> + <style name="SudFullDynamicColorThemeGlifV3.Light" parent="SudDynamicColorThemeGlifV3.Light" /> <style name="Animation.SudWindowAnimation" parent="@android:style/Animation.Activity"> <item name="android:activityOpenEnterAnimation">@anim/sud_slide_next_in</item> @@ -257,16 +344,38 @@ <style name="SudThemeGlif.DayNight" parent="SudThemeGlif.Light" /> <style name="SudThemeGlifV2.DayNight" parent="SudThemeGlifV2.Light" /> <style name="SudThemeGlifV3.DayNight" parent="SudThemeGlifV3.Light" /> + <style name="SudDynamicColorThemeGlifV3.DayNight" parent="SudDynamicColorThemeGlifV3.Light" /> + <style name="SudFullDynamicColorThemeGlifV3.DayNight" parent="SudFullDynamicColorThemeGlifV3.Light" /> <!-- Content styles --> <!-- Ignore UnusedResources: Used by clients --> <style name="SudContentFrame" tools:ignore="UnusedResources"> <item name="android:clipToPadding">false</item> - <item name="android:paddingTop">@dimen/sud_content_frame_padding_top</item> - <item name="android:paddingLeft">?attr/sudMarginSides</item> - <item name="android:paddingRight">?attr/sudMarginSides</item> - <item name="android:paddingBottom">@dimen/sud_content_frame_padding_bottom</item> + <item name="android:paddingTop">?attr/sudContentFramePaddingTop</item> + <item name="android:paddingLeft">?attr/sudMarginStart</item> + <item name="android:paddingRight">?attr/sudMarginEnd</item> + <item name="android:paddingBottom">?attr/sudContentFramePaddingBottom</item> + </style> + + <style name="SudLoadingContentFrame" tools:ignore="UnusedResources"> + <item name="android:clipToPadding">false</item> + <item name="android:paddingTop">?attr/sudLoadingContentFramePaddingTop</item> + <item name="android:paddingLeft">?attr/sudLoadingContentFramePaddingStart</item> + <item name="android:paddingRight">?attr/sudLoadingContentFramePaddingEnd</item> + <item name="android:paddingBottom">?attr/sudLoadingContentFramePaddingBottom</item> + </style> + + <!-- Content info --> + + <style name="SudInfoContainer"> + <item name="android:paddingTop">@dimen/sud_content_info_padding_top</item> + <item name="android:paddingBottom">@dimen/sud_content_info_padding_bottom</item> + </style> + + <style name="SudInfoDescription"> + <item name="android:textSize">@dimen/sud_content_info_text_size</item> + <item name="android:lineSpacingExtra">@dimen/sud_content_info_line_spacing_extra</item> </style> <!-- Ignore UnusedResources: Used by clients --> @@ -338,10 +447,10 @@ <style name="SudFillContentLayout"> <item name="android:minWidth">@dimen/sud_content_illustration_min_width</item> <item name="android:minHeight">@dimen/sud_content_illustration_min_height</item> - <item name="android:maxWidth">@dimen/sud_content_illustration_max_width</item> - <item name="android:maxHeight">@dimen/sud_content_illustration_max_height</item> - <item name="android:paddingTop">@dimen/sud_content_illustration_padding_vertical</item> - <item name="android:paddingBottom">@dimen/sud_content_illustration_padding_vertical</item> + <item name="android:maxWidth">?attr/sudContentIllustrationMaxWidth</item> + <item name="android:maxHeight">?attr/sudContentIllustrationMaxHeight</item> + <item name="android:paddingTop">?attr/sudContentIllustrationPaddingTop</item> + <item name="android:paddingBottom">?attr/sudContentIllustrationPaddingBottom</item> </style> <!-- Ignore UnusedResources: used by clients --> @@ -404,8 +513,8 @@ <style name="SudHeaderTitle" parent="SudBaseHeaderTitle"> <item name="android:layout_marginBottom">@dimen/sud_header_title_margin_bottom</item> - <item name="android:layout_marginLeft">?attr/sudMarginSides</item> - <item name="android:layout_marginRight">?attr/sudMarginSides</item> + <item name="android:layout_marginLeft">?attr/sudMarginStart</item> + <item name="android:layout_marginRight">?attr/sudMarginEnd</item> <item name="android:lineSpacingExtra">@dimen/sud_header_title_line_spacing_extra</item> <item name="android:paddingBottom">@dimen/sud_header_title_padding_bottom</item> <item name="android:paddingTop">@dimen/sud_header_title_padding_top</item> @@ -510,12 +619,12 @@ <style name="SudItemContainer"> <item name="android:minHeight">?android:attr/listPreferredItemHeight</item> - <item name="android:paddingBottom">@dimen/sud_items_padding_vertical</item> + <item name="android:paddingBottom">@dimen/sud_items_padding_bottom</item> <item name="android:paddingEnd" tools:ignore="NewApi">?attr/listPreferredItemPaddingRight</item> <item name="android:paddingLeft">?attr/listPreferredItemPaddingLeft</item> <item name="android:paddingRight">?attr/listPreferredItemPaddingRight</item> <item name="android:paddingStart" tools:ignore="NewApi">?attr/listPreferredItemPaddingLeft</item> - <item name="android:paddingTop">@dimen/sud_items_padding_vertical</item> + <item name="android:paddingTop">@dimen/sud_items_padding_top</item> </style> <style name="SudItemContainer.Description" parent="SudItemContainer"> @@ -577,24 +686,44 @@ <style name="SudGlifHeaderTitle" parent="SudBaseHeaderTitle"> <item name="android:gravity">?attr/sudGlifHeaderGravity</item> - <item name="android:layout_marginBottom">@dimen/sud_glif_header_title_margin_bottom</item> - <item name="android:layout_marginLeft">?attr/sudMarginSides</item> - <item name="android:layout_marginRight">?attr/sudMarginSides</item> - <item name="android:layout_marginTop">@dimen/sud_glif_header_title_margin_top</item> + <item name="android:layout_marginBottom">?attr/sucGlifHeaderMarginBottom</item> + <item name="android:layout_marginLeft">?attr/sudMarginStart</item> + <item name="android:layout_marginRight">?attr/sudMarginEnd</item> + <item name="android:layout_marginTop">?attr/sucGlifHeaderMarginTop</item> <item name="android:fontFamily" tools:targetApi="jelly_bean">@string/sudFontSecondary</item> <item name="android:textAlignment" tools:targetApi="jelly_bean_mr1">gravity</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> + + <style name="SudGlifDescription" parent="SudDescription.Glif"> + <item name="android:layout_marginTop">?attr/sudGlifDescriptionMarginTop</item> + <item name="android:layout_marginBottom">?attr/sudGlifDescriptionMarginBottom</item> + <item name="android:layout_marginLeft">?attr/sudMarginStart</item> + <item name="android:layout_marginStart">?attr/sudMarginStart</item> + <item name="android:layout_marginRight">?attr/sudMarginEnd</item> + <item name="android:layout_marginEnd">?attr/sudMarginEnd</item> + <item name="android:fontFamily" tools:targetApi="jelly_bean">@string/sudFontSecondary</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + <style name="SudGlifHeaderContainer"> <item name="android:gravity">?attr/sudGlifHeaderGravity</item> + <item name="android:layout_marginBottom">?attr/sucHeaderContainerMarginBottom</item> + </style> + + <style name="SudGlifIconContainer"> + <item name="android:layout_marginLeft">?attr/sudMarginStart</item> + <item name="android:layout_marginRight">?attr/sudMarginEnd</item> + <item name="android:layout_marginTop">?attr/sucGlifIconMarginTop</item> + <item name="android:maxHeight">?attr/sudGlifIconSize</item> </style> <style name="SudGlifIcon"> - <item name="android:layout_marginLeft">?attr/sudMarginSides</item> - <item name="android:layout_marginRight">?attr/sudMarginSides</item> - <item name="android:layout_marginTop">@dimen/sud_glif_margin_top</item> + <item name="android:layout_marginLeft">?attr/sudMarginStart</item> + <item name="android:layout_marginRight">?attr/sudMarginEnd</item> + <item name="android:layout_marginTop">?attr/sucGlifIconMarginTop</item> <item name="android:adjustViewBounds">true</item> - <item name="android:maxHeight">@dimen/sud_glif_icon_max_height</item> + <item name="android:maxHeight">?attr/sudGlifIconSize</item> <item name="android:scaleType">centerInside</item> </style> @@ -604,12 +733,12 @@ </style> <style name="TextAppearance.SudGlifItemTitle" parent="android:TextAppearance"> - <item name="android:textSize">16sp</item> + <item name="android:textSize">@dimen/sud_items_title_text_size</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> <style name="TextAppearance.SudGlifItemSummary" parent="android:TextAppearance"> - <item name="android:textSize">14sp</item> + <item name="android:textSize">@dimen/sud_items_summary_text_size</item> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> @@ -651,29 +780,42 @@ <item name="android:minHeight">@dimen/sud_edit_text_min_height</item> </style> - <style name="SudAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert"> + <style name="SudAlertDialogThemeCompat" parent="Theme.AppCompat.Dialog.Alert"> <item name="android:textAllCaps">false</item> <item name="android:colorBackground">@color/sud_glif_v3_dialog_background_color_dark</item> <item name="colorAccent">@color/sud_color_accent_glif_v3_dark</item> <item name="dialogCornerRadius">@dimen/sud_glif_alert_dialog_corner_radius</item> </style> - <style name="SudAlertDialogTheme.Light" parent="Theme.AppCompat.Light.Dialog.Alert"> + <style name="SudAlertDialogThemeCompat.Light" parent="Theme.AppCompat.Light.Dialog.Alert"> <item name="android:textAllCaps">false</item> <item name="colorAccent">@color/sud_color_accent_glif_v3_light</item> <item name="dialogCornerRadius">@dimen/sud_glif_alert_dialog_corner_radius</item> </style> + <style name="SudAlertDialogTheme" parent="SudAlertDialogThemeCompat"/> + <style name="SudAlertDialogTheme.Light" parent="SudAlertDialogThemeCompat.Light"/> + <style name="SudDateTimePickerDialogTheme" parent="Theme.AppCompat.Dialog"> <item name="android:textAllCaps">false</item> <item name="colorAccent">@color/sud_color_accent_glif_v3_dark</item> <item name="dialogCornerRadius">@dimen/sud_glif_alert_dialog_corner_radius</item> + <item name="android:windowBackground">@drawable/sud_dialog_background_dark</item> </style> <style name="SudDateTimePickerDialogTheme.Light" parent="Theme.AppCompat.Light.Dialog"> <item name="android:textAllCaps">false</item> <item name="colorAccent">@color/sud_color_accent_glif_v3_light</item> <item name="dialogCornerRadius">@dimen/sud_glif_alert_dialog_corner_radius</item> + <item name="android:windowBackground">@drawable/sud_dialog_background_light</item> + </style> + + <style name="SudSwitchBarStyle"> + <item name="android:layout_gravity">center_vertical</item> + <item name="track">@drawable/sud_switch_track_selector</item> + <item name="android:track">@drawable/sud_switch_track_selector</item> + <item name="android:thumb">@drawable/sud_switch_thumb_selector</item> + <item name="android:switchMinWidth">@dimen/sud_switch_min_width</item> </style> </resources> diff --git a/main/src/com/google/android/setupdesign/DividerItemDecoration.java b/main/src/com/google/android/setupdesign/DividerItemDecoration.java index fa0166f..df86b23 100644 --- a/main/src/com/google/android/setupdesign/DividerItemDecoration.java +++ b/main/src/com/google/android/setupdesign/DividerItemDecoration.java @@ -21,10 +21,10 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import androidx.annotation.IntDef; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.RecyclerView; import android.view.View; +import androidx.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -125,7 +125,7 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration { } } - private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { + protected boolean shouldDrawDividerBelow(View view, RecyclerView parent) { final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); final int index = holder.getLayoutPosition(); final int lastItemIndex = parent.getAdapter().getItemCount() - 1; diff --git a/main/src/com/google/android/setupdesign/GlifLayout.java b/main/src/com/google/android/setupdesign/GlifLayout.java index de4f8c1..829e3d7 100644 --- a/main/src/com/google/android/setupdesign/GlifLayout.java +++ b/main/src/com/google/android/setupdesign/GlifLayout.java @@ -22,11 +22,8 @@ import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Build.VERSION_CODES; -import androidx.annotation.ColorInt; -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -35,16 +32,25 @@ import android.view.ViewStub; import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.TextView; +import androidx.annotation.ColorInt; +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import com.google.android.setupcompat.PartnerCustomizationLayout; import com.google.android.setupcompat.partnerconfig.PartnerConfig; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupcompat.template.StatusBarMixin; +import com.google.android.setupdesign.template.DescriptionMixin; import com.google.android.setupdesign.template.HeaderMixin; import com.google.android.setupdesign.template.IconMixin; +import com.google.android.setupdesign.template.IllustrationProgressMixin; import com.google.android.setupdesign.template.ProgressBarMixin; import com.google.android.setupdesign.template.RequireScrollMixin; import com.google.android.setupdesign.template.ScrollViewScrollHandlingDelegate; import com.google.android.setupdesign.util.DescriptionStyler; +import com.google.android.setupdesign.util.LayoutStyler; +import com.google.android.setupdesign.util.PartnerStyleHelper; /** * Layout for the GLIF theme used in Setup Wizard for N. @@ -67,8 +73,6 @@ import com.google.android.setupdesign.util.DescriptionStyler; */ public class GlifLayout extends PartnerCustomizationLayout { - private static final String TAG = "GlifLayout"; - private ColorStateList primaryColor; private boolean backgroundPatterned = true; @@ -105,6 +109,9 @@ public class GlifLayout extends PartnerCustomizationLayout { // All the constructors delegate to this init method. The 3-argument constructor is not // available in LinearLayout before v11, so call super with the exact same arguments. private void init(AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SudGlifLayout, defStyleAttr, 0); @@ -113,8 +120,10 @@ public class GlifLayout extends PartnerCustomizationLayout { applyPartnerHeavyThemeResource = shouldApplyPartnerResource() && usePartnerHeavyTheme; registerMixin(HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr)); + registerMixin(DescriptionMixin.class, new DescriptionMixin(this, attrs, defStyleAttr)); registerMixin(IconMixin.class, new IconMixin(this, attrs, defStyleAttr)); - registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this)); + registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this, attrs, defStyleAttr)); + registerMixin(IllustrationProgressMixin.class, new IllustrationProgressMixin(this)); final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this); registerMixin(RequireScrollMixin.class, requireScrollMixin); @@ -131,7 +140,21 @@ public class GlifLayout extends PartnerCustomizationLayout { if (applyPartnerHeavyThemeResource) { updateContentBackgroundColorWithPartnerConfig(); + + View view = findManagedViewById(R.id.sud_layout_content); + if (view != null) { + // The margin of content is defined by @style/SudContentFrame. The Setupdesign library + // cannot obtain the content resource ID of the client, so the value of the content margin + // cannot be adjusted through GlifLayout. If the margin sides are changed through the + // partner config, it can only be based on the increased or decreased value to adjust the + // value of pading. In this way, the value of content margin plus padding will be equal to + // the value of partner config. + LayoutStyler.applyPartnerCustomizationExtraPaddingStyle(view); + + applyPartnerCustomizationContentPaddingTopStyle(view); + } } + updateLandscapeMiddleHorizontalSpacing(); ColorStateList backgroundColor = a.getColorStateList(R.styleable.SudGlifLayout_sudBackgroundBaseColor); @@ -153,15 +176,79 @@ public class GlifLayout extends PartnerCustomizationLayout { super.onFinishInflate(); getMixin(IconMixin.class).tryApplyPartnerCustomizationStyle(); getMixin(HeaderMixin.class).tryApplyPartnerCustomizationStyle(); + getMixin(DescriptionMixin.class).tryApplyPartnerCustomizationStyle(); tryApplyPartnerCustomizationStyleToShortDescription(); } + // TODO: remove when all sud_layout_description has migrated to + // DescriptionMixin(sud_layout_subtitle) private void tryApplyPartnerCustomizationStyleToShortDescription() { - if (applyPartnerHeavyThemeResource) { - TextView description = - this.findManagedViewById(com.google.android.setupdesign.R.id.sud_layout_description); - if (description != null) { - DescriptionStyler.applyPartnerCustomizationStyle(description); + TextView description = this.findManagedViewById(R.id.sud_layout_description); + if (description != null) { + if (applyPartnerHeavyThemeResource) { + DescriptionStyler.applyPartnerCustomizationHeavyStyle(description); + } else if (shouldApplyPartnerResource()) { + DescriptionStyler.applyPartnerCustomizationLightStyle(description); + } + } + } + + protected void updateLandscapeMiddleHorizontalSpacing() { + int horizontalSpacing = + getResources().getDimensionPixelSize(R.dimen.sud_glif_land_middle_horizontal_spacing); + + View headerView = this.findManagedViewById(R.id.sud_landscape_header_area); + if (headerView != null) { + if (PartnerConfigHelper.get(getContext()) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_END)) { + int layoutMarginEnd = + (int) + PartnerConfigHelper.get(getContext()) + .getDimension(getContext(), PartnerConfig.CONFIG_LAYOUT_MARGIN_END); + + int paddingEnd = (horizontalSpacing / 2) - layoutMarginEnd; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + headerView.setPadding( + headerView.getPaddingStart(), + headerView.getPaddingTop(), + paddingEnd, + headerView.getPaddingBottom()); + } else { + headerView.setPadding( + headerView.getPaddingLeft(), + headerView.getPaddingTop(), + paddingEnd, + headerView.getPaddingBottom()); + } + } + } + + View contentView = this.findManagedViewById(R.id.sud_landscape_content_area); + if (contentView != null) { + if (PartnerConfigHelper.get(getContext()) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_START)) { + int layoutMarginStart = + (int) + PartnerConfigHelper.get(getContext()) + .getDimension(getContext(), PartnerConfig.CONFIG_LAYOUT_MARGIN_START); + + int paddingStart = 0; + if (headerView != null) { + paddingStart = (horizontalSpacing / 2) - layoutMarginStart; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + contentView.setPadding( + paddingStart, + contentView.getPaddingTop(), + contentView.getPaddingEnd(), + contentView.getPaddingBottom()); + } else { + contentView.setPadding( + paddingStart, + contentView.getPaddingTop(), + contentView.getPaddingRight(), + contentView.getPaddingBottom()); + } } } } @@ -187,8 +274,8 @@ public class GlifLayout extends PartnerCustomizationLayout { * the content area outside of the scrolling container. The header can only be inflated once per * instance of this layout. * - * @param header The layout to be inflated as the header. - * @return The root of the inflated header view. + * @param header The layout to be inflated as the header + * @return The root of the inflated header view */ public View inflateStickyHeader(@LayoutRes int header) { ViewStub stickyHeaderStub = findManagedViewById(R.id.sud_layout_sticky_header); @@ -217,6 +304,35 @@ public class GlifLayout extends PartnerCustomizationLayout { return getMixin(HeaderMixin.class).getText(); } + public TextView getDescriptionTextView() { + return getMixin(DescriptionMixin.class).getTextView(); + } + + /** + * Sets the description text and also sets the text visibility to visible. This can also be set + * via the XML attribute {@code app:sudDescriptionText}. + * + * @param title The resource ID of the text to be set as description + */ + public void setDescriptionText(@StringRes int title) { + getMixin(DescriptionMixin.class).setText(title); + } + + /** + * Sets the description text and also sets the text visibility to visible. This can also be set + * via the XML attribute {@code app:sudDescriptionText}. + * + * @param title The text to be set as description + */ + public void setDescriptionText(CharSequence title) { + getMixin(DescriptionMixin.class).setText(title); + } + + /** Returns the current description text. */ + public CharSequence getDescriptionText() { + return getMixin(DescriptionMixin.class).getText(); + } + public void setHeaderColor(ColorStateList color) { getMixin(HeaderMixin.class).setTextColor(color); } @@ -234,6 +350,24 @@ public class GlifLayout extends PartnerCustomizationLayout { } /** + * Sets the visibility of header area in landscape mode. These views inlcudes icon, header title + * and subtitle. It can make the content view become full screen when set false. + */ + @TargetApi(Build.VERSION_CODES.S) + public void setLandscapeHeaderAreaVisible(boolean visible) { + View view = this.findManagedViewById(R.id.sud_landscape_header_area); + if (view == null) { + return; + } + if (visible) { + view.setVisibility(View.VISIBLE); + } else { + view.setVisibility(View.GONE); + } + updateLandscapeMiddleHorizontalSpacing(); + } + + /** * Sets the primary color of this layout, which will be used to determine the color of the * progress bar and the background pattern. */ @@ -253,7 +387,7 @@ public class GlifLayout extends PartnerCustomizationLayout { * drawn with this color. * * @param color The color to use as the base color of the background. If {@code null}, {@link - * #getPrimaryColor()} will be used. + * #getPrimaryColor()} will be used */ public void setBackgroundBaseColor(@Nullable ColorStateList color) { backgroundBaseColor = color; @@ -316,15 +450,45 @@ public class GlifLayout extends PartnerCustomizationLayout { * Returns if the current layout/activity applies heavy partner customized configurations or not. */ public boolean shouldApplyPartnerHeavyThemeResource() { - return applyPartnerHeavyThemeResource; + + return applyPartnerHeavyThemeResource + || (shouldApplyPartnerResource() + && PartnerConfigHelper.shouldApplyExtendedPartnerConfig(getContext())); } /** Updates the background color of this layout with the partner-customizable background color. */ private void updateContentBackgroundColorWithPartnerConfig() { + // If full dynamic color enabled which means this activity is running outside of setup + // flow, the colors should refer to R.style.SudFullDynamicColorThemeGlifV3. + if (useFullDynamicColor()) { + return; + } + @ColorInt int color = PartnerConfigHelper.get(getContext()) .getColor(getContext(), PartnerConfig.CONFIG_LAYOUT_BACKGROUND_COLOR); this.getRootView().setBackgroundColor(color); } + + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + protected static void applyPartnerCustomizationContentPaddingTopStyle(View view) { + Context context = view.getContext(); + boolean partnerPaddingTopAvailable = + PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CONTENT_PADDING_TOP); + + if (PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(view) + && partnerPaddingTopAvailable) { + int paddingTop = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_CONTENT_PADDING_TOP); + + if (paddingTop != view.getPaddingTop()) { + view.setPadding( + view.getPaddingStart(), paddingTop, view.getPaddingEnd(), view.getPaddingBottom()); + } + } + } } diff --git a/main/src/com/google/android/setupdesign/GlifListLayout.java b/main/src/com/google/android/setupdesign/GlifListLayout.java index 31335cc..60f0343 100644 --- a/main/src/com/google/android/setupdesign/GlifListLayout.java +++ b/main/src/com/google/android/setupdesign/GlifListLayout.java @@ -63,12 +63,22 @@ public class GlifListLayout extends GlifLayout { } private void init(AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + listMixin = new ListMixin(this, attrs, defStyleAttr); registerMixin(ListMixin.class, listMixin); final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); requireScrollMixin.setScrollHandlingDelegate( new ListViewScrollHandlingDelegate(requireScrollMixin, getListView())); + + View view = this.findManagedViewById(R.id.sud_landscape_content_area); + if (view != null) { + applyPartnerCustomizationContentPaddingTopStyle(view); + } + updateLandscapeMiddleHorizontalSpacing(); } @Override diff --git a/main/src/com/google/android/setupdesign/GlifRecyclerLayout.java b/main/src/com/google/android/setupdesign/GlifRecyclerLayout.java index c74cff0..5e75436 100644 --- a/main/src/com/google/android/setupdesign/GlifRecyclerLayout.java +++ b/main/src/com/google/android/setupdesign/GlifRecyclerLayout.java @@ -64,12 +64,22 @@ public class GlifRecyclerLayout extends GlifLayout { } private void init(AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + recyclerMixin.parseAttributes(attrs, defStyleAttr); registerMixin(RecyclerMixin.class, recyclerMixin); final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); requireScrollMixin.setScrollHandlingDelegate( new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView())); + + View view = this.findManagedViewById(R.id.sud_landscape_content_area); + if (view != null) { + applyPartnerCustomizationContentPaddingTopStyle(view); + } + updateLandscapeMiddleHorizontalSpacing(); } @Override diff --git a/main/src/com/google/android/setupdesign/SetupWizardItemsLayout.java b/main/src/com/google/android/setupdesign/SetupWizardItemsLayout.java index 2f3dd86..28f95fb 100644 --- a/main/src/com/google/android/setupdesign/SetupWizardItemsLayout.java +++ b/main/src/com/google/android/setupdesign/SetupWizardItemsLayout.java @@ -17,9 +17,9 @@ package com.google.android.setupdesign; import android.content.Context; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.widget.ListAdapter; +import androidx.annotation.Nullable; import com.google.android.setupdesign.items.ItemAdapter; /** @deprecated Use {@link SetupWizardListLayout} instead. */ diff --git a/main/src/com/google/android/setupdesign/SetupWizardLayout.java b/main/src/com/google/android/setupdesign/SetupWizardLayout.java index 5797aa7..b91cec7 100644 --- a/main/src/com/google/android/setupdesign/SetupWizardLayout.java +++ b/main/src/com/google/android/setupdesign/SetupWizardLayout.java @@ -40,6 +40,7 @@ import android.widget.ScrollView; import android.widget.TextView; import com.google.android.setupcompat.internal.TemplateLayout; import com.google.android.setupcompat.template.SystemNavBarMixin; +import com.google.android.setupdesign.template.DescriptionMixin; import com.google.android.setupdesign.template.HeaderMixin; import com.google.android.setupdesign.template.NavigationBarMixin; import com.google.android.setupdesign.template.ProgressBarMixin; @@ -80,10 +81,15 @@ public class SetupWizardLayout extends TemplateLayout { // All the constructors delegate to this init method. The 3-argument constructor is not // available in LinearLayout before v11, so call super with the exact same arguments. private void init(AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + registerMixin(SystemNavBarMixin.class, new SystemNavBarMixin(this, /* window= */ null)); registerMixin( HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr)); + registerMixin(DescriptionMixin.class, new DescriptionMixin(this, attrs, defStyleAttr)); registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this)); registerMixin(NavigationBarMixin.class, new NavigationBarMixin(this)); final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this); diff --git a/main/src/com/google/android/setupdesign/SetupWizardListLayout.java b/main/src/com/google/android/setupdesign/SetupWizardListLayout.java index 760602f..dcb35d2 100644 --- a/main/src/com/google/android/setupdesign/SetupWizardListLayout.java +++ b/main/src/com/google/android/setupdesign/SetupWizardListLayout.java @@ -59,6 +59,10 @@ public class SetupWizardListLayout extends SetupWizardLayout { } private void init(AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + listMixin = new ListMixin(this, attrs, defStyleAttr); registerMixin(ListMixin.class, listMixin); diff --git a/main/src/com/google/android/setupdesign/SetupWizardRecyclerLayout.java b/main/src/com/google/android/setupdesign/SetupWizardRecyclerLayout.java index f0dc138..f2a7e3d 100644 --- a/main/src/com/google/android/setupdesign/SetupWizardRecyclerLayout.java +++ b/main/src/com/google/android/setupdesign/SetupWizardRecyclerLayout.java @@ -60,6 +60,10 @@ public class SetupWizardRecyclerLayout extends SetupWizardLayout { } private void init(AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + recyclerMixin.parseAttributes(attrs, defStyleAttr); registerMixin(RecyclerMixin.class, recyclerMixin); diff --git a/main/src/com/google/android/setupdesign/accessibility/LinkAccessibilityHelper.java b/main/src/com/google/android/setupdesign/accessibility/LinkAccessibilityHelper.java index ed9daf3..1a55b25 100644 --- a/main/src/com/google/android/setupdesign/accessibility/LinkAccessibilityHelper.java +++ b/main/src/com/google/android/setupdesign/accessibility/LinkAccessibilityHelper.java @@ -19,12 +19,9 @@ package com.google.android.setupdesign.accessibility; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.core.view.accessibility.AccessibilityNodeProviderCompat; -import androidx.customview.widget.ExploreByTouchHelper; import android.text.Layout; import android.text.Spanned; import android.text.style.ClickableSpan; @@ -34,6 +31,9 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.customview.widget.ExploreByTouchHelper; import java.util.List; /** diff --git a/main/src/com/google/android/setupdesign/items/AbstractItemHierarchy.java b/main/src/com/google/android/setupdesign/items/AbstractItemHierarchy.java index 267e4ab..45c8e9e 100644 --- a/main/src/com/google/android/setupdesign/items/AbstractItemHierarchy.java +++ b/main/src/com/google/android/setupdesign/items/AbstractItemHierarchy.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; +import android.view.View; import com.google.android.setupdesign.R; import java.util.ArrayList; @@ -33,13 +34,13 @@ public abstract class AbstractItemHierarchy implements ItemHierarchy { /* non-static section */ private final ArrayList<Observer> observers = new ArrayList<>(); - private int id = 0; + private int id = View.NO_ID; public AbstractItemHierarchy() {} public AbstractItemHierarchy(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SudAbstractItem); - id = a.getResourceId(R.styleable.SudAbstractItem_android_id, 0); + id = a.getResourceId(R.styleable.SudAbstractItem_android_id, View.NO_ID); a.recycle(); } diff --git a/main/src/com/google/android/setupdesign/items/DescriptionItem.java b/main/src/com/google/android/setupdesign/items/DescriptionItem.java index 24fbf5f..36ccfb9 100644 --- a/main/src/com/google/android/setupdesign/items/DescriptionItem.java +++ b/main/src/com/google/android/setupdesign/items/DescriptionItem.java @@ -19,17 +19,24 @@ package com.google.android.setupdesign.items; import android.content.Context; import android.util.AttributeSet; import android.view.View; +import android.widget.FrameLayout; import android.widget.TextView; +import com.google.android.setupcompat.PartnerCustomizationLayout; +import com.google.android.setupdesign.GlifLayout; import com.google.android.setupdesign.R; import com.google.android.setupdesign.util.DescriptionStyler; /** * Definition of an item in an {@link ItemHierarchy}. An item is usually defined in XML and inflated * using {@link ItemInflater}. + * + * @deprecated Use {@link com.google.android.setupdesign.template.DescriptionMixin} instead. */ +@Deprecated public class DescriptionItem extends Item { - private boolean applyPartnerDescriptionStyle = false; + private boolean partnerDescriptionHeavyStyle = false; + private boolean partnerDescriptionLightStyle = false; public DescriptionItem() { super(); @@ -39,16 +46,40 @@ public class DescriptionItem extends Item { super(context, attrs); } - public boolean shouldApplyPartnerDescriptionStyle() { - return applyPartnerDescriptionStyle; + /** + * Returns true if the description of partner's layout should apply heavy style, this depends on + * if the layout fulfill conditions in {@code applyPartnerHeavyThemeResource} of {@link + * com.google.android.setupdesign.GlifLayout} + */ + public boolean shouldApplyPartnerDescriptionHeavyStyle() { + return partnerDescriptionHeavyStyle; + } + + /** + * Returns true if the description of partner's layout should apply light style, this depends on + * if the layout fulfill conditions in {@code shouldApplyPartnerResource} of {@link + * com.google.android.setupcompat.PartnerCustomizationLayout} + */ + public boolean shouldApplyPartnerDescriptionLightStyle() { + return partnerDescriptionLightStyle; } /** * Applies partner description style on the title of the item, i.e. the TextView with {@code * R.id.sud_items_title}. + * + * @param layout A layout indicates if the description of partner's layout should apply heavy or + * light style */ - public void setApplyPartnerDescriptionStyle(boolean applyPartnerDescriptionStyle) { - this.applyPartnerDescriptionStyle = applyPartnerDescriptionStyle; + public void setPartnerDescriptionStyle(FrameLayout layout) { + if (layout instanceof GlifLayout) { + this.partnerDescriptionHeavyStyle = + ((GlifLayout) layout).shouldApplyPartnerHeavyThemeResource(); + } + if (layout instanceof PartnerCustomizationLayout) { + this.partnerDescriptionLightStyle = + ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource(); + } notifyItemChanged(); } @@ -56,8 +87,10 @@ public class DescriptionItem extends Item { public void onBindView(View view) { super.onBindView(view); TextView label = (TextView) view.findViewById(R.id.sud_items_title); - if (shouldApplyPartnerDescriptionStyle()) { - DescriptionStyler.applyPartnerCustomizationStyle(label); + if (shouldApplyPartnerDescriptionHeavyStyle()) { + DescriptionStyler.applyPartnerCustomizationHeavyStyle(label); + } else if (shouldApplyPartnerDescriptionLightStyle()) { + DescriptionStyler.applyPartnerCustomizationLightStyle(label); } } } diff --git a/main/src/com/google/android/setupdesign/items/ExpandableSwitchItem.java b/main/src/com/google/android/setupdesign/items/ExpandableSwitchItem.java index 42438d6..29eaf23 100644 --- a/main/src/com/google/android/setupdesign/items/ExpandableSwitchItem.java +++ b/main/src/com/google/android/setupdesign/items/ExpandableSwitchItem.java @@ -35,6 +35,7 @@ import android.view.View.OnClickListener; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.TextView; import com.google.android.setupdesign.R; +import com.google.android.setupdesign.util.LayoutStyler; import com.google.android.setupdesign.view.CheckableLinearLayout; /** @@ -179,6 +180,8 @@ public class ExpandableSwitchItem extends SwitchItem // Expandable switch item has focusability on the expandable layout on the left, and the // switch on the right, but not the item itself. view.setFocusable(false); + + LayoutStyler.applyPartnerCustomizationLayoutPaddingStyle(content); } @Override diff --git a/main/src/com/google/android/setupdesign/items/Item.java b/main/src/com/google/android/setupdesign/items/Item.java index e5d173f..fd3f2eb 100644 --- a/main/src/com/google/android/setupdesign/items/Item.java +++ b/main/src/com/google/android/setupdesign/items/Item.java @@ -20,8 +20,6 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.Drawable; -import androidx.annotation.ColorInt; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; @@ -29,7 +27,11 @@ import android.view.ViewGroup.LayoutParams; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; import com.google.android.setupdesign.R; +import com.google.android.setupdesign.util.ItemStyler; +import com.google.android.setupdesign.util.LayoutStyler; /** * Definition of an item in an {@link ItemHierarchy}. An item is usually defined in XML and inflated @@ -200,6 +202,16 @@ public class Item extends AbstractItem { } view.setId(getViewId()); + + // ExpandableSwitchItem uses its child view to apply the style SudItemContainer. It is not + // possible to directly adjust the padding start/end of the item's layout here. It needs to + // get its child view to adjust it first, so skip the Layout padding adjustment. + // If the item view is a header layout, it doesn't need to adjust the layout padding start/end + // here. It will be adjusted by HeaderMixin. + if (!(this instanceof ExpandableSwitchItem) && view.getId() != R.id.sud_layout_header) { + LayoutStyler.applyPartnerCustomizationLayoutPaddingStyle(view); + } + ItemStyler.applyPartnerCustomizationItemStyle(view); } /** diff --git a/main/src/com/google/android/setupdesign/items/ItemHierarchy.java b/main/src/com/google/android/setupdesign/items/ItemHierarchy.java index b22e9ef..6a5b51f 100644 --- a/main/src/com/google/android/setupdesign/items/ItemHierarchy.java +++ b/main/src/com/google/android/setupdesign/items/ItemHierarchy.java @@ -71,7 +71,7 @@ public interface ItemHierarchy { /** * Get the item at position. * - * @param position An integer from 0 to {@link #getCount()}}, which indicates the position in this + * @param position An integer from 0 to {@link #getCount()}, which indicates the position in this * item hierarchy to get the child item. * @return A representation of the item at {@code position}. Must not be {@code null}. */ diff --git a/main/src/com/google/android/setupdesign/items/ItemViewHolder.java b/main/src/com/google/android/setupdesign/items/ItemViewHolder.java index f79b2b6..3cffddd 100644 --- a/main/src/com/google/android/setupdesign/items/ItemViewHolder.java +++ b/main/src/com/google/android/setupdesign/items/ItemViewHolder.java @@ -20,7 +20,11 @@ import androidx.recyclerview.widget.RecyclerView; import android.view.View; import com.google.android.setupdesign.DividerItemDecoration; -class ItemViewHolder extends RecyclerView.ViewHolder +/** + * ViewHolder for the RecyclerItemAdapter that describes an item view and metadata about its place + * within the RecyclerView. + */ +public class ItemViewHolder extends RecyclerView.ViewHolder implements DividerItemDecoration.DividedViewHolder { private boolean isEnabled; diff --git a/main/src/com/google/android/setupdesign/items/RecyclerItemAdapter.java b/main/src/com/google/android/setupdesign/items/RecyclerItemAdapter.java index 3526bf5..dbbda29 100644 --- a/main/src/com/google/android/setupdesign/items/RecyclerItemAdapter.java +++ b/main/src/com/google/android/setupdesign/items/RecyclerItemAdapter.java @@ -21,12 +21,12 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; -import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.VisibleForTesting; import com.google.android.setupcompat.partnerconfig.PartnerConfig; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupdesign.R; @@ -61,6 +61,7 @@ public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder> private final ItemHierarchy itemHierarchy; @VisibleForTesting public final boolean applyPartnerHeavyThemeResource; + @VisibleForTesting public final boolean useFullDynamicColor; private OnItemSelectedListener listener; public RecyclerItemAdapter(ItemHierarchy hierarchy) { @@ -68,7 +69,15 @@ public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder> } public RecyclerItemAdapter(ItemHierarchy hierarchy, boolean applyPartnerHeavyThemeResource) { + this(hierarchy, applyPartnerHeavyThemeResource, /* useFullDynamicColor= */ false); + } + + public RecyclerItemAdapter( + ItemHierarchy hierarchy, + boolean applyPartnerHeavyThemeResource, + boolean useFullDynamicColor) { this.applyPartnerHeavyThemeResource = applyPartnerHeavyThemeResource; + this.useFullDynamicColor = useFullDynamicColor; itemHierarchy = hierarchy; itemHierarchy.registerObserver(this); } @@ -118,7 +127,9 @@ public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder> } else { background = view.getBackground(); if (background == null) { - if (applyPartnerHeavyThemeResource) { + // If full dynamic color enabled which means this activity is running outside of setup + // flow, the colors should refer to R.style.SudFullDynamicColorThemeGlifV3. + if (applyPartnerHeavyThemeResource && !useFullDynamicColor) { int color = PartnerConfigHelper.get(view.getContext()) .getColor(view.getContext(), PartnerConfig.CONFIG_LAYOUT_BACKGROUND_COLOR); diff --git a/main/src/com/google/android/setupdesign/items/ReflectionInflater.java b/main/src/com/google/android/setupdesign/items/ReflectionInflater.java index 329d240..8123c18 100644 --- a/main/src/com/google/android/setupdesign/items/ReflectionInflater.java +++ b/main/src/com/google/android/setupdesign/items/ReflectionInflater.java @@ -17,10 +17,10 @@ package com.google.android.setupdesign.items; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.InflateException; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.lang.reflect.Constructor; import java.util.HashMap; diff --git a/main/src/com/google/android/setupdesign/items/SimpleInflater.java b/main/src/com/google/android/setupdesign/items/SimpleInflater.java index c7e370a..0c7d1d9 100644 --- a/main/src/com/google/android/setupdesign/items/SimpleInflater.java +++ b/main/src/com/google/android/setupdesign/items/SimpleInflater.java @@ -18,11 +18,11 @@ package com.google.android.setupdesign.items; import android.content.res.Resources; import android.content.res.XmlResourceParser; -import androidx.annotation.NonNull; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import android.view.InflateException; +import androidx.annotation.NonNull; import java.io.IOException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/main/src/com/google/android/setupdesign/span/LinkSpan.java b/main/src/com/google/android/setupdesign/span/LinkSpan.java index 7f1f02b..7d91012 100644 --- a/main/src/com/google/android/setupdesign/span/LinkSpan.java +++ b/main/src/com/google/android/setupdesign/span/LinkSpan.java @@ -19,7 +19,6 @@ package com.google.android.setupdesign.span; import android.content.Context; import android.content.ContextWrapper; import android.os.Build; -import androidx.annotation.Nullable; import android.text.Selection; import android.text.Spannable; import android.text.TextPaint; @@ -27,6 +26,7 @@ import android.text.style.ClickableSpan; import android.util.Log; import android.view.View; import android.widget.TextView; +import androidx.annotation.Nullable; /** * A clickable span that will listen for click events and send it back to the context. To use this diff --git a/main/src/com/google/android/setupdesign/template/DescriptionMixin.java b/main/src/com/google/android/setupdesign/template/DescriptionMixin.java new file mode 100644 index 0000000..bbccf82 --- /dev/null +++ b/main/src/com/google/android/setupdesign/template/DescriptionMixin.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2020 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.google.android.setupdesign.template; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import com.google.android.setupcompat.PartnerCustomizationLayout; +import com.google.android.setupcompat.internal.TemplateLayout; +import com.google.android.setupcompat.template.Mixin; +import com.google.android.setupdesign.R; +import com.google.android.setupdesign.util.HeaderAreaStyler; +import com.google.android.setupdesign.util.PartnerStyleHelper; + +/** + * A {@link com.google.android.setupcompat.template.Mixin} for setting and getting the description + * text. + */ +public class DescriptionMixin implements Mixin { + + private static final String TAG = "DescriptionMixin"; + private final TemplateLayout templateLayout; + + /** + * A {@link com.google.android.setupcompat.template.Mixin} for setting and getting the + * description. + * + * @param layout The layout this Mixin belongs to + * @param attrs XML attributes given to the layout + * @param defStyleAttr The default style attribute as given to the constructor of the layout + */ + public DescriptionMixin( + @NonNull TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + templateLayout = layout; + + final TypedArray a = + layout + .getContext() + .obtainStyledAttributes(attrs, R.styleable.SudDescriptionMixin, defStyleAttr, 0); + + // Set the description text + final CharSequence descriptionText = + a.getText(R.styleable.SudDescriptionMixin_sudDescriptionText); + if (descriptionText != null) { + setText(descriptionText); + } + // Set the description text color + final ColorStateList descriptionTextColor = + a.getColorStateList(R.styleable.SudDescriptionMixin_sudDescriptionTextColor); + if (descriptionTextColor != null) { + setTextColor(descriptionTextColor); + } + + a.recycle(); + } + + /** + * Applies the partner customizations to the description text (contains text alignment) and + * background, if apply heavy theme resource, it will apply all partner customizations, otherwise, + * only apply alignment style. + */ + public void tryApplyPartnerCustomizationStyle() { + TextView description = templateLayout.findManagedViewById(R.id.sud_layout_subtitle); + boolean partnerHeavyThemeLayout = PartnerStyleHelper.isPartnerHeavyThemeLayout(templateLayout); + if (partnerHeavyThemeLayout) { + if (description != null) { + HeaderAreaStyler.applyPartnerCustomizationDescriptionHeavyStyle(description); + } + } else if (templateLayout instanceof PartnerCustomizationLayout + && ((PartnerCustomizationLayout) templateLayout).shouldApplyPartnerResource()) { + if (description != null) { + HeaderAreaStyler.applyPartnerCustomizationDescriptionLightStyle(description); + } + } + } + + /** Returns the TextView displaying the description. */ + public TextView getTextView() { + return (TextView) templateLayout.findManagedViewById(R.id.sud_layout_subtitle); + } + + /** + * Sets the description text and also sets the text visibility to visible. This can also be set + * via the XML attribute {@code app:sudDescriptionText}. + * + * @param title The resource ID of the text to be set as description + */ + public void setText(@StringRes int title) { + final TextView titleView = getTextView(); + if (titleView != null && title != 0) { + titleView.setText(title); + setVisibility(View.VISIBLE); + } else { + Log.w(TAG, "Fail to set text due to either invalid resource id or text view not found."); + } + } + + /** + * Sets the description text and also sets the text visibility to visible. This can also be set + * via the XML attribute {@code app:sudDescriptionText}. + * + * @param title The text to be set as description + */ + public void setText(CharSequence title) { + final TextView titleView = getTextView(); + if (titleView != null) { + titleView.setText(title); + setVisibility(View.VISIBLE); + } + } + + /** Returns the current description text. */ + public CharSequence getText() { + final TextView titleView = getTextView(); + return titleView != null ? titleView.getText() : null; + } + + /** Sets the visibility of description text */ + public void setVisibility(int visibility) { + final TextView titleView = getTextView(); + if (titleView != null) { + titleView.setVisibility(visibility); + } + } + + /** + * Sets the color of the description text. This can also be set via XML using {@code + * app:sudDescriptionTextColor}. + * + * @param color The text color of the description + */ + public void setTextColor(ColorStateList color) { + final TextView titleView = getTextView(); + if (titleView != null) { + titleView.setTextColor(color); + } + } + + /** Returns the current text color of the description. */ + public ColorStateList getTextColor() { + final TextView titleView = getTextView(); + return titleView != null ? titleView.getTextColors() : null; + } + + /** + * Call this method ONLY when a layout migrates from {@link + * com.google.android.setupdesign.items.DescriptionItem} to {@link DescriptionMixin}. + * + * <p>If a screen is migrated from {@link com.google.android.setupdesign.items.DescriptionItem} it + * will looks slightly different from the original UI. This method helps keeping the UI consistent + * with the original UI. + */ + public void adjustLegacyDescriptionItem() { + final TextView titleView = getTextView(); + final Context context = titleView.getContext(); + + final ViewGroup.LayoutParams lp = titleView.getLayoutParams(); + if (lp instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; + int extraBottomMargin = + (int) context.getResources().getDimension(R.dimen.sud_description_margin_bottom_extra); + int extraTopMargin = + (int) context.getResources().getDimension(R.dimen.sud_description_margin_top_extra); + mlp.setMargins( + mlp.leftMargin, + mlp.topMargin + extraTopMargin, + mlp.rightMargin, + mlp.bottomMargin + extraBottomMargin); + } + } +} diff --git a/main/src/com/google/android/setupdesign/template/HeaderMixin.java b/main/src/com/google/android/setupdesign/template/HeaderMixin.java index 0a6eb9e..d040c94 100644 --- a/main/src/com/google/android/setupdesign/template/HeaderMixin.java +++ b/main/src/com/google/android/setupdesign/template/HeaderMixin.java @@ -16,19 +16,30 @@ package com.google.android.setupdesign.template; +import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; -import androidx.annotation.AttrRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import android.os.Build; import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; import android.view.ViewParent; +import android.view.ViewTreeObserver; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.google.android.setupcompat.internal.TemplateLayout; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupcompat.template.Mixin; import com.google.android.setupdesign.R; import com.google.android.setupdesign.util.HeaderAreaStyler; +import com.google.android.setupdesign.util.LayoutStyler; import com.google.android.setupdesign.util.PartnerStyleHelper; /** @@ -37,11 +48,19 @@ import com.google.android.setupdesign.util.PartnerStyleHelper; public class HeaderMixin implements Mixin { private final TemplateLayout templateLayout; + @VisibleForTesting boolean autoTextSizeEnabled = false; + private float headerAutoSizeMaxTextSizeInPx; + private float headerAutoSizeMinTextSizeInPx; + private float headerAutoSizeLineExtraSpacingInPx; + private int headerAutoSizeMaxLineOfMaxSize; + private static final int AUTO_SIZE_DEFAULT_MAX_LINES = 6; /** - * @param layout The layout this Mixin belongs to. - * @param attrs XML attributes given to the layout. - * @param defStyleAttr The default style attribute as given to the constructor of the layout. + * A {@link com.google.android.setupcompat.template.Mixin} for setting and getting the Header. + * + * @param layout The layout this Mixin belongs to + * @param attrs XML attributes given to the layout + * @param defStyleAttr The default style attribute as given to the constructor of the layout */ public HeaderMixin( @NonNull TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { @@ -52,41 +71,103 @@ public class HeaderMixin implements Mixin { .getContext() .obtainStyledAttributes(attrs, R.styleable.SucHeaderMixin, defStyleAttr, 0); - // Set the header text final CharSequence headerText = a.getText(R.styleable.SucHeaderMixin_sucHeaderText); + final ColorStateList headerTextColor = + a.getColorStateList(R.styleable.SucHeaderMixin_sucHeaderTextColor); + + a.recycle(); + + // overlay the Auto size config settings + updateAutoTextSizeWithPartnerConfig(); + + // Set the header text if (headerText != null) { setText(headerText); } // Set the header text color - final ColorStateList headerTextColor = - a.getColorStateList(R.styleable.SucHeaderMixin_sucHeaderTextColor); if (headerTextColor != null) { setTextColor(headerTextColor); } + } - a.recycle(); + private void updateAutoTextSizeWithPartnerConfig() { + Context context = templateLayout.getContext(); + if (!PartnerStyleHelper.isPartnerHeavyThemeLayout(templateLayout) + || !PartnerConfigHelper.shouldApplyExtendedPartnerConfig(context)) { + autoTextSizeEnabled = false; + return; + } + // overridden by partner resource + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_HEADER_AUTO_SIZE_ENABLED)) { + autoTextSizeEnabled = + PartnerConfigHelper.get(context) + .getBoolean( + context, PartnerConfig.CONFIG_HEADER_AUTO_SIZE_ENABLED, autoTextSizeEnabled); + } + if (!autoTextSizeEnabled) { + return; + } + + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_HEADER_AUTO_SIZE_MAX_TEXT_SIZE)) { + headerAutoSizeMaxTextSizeInPx = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_HEADER_AUTO_SIZE_MAX_TEXT_SIZE); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_HEADER_AUTO_SIZE_MIN_TEXT_SIZE)) { + headerAutoSizeMinTextSizeInPx = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_HEADER_AUTO_SIZE_MIN_TEXT_SIZE); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA)) { + headerAutoSizeLineExtraSpacingInPx = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE)) { + headerAutoSizeMaxLineOfMaxSize = + PartnerConfigHelper.get(context) + .getInteger(context, PartnerConfig.CONFIG_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE, 0); + } + if ((headerAutoSizeMaxLineOfMaxSize < 1) + || (headerAutoSizeMinTextSizeInPx <= 0) + || (headerAutoSizeMaxTextSizeInPx < headerAutoSizeMinTextSizeInPx)) { + Log.w("HeaderMixin", "Invalid configs, disable auto text size."); + autoTextSizeEnabled = false; + } } /** - * Tries to apply the partner customizations to the header text and background if the layout of - * this {@link HeaderMixin} is set to apply partner heavy theme resource. + * Applies the partner customizations to the header text (contains text alignment), background, + * and margin. If apply heavy theme resource, it will apply all partner customizations, otherwise, + * only apply alignment style. In addition, if only enable extended customized flag, the margin + * style will be applied. */ public void tryApplyPartnerCustomizationStyle() { - if (!PartnerStyleHelper.isPartnerHeavyThemeLayout(templateLayout)) { - return; - } - TextView header = templateLayout.findManagedViewById(R.id.suc_layout_title); - if (header != null) { - HeaderAreaStyler.applyPartnerCustomizationHeaderStyle(header); + boolean partnerLightThemeLayout = PartnerStyleHelper.isPartnerLightThemeLayout(templateLayout); + boolean partnerHeavyThemeLayout = PartnerStyleHelper.isPartnerHeavyThemeLayout(templateLayout); + if (partnerHeavyThemeLayout) { + View headerAreaView = templateLayout.findManagedViewById(R.id.sud_layout_header); + HeaderAreaStyler.applyPartnerCustomizationHeaderHeavyStyle(header); + HeaderAreaStyler.applyPartnerCustomizationHeaderAreaStyle((ViewGroup) headerAreaView); + LayoutStyler.applyPartnerCustomizationExtraPaddingStyle(headerAreaView); + // overlay the Auto size config settings + updateAutoTextSizeWithPartnerConfig(); + } else if (partnerLightThemeLayout) { + HeaderAreaStyler.applyPartnerCustomizationHeaderLightStyle(header); } - LinearLayout headerLayout = templateLayout.findManagedViewById(R.id.sud_layout_header); - if (headerLayout != null) { - HeaderAreaStyler.applyPartnerCustomizationHeaderAreaStyle(headerLayout); + if (autoTextSizeEnabled) { + // Override the text size setting of the header + autoAdjustTextSize(header); } } - /** @return The TextView displaying the header. */ + /** Returns the TextView displaying the header. */ public TextView getTextView() { return (TextView) templateLayout.findManagedViewById(R.id.suc_layout_title); } @@ -94,11 +175,15 @@ public class HeaderMixin implements Mixin { /** * Sets the header text. This can also be set via the XML attribute {@code app:sucHeaderText}. * - * @param title The resource ID of the text to be set as header. + * @param title The resource ID of the text to be set as header */ public void setText(int title) { final TextView titleView = getTextView(); if (titleView != null) { + if (autoTextSizeEnabled) { + // Override the text size setting of the header + autoAdjustTextSize(titleView); + } titleView.setText(title); } } @@ -106,16 +191,58 @@ public class HeaderMixin implements Mixin { /** * Sets the header text. This can also be set via the XML attribute {@code app:sucHeaderText}. * - * @param title The text to be set as header. + * @param title The text to be set as header */ public void setText(CharSequence title) { final TextView titleView = getTextView(); if (titleView != null) { + if (autoTextSizeEnabled) { + // Override the text size setting of the header + autoAdjustTextSize(titleView); + } titleView.setText(title); } } - /** @return The current header text. */ + private void autoAdjustTextSize(TextView titleView) { + if (titleView == null) { + return; + } + // preset as the max size + titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, headerAutoSizeMaxTextSizeInPx); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + titleView.setLineHeight( + Math.round(headerAutoSizeLineExtraSpacingInPx + headerAutoSizeMaxTextSizeInPx)); + } + titleView.setMaxLines(AUTO_SIZE_DEFAULT_MAX_LINES); + + // reset text size if the line count for max text size > headerAutoSizeMaxLineOfMaxTextSize + titleView + .getViewTreeObserver() + .addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + // Remove listener to avoid this called every frame + titleView.getViewTreeObserver().removeOnPreDrawListener(this); + int lineCount = titleView.getLineCount(); + if (lineCount > headerAutoSizeMaxLineOfMaxSize) { + // reset text size + titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, headerAutoSizeMinTextSizeInPx); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + titleView.setLineHeight( + Math.round( + headerAutoSizeLineExtraSpacingInPx + headerAutoSizeMinTextSizeInPx)); + } + titleView.invalidate(); + return false; // false to skip this frame + } + return true; + } + }); + } + + /** Returns the current header text. */ public CharSequence getText() { final TextView titleView = getTextView(); return titleView != null ? titleView.getText() : null; @@ -133,7 +260,7 @@ public class HeaderMixin implements Mixin { * Sets the color of the header text. This can also be set via XML using {@code * app:sucHeaderTextColor}. * - * @param color The text color of the header. + * @param color The text color of the header */ public void setTextColor(ColorStateList color) { final TextView titleView = getTextView(); @@ -142,7 +269,11 @@ public class HeaderMixin implements Mixin { } } - /** Sets the background color of the header's parent LinearLayout */ + /** + * Sets the background color of the header's parent LinearLayout. + * + * @param color The background color of the header's parent LinearLayout + */ public void setBackgroundColor(int color) { final TextView titleView = getTextView(); if (titleView != null) { diff --git a/main/src/com/google/android/setupdesign/template/IconMixin.java b/main/src/com/google/android/setupdesign/template/IconMixin.java index dca934f..75e624a 100644 --- a/main/src/com/google/android/setupdesign/template/IconMixin.java +++ b/main/src/com/google/android/setupdesign/template/IconMixin.java @@ -22,12 +22,13 @@ import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import androidx.annotation.ColorInt; -import androidx.annotation.DrawableRes; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; import android.widget.ImageView; +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; import com.google.android.setupcompat.internal.TemplateLayout; import com.google.android.setupcompat.template.Mixin; import com.google.android.setupdesign.R; @@ -44,15 +45,17 @@ public class IconMixin implements Mixin { private final int originalHeight; private final ImageView.ScaleType originalScaleType; - + private final Context context; /** - * @param layout The template layout that this Mixin is a part of. - * @param attrs XML attributes given to the layout. - * @param defStyleAttr The default style attribute as given to the constructor of the layout. + * A {@link com.google.android.setupcompat.template.Mixin} for setting and getting the Icon. + * + * @param layout The template layout that this Mixin is a part of + * @param attrs XML attributes given to the layout + * @param defStyleAttr The default style attribute as given to the constructor of the layout */ public IconMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { templateLayout = layout; - final Context context = layout.getContext(); + context = layout.getContext(); ImageView iconView = getView(); if (iconView != null) { @@ -65,14 +68,17 @@ public class IconMixin implements Mixin { } final TypedArray a = - context.obtainStyledAttributes(attrs, R.styleable.SudIconMixin, defStyleAttr, 0); + context.obtainStyledAttributes( + attrs, R.styleable.SudIconMixin, defStyleAttr, /* defStyleRes= */ 0); - final @DrawableRes int icon = a.getResourceId(R.styleable.SudIconMixin_android_icon, 0); + final @DrawableRes int icon = + a.getResourceId(R.styleable.SudIconMixin_android_icon, /* defValue= */ 0); if (icon != 0) { setIcon(icon); } - final boolean upscaleIcon = a.getBoolean(R.styleable.SudIconMixin_sudUpscaleIcon, false); + final boolean upscaleIcon = + a.getBoolean(R.styleable.SudIconMixin_sudUpscaleIcon, /* defValue= */ false); setUpscaleIcon(upscaleIcon); final @ColorInt int iconTint = @@ -84,38 +90,40 @@ public class IconMixin implements Mixin { a.recycle(); } - /** - * Tries to apply the partner customization to the header icon if the layout of this {@link - * IconMixin} is set to apply partner heavy theme resource. - */ + /** Tries to apply the partner customization to the header icon. */ public void tryApplyPartnerCustomizationStyle() { - if (!PartnerStyleHelper.isPartnerHeavyThemeLayout(templateLayout)) { - return; - } - - ImageView iconImage = templateLayout.findManagedViewById(R.id.sud_layout_icon); - if (iconImage != null) { - HeaderAreaStyler.applyPartnerCustomizationIconStyle(iconImage); + if (PartnerStyleHelper.isPartnerHeavyThemeLayout(templateLayout)) { + // apply partner heavy configs + HeaderAreaStyler.applyPartnerCustomizationIconStyle(getView(), getContainerView()); + } else if (PartnerStyleHelper.isPartnerLightThemeLayout(templateLayout)) { + // apply partner light configs + HeaderAreaStyler.applyPartnerCustomizationIconStyle(getView()); } } /** * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. * - * @param icon A drawable icon. + * @param icon A drawable icon */ public void setIcon(Drawable icon) { final ImageView iconView = getView(); if (iconView != null) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (icon != null) { + icon.applyTheme(context.getTheme()); + } + } iconView.setImageDrawable(icon); iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE); + setIconContainerVisibility(iconView.getVisibility()); } } /** * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. * - * @param icon A drawable icon resource. + * @param icon A drawable icon resource */ public void setIcon(@DrawableRes int icon) { final ImageView iconView = getView(); @@ -124,6 +132,7 @@ public class IconMixin implements Mixin { // support lib users, which enables vector drawable compat to work on versions pre-L. iconView.setImageResource(icon); iconView.setVisibility(icon != 0 ? View.VISIBLE : View.GONE); + setIconContainerVisibility(iconView.getVisibility()); } } @@ -133,7 +142,11 @@ public class IconMixin implements Mixin { return iconView != null ? iconView.getDrawable() : null; } - /** Forces the icon view to be as big as desired in the style. */ + /** + * Forces the icon view to be as big as desired in the style. + * + * @param shouldUpscaleIcon If scale icon view as desired + */ public void setUpscaleIcon(boolean shouldUpscaleIcon) { final int maxHeight; final ImageView iconView = getView(); @@ -150,7 +163,11 @@ public class IconMixin implements Mixin { } } - /** Tints the icon on this layout to the given color. */ + /** + * Tints the icon on this layout to the given color. + * + * @param tint The color to tint the icon + */ public void setIconTint(@ColorInt int tint) { final ImageView iconView = getView(); if (iconView != null) { @@ -158,7 +175,11 @@ public class IconMixin implements Mixin { } } - /** Sets the content description of the icon view */ + /** + * Sets the content description of the icon view. + * + * @param description The description char + */ public void setContentDescription(CharSequence description) { final ImageView iconView = getView(); if (iconView != null) { @@ -172,16 +193,32 @@ public class IconMixin implements Mixin { return iconView != null ? iconView.getContentDescription() : null; } - /** Sets the visibiltiy of the icon view */ + /** + * Sets the visibility of the icon view. + * + * @param visibility Set it visible or not + */ public void setVisibility(int visibility) { final ImageView iconView = getView(); if (iconView != null) { iconView.setVisibility(visibility); + setIconContainerVisibility(visibility); } } - /** @return The ImageView responsible for displaying the icon. */ + /** Returns the ImageView responsible for displaying the icon. */ protected ImageView getView() { return (ImageView) templateLayout.findManagedViewById(R.id.sud_layout_icon); } + + /** Returns the container of the ImageView responsible for displaying the icon. */ + protected FrameLayout getContainerView() { + return (FrameLayout) templateLayout.findManagedViewById(R.id.sud_layout_icon_container); + } + + private void setIconContainerVisibility(int visibility) { + if (getContainerView() != null) { + getContainerView().setVisibility(visibility); + } + } } diff --git a/main/src/com/google/android/setupdesign/template/IllustrationProgressMixin.java b/main/src/com/google/android/setupdesign/template/IllustrationProgressMixin.java new file mode 100644 index 0000000..23b9fa0 --- /dev/null +++ b/main/src/com/google/android/setupdesign/template/IllustrationProgressMixin.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2020 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.google.android.setupdesign.template; + +import static android.view.View.GONE; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build.VERSION_CODES; +import android.view.View; +import android.view.ViewStub; +import android.widget.ProgressBar; +import android.widget.TextView; +import androidx.annotation.Nullable; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupcompat.partnerconfig.ResourceEntry; +import com.google.android.setupcompat.template.Mixin; +import com.google.android.setupdesign.GlifLayout; +import com.google.android.setupdesign.R; +import com.google.android.setupdesign.view.IllustrationVideoView; + +// TODO: remove this mixin after migrate to new GlifLoadingLayout +/** + * A {@link Mixin} for showing a progress illustration. + * + * @deprecated Will be replaced by GlifLoadingLayout. + */ +@TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) +@Deprecated +public class IllustrationProgressMixin implements Mixin { + + private final GlifLayout glifLayout; + private final Context context; + + private ProgressConfig progressConfig = ProgressConfig.CONFIG_DEFAULT; + private String progressDescription; + + public IllustrationProgressMixin(GlifLayout layout) { + this.glifLayout = layout; + context = layout.getContext(); + } + + /** + * Sets whether the progress layout is shown. If the progress layout has not been inflated from + * the stub, this method will inflate the progress layout. + * + * @param shown True to show the progress layout, false to set the layout visibiltiy to {@code + * GONE} + */ + public void setShown(boolean shown) { + if (!shown) { + View view = peekProgressIllustrationLayout(); + if (view != null) { + view.setVisibility(GONE); + } + } else { + View view = getProgressIllustrationLayout(); + if (view != null) { + view.setVisibility(VISIBLE); + + if (progressDescription != null) { + TextView descriptionView = view.findViewById(R.id.sud_layout_description); + if (descriptionView != null) { + descriptionView.setVisibility(VISIBLE); + descriptionView.setText(progressDescription); + } + } + } + } + } + + /** Returns true if the progress layout is currently shown. */ + public boolean isShown() { + View view = peekProgressIllustrationLayout(); + return view != null && view.getVisibility() == VISIBLE; + } + + /** + * Sets the type of progress illustration. + * + * @param config {@link ProgressConfig} + */ + public void setProgressConfig(ProgressConfig config) { + this.progressConfig = config; + + // When ViewStub not inflated, do nothing. It will set illustration resource when inflate + // layout. + if (peekProgressIllustrationLayout() != null) { + setIllustrationResource(); + } + } + + /** + * Sets the description text of progress + * @param description the description text. + */ + public void setProgressIllustrationDescription(String description) { + progressDescription = description; + + if (isShown()) { + final View progressLayout = getProgressIllustrationLayout(); + if (progressLayout != null) { + TextView descriptionView = progressLayout.findViewById(R.id.sud_layout_description); + if (description != null) { + descriptionView.setVisibility(VISIBLE); + descriptionView.setText(description); + } else { + descriptionView.setVisibility(INVISIBLE); + descriptionView.setText(description); + } + } + } + } + + @Nullable private View getProgressIllustrationLayout() { + final View progressLayout = peekProgressIllustrationLayout(); + if (progressLayout == null) { + final ViewStub viewStub = + glifLayout.findManagedViewById(R.id.sud_layout_illustration_progress_stub); + + if (viewStub != null) { + viewStub.inflate(); + setIllustrationResource(); + } + } + + return peekProgressIllustrationLayout(); + } + + private void setIllustrationResource() { + IllustrationVideoView illustrationVideoView = + glifLayout.findManagedViewById(R.id.sud_progress_illustration); + ProgressBar progressBar = glifLayout.findManagedViewById(R.id.sud_progress_bar); + + PartnerConfigHelper partnerConfigHelper = PartnerConfigHelper.get(context); + ResourceEntry resourceEntry = + partnerConfigHelper.getIllustrationResourceEntry( + context, progressConfig.getPartnerConfig()); + + if (resourceEntry != null) { + progressBar.setVisibility(GONE); + illustrationVideoView.setVisibility(VISIBLE); + illustrationVideoView.setVideoResourceEntry(resourceEntry); + } else { + progressBar.setVisibility(VISIBLE); + illustrationVideoView.setVisibility(GONE); + } + } + + @Nullable private View peekProgressIllustrationLayout() { + return glifLayout.findViewById(R.id.sud_layout_progress_illustration); + } + + /** The progress config used to maps to different animation */ + public enum ProgressConfig { + CONFIG_DEFAULT(PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_DEFAULT), + CONFIG_ACCOUNT(PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_ACCOUNT), + CONFIG_CONNECTION(PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_CONNECTION), + CONFIG_UPDATE(PartnerConfig.CONFIG_PROGRESS_ILLUSTRATION_UPDATE); + + private final PartnerConfig config; + + ProgressConfig(PartnerConfig config) { + if (config.getResourceType() != ResourceType.ILLUSTRATION) { + throw new IllegalArgumentException( + "Illustration progress only allow illustration resource"); + } + this.config = config; + } + + PartnerConfig getPartnerConfig() { + return config; + } + } +} diff --git a/main/src/com/google/android/setupdesign/template/ListMixin.java b/main/src/com/google/android/setupdesign/template/ListMixin.java index 38928aa..5963a79 100644 --- a/main/src/com/google/android/setupdesign/template/ListMixin.java +++ b/main/src/com/google/android/setupdesign/template/ListMixin.java @@ -21,21 +21,24 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION_CODES; -import androidx.annotation.AttrRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.widget.HeaderViewListAdapter; import android.widget.ListAdapter; import android.widget.ListView; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.setupcompat.internal.TemplateLayout; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupcompat.template.Mixin; import com.google.android.setupdesign.R; import com.google.android.setupdesign.items.ItemAdapter; import com.google.android.setupdesign.items.ItemGroup; import com.google.android.setupdesign.items.ItemInflater; import com.google.android.setupdesign.util.DrawableLayoutDirectionHelper; +import com.google.android.setupdesign.util.PartnerStyleHelper; /** A {@link Mixin} for interacting with ListViews. */ public class ListMixin implements Mixin { @@ -64,18 +67,56 @@ public class ListMixin implements Mixin { final ItemGroup inflated = (ItemGroup) new ItemInflater(context).inflate(entries); setAdapter(new ItemAdapter(inflated)); } - int dividerInset = a.getDimensionPixelSize(R.styleable.SudListMixin_sudDividerInset, -1); - if (dividerInset != -1) { - setDividerInset(dividerInset); - } else { - int dividerInsetStart = - a.getDimensionPixelSize(R.styleable.SudListMixin_sudDividerInsetStart, 0); - int dividerInsetEnd = a.getDimensionPixelSize(R.styleable.SudListMixin_sudDividerInsetEnd, 0); - setDividerInsets(dividerInsetStart, dividerInsetEnd); + + boolean isDividerDisplay = isDividerShown(context); + if (isDividerDisplay) { + int dividerInset = a.getDimensionPixelSize(R.styleable.SudListMixin_sudDividerInset, -1); + if (dividerInset != -1) { + setDividerInset(dividerInset); + } else { + int dividerInsetStart = + a.getDimensionPixelSize(R.styleable.SudListMixin_sudDividerInsetStart, 0); + int dividerInsetEnd = + a.getDimensionPixelSize(R.styleable.SudListMixin_sudDividerInsetEnd, 0); + + if (PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(templateLayout)) { + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_START)) { + dividerInsetStart = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_LAYOUT_MARGIN_START); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_END)) { + dividerInsetEnd = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_LAYOUT_MARGIN_END); + } + } + setDividerInsets(dividerInsetStart, dividerInsetEnd); + } } a.recycle(); } + private boolean isDividerShown(Context context) { + if (PartnerStyleHelper.shouldApplyPartnerResource(templateLayout)) { + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_ITEMS_DIVIDER_SHOWN)) { + boolean isDividerDisplayed = + PartnerConfigHelper.get(context) + .getBoolean(context, PartnerConfig.CONFIG_ITEMS_DIVIDER_SHOWN, true); + if (!isDividerDisplayed) { + getListView().setDivider(null); + return isDividerDisplayed; + } + } + } + return true; + } + /** * @return The list view contained in the layout, as marked by {@code @android:id/list}. This will * return {@code null} if the list doesn't exist in the layout. diff --git a/main/src/com/google/android/setupdesign/template/ListViewScrollHandlingDelegate.java b/main/src/com/google/android/setupdesign/template/ListViewScrollHandlingDelegate.java index 2040c6b..3c7fdfd 100644 --- a/main/src/com/google/android/setupdesign/template/ListViewScrollHandlingDelegate.java +++ b/main/src/com/google/android/setupdesign/template/ListViewScrollHandlingDelegate.java @@ -16,12 +16,12 @@ package com.google.android.setupdesign.template; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.Log; import android.widget.AbsListView; import android.widget.ListAdapter; import android.widget.ListView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.setupdesign.template.RequireScrollMixin.ScrollHandlingDelegate; /** diff --git a/main/src/com/google/android/setupdesign/template/ProgressBarMixin.java b/main/src/com/google/android/setupdesign/template/ProgressBarMixin.java index 7e55d51..da1b997 100644 --- a/main/src/com/google/android/setupdesign/template/ProgressBarMixin.java +++ b/main/src/com/google/android/setupdesign/template/ProgressBarMixin.java @@ -17,12 +17,16 @@ package com.google.android.setupdesign.template; import android.content.res.ColorStateList; +import android.content.res.TypedArray; import android.os.Build; import android.os.Build.VERSION_CODES; -import androidx.annotation.Nullable; +import android.util.AttributeSet; import android.view.View; import android.view.ViewStub; import android.widget.ProgressBar; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.setupcompat.internal.TemplateLayout; import com.google.android.setupcompat.template.Mixin; import com.google.android.setupdesign.R; @@ -31,17 +35,64 @@ import com.google.android.setupdesign.R; public class ProgressBarMixin implements Mixin { private final TemplateLayout templateLayout; + private final boolean useBottomProgressBar; @Nullable private ColorStateList color; /** @param layout The layout this mixin belongs to. */ - public ProgressBarMixin(TemplateLayout layout) { + public ProgressBarMixin(@NonNull TemplateLayout layout) { + this(layout, null, 0); + } + + /** + * Constructor that allow using the bottom progress bar. + * + * @param layout The layout this mixin belongs to. + * @param useBottomProgressBar Whether to use bottom progress + */ + public ProgressBarMixin(@NonNull TemplateLayout layout, boolean useBottomProgressBar) { + templateLayout = layout; + this.useBottomProgressBar = useBottomProgressBar; + } + + /** + * Constructor that provide styled attribute information in this Context's theme. + * + * @param layout The {@link TemplateLayout} containing this mixin. + * @param attrs XML attributes given to the layout. + * @param defStyleAttr The default style attribute as given to the constructor of the layout. + */ + public ProgressBarMixin( + @NonNull TemplateLayout layout, AttributeSet attrs, @AttrRes int defStyleAttr) { templateLayout = layout; + + boolean useBottomProgressBar = false; + if (attrs != null) { + final TypedArray a = + layout + .getContext() + .obtainStyledAttributes(attrs, R.styleable.SudProgressBarMixin, defStyleAttr, 0); + + if (a.hasValue(R.styleable.SudProgressBarMixin_sudUseBottomProgressBar)) { + // Set whether we use bottom progress bar or not + useBottomProgressBar = + a.getBoolean(R.styleable.SudProgressBarMixin_sudUseBottomProgressBar, false); + } + + a.recycle(); + + // To avoid bottom progressbar bouncing, change the view state from GONE to INVISIBLE + setShown(false); + } + + this.useBottomProgressBar = useBottomProgressBar; } /** @return True if the progress bar is currently shown. */ public boolean isShown() { - final View progressBar = templateLayout.findManagedViewById(R.id.sud_layout_progress); + final View progressBar = + templateLayout.findManagedViewById( + useBottomProgressBar ? R.id.sud_glif_progress_bar : R.id.sud_layout_progress); return progressBar != null && progressBar.getVisibility() == View.VISIBLE; } @@ -60,7 +111,7 @@ public class ProgressBarMixin implements Mixin { } else { View progressBar = peekProgressBar(); if (progressBar != null) { - progressBar.setVisibility(View.GONE); + progressBar.setVisibility(useBottomProgressBar ? View.INVISIBLE : View.GONE); } } } @@ -73,8 +124,8 @@ public class ProgressBarMixin implements Mixin { * progress bar built-in. */ private ProgressBar getProgressBar() { - final View progressBar = peekProgressBar(); - if (progressBar == null) { + final View progressBarView = peekProgressBar(); + if (progressBarView == null && !useBottomProgressBar) { final ViewStub progressBarStub = (ViewStub) templateLayout.findManagedViewById(R.id.sud_layout_progress_stub); if (progressBarStub != null) { @@ -94,7 +145,9 @@ public class ProgressBarMixin implements Mixin { * or if the template does not contain a progress bar. */ public ProgressBar peekProgressBar() { - return (ProgressBar) templateLayout.findManagedViewById(R.id.sud_layout_progress); + return (ProgressBar) + templateLayout.findManagedViewById( + useBottomProgressBar ? R.id.sud_glif_progress_bar : R.id.sud_layout_progress); } /** Sets the color of the indeterminate progress bar. This method is a no-op on SDK < 21. */ diff --git a/main/src/com/google/android/setupdesign/template/RecyclerMixin.java b/main/src/com/google/android/setupdesign/template/RecyclerMixin.java index 11e9144..b327060 100644 --- a/main/src/com/google/android/setupdesign/template/RecyclerMixin.java +++ b/main/src/com/google/android/setupdesign/template/RecyclerMixin.java @@ -21,15 +21,17 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION_CODES; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.Adapter; import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.util.AttributeSet; import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.setupcompat.internal.TemplateLayout; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupcompat.template.Mixin; import com.google.android.setupdesign.DividerItemDecoration; import com.google.android.setupdesign.GlifLayout; @@ -38,6 +40,7 @@ import com.google.android.setupdesign.items.ItemHierarchy; import com.google.android.setupdesign.items.ItemInflater; import com.google.android.setupdesign.items.RecyclerItemAdapter; import com.google.android.setupdesign.util.DrawableLayoutDirectionHelper; +import com.google.android.setupdesign.util.PartnerStyleHelper; import com.google.android.setupdesign.view.HeaderRecyclerView; import com.google.android.setupdesign.view.HeaderRecyclerView.HeaderAdapter; @@ -64,6 +67,7 @@ public class RecyclerMixin implements Mixin { private int dividerInsetStart; private int dividerInsetEnd; + private boolean isDividerDisplay = true; /** * Creates the RecyclerMixin. Unlike typical mixins which are created in the constructor, this @@ -86,7 +90,22 @@ public class RecyclerMixin implements Mixin { header = ((HeaderRecyclerView) recyclerView).getHeader(); } - this.recyclerView.addItemDecoration(dividerDecoration); + isDividerDisplay = isShowItemsDivider(); + if (isDividerDisplay) { + this.recyclerView.addItemDecoration(dividerDecoration); + } + } + + private boolean isShowItemsDivider() { + // Skips to add item decoration if config flag is false. + if (PartnerStyleHelper.shouldApplyPartnerResource(templateLayout)) { + if (PartnerConfigHelper.get(recyclerView.getContext()) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_ITEMS_DIVIDER_SHOWN)) { + return PartnerConfigHelper.get(recyclerView.getContext()) + .getBoolean(recyclerView.getContext(), PartnerConfig.CONFIG_ITEMS_DIVIDER_SHOWN, true); + } + } + return true; } /** @@ -108,16 +127,24 @@ public class RecyclerMixin implements Mixin { final ItemHierarchy inflated = new ItemInflater(context).inflate(entries); boolean applyPartnerHeavyThemeResource = false; + boolean useFullDynamicColor = false; if (templateLayout instanceof GlifLayout) { applyPartnerHeavyThemeResource = ((GlifLayout) templateLayout).shouldApplyPartnerHeavyThemeResource(); + useFullDynamicColor = ((GlifLayout) templateLayout).useFullDynamicColor(); } final RecyclerItemAdapter adapter = - new RecyclerItemAdapter(inflated, applyPartnerHeavyThemeResource); + new RecyclerItemAdapter(inflated, applyPartnerHeavyThemeResource, useFullDynamicColor); adapter.setHasStableIds(a.getBoolean(R.styleable.SudRecyclerMixin_sudHasStableIds, false)); setAdapter(adapter); } + + if (!isDividerDisplay) { + a.recycle(); + return; + } + int dividerInset = a.getDimensionPixelSize(R.styleable.SudRecyclerMixin_sudDividerInset, -1); if (dividerInset != -1) { setDividerInset(dividerInset); @@ -126,6 +153,23 @@ public class RecyclerMixin implements Mixin { a.getDimensionPixelSize(R.styleable.SudRecyclerMixin_sudDividerInsetStart, 0); int dividerInsetEnd = a.getDimensionPixelSize(R.styleable.SudRecyclerMixin_sudDividerInsetEnd, 0); + + if (PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(templateLayout)) { + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_START)) { + dividerInsetStart = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_LAYOUT_MARGIN_START); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_END)) { + dividerInsetEnd = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_LAYOUT_MARGIN_END); + } + } setDividerInsets(dividerInsetStart, dividerInsetEnd); } @@ -172,7 +216,7 @@ public class RecyclerMixin implements Mixin { * @return The adapter, or {@code null} if the recycler view has no adapter. */ public Adapter<? extends ViewHolder> getAdapter() { - @SuppressWarnings("unchecked") // RecyclerView.getAdapter returns raw type :( + // RecyclerView.getAdapter returns raw type :( final RecyclerView.Adapter<? extends ViewHolder> adapter = recyclerView.getAdapter(); if (adapter instanceof HeaderAdapter) { return ((HeaderAdapter<? extends ViewHolder>) adapter).getWrappedAdapter(); diff --git a/main/src/com/google/android/setupdesign/template/RecyclerViewScrollHandlingDelegate.java b/main/src/com/google/android/setupdesign/template/RecyclerViewScrollHandlingDelegate.java index 71094cf..b7a8d4a 100644 --- a/main/src/com/google/android/setupdesign/template/RecyclerViewScrollHandlingDelegate.java +++ b/main/src/com/google/android/setupdesign/template/RecyclerViewScrollHandlingDelegate.java @@ -16,10 +16,10 @@ package com.google.android.setupdesign.template; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.setupdesign.template.RequireScrollMixin.ScrollHandlingDelegate; /** diff --git a/main/src/com/google/android/setupdesign/template/RequireScrollMixin.java b/main/src/com/google/android/setupdesign/template/RequireScrollMixin.java index f1c7cbe..62c503c 100644 --- a/main/src/com/google/android/setupdesign/template/RequireScrollMixin.java +++ b/main/src/com/google/android/setupdesign/template/RequireScrollMixin.java @@ -19,12 +19,12 @@ package com.google.android.setupdesign.template; import android.content.Context; import android.os.Handler; import android.os.Looper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import com.google.android.setupcompat.internal.TemplateLayout; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.template.Mixin; diff --git a/main/src/com/google/android/setupdesign/template/ScrollViewScrollHandlingDelegate.java b/main/src/com/google/android/setupdesign/template/ScrollViewScrollHandlingDelegate.java index 0fbe5ce..852656f 100644 --- a/main/src/com/google/android/setupdesign/template/ScrollViewScrollHandlingDelegate.java +++ b/main/src/com/google/android/setupdesign/template/ScrollViewScrollHandlingDelegate.java @@ -16,10 +16,10 @@ package com.google.android.setupdesign.template; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.Log; import android.widget.ScrollView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.setupdesign.template.RequireScrollMixin.ScrollHandlingDelegate; import com.google.android.setupdesign.view.BottomScrollView; import com.google.android.setupdesign.view.BottomScrollView.BottomScrollListener; diff --git a/main/src/com/google/android/setupdesign/transition/TransitionHelper.java b/main/src/com/google/android/setupdesign/transition/TransitionHelper.java new file mode 100644 index 0000000..ec2c480 --- /dev/null +++ b/main/src/com/google/android/setupdesign/transition/TransitionHelper.java @@ -0,0 +1,669 @@ +/* + * Copyright (C) 2020 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.google.android.setupdesign.transition; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ActivityOptions; +import android.app.Fragment; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.Log; +import android.view.Window; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import com.google.android.material.transition.platform.MaterialSharedAxis; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupcompat.util.BuildCompatUtils; +import com.google.android.setupdesign.R; +import com.google.android.setupdesign.util.ThemeHelper; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Helper class for apply the transition to the pages which uses platform version. */ +public class TransitionHelper { + + private static final String TAG = "TransitionHelper"; + + /* + * In Setup Wizard, all Just-a-sec style screens (i.e. screens that has an indeterminate + * progress bar and automatically finishes itself), should do a cross-fade when entering or + * exiting the screen. For all other screens, the transition should be a slide-in-from-right + * or customized. + * + * We use two different ways to override the transitions. The first is calling + * overridePendingTransition in code, and the second is using windowAnimationStyle in the theme. + * They have the following priority when framework is figuring out what transition to use: + * 1. overridePendingTransition, entering activity (highest priority) + * 2. overridePendingTransition, exiting activity + * 3. windowAnimationStyle, entering activity + * 4. windowAnimationStyle, exiting activity + * + * This is why, in general, overridePendingTransition is used to specify the fade animation, + * while windowAnimationStyle is used to specify the slide transition. This way fade animation + * will take priority over the slide animation. + * + * Below are types of animation when switching activities. These are return values for + * {@link #getTransition()}. Each of these values represents 4 animations: (backward exit, + * backward enter, forward exit, forward enter). + * + * We override the transition in the following flow + * +--------------+-------------------------+--------------------------+ + * | | going forward | going backward | + * +--------------+-------------------------+--------------------------+ + * | old activity | startActivity(OnResult) | onActivityResult | + * +--------------+-------------------------+--------------------------+ + * | new activity | onStart | finish (RESULT_CANCELED) | + * +--------------+-------------------------+--------------------------+ + */ + + /** The constant of transition type. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TRANSITION_NONE, + TRANSITION_NO_OVERRIDE, + TRANSITION_FRAMEWORK_DEFAULT, + TRANSITION_SLIDE, + TRANSITION_FADE, + TRANSITION_FRAMEWORK_DEFAULT_PRE_P, + TRANSITION_CAPTIVE, + }) + public @interface TransitionType {} + + /** No transition, as in overridePendingTransition(0, 0). */ + public static final int TRANSITION_NONE = -1; + + /** + * No override. If this is specified as the transition, overridePendingTransition will not be + * called. + */ + public static final int TRANSITION_NO_OVERRIDE = 0; + + /** + * Override the transition to the framework default. This values are read from {@link + * android.R.style#Animation_Activity}. + */ + public static final int TRANSITION_FRAMEWORK_DEFAULT = 1; + + /** Override the transition to a slide-in-from-right (or from-left for RTL locales). */ + public static final int TRANSITION_SLIDE = 2; + + /** + * Override the transition to fade in the new activity, while keeping the old activity. Setup + * wizard does not use cross fade to avoid the bright-dim-bright effect when transitioning between + * two screens that look similar. + */ + public static final int TRANSITION_FADE = 3; + + /** Override the transition to the old framework default pre P. */ + public static final int TRANSITION_FRAMEWORK_DEFAULT_PRE_P = 4; + + /** + * Override the transition to the specific transition and the transition type will depends on the + * partner resource. + */ + // TODO: Add new partner resource to determine which transition type would be apply. + public static final int TRANSITION_CAPTIVE = 5; + + /** + * No override. If this is specified as the transition, the enter/exit transition of the window + * will not be set and keep original behavior. + */ + public static final int CONFIG_TRANSITION_NONE = 0; + + /** Override the transition to the specific type that will depend on the partner resource. */ + public static final int CONFIG_TRANSITION_SHARED_X_AXIS = 1; + + /** + * Passed in an intent as EXTRA_ACTIVITY_OPTIONS. This is the {@link ActivityOptions} of the + * transition used in {@link Activity#startActivity} or {@link Activity#startActivityForResult}. + */ + public static final String EXTRA_ACTIVITY_OPTIONS = "sud:activity_options"; + + /** A flag to avoid the {@link Activity#finish} been called more than once. */ + @VisibleForTesting static boolean isFinishCalled = false; + + /** A flag to avoid the {@link Activity#startActivity} called more than once. */ + @VisibleForTesting static boolean isStartActivity = false; + + /** A flag to avoid the {@link Activity#startActivityForResult} called more than once. */ + @VisibleForTesting static boolean isStartActivityForResult = false; + + private TransitionHelper() {} + + /** + * Apply the transition for going forward which is decided by partner resource {@link + * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. + * The default transition that will be applied is {@link #TRANSITION_SLIDE}. The timing to apply + * the transition is going forward from the previous activity to this, or going forward from this + * activity to the next. + * + * <p>For example, in the flow below, the forward transitions will be applied to all arrows + * pointing to the right. Previous screen --> This screen --> Next screen + */ + @TargetApi(VERSION_CODES.LOLLIPOP) + public static void applyForwardTransition(Activity activity) { + applyForwardTransition(activity, TRANSITION_CAPTIVE); + } + + /** + * Apply the transition for going forward which is decided by partner resource {@link + * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. + * The default transition that will be applied is {@link #CONFIG_TRANSITION_NONE}. The timing to + * apply the transition is going forward from the previous {@link Fragment} to this, or going + * forward from this {@link Fragment} to the next. + */ + @TargetApi(VERSION_CODES.M) + public static void applyForwardTransition(Fragment fragment) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { + if (getConfigTransitionType(fragment.getContext()) == CONFIG_TRANSITION_SHARED_X_AXIS) { + MaterialSharedAxis exitTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); + fragment.setExitTransition(exitTransition); + + MaterialSharedAxis enterTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); + fragment.setEnterTransition(enterTransition); + } else { + Log.w(TAG, "Not apply the forward transition for platform fragment."); + } + } else { + Log.w( + TAG, + "Not apply the forward transition for platform fragment. The API is supported from" + + " Android Sdk " + + VERSION_CODES.M); + } + } + + /** + * Apply the transition for going forward. This is applied when going forward from the previous + * activity to this, or going forward from this activity to the next. + * + * <p>For example, in the flow below, the forward transitions will be applied to all arrows + * pointing to the right. Previous screen --> This screen --> Next screen + */ + @TargetApi(VERSION_CODES.LOLLIPOP) + public static void applyForwardTransition(Activity activity, @TransitionType int transitionId) { + if (transitionId == TRANSITION_SLIDE) { + activity.overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); + } else if (transitionId == TRANSITION_FADE) { + activity.overridePendingTransition(android.R.anim.fade_in, R.anim.sud_stay); + } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT) { + TypedArray typedArray = + activity.obtainStyledAttributes( + android.R.style.Animation_Activity, + new int[] { + android.R.attr.activityOpenEnterAnimation, android.R.attr.activityOpenExitAnimation + }); + activity.overridePendingTransition( + typedArray.getResourceId(/* index= */ 0, /* defValue= */ 0), + typedArray.getResourceId(/* index= */ 1, /* defValue= */ 0)); + typedArray.recycle(); + } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT_PRE_P) { + activity.overridePendingTransition( + R.anim.sud_pre_p_activity_open_enter, R.anim.sud_pre_p_activity_open_exit); + } else if (transitionId == TRANSITION_NONE) { + // For TRANSITION_NONE, turn off the transition + activity.overridePendingTransition(/* enterAnim= */ 0, /* exitAnim= */ 0); + } else if (transitionId == TRANSITION_CAPTIVE) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + // 1. Do not change the transition behavior by default + // 2. If the flag present, apply the transition from transition type + if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { + Window window = activity.getWindow(); + if (window != null) { + MaterialSharedAxis exitTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); + window.setExitTransition(exitTransition); + + window.setAllowEnterTransitionOverlap(true); + + MaterialSharedAxis enterTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); + window.setEnterTransition(enterTransition); + } else { + Log.w(TAG, "applyForwardTransition: Invalid window=" + window); + } + } + } else { + Log.w(TAG, "This API is supported from Android Sdk " + VERSION_CODES.LOLLIPOP); + } + } + // For TRANSITION_NO_OVERRIDE or other values, do not override the transition + } + + /** + * Apply the transition for going backward which is decided by partner resource {@link + * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. + * The default transition that will be applied is {@link #TRANSITION_SLIDE}. The timing to apply + * the transition is going backward from the next activity to this, or going backward from this + * activity to the previous. + * + * <p>For example, in the flow below, the backward transitions will be applied to all arrows + * pointing to the left. Previous screen <-- This screen <-- Next screen + */ + @TargetApi(VERSION_CODES.LOLLIPOP) + public static void applyBackwardTransition(Activity activity) { + applyBackwardTransition(activity, TRANSITION_CAPTIVE); + } + + /** + * Apply the transition for going backward which is decided by partner resource {@link + * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. + * The default transition that will be applied is {@link #CONFIG_TRANSITION_NONE}. The timing to + * apply the transition is going backward from the next {@link Fragment} to this, or going + * backward from this {@link Fragment} to the previous. + */ + @TargetApi(VERSION_CODES.M) + public static void applyBackwardTransition(Fragment fragment) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { + if (getConfigTransitionType(fragment.getContext()) == CONFIG_TRANSITION_SHARED_X_AXIS) { + MaterialSharedAxis returnTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); + fragment.setReturnTransition(returnTransition); + + MaterialSharedAxis reenterTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); + fragment.setReenterTransition(reenterTransition); + } else { + Log.w(TAG, "Not apply the backward transition for platform fragment."); + } + } else { + Log.w( + TAG, + "Not apply the backward transition for platform fragment. The API is supported from" + + " Android Sdk " + + VERSION_CODES.M); + } + } + + /** + * Apply the transition for going backward. This is applied when going backward from the next + * activity to this, or going backward from this activity to the previous. + * + * <p>For example, in the flow below, the backward transitions will be applied to all arrows + * pointing to the left. Previous screen <-- This screen <-- Next screen + */ + @TargetApi(VERSION_CODES.LOLLIPOP) + public static void applyBackwardTransition(Activity activity, @TransitionType int transitionId) { + if (transitionId == TRANSITION_SLIDE) { + activity.overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out); + } else if (transitionId == TRANSITION_FADE) { + activity.overridePendingTransition(R.anim.sud_stay, android.R.anim.fade_out); + } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT) { + TypedArray typedArray = + activity.obtainStyledAttributes( + android.R.style.Animation_Activity, + new int[] { + android.R.attr.activityCloseEnterAnimation, + android.R.attr.activityCloseExitAnimation + }); + activity.overridePendingTransition( + typedArray.getResourceId(/* index= */ 0, /* defValue= */ 0), + typedArray.getResourceId(/* index= */ 1, /* defValue= */ 0)); + typedArray.recycle(); + } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT_PRE_P) { + activity.overridePendingTransition( + R.anim.sud_pre_p_activity_close_enter, R.anim.sud_pre_p_activity_close_exit); + } else if (transitionId == TRANSITION_NONE) { + // For TRANSITION_NONE, turn off the transition + activity.overridePendingTransition(/* enterAnim= */ 0, /* exitAnim= */ 0); + } else if (transitionId == TRANSITION_CAPTIVE) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + // 1. Do not change the transition behavior by default + // 2. If the flag present, apply the transition from transition type + if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { + Window window = activity.getWindow(); + if (window != null) { + MaterialSharedAxis reenterTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); + window.setReenterTransition(reenterTransition); + + MaterialSharedAxis returnTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); + window.setReturnTransition(returnTransition); + } else { + Log.w(TAG, "applyBackwardTransition: Invalid window=" + window); + } + } + } else { + Log.w(TAG, "This API is supported from Android Sdk " + VERSION_CODES.LOLLIPOP); + } + } + // For TRANSITION_NO_OVERRIDE or other values, do not override the transition + } + + /** + * A wrapper method, create an {@link android.app.ActivityOptions} to transition between + * activities as the {@link ActivityOptions} parameter of {@link Activity#startActivity}. + * + * @throws IllegalArgumentException is thrown when {@code activity} or {@code intent} is null. + * @throws android.content.ActivityNotFoundException if there was no {@link Activity} found to run + * the given Intent. + */ + public static void startActivityWithTransition(Activity activity, Intent intent) { + startActivityWithTransition(activity, intent, /* overrideActivityOptions= */ null); + } + + /** + * A wrapper method, create an {@link android.app.ActivityOptions} to transition between + * activities as the {@link ActivityOptions} parameter of {@link Activity#startActivity}. + * + * @throws IllegalArgumentException is thrown when {@code activity} or {@code intent} is null. + * @throws android.content.ActivityNotFoundException if there was no {@link Activity} found to run + * the given Intent. + */ + public static void startActivityWithTransition( + Activity activity, Intent intent, Bundle overrideActivityOptions) { + if (activity == null) { + throw new IllegalArgumentException("Invalid activity=" + activity); + } + + if (intent == null) { + throw new IllegalArgumentException("Invalid intent=" + intent); + } + + if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK) { + Log.e( + TAG, + "The transition won't take effect since the WindowManager does not allow override new" + + " task transitions"); + } + + if (!isStartActivity) { + isStartActivity = true; + if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (activity.getWindow() != null + && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { + Log.w( + TAG, + "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); + } + + Bundle bundleActivityOptions; + if (overrideActivityOptions != null) { + bundleActivityOptions = overrideActivityOptions; + } else { + bundleActivityOptions = makeActivityOptions(activity, intent); + } + intent.putExtra(EXTRA_ACTIVITY_OPTIONS, (Parcelable) bundleActivityOptions); + activity.startActivity(intent, bundleActivityOptions); + } else { + Log.w( + TAG, + "Fallback to using startActivity due to the" + + " ActivityOptions#makeSceneTransitionAnimation is supported from Android Sdk " + + VERSION_CODES.LOLLIPOP); + startActivityWithTransitionInternal(activity, intent, overrideActivityOptions); + } + } else { + startActivityWithTransitionInternal(activity, intent, overrideActivityOptions); + } + } + isStartActivity = false; + } + + private static void startActivityWithTransitionInternal( + Activity activity, Intent intent, Bundle overrideActivityOptions) { + try { + if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { + if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS + && overrideActivityOptions != null) { + activity.startActivity(intent, overrideActivityOptions); + } else { + activity.startActivity(intent); + } + } else { + Log.w( + TAG, + "Fallback to using startActivity(Intent) due to the startActivity(Intent, Bundle) is" + + " supported from Android Sdk " + + VERSION_CODES.JELLY_BEAN); + activity.startActivity(intent); + } + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity not found when startActivity with transition."); + isStartActivity = false; + throw e; + } + } + + public static void startActivityForResultWithTransition( + Activity activity, Intent intent, int requestCode) { + startActivityForResultWithTransition( + activity, intent, requestCode, /* overrideActivityOptions= */ null); + } + + /** + * A wrapper method, create an {@link android.app.ActivityOptions} to transition between + * activities as the {@code activityOptions} parameter of {@link Activity#startActivityForResult}. + * + * @throws IllegalArgumentException is thrown when {@code activity} or {@code intent} is null. + * @throws android.content.ActivityNotFoundException if there was no {@link Activity} found to run + * the given Intent. + */ + public static void startActivityForResultWithTransition( + Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions) { + if (activity == null) { + throw new IllegalArgumentException("Invalid activity=" + activity); + } + + if (intent == null) { + throw new IllegalArgumentException("Invalid intent=" + intent); + } + + if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK) { + Log.e( + TAG, + "The transition won't take effect since the WindowManager does not allow override new" + + " task transitions"); + } + + if (!isStartActivityForResult) { + isStartActivityForResult = true; + if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (activity.getWindow() != null + && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { + Log.w( + TAG, + "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); + } + + Bundle bundleActivityOptions; + if (overrideActivityOptions != null) { + bundleActivityOptions = overrideActivityOptions; + } else { + bundleActivityOptions = makeActivityOptions(activity, intent); + } + intent.putExtra(EXTRA_ACTIVITY_OPTIONS, (Parcelable) bundleActivityOptions); + activity.startActivityForResult(intent, requestCode, bundleActivityOptions); + } else { + Log.w( + TAG, + "Fallback to using startActivityForResult API due to the" + + " ActivityOptions#makeSceneTransitionAnimation is supported from Android Sdk " + + VERSION_CODES.LOLLIPOP); + startActivityForResultWithTransitionInternal( + activity, intent, requestCode, overrideActivityOptions); + } + } else { + startActivityForResultWithTransitionInternal( + activity, intent, requestCode, overrideActivityOptions); + } + isStartActivityForResult = false; + } + } + + private static void startActivityForResultWithTransitionInternal( + Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions) { + try { + if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { + if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS + && overrideActivityOptions != null) { + activity.startActivityForResult(intent, requestCode, overrideActivityOptions); + } else { + activity.startActivityForResult(intent, requestCode); + } + } else { + Log.w( + TAG, + "Fallback to using startActivityForResult(Intent) due to the" + + " startActivityForResult(Intent,int) is supported from Android Sdk " + + VERSION_CODES.JELLY_BEAN); + activity.startActivityForResult(intent, requestCode); + } + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity not found when startActivityForResult with transition."); + isStartActivityForResult = false; + throw e; + } + } + + /** + * A wrapper method, calling {@link Activity#finishAfterTransition()} to trigger exit transition + * when running in Android S and the transition type {link #CONFIG_TRANSITION_SHARED_X_AXIS}. + * + * @throws IllegalArgumentException is thrown when {@code activity} is null. + */ + public static void finishActivity(Activity activity) { + if (activity == null) { + throw new IllegalArgumentException("Invalid activity=" + activity); + } + + // Avoids finish been called more than once. + if (!isFinishCalled) { + isFinishCalled = true; + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP + && getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { + activity.finishAfterTransition(); + } else { + Log.w( + TAG, + "Fallback to using Activity#finish() due to the" + + " Activity#finishAfterTransition() is supported from Android Sdk " + + VERSION_CODES.LOLLIPOP); + activity.finish(); + } + } + isFinishCalled = false; + } + + /** + * Returns the transition type from the {@link PartnerConfig#CONFIG_TRANSITION_TYPE} partner + * resource on Android S, otherwise returns {@link #CONFIG_TRANSITION_NONE}. + */ + public static int getConfigTransitionType(Context context) { + return BuildCompatUtils.isAtLeastS() && ThemeHelper.shouldApplyExtendedPartnerConfig(context) + ? PartnerConfigHelper.get(context) + .getInteger(context, PartnerConfig.CONFIG_TRANSITION_TYPE, CONFIG_TRANSITION_NONE) + : CONFIG_TRANSITION_NONE; + } + + /** + * A wrapper method, create a {@link Bundle} from {@link ActivityOptions} to transition between + * Activities using cross-Activity scene animations. This {@link Bundle} that can be used with + * {@link Context#startActivity(Intent, Bundle)} and related methods. + * + * <p>Example usage: + * + * <pre>{@code + * Intent intent = new Intent("com.example.NEXT_ACTIVITY"); + * activity.startActivity(intent, TransitionHelper.makeActivityOptions(activity, intent, null); + * }</pre> + * + * <p>Unexpected usage: + * + * <pre>{@code + * Intent intent = new Intent("com.example.NEXT_ACTIVITY"); + * Intent intent2 = new Intent("com.example.NEXT_ACTIVITY"); + * activity.startActivity(intent, TransitionHelper.makeActivityOptions(activity, intent2, null); + * }</pre> + */ + @Nullable + public static Bundle makeActivityOptions(Activity activity, Intent intent) { + return makeActivityOptions(activity, intent, false); + } + + /** + * A wrapper method, create a {@link Bundle} from {@link ActivityOptions} to transition between + * Activities using cross-Activity scene animations. This {@link Bundle} that can be used with + * {@link Context#startActivity(Intent, Bundle)} and related methods. When this {@code activity} + * is a no UI activity(the activity doesn't inflate any layouts), you will need to pass the bundle + * coming from previous UI activity as the {@link ActivityOptions}, otherwise, the transition + * won't be take effect. The {@code overrideActivityOptionsFromIntent} is supporting this purpose + * to return the {@link ActivityOptions} instead of creating from this no UI activity while the + * transition is apply {@link #CONFIG_TRANSITION_SHARED_X_AXIS} config. Moreover, the + * startActivity*WithTransition relative methods and {@link #makeActivityOptions} will put {@link + * ActivityOptions} to the {@code intent} by default, you can get the {@link ActivityOptions} + * which makes from previous activity by accessing {@link #EXTRA_ACTIVITY_OPTIONS} extra from + * {@link Activity#getIntent()}. + * + * <p>Example usage of a no UI activity: + * + * <pre>{@code + * Intent intent = new Intent("com.example.NEXT_ACTIVITY"); + * activity.startActivity(intent, TransitionHelper.makeActivityOptions(activity, intent, true); + * }</pre> + */ + @Nullable + public static Bundle makeActivityOptions( + Activity activity, Intent intent, boolean overrideActivityOptionsFromIntent) { + Bundle resultBundle = null; + if (activity == null || intent == null) { + return resultBundle; + } + + if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK) { + Log.e( + TAG, + "The transition won't take effect since the WindowManager does not allow override new" + + " task transitions"); + } + + if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (activity.getWindow() != null + && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { + Log.w( + TAG, + "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); + } + + if (overrideActivityOptionsFromIntent && activity.getIntent() != null) { + resultBundle = activity.getIntent().getBundleExtra(EXTRA_ACTIVITY_OPTIONS); + } else { + resultBundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle(); + } + intent.putExtra(EXTRA_ACTIVITY_OPTIONS, (Parcelable) resultBundle); + return resultBundle; + } + } + + return resultBundle; + } +} diff --git a/main/src/com/google/android/setupdesign/transition/support/TransitionHelper.java b/main/src/com/google/android/setupdesign/transition/support/TransitionHelper.java new file mode 100644 index 0000000..8fd9d6e --- /dev/null +++ b/main/src/com/google/android/setupdesign/transition/support/TransitionHelper.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 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.google.android.setupdesign.transition.support; + +import static com.google.android.setupdesign.transition.TransitionHelper.CONFIG_TRANSITION_SHARED_X_AXIS; +import static com.google.android.setupdesign.transition.TransitionHelper.getConfigTransitionType; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import androidx.fragment.app.Fragment; +import android.util.Log; +import android.view.Window; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityOptionsCompat; +import com.google.android.material.transition.platform.MaterialSharedAxis; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; + +/** Helper class for apply the transition to the pages which uses support library. */ +public class TransitionHelper { + + private static final String TAG = "TransitionHelper"; + + private TransitionHelper() {} + + /** + * Apply the transition for going forward which is decided by partner resource {@link + * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. + * The default transition that will be applied is {@link + * com.google.android.setupdesign.transition.TransitionHelper#CONFIG_TRANSITION_NONE}. The timing + * to apply the transition is going forward from the previous {@link Fragment} to this, or going + * forward from this {@link Fragment} to the next. + */ + @TargetApi(VERSION_CODES.M) + public static void applyForwardTransition(Fragment fragment) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { + if (CONFIG_TRANSITION_SHARED_X_AXIS == getConfigTransitionType(fragment.getContext())) { + MaterialSharedAxis exitTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); + fragment.setExitTransition(exitTransition); + + MaterialSharedAxis enterTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); + fragment.setEnterTransition(enterTransition); + } else { + Log.w(TAG, "Not apply the forward transition for support lib's fragment."); + } + } else { + Log.w( + TAG, + "Not apply the forward transition for support lib's fragment. The API is supported from" + + " Android Sdk " + + VERSION_CODES.M); + } + } + + /** + * Apply the transition for going backward which is decided by partner resource {@link + * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. + * The default transition that will be applied is {@link + * com.google.android.setupdesign.transition.TransitionHelper#CONFIG_TRANSITION_NONE}. The timing + * to apply the transition is going backward from the next {@link Fragment} to this, or going + * backward from this {@link Fragment} to the previous. + */ + @TargetApi(VERSION_CODES.M) + public static void applyBackwardTransition(Fragment fragment) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { + if (CONFIG_TRANSITION_SHARED_X_AXIS == getConfigTransitionType(fragment.getContext())) { + MaterialSharedAxis returnTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); + fragment.setReturnTransition(returnTransition); + + MaterialSharedAxis reenterTransition = + new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); + fragment.setReenterTransition(reenterTransition); + } else { + Log.w(TAG, "Not apply the backward transition for support lib's fragment."); + } + } else { + Log.w( + TAG, + "Not apply the backward transition for support lib's fragment. The API is supported from" + + " Android Sdk " + + VERSION_CODES.M); + } + } + + /** + * A wrapper method, create an {@link ActivityOptionsCompat} to transition between activities as + * the {@link ActivityOptionsCompat} parameter of {@link + * androidx.activity.result.ActivityResultLauncher#launch(I, ActivityOptionsCompat)} method. + */ + @Nullable + public static ActivityOptionsCompat makeActivityOptionsCompat(Activity activity) { + ActivityOptionsCompat activityOptionsCompat = null; + if (activity == null) { + return activityOptionsCompat; + } + + if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (activity.getWindow() != null + && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { + Log.w( + TAG, + "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); + } + + activityOptionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(activity); + } + } + + return activityOptionsCompat; + } +} diff --git a/main/src/com/google/android/setupdesign/util/ButtonStyler.java b/main/src/com/google/android/setupdesign/util/ButtonStyler.java new file mode 100644 index 0000000..8eb92af --- /dev/null +++ b/main/src/com/google/android/setupdesign/util/ButtonStyler.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 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.google.android.setupdesign.util; + +import android.content.Context; +import android.widget.Button; +import com.google.android.setupcompat.template.FooterButtonStyleUtils; + +/** Utils for buttons in GlifLayout content area to get the style as footer buttons. */ +public class ButtonStyler { + + /** Apply the partner primary button style to given {@code button}. */ + public static void applyPartnerCustomizationPrimaryButtonStyle(Context context, Button button) { + if (button == null || context == null) { + return; + } + FooterButtonStyleUtils.applyPrimaryButtonPartnerResource( + context, button, ThemeHelper.shouldApplyDynamicColor(context)); + } + + /** Apply the partner secondary button style to given {@code button}. */ + public static void applyPartnerCustomizationSecondaryButtonStyle(Context context, Button button) { + if (button == null || context == null) { + return; + } + FooterButtonStyleUtils.applySecondaryButtonPartnerResource( + context, button, ThemeHelper.shouldApplyDynamicColor(context)); + } + + private ButtonStyler() {} +} diff --git a/main/src/com/google/android/setupdesign/util/ContentStyler.java b/main/src/com/google/android/setupdesign/util/ContentStyler.java index 0ab49b8..d390780 100644 --- a/main/src/com/google/android/setupdesign/util/ContentStyler.java +++ b/main/src/com/google/android/setupdesign/util/ContentStyler.java @@ -17,19 +17,33 @@ package com.google.android.setupdesign.util; import android.content.Context; +import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; import android.widget.TextView; +import androidx.annotation.Nullable; import com.google.android.setupcompat.partnerconfig.PartnerConfig; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupdesign.R; import com.google.android.setupdesign.util.TextViewPartnerStyler.TextPartnerConfigs; import java.util.Locale; /** - * Applies the partner style of content to the given TextView {@code contentText}. The user needs to - * check if the {@code contentText} should apply partner heavy theme before calling this method. + * Applies the partner style of content to the given TextView {@code contentText}. The theme should + * set partner heavy theme first, and then the partner style of content would be applied. The user + * can also check if the {@code contentText} should apply partner heavy theme before calling this + * method. */ public final class ContentStyler { - public static void applyPartnerCustomizationStyle(TextView contentText) { + public static void applyBodyPartnerCustomizationStyle(TextView contentText) { + if (!PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(contentText)) { + return; + } TextViewPartnerStyler.applyPartnerCustomizationStyle( contentText, @@ -38,10 +52,149 @@ public final class ContentStyler { PartnerConfig.CONFIG_CONTENT_LINK_TEXT_COLOR, PartnerConfig.CONFIG_CONTENT_TEXT_SIZE, PartnerConfig.CONFIG_CONTENT_FONT_FAMILY, + null, + null, ContentStyler.getPartnerContentTextGravity(contentText.getContext()))); } - public static int getPartnerContentTextGravity(Context context) { + /** + * Applies the partner heavy style of content info to the given views including content info + * container, content info icon and content info text, the given views should be included in a + * layout which the same view hierarchy with {@link R.layout#sud_content_info}. + * + * @param infoContainer A view the container resource of content info + * @param infoIcon A image view the icon resource of content info + * @param infoText A text view content info resource + */ + public static void applyInfoPartnerCustomizationStyle( + @Nullable View infoContainer, @Nullable ImageView infoIcon, TextView infoText) { + if (!PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(infoText)) { + return; + } + + Context context = infoText.getContext(); + + boolean textSizeConfigAvailable = + PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CONTENT_INFO_TEXT_SIZE); + boolean fontFamilyConfigAvailable = + PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CONTENT_INFO_FONT_FAMILY); + + TextViewPartnerStyler.applyPartnerCustomizationStyle( + infoText, + new TextPartnerConfigs( + null, + null, + textSizeConfigAvailable ? PartnerConfig.CONFIG_CONTENT_INFO_TEXT_SIZE : null, + fontFamilyConfigAvailable ? PartnerConfig.CONFIG_CONTENT_INFO_FONT_FAMILY : null, + null, + null, + 0)); + + // TODO: Move CONFIG_CONTENT_INFO_LINE_SPACING_EXTRA to TextPartnerConfigs for + // customize + boolean isAtLeastP = VERSION.SDK_INT >= VERSION_CODES.P; + if (isAtLeastP + && PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CONTENT_INFO_LINE_SPACING_EXTRA)) { + int textLineSpacingExtraInPx = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_CONTENT_INFO_LINE_SPACING_EXTRA); + + float infoTextSizeInPx = infoText.getTextSize(); + if (textSizeConfigAvailable) { + float textSizeInPx = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_CONTENT_INFO_TEXT_SIZE, 0); + if (textSizeInPx > 0) { + infoTextSizeInPx = textSizeInPx; + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + infoText.setLineHeight(Math.round(textLineSpacingExtraInPx + infoTextSizeInPx)); + } + } + + if (infoIcon != null) { + ViewGroup.LayoutParams lp = infoIcon.getLayoutParams(); + + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CONTENT_INFO_ICON_SIZE)) { + int oldHeight = lp.height; + lp.height = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_CONTENT_INFO_ICON_SIZE); + lp.width = lp.width * (lp.height / oldHeight); + infoIcon.setScaleType(ScaleType.FIT_CENTER); + } + + boolean partnerConfigAvailable = + PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CONTENT_INFO_ICON_MARGIN_END); + if (partnerConfigAvailable && lp instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; + int endMargin = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_CONTENT_INFO_ICON_MARGIN_END); + mlp.setMargins(mlp.leftMargin, mlp.topMargin, endMargin, mlp.bottomMargin); + } + } + + if (infoContainer != null) { + float paddingTop; + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CONTENT_INFO_PADDING_TOP)) { + paddingTop = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_CONTENT_INFO_PADDING_TOP); + } else { + paddingTop = infoContainer.getPaddingTop(); + } + + float paddingBottom; + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CONTENT_INFO_PADDING_BOTTOM)) { + paddingBottom = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_CONTENT_INFO_PADDING_BOTTOM); + } else { + paddingBottom = infoContainer.getPaddingBottom(); + } + + if (paddingTop != infoContainer.getPaddingTop() + || paddingBottom != infoContainer.getPaddingBottom()) { + infoContainer.setPadding(0, (int) paddingTop, 0, (int) paddingBottom); + } + } + } + + /** + * Returns the layout margin start from partner config. If the activity of given {@code context} + * does not enable the partner heavy theme, then returns the default value from GlifTheme. + * + * @param context The context of a GlifLayout activity. + */ + public static float getPartnerContentMarginStart(Context context) { + // default value is GlifTheme layout margin start. + // That is the attr sudMarginStart, and the value is sud_layout_margin_sides. + float result = context.getResources().getDimension(R.dimen.sud_layout_margin_sides); + if (PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(context)) { + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_START)) { + result = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_LAYOUT_MARGIN_START, result); + } + } + return result; + } + + private static int getPartnerContentTextGravity(Context context) { String gravity = PartnerConfigHelper.get(context) .getString(context, PartnerConfig.CONFIG_CONTENT_LAYOUT_GRAVITY); diff --git a/main/src/com/google/android/setupdesign/util/DescriptionStyler.java b/main/src/com/google/android/setupdesign/util/DescriptionStyler.java index 0a05f9d..0a786c6 100644 --- a/main/src/com/google/android/setupdesign/util/DescriptionStyler.java +++ b/main/src/com/google/android/setupdesign/util/DescriptionStyler.java @@ -22,13 +22,18 @@ import com.google.android.setupdesign.util.TextViewPartnerStyler.TextPartnerConf /** * Applies the partner style of description to the given TextView {@code description}. The user - * needs to check if the {@code description} should apply partner heavy theme before calling this - * method. + * needs to check if the {@code description} should apply partner heavy theme or light theme before + * calling this method, only heavy theme can apply for all configs. */ public final class DescriptionStyler { - public static void applyPartnerCustomizationStyle(TextView description) { - + /** + * Applies the partner heavy style of description to the given text view. Must check the current + * text view applies partner customized configurations to heavy theme before applying. + * + * @param description A text view description resource + */ + public static void applyPartnerCustomizationHeavyStyle(TextView description) { TextViewPartnerStyler.applyPartnerCustomizationStyle( description, new TextPartnerConfigs( @@ -36,6 +41,27 @@ public final class DescriptionStyler { PartnerConfig.CONFIG_DESCRIPTION_LINK_TEXT_COLOR, PartnerConfig.CONFIG_DESCRIPTION_TEXT_SIZE, PartnerConfig.CONFIG_DESCRIPTION_FONT_FAMILY, + null, + null, + PartnerStyleHelper.getLayoutGravity(description.getContext()))); + } + + /** + * Applies the partner light style of description to the given text view. Must check the current + * text view applies partner customized configurations to light theme before applying. + * + * @param description A text view description resource + */ + public static void applyPartnerCustomizationLightStyle(TextView description) { + TextViewPartnerStyler.applyPartnerCustomizationLightStyle( + description, + new TextPartnerConfigs( + null, + null, + null, + null, + null, + null, PartnerStyleHelper.getLayoutGravity(description.getContext()))); } diff --git a/main/src/com/google/android/setupdesign/util/DynamicColorPalette.java b/main/src/com/google/android/setupdesign/util/DynamicColorPalette.java new file mode 100644 index 0000000..2db8c75 --- /dev/null +++ b/main/src/com/google/android/setupdesign/util/DynamicColorPalette.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021 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.google.android.setupdesign.util; + +import android.content.Context; +import androidx.annotation.ColorInt; +import androidx.annotation.IntDef; +import androidx.annotation.VisibleForTesting; +import com.google.android.setupdesign.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** The class to get dynamic colors. */ +public final class DynamicColorPalette { + + @VisibleForTesting static int colorRes = 0; + + private DynamicColorPalette() {} + + /** Dynamic color category. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + ColorType.ACCENT, + ColorType.PRIMARY_TEXT, + ColorType.SECONDARY_TEXT, + ColorType.DISABLED_OPTION, + ColorType.ERROR_WARNING, + ColorType.SUCCESS_DONE, + ColorType.FALLBACK_ACCENT, + ColorType.BACKGROUND_SURFACE, + }) + public @interface ColorType { + int ACCENT = 0; + int PRIMARY_TEXT = 1; + int SECONDARY_TEXT = 2; + int DISABLED_OPTION = 3; + int ERROR_WARNING = 4; + int SUCCESS_DONE = 5; + int FALLBACK_ACCENT = 6; + int BACKGROUND_SURFACE = 7; + } + + @ColorInt + public static int getColor(Context context, @ColorType int dynamicColorCategory) { + switch (dynamicColorCategory) { + case ColorType.ACCENT: + colorRes = R.color.sud_dynamic_color_accent_glif_v3; + break; + case ColorType.PRIMARY_TEXT: + colorRes = R.color.sud_system_primary_text; + break; + case ColorType.SECONDARY_TEXT: + colorRes = R.color.sud_system_secondary_text; + break; + case ColorType.DISABLED_OPTION: + colorRes = R.color.sud_system_tertiary_text_inactive; + break; + case ColorType.ERROR_WARNING: + colorRes = R.color.sud_system_error_warning; + break; + case ColorType.SUCCESS_DONE: + colorRes = R.color.sud_system_success_done; + break; + case ColorType.FALLBACK_ACCENT: + colorRes = R.color.sud_system_fallback_accent; + break; + case ColorType.BACKGROUND_SURFACE: + colorRes = R.color.sud_system_background_surface; + break; + // fall out + } + + return context.getResources().getColor(colorRes); + } +} diff --git a/main/src/com/google/android/setupdesign/util/HeaderAreaStyler.java b/main/src/com/google/android/setupdesign/util/HeaderAreaStyler.java index cd5135d..1b3daac 100644 --- a/main/src/com/google/android/setupdesign/util/HeaderAreaStyler.java +++ b/main/src/com/google/android/setupdesign/util/HeaderAreaStyler.java @@ -16,24 +16,46 @@ package com.google.android.setupdesign.util; +import static com.google.android.setupcompat.util.BuildCompatUtils.isAtLeastS; + import android.content.Context; -import androidx.annotation.Nullable; +import android.graphics.drawable.VectorDrawable; +import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.util.Log; import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; +import android.widget.ImageView.ScaleType; import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import com.google.android.setupcompat.partnerconfig.PartnerConfig; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupdesign.util.TextViewPartnerStyler.TextPartnerConfigs; /** - * Helper class to apply the partner customization for the header area widgets. The user needs to - * check if the header area widgets should apply partner heavy theme before calling these methods. + * Applies the partner customization for the header area widgets. The user needs to check if the + * header area widgets should apply partner heavy theme or light theme before calling these methods. */ public final class HeaderAreaStyler { - /** Applies the partner style of header text to the given textView {@code header}. */ - public static void applyPartnerCustomizationHeaderStyle(@Nullable TextView header) { + private static final String TAG = "HeaderAreaStyler"; + + @VisibleForTesting + static final String WARN_TO_USE_DRAWABLE = + "To achieve scaling icon in SetupDesign lib, should use vector drawable icon from "; + + /** + * Applies the partner heavy style of header text to the given textView {@code header}. + * + * @param header A header text would apply partner heavy style + */ + public static void applyPartnerCustomizationHeaderHeavyStyle(@Nullable TextView header) { if (header == null) { return; @@ -45,38 +67,199 @@ public final class HeaderAreaStyler { null, PartnerConfig.CONFIG_HEADER_TEXT_SIZE, PartnerConfig.CONFIG_HEADER_FONT_FAMILY, + PartnerConfig.CONFIG_HEADER_TEXT_MARGIN_TOP, + PartnerConfig.CONFIG_HEADER_TEXT_MARGIN_BOTTOM, PartnerStyleHelper.getLayoutGravity(header.getContext()))); } - /** Applies the partner style of header background to the given layout {@code headerArea}. */ - public static void applyPartnerCustomizationHeaderAreaStyle(ViewGroup headerArea) { + /** + * Applies the partner heavy style of description text to the given textView {@code description}. + * + * @param description A description text would apply partner heavy style + */ + public static void applyPartnerCustomizationDescriptionHeavyStyle( + @Nullable TextView description) { + + if (description == null) { + return; + } + TextViewPartnerStyler.applyPartnerCustomizationStyle( + description, + new TextPartnerConfigs( + PartnerConfig.CONFIG_DESCRIPTION_TEXT_COLOR, + PartnerConfig.CONFIG_DESCRIPTION_LINK_TEXT_COLOR, + PartnerConfig.CONFIG_DESCRIPTION_TEXT_SIZE, + PartnerConfig.CONFIG_DESCRIPTION_FONT_FAMILY, + PartnerConfig.CONFIG_DESCRIPTION_TEXT_MARGIN_TOP, + PartnerConfig.CONFIG_DESCRIPTION_TEXT_MARGIN_BOTTOM, + PartnerStyleHelper.getLayoutGravity(description.getContext()))); + } + + /** + * Applies the partner light style of header text to the given textView {@code header}. + * + * @param header A header text would apply partner light style + */ + public static void applyPartnerCustomizationHeaderLightStyle(@Nullable TextView header) { + + if (header == null) { + return; + } + + TextViewPartnerStyler.applyPartnerCustomizationLightStyle( + header, + new TextPartnerConfigs( + null, + null, + null, + null, + null, + null, + PartnerStyleHelper.getLayoutGravity(header.getContext()))); + } + + /** + * Applies the partner light style of description text to the given textView {@code description}. + * + * @param description A description text would apply partner light style + */ + public static void applyPartnerCustomizationDescriptionLightStyle( + @Nullable TextView description) { + + if (description == null) { + return; + } + TextViewPartnerStyler.applyPartnerCustomizationLightStyle( + description, + new TextPartnerConfigs( + null, + null, + null, + null, + null, + null, + PartnerStyleHelper.getLayoutGravity(description.getContext()))); + } + + /** + * Applies the partner style of header area to the given layout {@code headerArea}. The theme + * should set partner heavy theme first, and then the partner style of header would be applied. As + * for the margin bottom of header, it would also be appied when heavy theme parter config is + * enabled. + * + * @param headerArea A ViewGroup would apply the partner style of header area + */ + public static void applyPartnerCustomizationHeaderAreaStyle(ViewGroup headerArea) { if (headerArea == null) { return; } - Context context = headerArea.getContext(); - int color = + if (PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(headerArea)) { + Context context = headerArea.getContext(); + + int color = + PartnerConfigHelper.get(context) + .getColor(context, PartnerConfig.CONFIG_HEADER_AREA_BACKGROUND_COLOR); + headerArea.setBackgroundColor(color); + + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_HEADER_CONTAINER_MARGIN_BOTTOM)) { + final ViewGroup.LayoutParams lp = headerArea.getLayoutParams(); + if (lp instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; + + int bottomMargin = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_HEADER_CONTAINER_MARGIN_BOTTOM); + mlp.setMargins(mlp.leftMargin, mlp.topMargin, mlp.rightMargin, bottomMargin); + headerArea.setLayoutParams(lp); + } + } + } + } + + /** + * Applies the partner heavy style of header icon to the given {@code iconImage}. The theme should + * check partner heavy theme first, and then the partner icon size would be applied. + * + * @param iconImage A ImageView would apply the partner style of header icon + * @param iconContainer The container of the header icon + */ + public static void applyPartnerCustomizationIconStyle( + @Nullable ImageView iconImage, FrameLayout iconContainer) { + if (iconImage == null || iconContainer == null) { + return; + } + + Context context = iconImage.getContext(); + int gravity = PartnerStyleHelper.getLayoutGravity(context); + if (gravity != 0) { + setGravity(iconImage, gravity); + } + + final ViewGroup.LayoutParams lp = iconContainer.getLayoutParams(); + boolean partnerConfigAvailable = PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_HEADER_AREA_BACKGROUND_COLOR); - headerArea.setBackgroundColor(color); + .isPartnerConfigAvailable(PartnerConfig.CONFIG_ICON_MARGIN_TOP); + if (partnerConfigAvailable && lp instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; + int topMargin = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_ICON_MARGIN_TOP); + mlp.setMargins(mlp.leftMargin, topMargin, mlp.rightMargin, mlp.bottomMargin); + } + + if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(PartnerConfig.CONFIG_ICON_SIZE)) { + + checkImageType(iconImage); + + final ViewGroup.LayoutParams lpIcon = iconImage.getLayoutParams(); + lpIcon.height = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_ICON_SIZE); + lpIcon.width = LayoutParams.WRAP_CONTENT; + iconImage.setScaleType(ScaleType.FIT_CENTER); + } } - /** Applies the partner style of header icon to the given {@code iconImage}. */ + /** Applies the partner light style of header icon to the given {@code iconImage}. */ public static void applyPartnerCustomizationIconStyle(@Nullable ImageView iconImage) { - if (iconImage == null) { return; } - int gravity = PartnerStyleHelper.getLayoutGravity(iconImage.getContext()); if (gravity != 0) { setGravity(iconImage, gravity); } } + private static void checkImageType(ImageView imageView) { + ViewTreeObserver vto = imageView.getViewTreeObserver(); + vto.addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + imageView.getViewTreeObserver().removeOnPreDrawListener(this); + // TODO: Remove when Partners all used Drawable icon image and never use + if (isAtLeastS() + && !(imageView.getDrawable() == null + || (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP + && imageView.getDrawable() instanceof VectorDrawable) + || imageView.getDrawable() instanceof VectorDrawableCompat) + && (Build.TYPE.equals("userdebug") || Build.TYPE.equals("eng"))) { + Log.w(TAG, WARN_TO_USE_DRAWABLE + imageView.getContext().getPackageName()); + } + return true; + } + }); + } + private static void setGravity(ImageView icon, int gravity) { - if (icon.getLayoutParams() instanceof LinearLayout.LayoutParams) { - LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) icon.getLayoutParams(); + if (icon.getLayoutParams() instanceof FrameLayout.LayoutParams) { + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) icon.getLayoutParams(); layoutParams.gravity = gravity; icon.setLayoutParams(layoutParams); } diff --git a/main/src/com/google/android/setupdesign/util/ItemStyler.java b/main/src/com/google/android/setupdesign/util/ItemStyler.java new file mode 100644 index 0000000..ecddfd4 --- /dev/null +++ b/main/src/com/google/android/setupdesign/util/ItemStyler.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2020 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.google.android.setupdesign.util; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; +import androidx.annotation.Nullable; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupdesign.R; +import com.google.android.setupdesign.util.TextViewPartnerStyler.TextPartnerConfigs; + +/** + * Applies the partner style of layout to the given View {@code view}. The user needs to check if + * the {@code view} should apply partner heavy theme before calling this method. + */ +public final class ItemStyler { + + /** + * Applies the heavy theme partner configs to the given listItemView {@code listItemView}. The + * user needs to check before calling this method: + * + * <p>1) If the {@code listItemView} should apply heavy theme resource by calling {@link + * PartnerStyleHelper#shouldApplyPartnerHeavyThemeResource}. + * + * <p>2) If the layout of the {@code listItemView} contains fixed resource IDs which attempts to + * apply heavy theme resources (The resource ID of the title is "sud_items_title" and the resource + * ID of the summary is "sud_items_summary"), refer to {@link R.layout#sud_items_default}. + * + * @param listItemView A view would be applied heavy theme styles + */ + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + public static void applyPartnerCustomizationItemStyle(@Nullable View listItemView) { + if (listItemView == null) { + return; + } + if (!PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(listItemView)) { + return; + } + + final TextView titleTextView = listItemView.findViewById(R.id.sud_items_title); + // apply title text style + applyPartnerCustomizationItemTitleStyle(titleTextView); + + // adjust list item view gravity + TextView summaryTextView = listItemView.findViewById(R.id.sud_items_summary); + if (summaryTextView.getVisibility() == View.GONE && listItemView instanceof LinearLayout) { + // Set list items to vertical center when there is no summary. + ((LinearLayout) listItemView).setGravity(Gravity.CENTER_VERTICAL); + } + + // apply summary text style + applyPartnerCustomizationItemSummaryStyle(summaryTextView); + + // apply list item view style + applyPartnerCustomizationItemViewLayoutStyle(listItemView); + } + + /** + * Applies the partner heavy style to the given list item title text view. Will check the current + * text view enabled the partner customized heavy theme configurations before applying. + * + * @param titleTextView A textView of a list item title text. + */ + public static void applyPartnerCustomizationItemTitleStyle(TextView titleTextView) { + if (!PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(titleTextView)) { + return; + } + TextViewPartnerStyler.applyPartnerCustomizationStyle( + titleTextView, + new TextPartnerConfigs( + null, + null, + PartnerConfig.CONFIG_ITEMS_TITLE_TEXT_SIZE, + PartnerConfig.CONFIG_ITEMS_TITLE_FONT_FAMILY, + null, + null, + PartnerStyleHelper.getLayoutGravity(titleTextView.getContext()))); + } + + /** + * Applies the partner heavy style to the given summary text view. Will check the current text + * view enabled the partner customized heavy theme configurations before applying. + * + * @param summaryTextView A textView of a list item summary text. + */ + public static void applyPartnerCustomizationItemSummaryStyle(TextView summaryTextView) { + if (!PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(summaryTextView)) { + return; + } + + TextViewPartnerStyler.applyPartnerCustomizationStyle( + summaryTextView, + new TextPartnerConfigs( + null, + null, + PartnerConfig.CONFIG_ITEMS_SUMMARY_TEXT_SIZE, + PartnerConfig.CONFIG_ITEMS_SUMMARY_FONT_FAMILY, + PartnerConfig.CONFIG_ITEMS_SUMMARY_MARGIN_TOP, + null, + PartnerStyleHelper.getLayoutGravity(summaryTextView.getContext()))); + } + + private static void applyPartnerCustomizationItemViewLayoutStyle(@Nullable View listItemView) { + Context context = listItemView.getContext(); + float paddingTop; + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_ITEMS_PADDING_TOP)) { + paddingTop = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_ITEMS_PADDING_TOP); + } else { + paddingTop = listItemView.getPaddingTop(); + } + + float paddingBottom; + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_ITEMS_PADDING_BOTTOM)) { + paddingBottom = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_ITEMS_PADDING_BOTTOM); + } else { + paddingBottom = listItemView.getPaddingBottom(); + } + + if (paddingTop != listItemView.getPaddingTop() + || paddingBottom != listItemView.getPaddingBottom()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + listItemView.setPadding( + listItemView.getPaddingStart(), + (int) paddingTop, + listItemView.getPaddingEnd(), + (int) paddingBottom); + } else { + listItemView.setPadding( + listItemView.getPaddingLeft(), + (int) paddingTop, + listItemView.getPaddingRight(), + (int) paddingBottom); + } + } + + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_ITEMS_MIN_HEIGHT)) { + float minHeight = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_ITEMS_MIN_HEIGHT); + listItemView.setMinimumHeight((int) minHeight); + } + } + + private ItemStyler() {} +} diff --git a/main/src/com/google/android/setupdesign/util/LayoutStyler.java b/main/src/com/google/android/setupdesign/util/LayoutStyler.java new file mode 100644 index 0000000..b707521 --- /dev/null +++ b/main/src/com/google/android/setupdesign/util/LayoutStyler.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2020 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.google.android.setupdesign.util; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build.VERSION_CODES; +import android.view.View; +import androidx.annotation.Nullable; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupdesign.R; + +/** + * Applies the partner style of layout to the given View {@code view}. The user needs to check if + * the {@code view} should apply partner heavy theme before calling this method. + */ +public final class LayoutStyler { + + /** + * Applies the partner layout padding style to the given view {@code view}. The theme should set + * partner heavy theme config first, and then the partner layout style would be applied. + * + * @param view A view would be applied partner layout padding style + */ + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + public static void applyPartnerCustomizationLayoutPaddingStyle(@Nullable View view) { + if (view == null) { + return; + } + + Context context = view.getContext(); + boolean partnerMarginStartAvailable = + PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_START); + boolean partnerMarginEndAvailable = + PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_END); + + if (PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(view) + && (partnerMarginStartAvailable || partnerMarginEndAvailable)) { + int paddingStart; + int paddingEnd; + if (partnerMarginStartAvailable) { + paddingStart = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_LAYOUT_MARGIN_START); + } else { + paddingStart = view.getPaddingStart(); + } + if (partnerMarginEndAvailable) { + paddingEnd = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_LAYOUT_MARGIN_END); + } else { + paddingEnd = view.getPaddingEnd(); + } + + if (paddingStart != view.getPaddingStart() || paddingEnd != view.getPaddingEnd()) { + view.setPadding(paddingStart, view.getPaddingTop(), paddingEnd, view.getPaddingBottom()); + } + } + } + + /** + * Applies the extra padding style to the given view {@code view}. This method is used when {@code + * view} already sets its margin, and like to extra padding make view.margin + view.pendding = + * global page margin. + * + * @param view A view would be applied extra padding style based on the layout margin of partner + * config. + */ + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + public static void applyPartnerCustomizationExtraPaddingStyle(@Nullable View view) { + if (view == null) { + return; + } + + Context context = view.getContext(); + boolean partnerMarginStartAvailable = + PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_START); + boolean partnerMarginEndAvailable = + PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_END); + + if (PartnerStyleHelper.shouldApplyPartnerHeavyThemeResource(view) + && (partnerMarginStartAvailable || partnerMarginEndAvailable)) { + int extraPaddingStart; + int extraPaddingEnd; + + TypedArray a = + context.obtainStyledAttributes(new int[] {R.attr.sudMarginStart, R.attr.sudMarginEnd}); + int layoutMarginStart = a.getDimensionPixelSize(0, 0); + int layoutMarginEnd = a.getDimensionPixelSize(1, 0); + a.recycle(); + + if (partnerMarginStartAvailable) { + extraPaddingStart = + ((int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_LAYOUT_MARGIN_START)) + - layoutMarginStart; + } else { + extraPaddingStart = view.getPaddingStart(); + } + + if (partnerMarginEndAvailable) { + extraPaddingEnd = + ((int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_LAYOUT_MARGIN_END)) + - layoutMarginEnd; + } else { + extraPaddingEnd = view.getPaddingEnd(); + } + + if (extraPaddingStart != view.getPaddingStart() || extraPaddingEnd != view.getPaddingEnd()) { + // If the view is a content view, padding start and padding end will be the same. + view.setPadding( + extraPaddingStart, + view.getPaddingTop(), + view.getId() == R.id.sud_layout_content ? extraPaddingStart : extraPaddingEnd, + view.getPaddingBottom()); + } + } + } + + private LayoutStyler() {} +} diff --git a/main/src/com/google/android/setupdesign/util/Partner.java b/main/src/com/google/android/setupdesign/util/Partner.java index aee5070..cf26f16 100644 --- a/main/src/com/google/android/setupdesign/util/Partner.java +++ b/main/src/com/google/android/setupdesign/util/Partner.java @@ -24,16 +24,20 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import android.util.Log; +import android.util.TypedValue; import androidx.annotation.AnyRes; import androidx.annotation.ArrayRes; +import androidx.annotation.BoolRes; import androidx.annotation.ColorRes; import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; -import android.util.Log; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -69,6 +73,17 @@ public class Partner { } /** + * Gets a boolean value from partner overlay, or if not available, gets the value from the + * original context instead. + * + * @see #getResourceEntry(Context, int) + */ + public static boolean getBoolean(Context context, @BoolRes int id) { + final ResourceEntry entry = getResourceEntry(context, id); + return entry.resources.getBoolean(entry.id); + } + + /** * Gets a drawable from partner overlay, or if not available, the drawable from the original * context. * @@ -107,6 +122,21 @@ public class Partner { } /** + * Gets an {@link Icon} from partner overlay, or if not available, the drawable from the original + * context. In some cases, icon can be set {@code null} to remove default icon. + * + * @see #getResourceEntry(Context, int) + */ + @Nullable + @RequiresApi(VERSION_CODES.M) + public static Icon getIcon(Context context, @DrawableRes int id) { + Partner.ResourceEntry entry = Partner.getResourceEntry(context, id); + return (getTypedValue(entry).data == 0) + ? null + : Icon.createWithResource(entry.packageName, entry.id); + } + + /** * Finds an entry of resource in the overlay package provided by partners. It will first look for * the resource in the overlay package, and if not available, will return the one in the original * context. @@ -214,4 +244,10 @@ public class Partner { public int getIdentifier(String name, String defType) { return resources.getIdentifier(name, defType, packageName); } + + private static TypedValue getTypedValue(ResourceEntry resourceEntry) { + TypedValue typedValue = new TypedValue(); + resourceEntry.resources.getValue(resourceEntry.id, typedValue, true); + return typedValue; + } } diff --git a/main/src/com/google/android/setupdesign/util/PartnerStyleHelper.java b/main/src/com/google/android/setupdesign/util/PartnerStyleHelper.java index d1d4a26..78900b1 100644 --- a/main/src/com/google/android/setupdesign/util/PartnerStyleHelper.java +++ b/main/src/com/google/android/setupdesign/util/PartnerStyleHelper.java @@ -16,19 +16,27 @@ package com.google.android.setupdesign.util; +import android.app.Activity; import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build; import android.view.Gravity; -import android.widget.FrameLayout; +import android.view.View; +import com.google.android.setupcompat.PartnerCustomizationLayout; +import com.google.android.setupcompat.internal.TemplateLayout; import com.google.android.setupcompat.partnerconfig.PartnerConfig; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.GlifLayout; +import com.google.android.setupdesign.R; import java.util.Locale; /** The helper reads styles from the partner configurations. */ public final class PartnerStyleHelper { + private static final String TAG = "PartnerStyleHelper"; /** - * Returns the partner configuration of layout gravity, usually apply to wigets in header area. + * Returns the partner configuration of layout gravity, usually apply to widgets in header area. */ public static int getLayoutGravity(Context context) { String gravity = @@ -47,14 +55,152 @@ public final class PartnerStyleHelper { } /** Returns the given layout if apply partner heavy theme. */ - public static boolean isPartnerHeavyThemeLayout(FrameLayout layout) { + public static boolean isPartnerHeavyThemeLayout(TemplateLayout layout) { if (!(layout instanceof GlifLayout)) { return false; } - if (!((GlifLayout) layout).shouldApplyPartnerHeavyThemeResource()) { + return ((GlifLayout) layout).shouldApplyPartnerHeavyThemeResource(); + } + + /** Returns the given layout if apply partner light theme. */ + public static boolean isPartnerLightThemeLayout(TemplateLayout layout) { + if (!(layout instanceof PartnerCustomizationLayout)) { + return false; + } + return ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource(); + } + + /** + * Returns if the current layout/activity of the given {@code view} applies partner customized + * configurations or not. + * + * @param view A PartnerCustomizationLayout view, would be used to get the activity and context. + */ + public static boolean shouldApplyPartnerResource(View view) { + if (view == null) { + return false; + } + if (view instanceof PartnerCustomizationLayout) { + return isPartnerLightThemeLayout((PartnerCustomizationLayout) view); + } + return shouldApplyPartnerResource(view.getContext()); + } + + private static boolean shouldApplyPartnerResource(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return false; + } + + if (!PartnerConfigHelper.get(context).isAvailable()) { return false; } - return true; + + Activity activity = null; + try { + activity = PartnerCustomizationLayout.lookupActivityFromContext(context); + if (activity != null) { + TemplateLayout layout = findLayoutFromActivity(activity); + if (layout instanceof PartnerCustomizationLayout) { + return ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource(); + } + } + } catch (IllegalArgumentException | ClassCastException ex) { + // fall through + } + + // try best to get partner resource settings from attrs + boolean isSetupFlow = false; + if (activity != null) { + isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent()); + } + TypedArray a = context.obtainStyledAttributes(new int[] {R.attr.sucUsePartnerResource}); + boolean usePartnerResource = a.getBoolean(0, true); + a.recycle(); + + return isSetupFlow || usePartnerResource; + } + + /** + * Returns if the current layout/activity applies heavy partner customized configurations or not. + * + * @param view A view would be used to get the activity and context. + */ + public static boolean shouldApplyPartnerHeavyThemeResource(View view) { + if (view == null) { + return false; + } + if (view instanceof GlifLayout) { + return isPartnerHeavyThemeLayout((GlifLayout) view); + } + return shouldApplyPartnerHeavyThemeResource(view.getContext()); + } + + static boolean shouldApplyPartnerHeavyThemeResource(Context context) { + try { + Activity activity = PartnerCustomizationLayout.lookupActivityFromContext(context); + TemplateLayout layout = findLayoutFromActivity(activity); + if (layout instanceof GlifLayout) { + return ((GlifLayout) layout).shouldApplyPartnerHeavyThemeResource(); + } + } catch (IllegalArgumentException | ClassCastException ex) { + // fall through + } + + // try best to get partner resource settings from attr + TypedArray a = context.obtainStyledAttributes(new int[] {R.attr.sudUsePartnerHeavyTheme}); + boolean usePartnerHeavyTheme = a.getBoolean(0, false); + a.recycle(); + usePartnerHeavyTheme = + usePartnerHeavyTheme || PartnerConfigHelper.shouldApplyExtendedPartnerConfig(context); + + return shouldApplyPartnerResource(context) && usePartnerHeavyTheme; + } + + /** + * Returns if the current layout/activity applies dynamic color configurations or not. + * + * @param view A GlifLayout view would be used to get the activity and context. + */ + public static boolean useDynamicColor(View view) { + if (view == null) { + return false; + } + return getDynamicColorAttributeFromTheme(view.getContext()); + } + + static boolean getDynamicColorAttributeFromTheme(Context context) { + try { + Activity activity = PartnerCustomizationLayout.lookupActivityFromContext(context); + TemplateLayout layout = findLayoutFromActivity(activity); + if (layout instanceof GlifLayout) { + return ((GlifLayout) layout).shouldApplyDynamicColor(); + } + } catch (IllegalArgumentException | ClassCastException ex) { + // fall through + } + + // try best to get dynamic color settings from attr + TypedArray a = context.obtainStyledAttributes(new int[] {R.attr.sucFullDynamicColor}); + boolean useDynamicColorTheme = + a.hasValue( + com.google + .android + .setupcompat + .R + .styleable + .SucPartnerCustomizationLayout_sucFullDynamicColor); + a.recycle(); + + return useDynamicColorTheme; + } + + private static TemplateLayout findLayoutFromActivity(Activity activity) { + if (activity == null) { + return null; + } + // This only worked after activity setContentView, otherwise it will return null + View rootView = activity.findViewById(R.id.suc_layout_status); + return rootView != null ? (TemplateLayout) rootView.getParent() : null; } private PartnerStyleHelper() {} diff --git a/main/src/com/google/android/setupdesign/util/TextViewPartnerStyler.java b/main/src/com/google/android/setupdesign/util/TextViewPartnerStyler.java index 552102e..7b4acea 100644 --- a/main/src/com/google/android/setupdesign/util/TextViewPartnerStyler.java +++ b/main/src/com/google/android/setupdesign/util/TextViewPartnerStyler.java @@ -18,10 +18,12 @@ package com.google.android.setupdesign.util; import android.content.Context; import android.graphics.Typeface; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.TypedValue; +import android.view.ViewGroup; +import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.setupcompat.partnerconfig.PartnerConfig; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; @@ -37,7 +39,9 @@ final class TextViewPartnerStyler { } Context context = textView.getContext(); - if (textPartnerConfigs.getTextColorConfig() != null) { + if (textPartnerConfigs.getTextColorConfig() != null + && PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(textPartnerConfigs.getTextColorConfig())) { int textColor = PartnerConfigHelper.get(context) .getColor(context, textPartnerConfigs.getTextColorConfig()); @@ -46,7 +50,10 @@ final class TextViewPartnerStyler { } } - if (textPartnerConfigs.getTextLinkedColorConfig() != null) { + if (textPartnerConfigs.getTextLinkedColorConfig() != null + && PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(textPartnerConfigs.getTextLinkedColorConfig()) + && !PartnerStyleHelper.useDynamicColor(textView)) { int linkTextColor = PartnerConfigHelper.get(context) .getColor(context, textPartnerConfigs.getTextLinkedColorConfig()); @@ -55,7 +62,9 @@ final class TextViewPartnerStyler { } } - if (textPartnerConfigs.getTextSizeConfig() != null) { + if (textPartnerConfigs.getTextSizeConfig() != null + && PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(textPartnerConfigs.getTextSizeConfig())) { float textSize = PartnerConfigHelper.get(context) .getDimension(context, textPartnerConfigs.getTextSizeConfig(), 0); @@ -64,7 +73,9 @@ final class TextViewPartnerStyler { } } - if (textPartnerConfigs.getTextFontFamilyConfig() != null) { + if (textPartnerConfigs.getTextFontFamilyConfig() != null + && PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(textPartnerConfigs.getTextFontFamilyConfig())) { String fontFamilyName = PartnerConfigHelper.get(context) .getString(context, textPartnerConfigs.getTextFontFamilyConfig()); @@ -74,6 +85,54 @@ final class TextViewPartnerStyler { } } + if (textPartnerConfigs.getTextMarginTop() != null + || textPartnerConfigs.getTextMarginBottom() != null) { + int topMargin; + int bottomMargin; + final ViewGroup.LayoutParams lp = textView.getLayoutParams(); + if (lp instanceof LinearLayout.LayoutParams) { + final LinearLayout.LayoutParams mlp = (LinearLayout.LayoutParams) lp; + if (textPartnerConfigs.getTextMarginTop() != null + && PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(textPartnerConfigs.getTextMarginTop())) { + topMargin = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, textPartnerConfigs.getTextMarginTop()); + } else { + topMargin = mlp.topMargin; + } + + if (textPartnerConfigs.getTextMarginBottom() != null + && PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(textPartnerConfigs.getTextMarginBottom())) { + bottomMargin = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, textPartnerConfigs.getTextMarginBottom()); + } else { + bottomMargin = mlp.bottomMargin; + } + mlp.setMargins(mlp.leftMargin, topMargin, mlp.rightMargin, bottomMargin); + textView.setLayoutParams(lp); + } + } + textView.setGravity(textPartnerConfigs.getTextGravity()); + } + + /** + * Applies given partner configurations {@code textPartnerConfigs} to the {@code textView}. + * + * @param textView A text view would apply the gravity + * @param textPartnerConfigs A partner conflagrations contains text gravity would be set + */ + public static void applyPartnerCustomizationLightStyle( + @NonNull TextView textView, @NonNull TextPartnerConfigs textPartnerConfigs) { + + if (textView == null || textPartnerConfigs == null) { + return; + } + textView.setGravity(textPartnerConfigs.getTextGravity()); } @@ -83,6 +142,8 @@ final class TextViewPartnerStyler { private final PartnerConfig textLinkedColorConfig; private final PartnerConfig textSizeConfig; private final PartnerConfig textFontFamilyConfig; + private final PartnerConfig textMarginTopConfig; + private final PartnerConfig textMarginBottomConfig; private final int textGravity; public TextPartnerConfigs( @@ -90,11 +151,15 @@ final class TextViewPartnerStyler { @Nullable PartnerConfig textLinkedColorConfig, @Nullable PartnerConfig textSizeConfig, @Nullable PartnerConfig textFontFamilyConfig, + @Nullable PartnerConfig textMarginTopConfig, + @Nullable PartnerConfig textMarginBottomConfig, int textGravity) { this.textColorConfig = textColorConfig; this.textLinkedColorConfig = textLinkedColorConfig; this.textSizeConfig = textSizeConfig; this.textFontFamilyConfig = textFontFamilyConfig; + this.textMarginTopConfig = textMarginTopConfig; + this.textMarginBottomConfig = textMarginBottomConfig; this.textGravity = textGravity; } @@ -114,6 +179,14 @@ final class TextViewPartnerStyler { return textFontFamilyConfig; } + public PartnerConfig getTextMarginTop() { + return textMarginTopConfig; + } + + public PartnerConfig getTextMarginBottom() { + return textMarginBottomConfig; + } + public int getTextGravity() { return textGravity; } diff --git a/main/src/com/google/android/setupdesign/util/ThemeHelper.java b/main/src/com/google/android/setupdesign/util/ThemeHelper.java index 4247d99..0b750c9 100644 --- a/main/src/com/google/android/setupdesign/util/ThemeHelper.java +++ b/main/src/com/google/android/setupdesign/util/ThemeHelper.java @@ -17,11 +17,22 @@ package com.google.android.setupdesign.util; import android.app.Activity; +import android.content.Context; import android.content.Intent; +import androidx.annotation.NonNull; +import androidx.annotation.StyleRes; +import com.google.android.setupcompat.PartnerCustomizationLayout; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupcompat.util.BuildCompatUtils; +import com.google.android.setupcompat.util.Logger; import com.google.android.setupcompat.util.WizardManagerHelper; +import com.google.android.setupdesign.R; +import java.util.Objects; /** The helper class holds the constant names of themes and util functions */ -public class ThemeHelper { +public final class ThemeHelper { + + private static final Logger LOG = new Logger("ThemeHelper"); /** * Passed in a setup wizard intent as {@link WizardManagerHelper#EXTRA_THEME}. This is the dark @@ -59,6 +70,16 @@ public class ThemeHelper { */ public static final String THEME_GLIF_V3_LIGHT = "glif_v3_light"; + /** + * Placeholder, not avirailed yet. + */ + public static final String THEME_GLIF_V4 = "glif_v4"; + + /** + * Placeholder, not avirailed yet. + */ + public static final String THEME_GLIF_V4_LIGHT = "glif_v4_light"; + public static final String THEME_HOLO = "holo"; public static final String THEME_HOLO_LIGHT = "holo_light"; public static final String THEME_MATERIAL = "material"; @@ -92,13 +113,15 @@ public class ThemeHelper { || THEME_MATERIAL_LIGHT.equals(theme) || THEME_GLIF_LIGHT.equals(theme) || THEME_GLIF_V2_LIGHT.equals(theme) - || THEME_GLIF_V3_LIGHT.equals(theme)) { + || THEME_GLIF_V3_LIGHT.equals(theme) + || THEME_GLIF_V4_LIGHT.equals(theme)) { return true; } else if (THEME_HOLO.equals(theme) || THEME_MATERIAL.equals(theme) || THEME_GLIF.equals(theme) || THEME_GLIF_V2.equals(theme) - || THEME_GLIF_V3.equals(theme)) { + || THEME_GLIF_V3.equals(theme) + || THEME_GLIF_V4.equals(theme)) { return false; } else { return def; @@ -125,4 +148,127 @@ public class ThemeHelper { public static void applyTheme(Activity activity) { ThemeResolver.getDefault().applyTheme(activity); } + + /** + * Checks whether SetupWizard supports the DayNight theme during setup flow; if it returns false, + * setup flow is always light theme. + * + * @return true if the SetupWizard is listening to system DayNight theme setting. + */ + public static boolean isSetupWizardDayNightEnabled(@NonNull Context context) { + return PartnerConfigHelper.isSetupWizardDayNightEnabled(context); + } + + /** + * Returns true if the partner provider of SetupWizard is ready to support more partner configs. + */ + public static boolean shouldApplyExtendedPartnerConfig(@NonNull Context context) { + return PartnerConfigHelper.shouldApplyExtendedPartnerConfig(context); + } + + /** + * Returns {@code true} if the partner provider of SetupWizard is ready to support dynamic color. + */ + public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) { + return PartnerConfigHelper.isSetupWizardDynamicColorEnabled(context); + } + + /** Returns {@code true} if this {@code context} should apply dynamic color. */ + public static boolean shouldApplyDynamicColor(@NonNull Context context) { + return shouldApplyExtendedPartnerConfig(context) && isSetupWizardDynamicColorEnabled(context); + } + + /** + * Returns a theme resource id if the {@link com.google.android.setupdesign.GlifLayout} should + * apply dynamic color. + * + * <p>Otherwise returns {@code 0}. + */ + @StyleRes + public static int getDynamicColorTheme(@NonNull Context context) { + @StyleRes int resId = 0; + + Activity activity; + try { + activity = PartnerCustomizationLayout.lookupActivityFromContext(context); + } catch (IllegalArgumentException ex) { + LOG.e(Objects.requireNonNull(ex.getMessage())); + return resId; + } + + boolean isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent()); + boolean isDayNightEnabled = isSetupWizardDayNightEnabled(context); + + if (isSetupFlow) { + // return theme for inside setup flow + resId = + isDayNightEnabled + ? R.style.SudDynamicColorThemeGlifV3_DayNight + : R.style.SudDynamicColorThemeGlifV3_Light; + } else { + // return theme for outside setup flow + resId = + isDayNightEnabled + ? R.style.SudFullDynamicColorThemeGlifV3_DayNight + : R.style.SudFullDynamicColorThemeGlifV3_Light; + LOG.atInfo( + "Return " + + (isDayNightEnabled + ? "SudFullDynamicColorThemeGlifV3_DayNight" + : "SudFullDynamicColorThemeGlifV3_Light")); + } + + LOG.atDebug( + "Gets the dynamic accentColor: [Light] " + + colorIntToHex(context, R.color.sud_dynamic_color_accent_glif_v3_light) + + ", " + + (BuildCompatUtils.isAtLeastS() + ? colorIntToHex(context, android.R.color.system_accent1_600) + : "n/a") + + ", [Dark] " + + colorIntToHex(context, R.color.sud_dynamic_color_accent_glif_v3_dark) + + ", " + + (BuildCompatUtils.isAtLeastS() + ? colorIntToHex(context, android.R.color.system_accent1_100) + : "n/a")); + + return resId; + } + + /** Returns {@code true} if the dynamic color is set. */ + public static boolean trySetDynamicColor(@NonNull Context context) { + if (!shouldApplyExtendedPartnerConfig(context)) { + LOG.w("SetupWizard does not supports the extended partner configs."); + return false; + } + + if (!isSetupWizardDynamicColorEnabled(context)) { + LOG.w("SetupWizard does not support the dynamic color or supporting status unknown."); + return false; + } + + Activity activity; + try { + activity = PartnerCustomizationLayout.lookupActivityFromContext(context); + } catch (IllegalArgumentException ex) { + LOG.e(Objects.requireNonNull(ex.getMessage())); + return false; + } + + @StyleRes int resId = getDynamicColorTheme(context); + if (resId != 0) { + activity.setTheme(resId); + } else { + LOG.w("Error occurred on getting dynamic color theme."); + return false; + } + + return true; + } + + private static String colorIntToHex(Context context, int colorInt) { + return String.format("#%06X", (0xFFFFFF & context.getResources().getColor(colorInt))); + } + + private ThemeHelper() {} } diff --git a/main/src/com/google/android/setupdesign/util/ThemeResolver.java b/main/src/com/google/android/setupdesign/util/ThemeResolver.java index 98b8883..c7a28b1 100644 --- a/main/src/com/google/android/setupdesign/util/ThemeResolver.java +++ b/main/src/com/google/android/setupdesign/util/ThemeResolver.java @@ -86,6 +86,17 @@ public class ThemeResolver { } /** + * Returns the style for the given SetupWizard intent. If the specified intent does not include + * the intent extra {@link WizardManagerHelper#EXTRA_THEME}, the default theme will be returned + * instead. Note that the default theme is returned without processing -- it may not be a DayNight + * theme even if {@link #useDayNight} is true. + */ + @StyleRes + public int resolve(Intent intent, boolean suppressDayNight) { + return resolve(intent.getStringExtra(WizardManagerHelper.EXTRA_THEME), suppressDayNight); + } + + /** * Returns the style for the given string theme. If the specified string theme is older than the * oldest supported theme, the default will be returned instead. Note that the default theme is * returned without processing -- it may not be a DayNight theme even if {@link #useDayNight} is @@ -128,7 +139,11 @@ public class ThemeResolver { /** Reads the theme from the intent, and applies the resolved theme to the activity. */ public void applyTheme(Activity activity) { - activity.setTheme(resolve(activity.getIntent())); + activity.setTheme( + resolve( + activity.getIntent(), + /* suppressDayNight= */ WizardManagerHelper.isAnySetupWizard(activity.getIntent()) + && !ThemeHelper.isSetupWizardDayNightEnabled(activity))); } /** diff --git a/main/src/com/google/android/setupdesign/view/BottomScrollView.java b/main/src/com/google/android/setupdesign/view/BottomScrollView.java index 83527b0..a3b250d 100644 --- a/main/src/com/google/android/setupdesign/view/BottomScrollView.java +++ b/main/src/com/google/android/setupdesign/view/BottomScrollView.java @@ -17,10 +17,10 @@ package com.google.android.setupdesign.view; import android.content.Context; -import androidx.annotation.VisibleForTesting; import android.util.AttributeSet; import android.view.View; import android.widget.ScrollView; +import androidx.annotation.VisibleForTesting; /** * An extension of ScrollView that will invoke a listener callback when the ScrollView needs @@ -68,7 +68,6 @@ public class BottomScrollView extends ScrollView { return listener; } - @VisibleForTesting public int getScrollThreshold() { return scrollThreshold; } diff --git a/main/src/com/google/android/setupdesign/view/CheckableLinearLayout.java b/main/src/com/google/android/setupdesign/view/CheckableLinearLayout.java index b12a20f..34d6ae3 100644 --- a/main/src/com/google/android/setupdesign/view/CheckableLinearLayout.java +++ b/main/src/com/google/android/setupdesign/view/CheckableLinearLayout.java @@ -19,10 +19,10 @@ package com.google.android.setupdesign.view; import android.annotation.TargetApi; import android.content.Context; import android.os.Build.VERSION_CODES; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.widget.Checkable; import android.widget.LinearLayout; +import androidx.annotation.Nullable; /** * A LinearLayout which is checkable. This will set the checked state when {@link diff --git a/main/src/com/google/android/setupdesign/view/FillContentLayout.java b/main/src/com/google/android/setupdesign/view/FillContentLayout.java index 49e195f..af49fbb 100644 --- a/main/src/com/google/android/setupdesign/view/FillContentLayout.java +++ b/main/src/com/google/android/setupdesign/view/FillContentLayout.java @@ -58,6 +58,10 @@ public class FillContentLayout extends FrameLayout { } private void init(Context context, AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SudFillContentLayout, defStyleAttr, 0); diff --git a/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java b/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java index b3161fd..57fc35d 100644 --- a/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java +++ b/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java @@ -218,6 +218,10 @@ public class HeaderRecyclerView extends RecyclerView { } private void init(AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + final TypedArray a = getContext() .obtainStyledAttributes(attrs, R.styleable.SudHeaderRecyclerView, defStyleAttr, 0); diff --git a/main/src/com/google/android/setupdesign/view/IconUniformityAppImageView.java b/main/src/com/google/android/setupdesign/view/IconUniformityAppImageView.java new file mode 100644 index 0000000..9f0e21d --- /dev/null +++ b/main/src/com/google/android/setupdesign/view/IconUniformityAppImageView.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2021 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.google.android.setupdesign.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Matrix.ScaleToFit; +import android.graphics.Outline; +import android.graphics.RectF; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import androidx.annotation.ColorRes; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.widget.ImageView; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import com.google.android.setupdesign.R; +import com.google.android.setupdesign.widget.CardBackgroundDrawable; + +/** An ImageView that displays an app icon according to the icon uniformity spec. */ +public class IconUniformityAppImageView extends ImageView + implements IconUniformityAppImageViewBindable { + // Scaling factor for inset on each side of legacy icon. + private static final Float LEGACY_SIZE_SCALE_FACTOR = 0.75f; + + private static final Float LEGACY_SIZE_SCALE_MARGIN_FACTOR = (1f - LEGACY_SIZE_SCALE_FACTOR) / 2f; + + // Apps & games radius is 20% of icon height. + private static final Float APPS_ICON_RADIUS_MULTIPLIER = 0.20f; + + @ColorRes private int backdropColorResId = 0; + + private static final boolean ON_L_PLUS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + + private CardBackgroundDrawable cardBackgroundDrawable; + /** Drawable used as background after the actual image data is visible. */ + private final GradientDrawable backdropDrawable = new GradientDrawable(); + + public IconUniformityAppImageView(Context context) { + super(context); + } + + public IconUniformityAppImageView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public IconUniformityAppImageView( + Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(23) + public IconUniformityAppImageView( + Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + backdropColorResId = R.color.sud_uniformity_backdrop_color; + backdropDrawable.setColor(ContextCompat.getColor(getContext(), backdropColorResId)); + } + + @Override + public void bindView(IconUniformityAppImageViewData viewData) { + if (Build.VERSION.SDK_INT <= 17) { + // clipPath is not supported on hardware accelerated canvas so won't take effect unless we + // manually set to software. + setLayerType(LAYER_TYPE_SOFTWARE, /* paint= */ null); + } + + setLegacyTransformationMatrix( + viewData.icon.getMinimumWidth(), + viewData.icon.getMinimumHeight(), + getLayoutParams().width, + getLayoutParams().height); + + float radius = getLayoutParams().height * APPS_ICON_RADIUS_MULTIPLIER; + + if (ON_L_PLUS) { + setBackgroundColor(ContextCompat.getColor(getContext(), backdropColorResId)); + backdropDrawable.setCornerRadius(radius); + setElevation(getContext().getResources().getDimension(R.dimen.sud_icon_uniformity_elevation)); + setClipToOutline(true); + setOutlineProvider( + new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect( + /* left= */ 0, + /* top= */ 0, + /* right= */ getLayoutParams().width, + /* bottom= */ getLayoutParams().height, + /* radius= */ radius); + } + }); + } else { + cardBackgroundDrawable = + new CardBackgroundDrawable( + ContextCompat.getColor(getContext(), backdropColorResId), + /* radius= */ radius, + /* inset= */ 0f); + cardBackgroundDrawable.setBounds( + /* left= */ 0, + /* top= */ 0, + /* right= */ getLayoutParams().width, + /* bottom= */ getLayoutParams().height); + } + + setImageDrawable(viewData.icon); + } + + @Override + public void onRecycle() { + setImageDrawable(null); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (!ON_L_PLUS && cardBackgroundDrawable != null) { + cardBackgroundDrawable.draw(canvas); + } + super.onDraw(canvas); + } + + private void setLegacyTransformationMatrix( + float drawableWidth, float drawableHeight, float imageViewWidth, float imageViewHeight) { + Matrix scaleMatrix = new Matrix(); + float verticalMargin = imageViewHeight * LEGACY_SIZE_SCALE_MARGIN_FACTOR; + float horizontalMargin = imageViewWidth * LEGACY_SIZE_SCALE_MARGIN_FACTOR; + RectF scrRectF = new RectF(0f, 0f, drawableWidth, drawableHeight); + RectF destRectF = + new RectF( + horizontalMargin, + verticalMargin, + imageViewWidth - horizontalMargin, + imageViewHeight - verticalMargin); + + scaleMatrix.setRectToRect(scrRectF, destRectF, ScaleToFit.FILL); + + setScaleType(ScaleType.MATRIX); + setImageMatrix(scaleMatrix); + } +} diff --git a/main/src/com/google/android/setupdesign/view/IconUniformityAppImageViewBindable.java b/main/src/com/google/android/setupdesign/view/IconUniformityAppImageViewBindable.java new file mode 100644 index 0000000..1eab81b --- /dev/null +++ b/main/src/com/google/android/setupdesign/view/IconUniformityAppImageViewBindable.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 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.google.android.setupdesign.view; + +import android.graphics.drawable.Drawable; + +/** ViewBindable for [IconUniformityAppImageView] */ +public interface IconUniformityAppImageViewBindable { + + /** Data for [IconUniformityAppImageView] */ + class IconUniformityAppImageViewData { + public Drawable icon; + + public IconUniformityAppImageViewData(Drawable icon) { + this.icon = icon; + } + } + + void bindView(IconUniformityAppImageViewData viewData); + + void onRecycle(); +} diff --git a/main/src/com/google/android/setupdesign/view/Illustration.java b/main/src/com/google/android/setupdesign/view/Illustration.java index 14072db..e890307 100644 --- a/main/src/com/google/android/setupdesign/view/Illustration.java +++ b/main/src/com/google/android/setupdesign/view/Illustration.java @@ -70,6 +70,10 @@ public class Illustration extends FrameLayout { // All the constructors delegate to this init method. The 3-argument constructor is not // available in FrameLayout before v11, so call super with the exact same arguments. private void init(AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + if (attrs != null) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SudIllustration, defStyleAttr, 0); diff --git a/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java b/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java index 2e4fd71..de71f7c 100644 --- a/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java +++ b/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java @@ -28,15 +28,16 @@ import android.media.MediaPlayer.OnPreparedListener; import android.media.MediaPlayer.OnSeekCompleteListener; import android.net.Uri; import android.os.Build.VERSION_CODES; -import androidx.annotation.Nullable; -import androidx.annotation.RawRes; -import androidx.annotation.VisibleForTesting; import android.util.AttributeSet; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.TextureView.SurfaceTextureListener; import android.view.View; +import androidx.annotation.Nullable; +import androidx.annotation.RawRes; +import androidx.annotation.VisibleForTesting; +import com.google.android.setupcompat.util.BuildCompatUtils; import com.google.android.setupdesign.R; import java.io.IOException; @@ -76,6 +77,8 @@ public class IllustrationVideoView extends TextureView private boolean prepared; + private boolean shouldPauseVideoWhenFinished = true; + /** * The visibility of this view as set by the user. This view combines this with {@link * #isMediaPlayerLoading} to determine the final visibility. @@ -92,9 +95,24 @@ public class IllustrationVideoView extends TextureView public IllustrationVideoView(Context context, AttributeSet attrs) { super(context, attrs); + if (!isInEditMode()) { + init(context, attrs); + } + } + + private void init(Context context, AttributeSet attrs) { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SudIllustrationVideoView); final int videoResId = a.getResourceId(R.styleable.SudIllustrationVideoView_sudVideo, 0); + + // TODO: remove the usage of BuildCompatUtils#isAtLeatestS if VERSION_CODE.S is + // support by system. + if (BuildCompatUtils.isAtLeastS()) { + boolean shouldPauseVideo = + a.getBoolean(R.styleable.SudIllustrationVideoView_sudPauseVideoWhenFinished, true); + setPauseVideoWhenFinished(shouldPauseVideo); + } + a.recycle(); setVideoResource(videoResId); @@ -143,12 +161,43 @@ public class IllustrationVideoView extends TextureView /** * Set the video to be played by this view. * + * @param resourceEntry the {@link com.google.android.setupdesign.util.Partner.ResourceEntry} of + * the video, typically an MP4 under res/raw. + */ + public void setVideoResourceEntry( + com.google.android.setupdesign.util.Partner.ResourceEntry resourceEntry) { + setVideoResource(resourceEntry.id, resourceEntry.packageName); + } + + /** + * Set the video to be played by this view. + * + * @param resourceEntry the {@link com.google.android.setupcompat.partnerconfig.ResourceEntry} of + * the video, typically an MP4 under res/raw. + */ + public void setVideoResourceEntry( + com.google.android.setupcompat.partnerconfig.ResourceEntry resourceEntry) { + setVideoResource(resourceEntry.getResourceId(), resourceEntry.getPackageName()); + } + + /** + * Set the video to be played by this view. + * * @param resId Resource ID of the video, typically an MP4 under res/raw. */ public void setVideoResource(@RawRes int resId) { setVideoResource(resId, getContext().getPackageName()); } + /** + * Sets whether the video pauses during the screen transition. + * + * @param paused Whether the video pauses. + */ + public void setPauseVideoWhenFinished(boolean paused) { + shouldPauseVideoWhenFinished = paused; + } + @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); @@ -304,8 +353,12 @@ public class IllustrationVideoView extends TextureView @Override public void stop() { - if (prepared && mediaPlayer != null) { - mediaPlayer.pause(); + if (shouldPauseVideoWhenFinished) { + if (prepared && mediaPlayer != null) { + mediaPlayer.pause(); + } + } else { + // do not pause the media player. } } diff --git a/main/src/com/google/android/setupdesign/view/IntrinsicSizeFrameLayout.java b/main/src/com/google/android/setupdesign/view/IntrinsicSizeFrameLayout.java index f714205..d02839b 100644 --- a/main/src/com/google/android/setupdesign/view/IntrinsicSizeFrameLayout.java +++ b/main/src/com/google/android/setupdesign/view/IntrinsicSizeFrameLayout.java @@ -21,7 +21,11 @@ import android.content.Context; import android.content.res.TypedArray; import android.os.Build.VERSION_CODES; import android.util.AttributeSet; +import android.view.ViewGroup; import android.widget.FrameLayout; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupcompat.util.BuildCompatUtils; import com.google.android.setupdesign.R; /** @@ -54,6 +58,10 @@ public class IntrinsicSizeFrameLayout extends FrameLayout { } private void init(Context context, AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.SudIntrinsicSizeFrameLayout, defStyleAttr, 0); @@ -62,6 +70,37 @@ public class IntrinsicSizeFrameLayout extends FrameLayout { intrinsicWidth = a.getDimensionPixelSize(R.styleable.SudIntrinsicSizeFrameLayout_android_width, 0); a.recycle(); + + if (BuildCompatUtils.isAtLeastS()) { + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CARD_VIEW_INTRINSIC_HEIGHT)) { + intrinsicHeight = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_CARD_VIEW_INTRINSIC_HEIGHT); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_CARD_VIEW_INTRINSIC_WIDTH)) { + intrinsicWidth = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_CARD_VIEW_INTRINSIC_WIDTH); + } + } + } + + @Override + public void setLayoutParams(ViewGroup.LayoutParams params) { + if (BuildCompatUtils.isAtLeastS()) { + // When both intrinsic height and width are 0, the card view style would be removed from + // foldable/tablet layout. It must set the layout width and height to MATCH_PARENT and then it + // can ignore the IntrinsicSizeFrameLayout from the foldable/tablet layout. + if (intrinsicHeight == 0 && intrinsicWidth == 0) { + params.width = ViewGroup.LayoutParams.MATCH_PARENT; + params.height = ViewGroup.LayoutParams.MATCH_PARENT; + } + } + super.setLayoutParams(params); } @Override diff --git a/main/src/com/google/android/setupdesign/view/NavigationBar.java b/main/src/com/google/android/setupdesign/view/NavigationBar.java index 9d978f0..df3bee4 100644 --- a/main/src/com/google/android/setupdesign/view/NavigationBar.java +++ b/main/src/com/google/android/setupdesign/view/NavigationBar.java @@ -21,12 +21,12 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build.VERSION_CODES; -import androidx.annotation.StyleableRes; import android.util.AttributeSet; import android.view.ContextThemeWrapper; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; +import androidx.annotation.StyleableRes; import com.google.android.setupdesign.R; /** @@ -103,6 +103,10 @@ public class NavigationBar extends LinearLayout implements View.OnClickListener // All the constructors delegate to this init method. The 3-argument constructor is not // available in LinearLayout before v11, so call super with the exact same arguments. private void init() { + if (isInEditMode()) { + return; + } + View.inflate(getContext(), R.layout.sud_navbar_view, this); nextButton = (Button) findViewById(R.id.sud_navbar_next); backButton = (Button) findViewById(R.id.sud_navbar_back); diff --git a/main/src/com/google/android/setupdesign/view/NavigationBarButton.java b/main/src/com/google/android/setupdesign/view/NavigationBarButton.java index 44a5b85..bb1e506 100644 --- a/main/src/com/google/android/setupdesign/view/NavigationBarButton.java +++ b/main/src/com/google/android/setupdesign/view/NavigationBarButton.java @@ -23,9 +23,9 @@ import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Build; -import androidx.annotation.NonNull; import android.util.AttributeSet; import android.widget.Button; +import androidx.annotation.NonNull; /** * Button for navigation bar, which includes tinting of its compound drawables to be used for dark @@ -45,6 +45,10 @@ public class NavigationBarButton extends Button { } private void init() { + if (isInEditMode()) { + return; + } + // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter, // so manually getting it and wrapping it in the compat drawable. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { diff --git a/main/src/com/google/android/setupdesign/view/RichTextView.java b/main/src/com/google/android/setupdesign/view/RichTextView.java index 338b856..f3348b4 100644 --- a/main/src/com/google/android/setupdesign/view/RichTextView.java +++ b/main/src/com/google/android/setupdesign/view/RichTextView.java @@ -106,6 +106,10 @@ public class RichTextView extends AppCompatTextView implements OnLinkClickListen } private void init() { + if (isInEditMode()) { + return; + } + accessibilityHelper = new LinkAccessibilityHelper(this); ViewCompat.setAccessibilityDelegate(this, accessibilityHelper); } diff --git a/main/src/com/google/android/setupdesign/view/StickyHeaderListView.java b/main/src/com/google/android/setupdesign/view/StickyHeaderListView.java index 3efb85c..07d1781 100644 --- a/main/src/com/google/android/setupdesign/view/StickyHeaderListView.java +++ b/main/src/com/google/android/setupdesign/view/StickyHeaderListView.java @@ -73,6 +73,10 @@ public class StickyHeaderListView extends ListView { } private void init(AttributeSet attrs, int defStyleAttr) { + if (isInEditMode()) { + return; + } + final TypedArray a = getContext() .obtainStyledAttributes(attrs, R.styleable.SudStickyHeaderListView, defStyleAttr, 0); diff --git a/main/src/com/google/android/setupdesign/widget/CardBackgroundDrawable.java b/main/src/com/google/android/setupdesign/widget/CardBackgroundDrawable.java new file mode 100644 index 0000000..b354921 --- /dev/null +++ b/main/src/com/google/android/setupdesign/widget/CardBackgroundDrawable.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021 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.google.android.setupdesign.widget; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Path.Direction; +import android.graphics.Path.FillType; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; + +/** A rounded rectangle drawable. */ +public class CardBackgroundDrawable extends Drawable { + private final float inset; + + private final Paint paint; + private final RectF cardBounds = new RectF(); + private final Path clipPath = new Path(); + + private float cornerRadius; + private boolean dirty = false; + + /** + * @param color Background color of the card to be rendered + * @param radius Corner rounding radius + * @param inset Inset from the edge of the canvas to the card + */ + public CardBackgroundDrawable(@ColorInt int color, float radius, float inset) { + cornerRadius = radius; + paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + paint.setColor(color); + this.inset = inset; + } + + @Override + public void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + dirty = true; + } + + @Override + public void setColorFilter(@Nullable ColorFilter cf) { + paint.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + public void setCornerRadius(float radius) { + if (cornerRadius == radius) { + return; + } + + cornerRadius = radius; + dirty = true; + invalidateSelf(); + } + + @Override + public void draw(Canvas canvas) { + if (dirty) { + buildComponents(getBounds()); + dirty = false; + } + + if (cornerRadius > 0) { + canvas.clipPath(clipPath); + } + } + + @Override + public void setAlpha(int alpha) {} + + private void buildComponents(Rect bounds) { + cardBounds.set(bounds); + cardBounds.inset(inset, inset); + + clipPath.reset(); + clipPath.setFillType(FillType.EVEN_ODD); + clipPath.addRoundRect(cardBounds, cornerRadius, cornerRadius, Direction.CW); + } +} |