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