Skip to content

Commit f40ee68

Browse files
committed
[Reply] Add email chip expand with Container Transform transition to Reply
1 parent 82a459b commit f40ee68

File tree

11 files changed

+337
-7
lines changed

11 files changed

+337
-7
lines changed

Reply/app/src/main/java/com/materialstudies/reply/data/Account.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.materialstudies.reply.data
1818

1919
import androidx.annotation.DrawableRes
20+
import androidx.annotation.Nullable
2021
import androidx.recyclerview.widget.DiffUtil
2122
import com.materialstudies.reply.R
2223

@@ -25,16 +26,17 @@ import com.materialstudies.reply.R
2526
* multiple accounts.
2627
*/
2728
data class Account(
28-
val id: Long,
29-
val uid: Long,
30-
val firstName: String,
31-
val lastName: String,
32-
val email: String,
33-
@DrawableRes val avatar: Int,
34-
var isCurrentAccount: Boolean = false
29+
val id: Long,
30+
val uid: Long,
31+
val firstName: String,
32+
val lastName: String,
33+
val email: String,
34+
@DrawableRes val avatar: Int,
35+
var isCurrentAccount: Boolean = false
3536
) {
3637
val fullName: String = "$firstName $lastName"
3738
@DrawableRes val checkedIcon: Int = if (isCurrentAccount) R.drawable.ic_done else 0
39+
val email2 : String = "$firstName@workmail.com".toLowerCase()
3840
}
3941

