Skip to content

Commit 953d531

Browse files
authored
[Reply] Added Search screen implementation with Shared Axis Z transition (material-components#35)
* Add initial search screen * Fix return transition * Add search suggestions * Cleanup * Refactor save/restore of exit/reenter transitions * Fix imports * Clean up * ConstraintLayout
1 parent fbb3050 commit 953d531

File tree

18 files changed

+551
-13
lines changed

18 files changed

+551
-13
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.materialstudies.reply.data
18+
19+
import androidx.annotation.DrawableRes
20+
21+
/**
22+
* An object which represents a search suggestion.
23+
*/
24+
data class SearchSuggestion(
25+
@DrawableRes val iconResId: Int,
26+
val title: String,
27+
val subtitle: String
28+
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.materialstudies.reply.data
18+
19+
import com.materialstudies.reply.R
20+
21+
/**
22+
* A static data store of [SearchSuggestion]s.
23+
*/
24+
object SearchSuggestionStore {
25+
26+
val YESTERDAY_SUGGESTIONS = listOf(
27+
SearchSuggestion(
28+
R.drawable.ic_schedule,
29+
"481 Van Brunt Street",
30+
"Brooklyn, NY"
31+
),
32+
SearchSuggestion(
33+
R.drawable.ic_home,
34+
"Home",
35+
"199 Pacific Street, Brooklyn, NY"
36+
)
37+
)
38+
39+
val THIS_WEEK_SUGGESTIONS = listOf(
40+
SearchSuggestion(
41+
R.drawable.ic_schedule,
42+
"BEP GA",
43+
"Forsyth Street, New York, NY"
44+
),
45+
SearchSuggestion(
46+
R.drawable.ic_schedule,
47+
"Sushi Nakazawa",
48+
"Commerce Street, New York, NY"
49+
),
50+
SearchSuggestion(
51+
R.drawable.ic_schedule,
52+
"IFC Center",
53+
"6th Avenue, New York, NY"
54+
)
55+
)
56+
}

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

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import androidx.appcompat.widget.Toolbar
2828
import androidx.navigation.NavController
2929
import androidx.navigation.NavDestination
3030
import androidx.navigation.findNavController
31+
import com.google.android.material.transition.Hold
32+
import com.google.android.material.transition.MaterialSharedAxis
3133
import com.materialstudies.reply.R
3234
import com.materialstudies.reply.databinding.ActivityMainBinding
3335
import com.materialstudies.reply.ui.compose.ComposeFragmentDirections
@@ -38,7 +40,10 @@ import com.materialstudies.reply.ui.nav.ChangeSettingsMenuStateAction
3840
import com.materialstudies.reply.ui.nav.HalfClockwiseRotateSlideAction
3941
import com.materialstudies.reply.ui.nav.HalfCounterClockwiseRotateSlideAction
4042
import com.materialstudies.reply.ui.nav.ShowHideFabStateAction
43+
import com.materialstudies.reply.ui.search.SearchFragmentDirections
4144
import com.materialstudies.reply.util.contentView
45+
import com.materialstudies.reply.util.currentNavigationFragment
46+
import com.materialstudies.reply.util.setOutgoingTransitions
4247
import kotlin.LazyThreadSafetyMode.NONE
4348

4449
class MainActivity : AppCompatActivity(),
@@ -73,8 +78,7 @@ class MainActivity : AppCompatActivity(),
7378
setShowMotionSpecResource(R.animator.fab_show)
7479
setHideMotionSpecResource(R.animator.fab_hide)
7580
setOnClickListener {
76-
findNavController(R.id.nav_host_fragment)
77-
.navigate(ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId))
81+
navigateToCompose()
7882
}
7983
}
8084

@@ -132,6 +136,10 @@ class MainActivity : AppCompatActivity(),
132136
currentEmailId = -1
133137
setBottomAppBarForCompose()
134138
}
139+
R.id.searchFragment -> {
140+
currentEmailId = -1
141+
setBottomAppBarForSearch()
142+
}
135143
}
136144
}
137145

@@ -178,6 +186,15 @@ class MainActivity : AppCompatActivity(),
178186
}
179187

