<commit_before>package jp.toastkid.yobidashi.browser

import android.app.ActivityOptions
import android.content.Context
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.webkit.ValueCallback
import androidx.annotation.LayoutRes
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.tbruyelle.rxpermissions2.RxPermissions
import io.reactivex.disposables.CompositeDisposable
import jp.toastkid.yobidashi.CommonFragmentAction
import jp.toastkid.yobidashi.R
import jp.toastkid.yobidashi.browser.bookmark.BookmarkInsertion
import jp.toastkid.yobidashi.browser.bookmark.model.Bookmark
import jp.toastkid.yobidashi.browser.floating.FloatingPreview
import jp.toastkid.yobidashi.browser.page_search.PageSearcherViewModel
import jp.toastkid.yobidashi.browser.reader.ReaderFragment
import jp.toastkid.yobidashi.browser.reader.ReaderFragmentViewModel
import jp.toastkid.yobidashi.browser.user_agent.UserAgent
import jp.toastkid.yobidashi.browser.user_agent.UserAgentDialogFragment
import jp.toastkid.yobidashi.databinding.FragmentBrowserBinding
import jp.toastkid.yobidashi.databinding.ModuleBrowserHeaderBinding
import jp.toastkid.yobidashi.libs.ActivityOptionsFactory
import jp.toastkid.yobidashi.libs.Toaster
import jp.toastkid.yobidashi.libs.Urls
import jp.toastkid.yobidashi.libs.intent.IntentFactory
import jp.toastkid.yobidashi.libs.preference.PreferenceApplier
import jp.toastkid.yobidashi.main.ContentScrollable
import jp.toastkid.yobidashi.main.HeaderViewModel
import jp.toastkid.yobidashi.main.MainActivity
import jp.toastkid.yobidashi.main.TabUiFragment
import jp.toastkid.yobidashi.main.content.ContentViewModel
import jp.toastkid.yobidashi.menu.MenuViewModel
import jp.toastkid.yobidashi.rss.extractor.RssUrlFinder
import jp.toastkid.yobidashi.search.SearchFragment
import jp.toastkid.yobidashi.search.SearchQueryExtractor
import jp.toastkid.yobidashi.tab.tab_list.TabListViewModel

/**
 * Internal browser fragment.
 *
 * @author toastkidjp
 */
