1 /*
<lambda>null2  * Copyright (C) 2019 The Android Open Source Project
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * 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
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 package com.example.android.bubbles.ui.chat
17 
18 import android.content.Intent
19 import android.graphics.drawable.Drawable
20 import android.os.Bundle
21 import android.transition.TransitionInflater
22 import android.view.LayoutInflater
23 import android.view.Menu
24 import android.view.MenuInflater
25 import android.view.MenuItem
26 import android.view.View
27 import android.view.ViewGroup
28 import android.view.inputmethod.EditorInfo
29 import android.widget.EditText
30 import android.widget.ImageButton
31 import android.widget.Toast
32 import androidx.fragment.app.Fragment
33 import androidx.lifecycle.Observer
34 import androidx.lifecycle.ViewModelProviders
35 import androidx.recyclerview.widget.LinearLayoutManager
36 import androidx.recyclerview.widget.RecyclerView
37 import com.bumptech.glide.Glide
38 import com.bumptech.glide.load.DataSource
39 import com.bumptech.glide.load.engine.GlideException
40 import com.bumptech.glide.request.RequestListener
41 import com.bumptech.glide.request.RequestOptions
42 import com.bumptech.glide.request.target.Target
43 import com.example.android.bubbles.R
44 import com.example.android.bubbles.VoiceCallActivity
45 import com.example.android.bubbles.getNavigationController
46 
47 /**
48  * The chat screen. This is used in the full app (MainActivity) as well as in the expanded Bubble (BubbleActivity).
49  */
50 class ChatFragment : Fragment() {
51 
52     companion object {
53         private const val ARG_ID = "id"
54         private const val ARG_FOREGROUND = "foreground"
55 
56         fun newInstance(id: Long, foreground: Boolean) = ChatFragment().apply {
57             arguments = Bundle().apply {
58                 putLong(ARG_ID, id)
59                 putBoolean(ARG_FOREGROUND, foreground)
60             }
61         }
62     }
63 
64     private lateinit var viewModel: ChatViewModel
65     private lateinit var input: EditText
66 
67     override fun onCreate(savedInstanceState: Bundle?) {
68         super.onCreate(savedInstanceState)
69         setHasOptionsMenu(true)
70         enterTransition = TransitionInflater.from(context).inflateTransition(R.transition.slide_bottom)
71     }
72 
73     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
74         return inflater.inflate(R.layout.chat_fragment, container, false)
75     }
76 
77     private val startPostponedTransitionOnEnd = object : RequestListener<Drawable> {
78         override fun onLoadFailed(
79             e: GlideException?,
80             model: Any?,
81             target: Target<Drawable>?,
82             isFirstResource: Boolean
83         ): Boolean {
84             startPostponedEnterTransition()
85             return false
86         }
87 
88         override fun onResourceReady(
89             resource: Drawable?,
90             model: Any?,
91             target: Target<Drawable>?,
92             dataSource: DataSource?,
93             isFirstResource: Boolean
94         ): Boolean {
95             startPostponedEnterTransition()
96             return false
97         }
98     }
99 
100     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
101         val id = arguments?.getLong(ARG_ID)
102         if (id == null) {
103             fragmentManager?.popBackStack()
104             return
105         }
106         val navigationController = getNavigationController()
107 
108         viewModel = ViewModelProviders.of(this).get(ChatViewModel::class.java)
109         viewModel.setChatId(id)
110 
111         val messages: RecyclerView = view.findViewById(R.id.messages)
112         val voiceCall: ImageButton = view.findViewById(R.id.voice_call)
113         input = view.findViewById(R.id.input)
114         val send: ImageButton = view.findViewById(R.id.send)
115 
116         val messageAdapter = MessageAdapter(view.context) { photo ->
117             navigationController.openPhoto(photo)
118         }
119         val linearLayoutManager = LinearLayoutManager(view.context).apply {
120             stackFromEnd = true
121         }
122         messages.run {
123             layoutManager = linearLayoutManager
124             adapter = messageAdapter
125         }
126 
127         viewModel.contact.observe(viewLifecycleOwner, Observer { chat ->
128             if (chat == null) {
129                 Toast.makeText(view.context, "Contact not found", Toast.LENGTH_SHORT).show()
130                 fragmentManager?.popBackStack()
131             } else {
132                 navigationController.updateAppBar { name, icon ->
133                     name.text = chat.name
134                     Glide.with(icon)
135                         .load(chat.icon)
136                         .apply(RequestOptions.circleCropTransform())
137                         .dontAnimate()
138                         .addListener(startPostponedTransitionOnEnd)
139                         .into(icon)
140                 }
141             }
142         })
143 
144         viewModel.messages.observe(viewLifecycleOwner, Observer {
145             messageAdapter.submitList(it)
146             linearLayoutManager.scrollToPosition(it.size - 1)
147         })
148 
149         voiceCall.setOnClickListener {
150             voiceCall()
151         }
152         send.setOnClickListener {
153             send()
154         }
155         input.setOnEditorActionListener { _, actionId, _ ->
156             if (actionId == EditorInfo.IME_ACTION_SEND) {
157                 send()
158                 true
159             } else {
160                 false
161             }
162         }
163     }
164 
165     override fun onStart() {
166         super.onStart()
167         val foreground = arguments?.getBoolean(ARG_FOREGROUND) == true
168         viewModel.foreground = foreground
169     }
170 
171     override fun onStop() {
172         super.onStop()
173         viewModel.foreground = false
174     }
175 
176     private fun voiceCall() {
177         val contact = viewModel.contact.value ?: return
178         startActivity(
179             Intent(requireActivity(), VoiceCallActivity::class.java)
180                 .putExtra(VoiceCallActivity.EXTRA_NAME, contact.name)
181                 .putExtra(VoiceCallActivity.EXTRA_ICON, contact.icon)
182         )
183     }
184 
185     private fun send() {
186         val text = input.text.toString()
187         if (text.isNotEmpty()) {
188             input.text.clear()
189             viewModel.send(text)
190         }
191     }
192 
193     override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
194         inflater?.inflate(R.menu.chat, menu)
195         menu?.findItem(R.id.action_show_as_bubble)?.let { item ->
196             viewModel.showAsBubbleVisible.observe(viewLifecycleOwner, Observer {
197                 item.isVisible = it
198             })
199         }
200         super.onCreateOptionsMenu(menu, inflater)
201     }
202 
203     override fun onOptionsItemSelected(item: MenuItem?): Boolean {
204         return when (item?.itemId) {
205             R.id.action_show_as_bubble -> {
206                 viewModel.showAsBubble()
207                 fragmentManager?.popBackStack()
208                 true
209             }
210             else -> super.onOptionsItemSelected(item)
211         }
212     }
213 }
214