Home | History | Annotate | Download | only in chat
      1 /*
      2  * 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