class BrowserFragment : Fragment(),
        TabUiFragment,
        CommonFragmentAction,
        UserAgentDialogFragment.Callback,
        ContentScrollable
{

    /**
     * RxPermissions.
     */
    private var rxPermissions: RxPermissions? = null

    /**
     * Preferences wrapper.
     */
    private lateinit var preferenceApplier: PreferenceApplier

    /**
     * Browser module.
     */
    private lateinit var browserModule: BrowserModule

    /**
     * Data binding object.
     */
    private var binding: FragmentBrowserBinding? = null

    private var headerBinding: ModuleBrowserHeaderBinding? = null

    private val searchQueryExtractor = SearchQueryExtractor()

    /**
     * Composite disposer.
     */
    private val disposables: CompositeDisposable = CompositeDisposable()

    /**
     * Floating preview object.
     */
    private var floatingPreview: FloatingPreview? = null

    private var menuViewModel: MenuViewModel? = null

    private var headerViewModel: HeaderViewModel? = null

    private var browserViewModel: BrowserViewModel? = null

    private val activityOptionsFactory = ActivityOptionsFactory()

    override fun onAttach(context: Context) {
        super.onAttach(context)
        activity?.let {
            rxPermissions = RxPermissions(it)
        }
    }

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        headerBinding = DataBindingUtil.inflate(inflater, R.layout.module_browser_header, container, false)
        headerBinding?.fragment = this

        binding = DataBindingUtil.inflate(inflater, layoutId, container, false)
        binding?.fragment = this

        binding?.swipeRefresher?.let {
            it.setOnRefreshListener { reload() }
            it.setOnChildScrollUpCallback { _, _ -> browserModule.disablePullToRefresh() }
            it.setDistanceToTriggerSync(8000)
            it.setSlingshotDistance(150)
        }

        val activityContext = context ?: return null

        preferenceApplier = PreferenceApplier(activityContext)

        browserModule = BrowserModule(activityContext, binding?.webViewContainer)

        setHasOptionsMenu(true)

        // TODO use data binding
        headerBinding?.urlBox?.setOnClickListener { tapHeader() }

        browserViewModel = ViewModelProviders.of(this).get(BrowserViewModel::class.java)

        return binding?.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val activity = requireActivity()
        menuViewModel = ViewModelProviders.of(activity)
                .get(MenuViewModel::class.java)

        initializeHeaderViewModels(activity)
    }

    private fun initializeHeaderViewModels(activity: FragmentActivity) {
        val viewModelProvider = ViewModelProviders.of(activity)
        headerViewModel = viewModelProvider.get(HeaderViewModel::class.java)

        headerViewModel?.stopProgress?.observe(activity, Observer { stop ->
            if (!stop || binding?.swipeRefresher?.isRefreshing == false) {
                return@Observer
            }
            stopSwipeRefresherLoading()
        })

        headerViewModel?.progress?.observe(activity, Observer { newProgress ->
            if (70 < newProgress) {
                headerBinding?.progress?.isVisible = false
                headerBinding?.reload?.setImageResource(R.drawable.ic_reload)
                //TODO refreshThumbnail()
                return@Observer
            }
            headerBinding?.progress?.let {
                it.isVisible = true
                it.progress = newProgress
                headerBinding?.reload?.setImageResource(R.drawable.ic_close)
            }
        })

        viewModelProvider.get(BrowserHeaderViewModel::class.java).also { viewModel ->
            viewModel.title.observe(activity, Observer { title ->
                if (title.isNullOrBlank()) {
                    return@Observer
                }
                headerBinding?.mainText?.text = title
            })

            viewModel.url.observe(activity, Observer { url ->
                if (url.isNullOrBlank()) {
                    return@Observer
                }
                headerBinding?.subText?.text = url
            })

            viewModel.reset.observe(activity, Observer {
                val headerView = headerBinding?.root ?: return@Observer
                headerViewModel?.replace(headerView)
            })

            viewModel.enableForward.observe(activity, Observer {
                updateForwardButtonState(it)
            })
        }

        viewModelProvider.get(LoadingViewModel::class.java)
                .onPageFinished
                .observe(
                        activity,
                        Observer { browserModule.saveArchiveForAutoArchive() }
                )

        viewModelProvider.get(BrowserFragmentViewModel::class.java)
                .loadWithNewTab
                .observe(activity, Observer {
                    browserModule.loadWithNewTab(it.first, it.second)
                })

        viewModelProvider.get(TabListViewModel::class.java)
                .tabCount
                .observe(activity, Observer { headerBinding?.tabCount?.setText(it.toString()) })

        viewModelProvider.get(PageSearcherViewModel::class.java).also { viewModel ->
            viewModel.find.observe(activity, Observer {
                browserModule.find(it)
            })

            viewModel.upward.observe(activity, Observer {
                browserModule.findUp()
            })

            viewModel.downward.observe(activity, Observer {
                browserModule.findDown()
            })
        }
    }

    override fun onCreateOptionsMenu(menu: android.view.Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.browser, menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.add_bookmark -> {
                val context = context ?: return true
                val faviconApplier = FaviconApplier(context)
                val url = browserModule.currentUrl() ?: ""
                BookmarkInsertion(
                        context,
                        browserModule.currentTitle(),
                        url,
                        faviconApplier.makePath(url),
                        Bookmark.getRootFolderName()
                ).insert()

                val parent = binding?.root ?: return true
                Toaster.snackShort(
                        parent,
                        context.getString(R.string.message_done_added_bookmark),
                        colorPair()
                )
                return true
            }
            R.id.add_archive -> {
                browserModule.saveArchive()
                return true
            }
            R.id.add_rss -> {
                RssUrlFinder(preferenceApplier).invoke(browserModule.currentUrl()) { binding?.root }
                return true
            }
            R.id.replace_home -> {
                browserModule.currentUrl()?.let {
                    val parent = binding?.root ?: return true
                    if (Urls.isInvalidUrl(it)) {
                        Toaster.snackShort(
                                parent,
                                R.string.message_cannot_replace_home_url,
                                colorPair()
                        )
                        return true
                    }
                    preferenceApplier.homeUrl = it
                    Toaster.snackShort(
                            parent,
                            getString(R.string.message_replace_home_url, it) ,
                            colorPair()
                    )
                }
            }
        }
        return super.onOptionsItemSelected(item)
    }

    fun reload() {
        if (headerBinding?.progress?.isVisible == true) {
            browserModule.stopLoading()
        } else {
            browserModule.reload()
        }
    }

    fun openReaderMode() {
        browserModule.invokeContentExtraction(ValueCallback(this::showReaderFragment))
    }

    // TODO should implement view model.
    fun tabList() {
        val mainActivity = activity as? MainActivity
        mainActivity?.switchTabList()
    }

    private fun updateForwardButtonState(enable: Boolean) {
        headerBinding?.forward?.isEnabled = enable
        headerBinding?.forward?.alpha = if (enable) 1f else 0.6f
    }

    private fun showReaderFragment(content: String) {
        val lineSeparator = System.getProperty("line.separator") ?: ""
        val replacedContent = content.replace("\\n", lineSeparator)

        ViewModelProviders.of(requireActivity())[ReaderFragmentViewModel::class.java]
                .close.observe(requireActivity(), Observer {
            fragmentManager?.popBackStack()
        })

        val readerFragment =
                fragmentManager?.findFragmentByTag(ReaderFragment::class.java.canonicalName)
                        ?: ReaderFragment.withContent(browserModule.currentTitle(), replacedContent)

        val transaction = fragmentManager?.beginTransaction()
        transaction?.setCustomAnimations(
                R.anim.slide_in_right, 0, 0, android.R.anim.slide_out_right)
        transaction?.replace(R.id.content, readerFragment, readerFragment::class.java.canonicalName)
        transaction?.addToBackStack(readerFragment::class.java.canonicalName)
        transaction?.commit()
    }

    fun showUserAgentSetting() {
        val fragmentManager = fragmentManager ?: return
        val dialogFragment = UserAgentDialogFragment()
        dialogFragment.setTargetFragment(this, 1)
        dialogFragment.show(
                fragmentManager,
                UserAgentDialogFragment::class.java.simpleName
        )
    }

    fun showPageInformation() {
        val fragmentManager = fragmentManager ?: return
        PageInformationDialogFragment()
                .also { it.arguments = browserModule.makeCurrentPageInformation() }
                .show(
                        fragmentManager,
                        PageInformationDialogFragment::class.java.simpleName
                )
    }

    fun showHtmlSource() {
        browserModule.invokeHtmlSourceExtraction(
                ValueCallback {
                    showReaderFragment(it.replace("\\u003C", "<"))
                }
        )
    }

    /**
     * Do browser back action.
     */
    fun back(): Boolean = browserModule.back()

    /**
     * Do browser forward action.
     */
    fun forward() = browserModule.forward()

    /**
     * TODO implement ViewModel.
     */
    fun search() {
        search(activityOptionsFactory.makeScaleUpBundle(binding?.root as View))
    }

    /**
     * Show search activity.
     *
     * @param option [ActivityOptions]
     */
    private fun search(option: ActivityOptions) {
        context?.let {
            val currentTitle = browserModule.currentTitle()
            val currentUrl = browserModule.currentUrl()
            val query = searchQueryExtractor.invoke(currentUrl)
            val makeIntent = if (TextUtils.isEmpty(query) || Urls.isValidUrl(query)) {
                SearchFragment.makeWith(it, currentTitle, currentUrl)
            } else {
                SearchFragment.makeWithQuery(it, query ?: "", currentTitle, currentUrl)
            }
            activity?.also { activity ->
                ViewModelProviders.of(activity)
                        .get(ContentViewModel::class.java)
                        .nextFragment(makeIntent)
            }
        }
    }

    /**
     * Go to bottom.
     */
    override fun toBottom() {
        browserModule.pageDown()
    }

    /**
     * Go to top.
     */
    override fun toTop() {
        browserModule.pageUp()
    }

    override fun onResume() {
        super.onResume()

        switchToolbarVisibility()

        val colorPair = colorPair()

        val fontColor = colorPair.fontColor()

        headerBinding?.also {
            it.icon.setColorFilter(fontColor)
            it.mainText.setTextColor(fontColor)
            it.subText.setTextColor(fontColor)
            it.reload.setColorFilter(fontColor)
            it.back.setColorFilter(fontColor)
            it.forward.setColorFilter(fontColor)
            it.tabIcon.setColorFilter(fontColor)
            it.tabCount.setTextColor(fontColor)
            it.pageInformation.setColorFilter(fontColor)
            it.userAgent.setColorFilter(fontColor)
            it.htmlSource.setColorFilter(fontColor)
        }

        browserModule.resizePool(preferenceApplier.poolSize)
        browserModule.applyNewAlpha()

        binding?.swipeRefresher?.let {
            it.setProgressBackgroundColorSchemeColor(preferenceApplier.color)
            it.setColorSchemeColors(preferenceApplier.fontColor)
        }
    }

    private fun switchToolbarVisibility() {
        when (preferenceApplier.browserScreenMode()) {
            ScreenMode.FULL_SCREEN -> headerViewModel?.hide()
            ScreenMode.EXPANDABLE, ScreenMode.FIXED -> headerViewModel?.show()
        }
    }

    override fun pressLongBack() = true

    override fun pressBack(): Boolean = hideOption() || back()

    override fun share() {
        startActivity(
                IntentFactory.makeShare(browserModule.makeShareMessage())
        )
    }

    private fun tapHeader() {
        val activityContext = context ?: return
        val currentTitle = browserModule.currentTitle()
        val currentUrl = browserModule.currentUrl()
        val inputText = if (preferenceApplier.enableSearchQueryExtract) {
            searchQueryExtractor(currentUrl) ?: currentUrl
        } else {
            currentUrl
        }

        val fragment = SearchFragment.makeWithQuery(
                activityContext,
                inputText ?: "",
                currentTitle,
                currentUrl
        )

        activity?.also { activity ->
            ViewModelProviders.of(activity)
                    .get(ContentViewModel::class.java)
                    .nextFragment(fragment)
        }
    }

    /**
     * TODO delete it.
     * Hide option menus.
     */
    private fun hideOption(): Boolean {
        return false
    }

    fun preview(url: String) {
        val webView = browserModule.getWebView(FloatingPreview.getSpecialId())
        if (webView == null) {
            Toaster.snackLong(
                    binding?.root as View,
                    R.string.message_preview_failed,
                    R.string.retry,
                    View.OnClickListener { preview(url) },
                    colorPair()
                    )
            return
        }

        if (floatingPreview == null) {
            floatingPreview = FloatingPreview(requireContext())
        }

        binding?.root?.let {
            floatingPreview?.show(it, webView, url)
        }
    }
    
    private fun colorPair() = preferenceApplier.colorPair()

    override fun onClickUserAgent(userAgent: UserAgent) {
        browserModule.resetUserAgent(userAgent.text())
        Toaster.snackShort(
                binding?.root as View,
                getString(R.string.format_result_user_agent, userAgent.title()),
                colorPair()
        )
    }

    fun stopSwipeRefresherLoading() {
        binding?.swipeRefresher?.isRefreshing = false
    }

    override fun onPause() {
        super.onPause()
        stopSwipeRefresherLoading()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        browserModule.onSaveInstanceState(outState)
    }

    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
        browserModule.onViewStateRestored(savedInstanceState)
    }

    override fun onDestroy() {
        super.onDestroy()
        disposables.clear()
        headerViewModel?.show()
        browserModule.dispose()
    }

    companion object {

        /**
         * Layout ID.
         */
        @LayoutRes
        private const val layoutId = R.layout.fragment_browser

    }

}
<commit_msg>bugfix #523 Disable posting header view content when browser fragment is not visible.<commit_after>package jp.toastkid.yobidashi.browser

