Skip to content
This repository was archived by the owner on Nov 21, 2024. It is now read-only.

Commit 0437f9a

Browse files
committed
Add Email details screen
Change-Id: I4738e15193d34708654d1ebfbff568caf78581bf
1 parent 35e9bd0 commit 0437f9a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+692
-144
lines changed

app/build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
apply plugin: 'com.android.application'
22
apply plugin: 'kotlin-android'
33
apply plugin: 'kotlin-kapt'
4+
apply plugin: "androidx.navigation.safeargs.kotlin"
45

56
android {
67
compileSdkVersion 29
@@ -35,8 +36,8 @@ dependencies {
3536
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
3637
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha06'
3738
implementation 'androidx.core:core-ktx:1.0.2'
38-
implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0-alpha05'
39-
implementation 'androidx.navigation:navigation-ui-ktx:2.1.0-alpha05'
39+
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
40+
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
4041

4142
// Glide
4243
implementation "com.github.bumptech.glide:glide:4.9.0"
Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.materialstudies.reply.data
22

3+
import androidx.annotation.DrawableRes
34
import androidx.recyclerview.widget.DiffUtil
45

56
/**
@@ -8,9 +9,10 @@ import androidx.recyclerview.widget.DiffUtil
89
data class Email(
910
val id: Int,
1011
val sender: String,
12+
val recipient: String,
1113
val subject: String,
1214
val body: String,
13-
val senderImg: Int,
15+
@DrawableRes val senderResId: Int,
1416
val attachments: List<EmailAttachment> = emptyList(),
1517
var isImportant: Boolean = false,
1618
var isStarred: Boolean = false
@@ -20,17 +22,7 @@ data class Email(
2022
}
2123

2224
object EmailDiffCallback : DiffUtil.ItemCallback<Email>() {
23-
override fun areItemsTheSame(oldItem: Email, newItem: Email): Boolean = oldItem.id == newItem.id
24-
25-
override fun areContentsTheSame(oldItem: Email, newItem: Email): Boolean {
26-
return oldItem.sender == newItem.sender &&
27-
oldItem.subject == newItem.subject &&
28-
oldItem.body == newItem.body &&
29-
oldItem.senderImg == newItem.senderImg &&
30-
oldItem.isStarred == newItem.isStarred &&
31-
oldItem.isImportant == newItem.isImportant &&
32-
oldItem.attachments.containsAll(newItem.attachments)
33-
34-
}
25+
override fun areItemsTheSame(oldItem: Email, newItem: Email) = oldItem.id == newItem.id
26+
override fun areContentsTheSame(oldItem: Email, newItem: Email) = oldItem == newItem
3527
}
3628

app/src/main/java/com/materialstudies/reply/data/EmailStore.kt

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,38 @@ class EmailStore {
1313
Email(
1414
0,
1515
"Google Express - 15m ago",
16+
"me",
1617
"Package shipped!",
17-
"Cucumber Mask Facial has shipped",
18+
"""
19+
Cucumber Mask Facial has shipped.
20+
21+
Keep an eye out for a package to arrive between this Thursday and next Tuesday. If for any reason you don't receive your package before the end of next week, please reach out to us for details on your shipment.
22+
23+
As always, thank you for shopping with us and we hope you love our specially formulated Cucumber Mask!
24+
""".trimIndent(),
1825
R.drawable.avatar_express,
1926
isStarred = true
2027
),
2128
Email(
2229
1,
2330
"Ali Connors - 25m ago",
31+
"me",
2432
"Brunch this weekend?",
25-
"I'll be in your neighborhood doing errands...",
33+
"""
34+
I'll be in your neighborhood doing errands and was hoping to catch you for a coffee this Saturday. If you don't have anything scheduled, it would be great to see you! It feels like its been forever.
35+
36+
If we do get a chance to get together, remind me to tell you about Kim. She stopped over at the house to say hey to the kids and told me all about her trip to Mexico.
37+
38+
Talk to you soon,
39+
40+
Ali
41+
""".trimIndent(),
2642
R.drawable.avatar_1
2743
),
2844
Email(
2945
2,
3046
"Sandra Adams - 6 hrs ago",
47+
"me",
3148
"Bonjour from Paris",
3249
"Here are some great shots from my trip...",
3350
R.drawable.avatar_2,
@@ -42,28 +59,46 @@ class EmailStore {
4259
Email(
4360
3,
4461
"Trevor Hansen - 12 hrs ago",
62+
"me",
4563
"High school reunion?",
46-
"",
64+
"""
65+
Hi friends,
66+
67+
I was at the grocery store on Sunday night.. when I ran into Genie Williams! I almost didn't recognize her afer 20 years!
68+
69+
Anyway, it turns out she is on the organizing committee for the high school reunion this fall. I don't know if you were planning on going or not, but she could definitely use our help in trying to track down lots of missing alums. If you can make it, we're doing a little phone-tree party at her place next Saturday, hoping that if we can find one person, thee more will...
70+
""".trimIndent(),
4771
R.drawable.avatar_3
4872
),
4973
Email(
5074
4,
5175
"Britta Holt - 18 hrs ago",
76+
"me",
5277
"Brazil trip",
53-
"Thought we might be able to go over some details about our upcoming vacation...",
78+
"""
79+
Thought we might be able to go over some details about our upcoming vacation.
80+
81+
I've been doing a bit of research and have come across a few paces in Northern Brazil that I think we should check out. One, the north has some of the most predictable wind on the planet. I'd love to get out on the ocean and kitesurf for a couple of days if we're going to be anywhere near or around Taiba. I hear it's beautiful there and if you're up for it, I'd love to go. Other than that, I haven't spent too much time looking into places along our road trip route. I'm assuming we can find places to stay and things to do as we drive and find places we think look interesting. But... I know you're more of a planner, so if you have ideas or places in mind, lets jot some ideas down!
82+
83+
Maybe we can jump on the phone later today if you have a second.
84+
85+
I've been doing a bit of research and have come across a few paces in Northern Brazil that I think we should check out. One, the north has some of the most predictable wind on the planet. I'd love to get out on the ocean and kitesurf for a couple of days if we're going to be anywhere near or around Taiba. I hear it's beautiful there and if you're up for it, I'd love to go. Other than that, I haven't spent too much time looking into places along our road trip route. I'm assuming we can find places to stay and things to do as we drive and find places we think look interesting. But... I know you're more of a planner, so if you have ideas or places in mind, lets jot some ideas down!
86+
""".trimIndent(),
5487
R.drawable.avatar_4,
5588
isStarred = true
5689
),
5790
Email(
5891
5,
5992
"Frank Hawkins - 20 hrs ago",
93+
"me",
6094
"Update to Your Itinerary",
6195
"",
6296
R.drawable.avatar_5
6397
),
6498
Email(
6599
6,
66100
"Britta Holt - 21 hrs ago",
101+
"me",
67102
"Recipe to try",
68103
"Raspberry Pie: We should make this pie recipe tonight! The filling is " +
69104
"very quick to put together.",
@@ -72,6 +107,7 @@ class EmailStore {
72107
Email(
73108
7,
74109
"Google Express - 22 hrs ago",
110+
"me",
75111
"Delivered",
76112
"Your shoes should be waiting for you at home!",
77113
R.drawable.avatar_7
@@ -86,6 +122,10 @@ class EmailStore {
86122
_emails.value = allEmails
87123
}
88124

125+
fun get(id: Int): Email? {
126+
return allEmails.firstOrNull { it.id == id }
127+
}
128+
89129
fun delete(id: Int) {
90130
allEmails.removeAll { it.id == id }
91131
_emails.value = allEmails

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

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@ package com.materialstudies.reply.ui
33
import androidx.appcompat.app.AppCompatActivity
44
import android.os.Bundle
55
import android.view.MenuItem
6+
import android.view.View
7+
import androidx.annotation.MenuRes
68
import androidx.appcompat.app.AppCompatDelegate
79
import androidx.appcompat.widget.Toolbar
10+
import androidx.core.content.ContextCompat
11+
import androidx.navigation.NavController
12+
import androidx.navigation.NavDestination
13+
import androidx.navigation.findNavController
814
import com.materialstudies.reply.R
915
import com.materialstudies.reply.databinding.ActivityMainBinding
1016
import com.materialstudies.reply.ui.nav.AlphaSlideAction
@@ -13,7 +19,9 @@ import com.materialstudies.reply.ui.nav.QuarterRotateSlideAction
1319
import com.materialstudies.reply.ui.nav.ShowHideFabStateAction
1420
import com.materialstudies.reply.util.contentView
1521

16-
class MainActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
22+
class MainActivity : AppCompatActivity(),
23+
Toolbar.OnMenuItemClickListener,
24+
NavController.OnDestinationChangedListener {
1725

1826
private val binding: ActivityMainBinding by contentView(R.layout.activity_main)
1927

@@ -23,31 +31,102 @@ class MainActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
2331
}
2432

2533
private fun setUpBottomNavigationAndFab() {
26-
binding.fab.setShowMotionSpecResource(R.animator.fab_show)
27-
binding.fab.setHideMotionSpecResource(R.animator.fab_hide)
34+
// Wrap binding.run to ensure ContentViewBindingDelegate is calling this Activity's
35+
// setContentView before accessing views
36+
binding.run {
37+
findNavController(R.id.nav_host_fragment).addOnDestinationChangedListener(
38+
this@MainActivity
39+
)
40+
}
41+
42+
// Set a custom animation for showing and hiding the FAB
43+
binding.fab.apply {
44+
setShowMotionSpecResource(R.animator.fab_show)
45+
setHideMotionSpecResource(R.animator.fab_hide)
46+
}
2847

2948
binding.bottomNavigationDrawer.apply {
3049
addOnSlideAction(QuarterRotateSlideAction(binding.bottomAppBarChevron))
3150
addOnSlideAction(AlphaSlideAction(binding.bottomAppBarTitle, true))
3251
addOnStateChangedAction(ShowHideFabStateAction(binding.fab))
3352
addOnStateChangedAction(ChangeSettingsMenuStateAction { showSettings ->
53+
// Toggle between the current destination's BAB menu and the menu which should
54+
// be displayed when the BottomNavigationDrawer is open.
3455
binding.bottomAppBar.replaceMenu(if (showSettings) {
3556
R.menu.bottom_app_bar_settings_menu
3657
} else {
37-
R.menu.bottom_app_bar_home_menu
58+
getBottomAppBarMenuForDestination()
3859
})
3960
})
4061
}
4162

42-
binding.bottomAppBar.setNavigationOnClickListener {
43-
binding.bottomNavigationDrawer.toggle()
63+
// Set up the BottomAppBar menu
64+
binding.bottomAppBar.apply {
65+
overflowIcon = ContextCompat.getDrawable(
66+
this@MainActivity,
67+
R.drawable.ic_more_vert_on_branded
68+
)
69+
setNavigationOnClickListener {
70+
binding.bottomNavigationDrawer.toggle()
71+
}
72+
setOnMenuItemClickListener(this@MainActivity)
4473
}
45-
binding.bottomAppBar.setOnMenuItemClickListener(this)
74+
75+
// Set up the BottomNavigationDrawer's open/close affordance
4676
binding.bottomAppBarContentContainer.setOnClickListener {
4777
binding.bottomNavigationDrawer.toggle()
4878
}
4979
}
5080

81+
override fun onDestinationChanged(
82+
controller: NavController,
83+
destination: NavDestination,
84+
arguments: Bundle?
85+
) {
86+
// Set the configuration of the BottomAppBar and FAB based on the current destination.
87+
when (destination.id) {
88+
R.id.homeFragment ->
89+
setBottomAppBarForHome(getBottomAppBarMenuForDestination(destination))
90+
R.id.emailFragment ->
91+
setBottomAppBarForEmail(getBottomAppBarMenuForDestination(destination))
92+
}
93+
}
94+
95+
/**
96+
* Helper function which returns the menu which should be displayed for the current
97+
* destination.
98+
*
99+
* Used both when the destination has changed, centralizing destination-to-menu mapping, as
100+
* well as switching between the alternate menu used when the BottomNavigationDrawer is
101+
* open and closed.
102+
*/
103+
@MenuRes
104+
private fun getBottomAppBarMenuForDestination(destination: NavDestination? = null): Int {
105+
val dest = destination ?: findNavController(R.id.nav_host_fragment).currentDestination
106+
return when (dest?.id) {
107+
R.id.homeFragment -> R.menu.bottom_app_bar_home_menu
108+
R.id.emailFragment -> R.menu.bottom_app_bar_email_menu
109+
else -> R.menu.bottom_app_bar_home_menu
110+
}
111+
}
112+
113+
private fun setBottomAppBarForHome(@MenuRes menuRes: Int) {
114+
binding.run {
115+
fab.setImageResource(R.drawable.ic_edit_on_secondary)
116+
bottomAppBar.replaceMenu(menuRes)
117+
bottomAppBarTitle.visibility = View.VISIBLE
118+
}
119+
}
120+
121+
private fun setBottomAppBarForEmail(@MenuRes menuRes: Int) {
122+
binding.run {
123+
fab.setImageResource(R.drawable.ic_reply_all_on_secondary)
124+
bottomAppBar.replaceMenu(menuRes)
125+
bottomAppBarTitle.visibility = View.INVISIBLE
126+
}
127+
}
128+
129+
51130
override fun onMenuItemClick(item: MenuItem?): Boolean {
52131
when (item?.itemId) {
53132
R.id.menu_theme -> {
@@ -64,6 +143,9 @@ class MainActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
64143
}.show(supportFragmentManager, null)
65144
}
66145

146+
/**
147+
* Set this Activity's night mode based on a user's in-app selection.
148+
*/
67149
private fun onDarkThemeMenuItemSelected(itemId: Int): Boolean {
68150
val nightMode = when (itemId) {
69151
R.id.menu_light -> AppCompatDelegate.MODE_NIGHT_NO
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.materialstudies.reply.ui.common
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import androidx.databinding.DataBindingUtil
6+
import androidx.recyclerview.widget.RecyclerView
7+
import com.materialstudies.reply.data.EmailAttachment
8+
9+
/**
10+
* Generic RecyclerView.Adapter to display [EmailAttachment]s.
11+
*/
12+
abstract class EmailAttachmentAdapter : RecyclerView.Adapter<EmailAttachmentViewHolder>() {
13+
14+
private var list: List<EmailAttachment> = emptyList()
15+
16+
override fun getItemCount() = list.size
17+
18+
override fun getItemViewType(position: Int) = getLayoutIdForPosition(position)
19+
20+
fun submitList(attachments: List<EmailAttachment>) {
21+
list = attachments
22+
notifyDataSetChanged()
23+
}
24+
25+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmailAttachmentViewHolder {
26+
return EmailAttachmentViewHolder(DataBindingUtil.inflate(
27+
LayoutInflater.from(parent.context),
28+
viewType,
29+
parent,
30+
false
31+
))
32+
}
33+
34+
override fun onBindViewHolder(holder: EmailAttachmentViewHolder, position: Int) {
35+
holder.bind(list[position])
36+
}
37+
38+
/**
39+
* Clients should implement this method to determine what layout is inflated for a given
40+
* position. The layout must include a data parameter named 'emailAttachment' with a type
41+
* of [EmailAttachment].
42+
*/
43+
abstract fun getLayoutIdForPosition(position: Int): Int
44+
45+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.materialstudies.reply.ui.common
2+
3+
import androidx.databinding.ViewDataBinding
4+
import androidx.databinding.library.baseAdapters.BR
5+
import androidx.recyclerview.widget.RecyclerView
6+
import com.materialstudies.reply.data.EmailAttachment
7+
8+
/**
9+
* Generic RecyclerView.ViewHolder which is able to bind layouts which expose a variable
10+
* for an [EmailAttachment].
11+
*/
12+
class EmailAttachmentViewHolder(
13+
private val binding: ViewDataBinding
14+
) : RecyclerView.ViewHolder(binding.root) {
15+
16+
fun bind(attachment: EmailAttachment) {
17+
binding.run {
18+
binding.setVariable(BR.emailAttachment, attachment)
19+
executePendingBindings()
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)