180188
private fun setBottomAppBarForCompose() {
189+
hideBottomAppBar()
190+
}
191+
192+
private fun setBottomAppBarForSearch() {
193+
hideBottomAppBar()
194+
binding.fab.hide()
195+
}
196+
197+
private fun hideBottomAppBar() {
181198
binding.run {
182199
bottomAppBar.performHide()
183200
// Get a handle on the animator that hides the bottom app bar so we can wait to hide
@@ -205,6 +222,7 @@ class MainActivity : AppCompatActivity(),
205222
bottomNavDrawer.close()
206223
showDarkThemeMenu()
207224
}
225+
R.id.menu_search -> navigateToSearch()
208226
}
209227
return true
210228
}
@@ -215,6 +233,25 @@ class MainActivity : AppCompatActivity(),
215233
}.show(supportFragmentManager, null)
216234
}
217235

236+
private fun navigateToCompose() {
237+
supportFragmentManager.currentNavigationFragment?.setOutgoingTransitions(
238+
exitTransition = Hold().apply {
239+
duration = resources.getInteger(R.integer.reply_motion_default_large).toLong()
240+
}
241+
)
242+
findNavController(R.id.nav_host_fragment)
243+
.navigate(ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId))
244+
}
245+
246+
private fun navigateToSearch() {
247+
supportFragmentManager.currentNavigationFragment?.setOutgoingTransitions(
248+
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false),
249+
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
250+
)
251+
findNavController(R.id.nav_host_fragment)
252+
.navigate(SearchFragmentDirections.actionGlobalSearchFragment())
253+
}
254+
218255
/**
219256
* Set this Activity's night mode based on a user's in-app selection.
220257
*/

Reply/app/src/main/java/com/materialstudies/reply/ui/email/EmailFragment.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,6 @@ class EmailFragment : Fragment() {
105105
duration = resources.getInteger(R.integer.reply_motion_default_large).toLong()
106106
interpolator = requireContext().themeInterpolator(R.attr.motionInterpolatorPersistent)
107107
}
108-
exitTransition = Hold().apply {
109-
duration = resources.getInteger(R.integer.reply_motion_default_large).toLong()
110-
}
111108
}
112109