import android.app.ActivityOptions
import android.content.Context
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.webkit.ValueCallback
import androidx.annotation.LayoutRes
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.tbruyelle.rxpermissions2.RxPermissions
import io.reactivex.disposables.CompositeDisposable
import jp.toastkid.yobidashi.CommonFragmentAction
import jp.toastkid.yobidashi.R
import jp.toastkid.yobidashi.browser.bookmark.BookmarkInsertion
import jp.toastkid.yobidashi.browser.bookmark.model.Bookmark
import jp.toastkid.yobidashi.browser.floating.FloatingPreview
import jp.toastkid.yobidashi.browser.page_search.PageSearcherViewModel
import jp.toastkid.yobidashi.browser.reader.ReaderFragment
import jp.toastkid.yobidashi.browser.reader.ReaderFragmentViewModel
import jp.toastkid.yobidashi.browser.user_agent.UserAgent
import jp.toastkid.yobidashi.browser.user_agent.UserAgentDialogFragment
import jp.toastkid.yobidashi.databinding.FragmentBrowserBinding
import jp.toastkid.yobidashi.databinding.ModuleBrowserHeaderBinding
import jp.toastkid.yobidashi.libs.ActivityOptionsFactory
import jp.toastkid.yobidashi.libs.Toaster
import jp.toastkid.yobidashi.libs.Urls
import jp.toastkid.yobidashi.libs.intent.IntentFactory
import jp.toastkid.yobidashi.libs.preference.PreferenceApplier
import jp.toastkid.yobidashi.main.ContentScrollable
import jp.toastkid.yobidashi.main.HeaderViewModel
import jp.toastkid.yobidashi.main.MainActivity
import jp.toastkid.yobidashi.main.TabUiFragment
import jp.toastkid.yobidashi.main.content.ContentViewModel
import jp.toastkid.yobidashi.menu.MenuViewModel
import jp.toastkid.yobidashi.rss.extractor.RssUrlFinder
import jp.toastkid.yobidashi.search.SearchFragment
import jp.toastkid.yobidashi.search.SearchQueryExtractor
import jp.toastkid.yobidashi.tab.tab_list.TabListViewModel

