ArticleDetailFragment.kt 6.92 KB
Newer Older
1
package fr.chenry.android.freshrss.components.articles
2 3

import android.content.Intent
4
import android.net.Uri
5
import android.os.Bundle
Christophe Henry's avatar
Christophe Henry committed
6
import android.view.*
7 8
import android.widget.Toast
import android.widget.Toast.LENGTH_SHORT
9
import androidx.appcompat.app.AppCompatActivity
10
import androidx.core.content.ContextCompat
11
import androidx.fragment.app.Fragment
12 13
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
14
import androidx.navigation.fragment.navArgs
15
import fr.chenry.android.freshrss.R
16 17
import fr.chenry.android.freshrss.components.articles.webviewutils.FRSSWebView
import fr.chenry.android.freshrss.components.articles.webviewutils.ShareIntent
18
import fr.chenry.android.freshrss.databinding.FragmentSubscriptionArticleDetailBinding
19
import fr.chenry.android.freshrss.store.Store
20 21
import fr.chenry.android.freshrss.store.database.models.Article
import fr.chenry.android.freshrss.store.database.models.ReadStatus
22 23 24
import fr.chenry.android.freshrss.store.viewmodels.ArticleVM
import fr.chenry.android.freshrss.store.viewmodels.ArticleVMF
import fr.chenry.android.freshrss.utils.*
25
import kotlinx.android.synthetic.main.fragment_subscription_article_detail.*
26
import kotlinx.coroutines.*
27

28 29 30 31
class ArticleDetailFragment: Fragment(), View.OnClickListener {
    private val args by navArgs<ArticleDetailFragmentArgs>()
    private val articleId by lazy {args.articleId}

32
    private val model: ArticleVM by lazy {
33
        ViewModelProvider(requireActivity().viewModelStore, ArticleVMF(requireF(), articleId))
34
            .get("articleId=$articleId", ArticleVM::class.java)
Christophe Henry's avatar
Christophe Henry committed
35
    }
36 37 38
    private val article: Article inline get() = model.liveData.value!!
    private val feedTitle: String inline get() = model.feedTitle.value!!
    private val supportActionBar inline get() = (requireActivity() as? AppCompatActivity)?.supportActionBar
39

40 41
    private val browserIntent by lazy {Intent(Intent.ACTION_VIEW, article.url)}

42 43 44 45 46 47
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
48
        val view = FragmentSubscriptionArticleDetailBinding.inflate(inflater, container, false)
49

50
        supportActionBar?.subtitle = article.title
51

52
        val activityInfos = browserIntent.resolveActivityInfo(requireActivity().packageManager, browserIntent.flags)
53

54
        view.apply {
55 56
            article = this@ArticleDetailFragment.article
            onClickListener = this@ArticleDetailFragment
57
            canOpenBrowser = activityInfos?.exported ?: false
58

59 60 61 62 63
            fragmentSubscriptionArticleDetailWebView.apply {
                load(this@ArticleDetailFragment, this@ArticleDetailFragment.article)
                if(requireF().preferences.retainScrollPosition)
                    scrollY = this@ArticleDetailFragment.article.scrollPosition
            }
64 65
        }

66
        if(article.readStatus == ReadStatus.UNREAD) setReadStatus(ReadStatus.READ)
Christophe Henry's avatar
Christophe Henry committed
67

68
        return view.root
69 70
    }

71 72 73
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.article_actionbar, menu)
        setUpReadStatusButton(menu)
74 75 76
        super.onCreateOptionsMenu(menu, inflater)
    }

77 78 79 80 81 82 83 84 85 86
    override fun onResume() {
        super.onResume()
        requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
    }

    override fun onPause() {
        super.onPause()
        requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
    }

87
    override fun onDestroyView() {
88
        supportActionBar?.subtitle = null
89
        val scrollPosition = fragment_subscription_article_detail_web_view.scrollY
90 91 92
        // database will be updated asynchronously. So we need to retrieve pointer on the DB before
        // the fragment is disconnected from the activity
        val database = requireF().db
93
        GlobalScope.launch {
94
            database.updateArticleScrollPosition(article.id, scrollPosition)
95
        }
96 97
        super.onDestroyView()
    }
98

99 100 101 102 103
    override fun onClick(view: View) = when(view.id) {
        R.id.fab_share -> {
            val chooserTitle = requireF().getString(R.string.share_article, feedTitle)
            startActivity(Intent.createChooser(ShareIntent.create(feedTitle, article), chooserTitle))
        }
104
        R.id.fab_open_browser -> startActivity(browserIntent)
105
        else -> NOOP()
106 107 108
    }

    private fun setUpReadStatusButton(menu: Menu) {
109 110
        val ctx = requireContext()

111
        menu.findItem(R.id.action_mark_read_status)?.let {
Christophe Henry's avatar
Christophe Henry committed
112
            fun mutateUi(article: Article) {
113
                when(article.readStatus) {
114
                    ReadStatus.READ -> {
115 116
                        it.icon = ContextCompat.getDrawable(ctx, R.drawable.ic_is_read_24dp)
                        it.title = ctx.getString(R.string.mark_unread)
117 118
                    }
                    ReadStatus.UNREAD -> {
119 120
                        it.icon = ContextCompat.getDrawable(ctx, R.drawable.ic_is_unread_24dp)
                        it.title = ctx.getString(R.string.mark_read)
121 122 123 124 125
                    }
                }
                it.isVisible = true
            }

Christophe Henry's avatar
Christophe Henry committed
126
            mutateUi(article)
127
            model.liveData.observe(viewLifecycleOwner, Observer(::mutateUi))
128
            it.setOnMenuItemClickListener {setReadStatus(article.readStatus.toggle()).let {true}}
129 130 131
        }
    }

132
    private fun setReadStatus(readStatus: ReadStatus) {
133
        if(!requireF().isConnectedToNetwork()) {
Christophe Henry's avatar
Refacto  
Christophe Henry committed
134
            Toast.makeText(context, R.string.no_internet_connection_avaible, Toast.LENGTH_LONG).show()
135
            return
Christophe Henry's avatar
Christophe Henry committed
136 137
        }

138
        if(article.readStatusRequestOnGoing) {
139
            Toast.makeText(context, R.string.request_already_ongoing, LENGTH_SHORT).show()
140
            return
141 142
        }

143 144
        val ctx = requireContext()

145 146 147 148 149 150 151
        /**
         * Major mistake: Kotlin's [Result] cannot be used as the result of a [GlobalScope.launch]
         * call. An intermediary function uses it as the indicator that the coroutine failed.
         * Now I now why using [Result] as a return type in Kotlin a forbidden...
         */
        @Suppress("DeferredResultUnused")
        GlobalScope.async {
152
            val result = Store.postReadStatus(article, readStatus)
153

154
            result.onFailure {
155 156 157 158
                this@ArticleDetailFragment.apply {
                    this.e(it)

                    val readText = when(readStatus) {
159 160
                        ReadStatus.READ -> ctx.getString(R.string.read)
                        ReadStatus.UNREAD -> ctx.getString(R.string.unread)
161
                    }.let {msg ->
162
                        ctx.getString(R.string.mark_read_status_authorization, msg)
163 164
                    }

165
                    val toastText = ctx.getString(R.string.unable_to, readText)
166 167

                    withContext(Dispatchers.Main) {
168
                        Toast.makeText(ctx, toastText, LENGTH_SHORT).show()
169 170 171 172
                    }
                }
            }
        }
173
    }
174
}