113110
private fun startTransitions() {

Reply/app/src/main/java/com/materialstudies/reply/ui/home/HomeFragment.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import com.materialstudies.reply.data.Email
3232
import com.materialstudies.reply.data.EmailStore
3333
import com.materialstudies.reply.databinding.FragmentHomeBinding
3434
import com.materialstudies.reply.ui.MenuBottomSheetDialogFragment
35+
import com.materialstudies.reply.util.setOutgoingTransitions
3536

3637
/**
3738
* A [Fragment] that displays a list of emails.
@@ -42,13 +43,6 @@ class HomeFragment : Fragment(), EmailAdapter.EmailAdapterListener {
4243

4344
private val emailAdapter = EmailAdapter(this)
4445

45-
override fun onCreate(savedInstanceState: Bundle?) {
46-
super.onCreate(savedInstanceState)
47-
exitTransition = Hold().apply {
48-
duration = resources.getInteger(R.integer.reply_motion_default_large).toLong()
49-
}
50-
}
51-
5246
override fun onCreateView(
5347
inflater: LayoutInflater,
5448
container: ViewGroup?,
@@ -78,6 +72,11 @@ class HomeFragment : Fragment(), EmailAdapter.EmailAdapterListener {
7872
}
7973

8074
override fun onEmailClicked(cardView: View, email: Email) {
75+
setOutgoingTransitions(
76+
exitTransition = Hold().apply {
77+
duration = resources.getInteger(R.integer.reply_motion_default_large).toLong()
78+
}
79+
)
8180
val extras = FragmentNavigatorExtras(cardView to cardView.transitionName)
8281
val directions = HomeFragmentDirections.actionHomeFragmentToEmailFragment(email.id)
8382
findNavController().navigate(directions, extras)
@@ -99,4 +98,4 @@ class HomeFragment : Fragment(), EmailAdapter.EmailAdapterListener {
9998
override fun onEmailArchived(email: Email) {
10099
EmailStore.delete(email.id)
101100
}
102-
}
101+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.materialstudies.reply.ui.search
18+
19+
import android.os.Bundle
20+
import android.view.LayoutInflater
21+
import android.view.View
22+
import android.view.ViewGroup
23+
import android.widget.TextView
24+
import androidx.annotation.StringRes
25+
import androidx.fragment.app.Fragment
26+
import androidx.navigation.fragment.findNavController
27+
import com.google.android.material.transition.MaterialSharedAxis
28+
import com.materialstudies.reply.R
29+
import com.materialstudies.reply.data.SearchSuggestion
30+
import com.materialstudies.reply.data.SearchSuggestionStore
31+
import com.materialstudies.reply.databinding.FragmentSearchBinding
32+
import com.materialstudies.reply.databinding.SearchSuggestionItemBinding
33+
import com.materialstudies.reply.databinding.SearchSuggestionTitleBinding
34+
35+
/**
36+
* A [Fragment] that displays search.
37+
*/
38+
class SearchFragment : Fragment() {
39+
40+
private lateinit var binding: FragmentSearchBinding
41+
42+
override fun onCreate(savedInstanceState: Bundle?) {
43+
super.onCreate(savedInstanceState)
44+
45+
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
46+
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
47+
}
48+
49+
override fun onCreateView(
50+
inflater: LayoutInflater,
51+
container: ViewGroup?,
52+
savedInstanceState: Bundle?
53+
): View? {
54+
binding = FragmentSearchBinding.inflate(inflater, container, false)
55+
return binding.root
56+
}
57+
58+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
59+
binding.searchToolbar.setNavigationOnClickListener { findNavController().navigateUp() }
60+
setUpSuggestions(binding.searchSuggestionContainer)
61+
}
62+
63+
private fun setUpSuggestions(suggestionContainer: ViewGroup) {
64+
addSuggestionTitleView(suggestionContainer, R.string.search_suggestion_title_yesterday)
65+
addSuggestionItemViews(suggestionContainer, SearchSuggestionStore.YESTERDAY_SUGGESTIONS)
66+
addSuggestionTitleView(suggestionContainer, R.string.search_suggestion_title_this_week)
67+
addSuggestionItemViews(suggestionContainer, SearchSuggestionStore.THIS_WEEK_SUGGESTIONS)
68+
}
69+
70+
private fun addSuggestionTitleView(parent: ViewGroup, @StringRes titleResId: Int) {
71+
val inflater = LayoutInflater.from(parent.context)
72+
val titleBinding = SearchSuggestionTitleBinding.inflate(inflater, parent, false)
73+
titleBinding.title = titleResId
74+
parent.addView(titleBinding.root)
75+
}
76+
77+
private fun addSuggestionItemViews(parent: ViewGroup, suggestions: List<SearchSuggestion>) {
78+
suggestions.forEach {
79+
val inflater = LayoutInflater.from(parent.context)
80+
val suggestionBinding = SearchSuggestionItemBinding.inflate(inflater, parent, false)
81+
suggestionBinding.suggestion = it
82+
parent.addView(suggestionBinding.root)
83+
}
84+
}
85+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.materialstudies.reply.util
18+
19+
import androidx.fragment.app.Fragment
20+
21+
/**
22+
* Sets the exit and reenter transitions, or nulls them out if not provided.
23+
*/
24+
fun Fragment.setOutgoingTransitions(
25+
exitTransition: Any? = null,
26+
reenterTransition: Any? = null
27+
) {
28+
this.exitTransition = exitTransition
29+
this.reenterTransition = reenterTransition
30+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.materialstudies.reply.util
18+
19+
import androidx.fragment.app.Fragment
20+
import androidx.fragment.app.FragmentManager
21+
import com.materialstudies.reply.R
22+
23+
val FragmentManager.currentNavigationFragment: Fragment?
24+
get() = findFragmentById(R.id.nav_host_fragment)?.childFragmentManager?.fragments?.first()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!--
2+
Copyright (c) 2020 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="@android:color/white"
22+
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
23+
</vector>

0 commit comments

Comments
 (0)