/**
 * Internal browser fragment.
 *
 * @author toastkidjp
 */
class BrowserFragment : Fragment(),
        TabUiFragment,
        CommonFragmentAction,
        UserAgentDialogFragment.Callback,
        ContentScrollable
{

    /**
     * RxPermissions.
     */
    private var rxPermissions: RxPermissions? = null

    /**
     * Preferences wrapper.
     */
    private lateinit var preferenceApplier: PreferenceApplier

    /**
     * Browser module.
     */
    private lateinit var browserModule: BrowserModule

    /**
     * Data binding object.
     */
    private var binding: FragmentBrowserBinding? = null

    private var headerBinding: ModuleBrowserHeaderBinding? = null

    private val searchQueryExtractor = SearchQueryExtractor()

    /**
     * Composite disposer.
     */
    private val disposables: CompositeDisposable = CompositeDisposable()

    /**
     * Floating preview object.
     */
    private var floatingPreview: FloatingPreview? = null

    private var menuViewModel: MenuViewModel? = null

    private var headerViewModel: HeaderViewModel? = null

    private var browserViewModel: BrowserViewModel? = null

    private val activityOptionsFactory = ActivityOptionsFactory()

    override fun onAttach(context: Context) {
        super.onAttach(context)
        activity?.let {
            rxPermissions = RxPermissions(it)
        }
    }

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        headerBinding = DataBindingUtil.inflate(inflater, R.layout.module_browser_header, container, false)
        headerBinding?.fragment = this

        binding = DataBindingUtil.inflate(inflater, layoutId, container, false)
        binding?.fragment = this

        binding?.swipeRefresher?.let {
            it.setOnRefreshListener { reload() }
            it.setOnChildScrollUpCallback { _, _ -> browserModule.disablePullToRefresh() }
            it.setDistanceToTriggerSync(8000)
            it.setSlingshotDistance(150)
        }

        val activityContext = context ?: return null

        preferenceApplier = PreferenceApplier(activityContext)

        browserModule = BrowserModule(activityContext, binding?.webViewContainer)

        setHasOptionsMenu(true)

        // TODO use data binding
        headerBinding?.urlBox?.setOnClickListener { tapHeader() }

        browserViewModel = ViewModelProviders.of(this).get(BrowserViewModel::class.java)

        return binding?.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val activity = requireActivity()
        menuViewModel = ViewModelProviders.of(activity)
                .get(MenuViewModel::class.java)

        initializeHeaderViewModels(activity)
    }

    private fun initializeHeaderViewModels(activity: FragmentActivity) {
        val viewModelProvider = ViewModelProviders.of(activity)
        headerViewModel = viewModelProvider.get(HeaderViewModel::class.java)

        headerViewModel?.stopProgress?.observe(activity, Observer { stop ->
            if (!stop || binding?.swipeRefresher?.isRefreshing == false) {
                return@Observer
            }
            stopSwipeRefresherLoading()
        })

        headerViewModel?.progress?.observe(activity, Observer { newProgress ->
            if (70 < newProgress) {
                headerBinding?.progress?.isVisible = false
                headerBinding?.reload?.setImageResource(R.drawable.ic_reload)
                //TODO refreshThumbnail()
                return@Observer
            }
            headerBinding?.progress?.let {
                it.isVisible = true
                it.progress = newProgress
                headerBinding?.reload?.setImageResource(R.drawable.ic_close)
            }
        })

        viewModelProvider.get(BrowserHeaderViewModel::class.java).also { viewModel ->
            viewModel.title.observe(activity, Observer { title ->
                if (title.isNullOrBlank()) {
                    return@Observer
                }
                headerBinding?.mainText?.text = title
            })

            viewModel.url.observe(activity, Observer { url ->
                if (url.isNullOrBlank()) {
                    return@Observer
                }
                headerBinding?.subText?.text = url
            })

            viewModel.reset.observe(activity, Observer {
                if (!isVisible) {
                    return@Observer
                }
                val headerView = headerBinding?.root ?: return@Observer
                headerViewModel?.replace(headerView)
            })

            viewModel.enableForward.observe(activity, Observer {
                updateForwardButtonState(it)
            })
        }

        viewModelProvider.get(LoadingViewModel::class.java)
                .onPageFinished
                .observe(
                        activity,
                        Observer { browserModule.saveArchiveForAutoArchive() }
                )

        viewModelProvider.get(BrowserFragmentViewModel::class.java)
                .loadWithNewTab
                .observe(activity, Observer {
                    browserModule.loadWithNewTab(it.first, it.second)
                })

        viewModelProvider.get(TabListViewModel::class.java)
                .tabCount
                .observe(activity, Observer { headerBinding?.tabCount?.setText(it.toString()) })

        viewModelProvider.get(PageSearcherViewModel::class.java).also { viewModel ->
            viewModel.find.observe(activity, Observer {
                browserModule.find(it)
            })

            viewModel.upward.observe(activity, Observer {
                browserModule.findUp()
            })

            viewModel.downward.observe(activity, Observer {
                browserModule.findDown()
            })
        }
    }

    override fun onCreateOptionsMenu(menu: android.view.Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.browser, menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.add_bookmark -> {
                val context = context ?: return true
                val faviconApplier = FaviconApplier(context)
                val url = browserModule.currentUrl() ?: ""
                BookmarkInsertion(
                        context,
                        browserModule.currentTitle(),
                        url,
                        faviconApplier.makePath(url),
                        Bookmark.getRootFolderName()
                ).insert()

                val parent = binding?.root ?: return true
                Toaster.snackShort(
                        parent,
                        context.getString(R.string.message_done_added_bookmark),
                        colorPair()
                )
                return true
            }
            R.id.add_archive -> {
                browserModule.saveArchive()
                return true
            }
            R.id.add_rss -> {
                RssUrlFinder(preferenceApplier).invoke(browserModule.currentUrl()) { binding?.root }
                return true
            }
            R.id.replace_home -> {
                browserModule.currentUrl()?.let {
                    val parent = binding?.root ?: return true
                    if (Urls.isInvalidUrl(it)) {
                        Toaster.snackShort(
                                parent,
                                R.string.message_cannot_replace_home_url,
                                colorPair()
                        )
                        return true
                    }
                    preferenceApplier.homeUrl = it
                    Toaster.snackShort(
                            parent,
                            getString(R.string.message_replace_home_url, it) ,
                            colorPair()
                    )
                }
            }
        }
        return super.onOptionsItemSelected(item)
    }

    fun reload() {
        if (headerBinding?.progress?.isVisible == true) {
            browserModule.stopLoading()
        } else {
            browserModule.reload()
        }
    }

    fun openReaderMode() {
        browserModule.invokeContentExtraction(ValueCallback(this::showReaderFragment))
    }

    // TODO should implement view model.
    fun tabList() {
        val mainActivity = activity as? MainActivity
        mainActivity?.switchTabList()
    }

    private fun updateForwardButtonState(enable: Boolean) {
        headerBinding?.forward?.isEnabled = enable
        headerBinding?.forward?.alpha = if (enable) 1f else 0.6f
    }

    private fun showReaderFragment(content: String) {
        val lineSeparator = System.getProperty("line.separator") ?: ""
        val replacedContent = content.replace("\\n", lineSeparator)

        ViewModelProviders.of(requireActivity())[ReaderFragmentViewModel::class.java]
                .close.observe(requireActivity(), Observer {
            fragmentManager?.popBackStack()
        })

        val readerFragment =
                fragmentManager?.findFragmentByTag(ReaderFragment::class.java.canonicalName)
                        ?: ReaderFragment.withContent(browserModule.currentTitle(), replacedContent)

        val transaction = fragmentManager?.beginTransaction()
        transaction?.setCustomAnimations(
                R.anim.slide_in_right, 0, 0, android.R.anim.slide_out_right)
        transaction?.replace(R.id.content, readerFragment, readerFragment::class.java.canonicalName)
        transaction?.addToBackStack(readerFragment::class.java.canonicalName)
        transaction?.commit()
    }

    fun showUserAgentSetting() {
        val fragmentManager = fragmentManager ?: return
        val dialogFragment = UserAgentDialogFragment()
        dialogFragment.setTargetFragment(this, 1)
        dialogFragment.show(
                fragmentManager,
                UserAgentDialogFragment::class.java.simpleName
        )
    }

    fun showPageInformation() {
        val fragmentManager = fragmentManager ?: return
        PageInformationDialogFragment()
                .also { it.arguments = browserModule.makeCurrentPageInformation() }
                .show(
                        fragmentManager,
                        PageInformationDialogFragment::class.java.simpleName
                )
    }

    fun showHtmlSource() {
        browserModule.invokeHtmlSourceExtraction(
                ValueCallback {
                    showReaderFragment(it.replace("\\u003C", "<"))
                }
        )
    }

    /**
     * Do browser back action.
     */
    fun back(): Boolean = browserModule.back()

    /**
     * Do browser forward action.
     */
    fun forward() = browserModule.forward()

    /**
     * TODO implement ViewModel.
     */
    fun search() {
        search(activityOptionsFactory.makeScaleUpBundle(binding?.root as View))
    }

    /**
     * Show search activity.
     *
     * @param option [ActivityOptions]
     */
    private fun search(option: ActivityOptions) {
        context?.let {
            val currentTitle = browserModule.currentTitle()
            val currentUrl = browserModule.currentUrl()
            val query = searchQueryExtractor.invoke(currentUrl)
            val makeIntent = if (TextUtils.isEmpty(query) || Urls.isValidUrl(query)) {
                SearchFragment.makeWith(it, currentTitle, currentUrl)
            } else {
                SearchFragment.makeWithQuery(it, query ?: "", currentTitle, currentUrl)
            }
            activity?.also { activity ->
                ViewModelProviders.of(activity)
                        .get(ContentViewModel::class.java)
                        .nextFragment(makeIntent)
            }
        }
    }

    /**
     * Go to bottom.
     */
    override fun toBottom() {
        browserModule.pageDown()
    }

    /**
     * Go to top.
     */
    override fun toTop() {
        browserModule.pageUp()
    }

    override fun onResume() {
        super.onResume()

        switchToolbarVisibility()

        val colorPair = colorPair()

        val fontColor = colorPair.fontColor()

        headerBinding?.also {
            it.icon.setColorFilter(fontColor)
            it.mainText.setTextColor(fontColor)
            it.subText.setTextColor(fontColor)
            it.reload.setColorFilter(fontColor)
            it.back.setColorFilter(fontColor)
            it.forward.setColorFilter(fontColor)
            it.tabIcon.setColorFilter(fontColor)
            it.tabCount.setTextColor(fontColor)
            it.pageInformation.setColorFilter(fontColor)
            it.userAgent.setColorFilter(fontColor)
            it.htmlSource.setColorFilter(fontColor)
        }

        browserModule.resizePool(preferenceApplier.poolSize)
        browserModule.applyNewAlpha()

        binding?.swipeRefresher?.let {
            it.setProgressBackgroundColorSchemeColor(preferenceApplier.color)
            it.setColorSchemeColors(preferenceApplier.fontColor)
        }
    }

    private fun switchToolbarVisibility() {
        when (preferenceApplier.browserScreenMode()) {
            ScreenMode.FULL_SCREEN -> headerViewModel?.hide()
            ScreenMode.EXPANDABLE, ScreenMode.FIXED -> headerViewModel?.show()
        }
    }

    override fun pressLongBack() = true

    override fun pressBack(): Boolean = hideOption() || back()

    override fun share() {
        startActivity(
                IntentFactory.makeShare(browserModule.makeShareMessage())
        )
    }

    private fun tapHeader() {
        val activityContext = context ?: return
        val currentTitle = browserModule.currentTitle()
        val currentUrl = browserModule.currentUrl()
        val inputText = if (preferenceApplier.enableSearchQueryExtract) {
            searchQueryExtractor(currentUrl) ?: currentUrl
        } else {
            currentUrl
        }

        val fragment = SearchFragment.makeWithQuery(
                activityContext,
                inputText ?: "",
                currentTitle,
                currentUrl
        )

        activity?.also { activity ->
            ViewModelProviders.of(activity)
                    .get(ContentViewModel::class.java)
                    .nextFragment(fragment)
        }
    }

    /**
     * TODO delete it.
     * Hide option menus.
     */
    private fun hideOption(): Boolean {
        return false
    }

    fun preview(url: String) {
        val webView = browserModule.getWebView(FloatingPreview.getSpecialId())
        if (webView == null) {
            Toaster.snackLong(
                    binding?.root as View,
                    R.string.message_preview_failed,
                    R.string.retry,
                    View.OnClickListener { preview(url) },
                    colorPair()
                    )
            return
        }

        if (floatingPreview == null) {
            floatingPreview = FloatingPreview(requireContext())
        }

        binding?.root?.let {
            floatingPreview?.show(it, webView, url)
        }
    }
    
    private fun colorPair() = preferenceApplier.colorPair()

    override fun onClickUserAgent(userAgent: UserAgent) {
        browserModule.resetUserAgent(userAgent.text())
        Toaster.snackShort(
                binding?.root as View,
                getString(R.string.format_result_user_agent, userAgent.title()),
                colorPair()
        )
    }

    fun stopSwipeRefresherLoading() {
        binding?.swipeRefresher?.isRefreshing = false
    }

    override fun onPause() {
        super.onPause()
        stopSwipeRefresherLoading()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        browserModule.onSaveInstanceState(outState)
    }

    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
        browserModule.onViewStateRestored(savedInstanceState)
    }

    override fun onDestroy() {
        super.onDestroy()
        disposables.clear()
        headerViewModel?.show()
        browserModule.dispose()
    }

    companion object {

        /**
         * Layout ID.
         */
        @LayoutRes
        private const val layoutId = R.layout.fragment_browser

    }

}

{'fraction_non_alphanumeric': 0.0857325315310521, 'fraction_numerical': 0.0010909477941567771, 'mean_word_length': 2.044390441474281, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1564941', 'n_tokens_mistral': 10043, 'n_tokens_neox': 9379, 'n_words': 1575}