4042
object AccountDiffCallback : DiffUtil.ItemCallback<Account>() {

Reply/app/src/main/java/com/materialstudies/reply/ui/compose/ComposeFragment.kt

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,42 @@ import androidx.fragment.app.Fragment
2626
import androidx.navigation.fragment.findNavController
2727
import androidx.navigation.fragment.navArgs
2828
import androidx.transition.Slide
29+
import androidx.transition.TransitionManager
2930
import com.google.android.material.transition.MaterialContainerTransform
3031
import com.materialstudies.reply.R
3132
import com.materialstudies.reply.data.Account
3233
import com.materialstudies.reply.data.AccountStore
3334
import com.materialstudies.reply.data.Email
3435
import com.materialstudies.reply.data.EmailStore
3536
import com.materialstudies.reply.databinding.ComposeRecipientChipBinding
37+
import com.materialstudies.reply.databinding.ComposeRecipientCardBinding
3638
import com.materialstudies.reply.databinding.FragmentComposeBinding
3739
import com.materialstudies.reply.util.themeColor
40+
import com.materialstudies.reply.util.themeInterpolator
3841
import kotlin.LazyThreadSafetyMode.NONE
3942

4043
/**
4144
* A [Fragment] which allows for the composition of a new email.
4245
*/
46+
/* My Handler Methods */
47+
interface ContainerTransformHandler {
48+
fun containerTransformChipToCard()
49+
fun containerTransformCardToChip()
50+
}
51+
4352
class ComposeFragment : Fragment() {
4453

54+
55+
private val containerTranformHandler: ContainerTransformHandler = object : ContainerTransformHandler {
56+
override fun containerTransformCardToChip() {
57+
containerTransformRecipientCardToChip()
58+
}
59+
60+
override fun containerTransformChipToCard() {
61+
containerTransformRecipientChipToCard()
62+
}
63+
}
64+
4565
private lateinit var binding: FragmentComposeBinding
4666

4767
private val args: ComposeFragmentArgs by navArgs()
@@ -69,13 +89,24 @@ class ComposeFragment : Fragment() {
6989
email = composeEmail
7090

7191
composeEmail.nonUserAccountRecipients.forEach { addRecipientChip(it) }
92+
composeEmail.nonUserAccountRecipients.forEach{ addRecipientsCard(it) }
93+
94+
binding.handlers = containerTranformHandler
95+
96+
binding.recipientAddIcon.setOnClickListener {
97+
containerTransformRecipientChipToCard()
98+
}
99+
binding.recipientCardView.setOnClickListener {
100+
containerTransformRecipientChipToCard()
101+
}
72102

73103
senderSpinner.adapter = ArrayAdapter(
74104
senderSpinner.context,
75105
R.layout.spinner_item_layout,
76106
AccountStore.getAllUserAccounts().map { it.email }
77107
)
78108

109+
79110
// Set transitions here so we are able to access Fragment's binding views.
80111
enterTransition = MaterialContainerTransform().apply {
81112
// Manually add the Views to be shared since this is not a standard Fragment to Fragment
@@ -110,4 +141,88 @@ class ComposeFragment : Fragment() {
110141
addView(chipBinding.root)
111142
}
112143
}
144+
145+
/**
146+
* Add a card for the given [Account] to the recipients card.
147+
*/
148+
private fun addRecipientsCard(acnt: Account) {
149+
binding.recipientCardView.run {
150+
val cardBinding = ComposeRecipientCardBinding.inflate(
151+
LayoutInflater.from(context),
152+
this,
153+
false
154+
).apply {
155+
account = acnt
156+
}
157+
158+
addView(cardBinding.root)
159+
}
160+
}
161+
162+
private fun prepareTransitions() {
163+
postponeEnterTransition()
164+
}
165+
166+
private fun startTransitions() {
167+
binding.executePendingBindings()
168+
// Delay creating the enterTransition until after we have inflated this Fragment's binding
169+
// and are able to access the view to be transitioned to.
170+
enterTransition = MaterialContainerTransform().apply {
171+
// Manually add the Views to be shared since this is not a standard Fragment to Fragment
172+
// shared element transition.
173+
startView = requireActivity().findViewById(R.id.fab)
174+
endView = binding.emailCardView
175+
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
176+
interpolator = requireContext().themeInterpolator(R.attr.motionInterpolatorPersistent)
177+
}
178+
returnTransition = Slide().apply {
179+
duration = resources.getInteger(R.integer.reply_motion_duration_medium).toLong()
180+
interpolator = requireContext().themeInterpolator(R.attr.motionInterpolatorOutgoing)
181+
}
182+
startPostponedEnterTransition()
183+
}
184+
185+
private fun containerTransformRecipientChipToCard() {
186+
val transform = MaterialContainerTransform().apply{
187+
// Manually tell the container transform which Views to transform between.
188+
startView = binding.recipientChipGroup
189+
endView = binding.recipientCardView
190+
191+
// Optionally add a curved path to the transform
192+
// pathMotion = MaterialArcMotion()
193+
194+
// Since View to View transforms often are not transforming into full screens,
195+
// remove the transition's scrim.
196+
scrimColor = Color.TRANSPARENT
197+
198+
}
199+
// Begin the transition by changing properties on the start and end views or
200+
// removing/adding them from the hierarchy.
201+
TransitionManager.beginDelayedTransition(binding.composeConstraintLayout, transform)
202+
binding.recipientCardView.visibility = View.VISIBLE
203+
binding.recipientChipGroup.visibility = View.GONE
204+
205+
}
206+
207+
private fun containerTransformRecipientCardToChip() {
208+
val transform = MaterialContainerTransform().apply{
209+
// Manually tell the container transform which Views to transform between.
210+
startView = binding.recipientCardView
211+
endView = binding.recipientChipGroup
212+
213+
// Optionally add a curved path to the transform
214+
// pathMotion = MaterialArcMotion()
215+
216+
// Since View to View transforms often are not transforming into full screens,
217+
// remove the transition's scrim.
218+
scrimColor = Color.TRANSPARENT
219+
220+
}
221+
// Begin the transition by changing properties on the start and end views or
222+
// removing/adding them from the hierarchy.
223+
TransitionManager.beginDelayedTransition(binding.composeConstraintLayout, transform)
224+
binding.recipientCardView.visibility = View.GONE
225+
binding.recipientChipGroup.visibility = View.VISIBLE
226+
227+
}
113228
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:shape="oval">
3+
<solid
4+
android:color="@color/color_on_primary_surface_emphasis_disabled"/>
5+
</shape>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!--
2+
Copyright (c) 2019 Google Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
in compliance with the License. You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software distributed under the License
10+
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
or implied. See the License for the specific language governing permissions and limitations under
12+
the License.
13+
-->
14+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
15+
android:width="24dp"
16+
android:height="24dp"
17+
android:viewportWidth="24"
18+
android:viewportHeight="24"
19+
android:tint="?attr/colorControlNormal">
20+
<path
21+
android:fillColor="#FFFFFFFF"
22+
android:pathData="M13,7h-2v4L7,11v2h4v4h2v-4h4v-2h-4zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
23+
</vector>
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright (c) 2019 Google Inc.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
6+
in compliance with the License. You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software distributed under the License
11+
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12+
or implied. See the License for the specific language governing permissions and limitations under
13+
the License.
14+
-->
15+
<layout xmlns:android="http://schemas.android.com/apk/res/android"
16+
xmlns:app="http://schemas.android.com/apk/res-auto"
17+
xmlns:tools="http://schemas.android.com/tools">
18+
19+
<data>
20+
<variable
21+
name="account"
22+
type="com.materialstudies.reply.data.Account" />
23+
<variable
24+
name="handlers"
25+
type="com.materialstudies.reply.ui.compose.ContainerTransformHandler"></variable>
26+
</data>
27+
28+
<com.google.android.material.card.MaterialCardView
29+
android:layout_width="match_parent"
30+
android:layout_height="wrap_content"
31+
android:clickable="true"
32+
android:background="@color/cardview_light_background"
33+
android:focusable="true"
34+
android:checkable="true">
35+
<LinearLayout
36+
android:layout_width="match_parent"
37+
android:layout_height="match_parent"
38+
android:orientation="vertical">
39+
40+
<!-- there was onclick method in this one, removed -->
41+
<androidx.constraintlayout.widget.ConstraintLayout
42+
android:layout_width="match_parent"
43+
android:layout_height="wrap_content"
44+
android:minHeight="?attr/listPreferredItemHeight"
45+
android:paddingHorizontal="@dimen/grid_2"
46+
android:background="@color/cardview_dark_background">
47+
48+
<ImageView
49+
android:id="@+id/account_profile_image_view"
50+
android:layout_width="@dimen/compose_recipient_profile_image_size"
51+
android:layout_height="@dimen/compose_recipient_profile_image_size"
52+
android:padding="@dimen/grid_1"
53+
app:layout_constraintStart_toStartOf="parent"
54+
app:layout_constraintTop_toTopOf="parent"
55+
app:layout_constraintBottom_toBottomOf="parent"
56+
android:contentDescription="@{account.email}"
57+
app:glideSrc="@{account.avatar}"
58+
app:glideCircularCrop="@{true}" />
59+
60+
<CheckedTextView
61+
android:id="@+id/account_address_text_view"
62+
android:layout_width="0dp"
63+
android:layout_height="wrap_content"
64+
android:layout_marginHorizontal="@dimen/grid_2"
65+
app:layout_constraintBottom_toBottomOf="parent"
66+
app:layout_constraintEnd_toEndOf="parent"
67+
app:layout_constraintStart_toEndOf="@id/account_profile_image_view"
68+
app:layout_constraintTop_toTopOf="parent"
69+
android:checked="@{account.currentAccount}"
70+
android:text="@{account.email}"
71+
android:background="@color/cardview_dark_background"
72+
android:textAppearance="?attr/textAppearanceBody1"
73+
android:textColor="@color/color_navigation_drawer_menu_item"
74+
app:drawableTint="@color/color_navigation_drawer_menu_item"
75+
app:drawableRight="@{account.checkedIcon}"
76+
android:drawablePadding="@dimen/grid_3"
77+
android:lines="1"
78+
android:ellipsize="end"
79+
tools:text="[email protected]" />
80+
81+
<ImageButton
82+
android:id="@+id/close_icon"
83+
android:layout_width="24dp"
84+
android:layout_height="24dp"
85+
android:layout_margin="@dimen/grid_0_5"
86+
android:padding="@dimen/min_icon_target_padding"
87+
android:background="@drawable/ic_circle"
88+
android:contentDescription="@string/compose_close_content_desc"
89+
android:onClick="@{() -> handlers.containerTransformCardToChip()"
90+
app:layout_constraintBottom_toBottomOf="parent"
91+
app:layout_constraintEnd_toEndOf="parent"
92+
app:layout_constraintTop_toTopOf="parent"
93+
app:srcCompat="@drawable/ic_close"
94+
app:tint="@color/color_on_surface_emphasis_disabled" />
95+
96+
</androidx.constraintlayout.widget.ConstraintLayout>
97+
98+
<!-- there was onclick method in this one, removed -->
99+
<androidx.constraintlayout.widget.ConstraintLayout
100+
android:layout_width="match_parent"
101+
android:layout_height="wrap_content"
102+
android:minHeight="?attr/listPreferredItemHeight"
103+
android:paddingHorizontal="@dimen/grid_2">
104+
<ImageView
105+
android:id="@+id/account_profile_image_view_2"
106+
android:layout_width="@dimen/compose_recipient_profile_image_size"
107+
android:layout_height="@dimen/compose_recipient_profile_image_size"
108+
android:padding="@dimen/grid_1"
109+
app:layout_constraintStart_toStartOf="parent"
110+
app:layout_constraintTop_toTopOf="parent"
111+
app:layout_constraintBottom_toBottomOf="parent"
112+
android:contentDescription="@{account.email}"
113+
app:glideSrc="@{account.avatar}"
114+
app:glideCircularCrop="@{true}" />
115+
116+
<CheckedTextView
117+
android:id="@+id/account_address_text_view_2"
118+
android:layout_width="0dp"
119+
android:layout_height="wrap_content"
120+
android:layout_marginHorizontal="@dimen/grid_2"
121+
app:layout_constraintBottom_toBottomOf="parent"
122+
app:layout_constraintEnd_toEndOf="parent"
123+
app:layout_constraintStart_toEndOf="@id/account_profile_image_view_2"
124+
app:layout_constraintTop_toTopOf="parent"
125+
android:checked="@{account.currentAccount}"
126+
android:text="@{account.email2}"
127+
android:textAppearance="?attr/textAppearanceBody1"
128+
android:textColor="@color/color_on_surface_emphasis_high"
129+
app:drawableTint="@color/color_navigation_drawer_menu_item"
130+
app:drawableRight="@{account.checkedIcon}"
131+
android:drawablePadding="@dimen/grid_3"
132+
android:lines="1"
133+
android:ellipsize="end"
134+
tools:text="[email protected]" />
135+
</androidx.constraintlayout.widget.ConstraintLayout>
136+
</LinearLayout>
137+
138+
139+
</com.google.android.material.card.MaterialCardView>
140+
141+
</layout>

Reply/app/src/main/res/layout/compose_recipient_chip.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
<variable
2020
name="account"
2121
type="com.materialstudies.reply.data.Account" />
22+
<variable
23+
name="handlers"
24+
type="com.materialstudies.reply.ui.compose.ContainerTransformHandler"/>
2225
</data>
2326

2427
<com.google.android.material.chip.Chip
@@ -27,6 +30,7 @@
2730
app:glideChipIcon="@{account.avatar}"
2831
app:glideChipIconCenterCrop="@{true}"
2932
app:glideChipIconCircularCrop="@{true}"
33+
android:onClick="@{() -> handlers.containerTransformChipToCard()"
3034
android:text="@{account.fullName}"/>
3135

3236
</layout>

0 commit comments

Comments
 (0)