QueryLogListFragment.kt 7.74 KB
Newer Older
1
2
3
package com.frostnerd.smokescreen.fragment.querylogfragment

import android.os.Bundle
Daniel Wolf's avatar
Daniel Wolf committed
4
5
6
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
7
import android.widget.TextView
8
import androidx.appcompat.widget.SearchView
9
10
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
11
12
import androidx.recyclerview.widget.RecyclerView
import com.frostnerd.cacheadapter.DefaultViewHolder
13
import com.frostnerd.cacheadapter.ModelAdapterBuilder
14
import com.frostnerd.dnstunnelproxy.QueryListener
15
import com.frostnerd.lifecyclemanagement.launchWithLifecycle
Daniel Wolf's avatar
Daniel Wolf committed
16
import com.frostnerd.lifecyclemanagement.launchWithLifecycleUi
17
import com.frostnerd.smokescreen.BackpressFragment
18
19
20
import com.frostnerd.smokescreen.R
import com.frostnerd.smokescreen.database.entities.DnsQuery
import com.frostnerd.smokescreen.database.getDatabase
Daniel Wolf's avatar
Daniel Wolf committed
21
import com.frostnerd.smokescreen.dialog.QueryLogFilterDialog
22
23
24
import com.frostnerd.smokescreen.fragment.QueryLogFragment
import com.frostnerd.smokescreen.util.LiveDataSource
import kotlinx.android.synthetic.main.fragment_querylog_list.*
Daniel Wolf's avatar
Daniel Wolf committed
25
import kotlinx.android.synthetic.main.fragment_querylog_list.view.*
26
import kotlinx.android.synthetic.main.item_logged_query.view.*
27

Daniel Wolf's avatar
Daniel Wolf committed
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
 * Copyright (C) 2019 Daniel Wolf (Ch4t4r)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You can contact the developer at daniel.wolf@frostnerd.com.
45
 */
46
47
class QueryLogListFragment: Fragment(), SearchView.OnQueryTextListener, BackpressFragment,
    SearchView.OnCloseListener {
48
49
    private lateinit var unfilteredAdapter:RecyclerView.Adapter<*>
    private var currentSearchText:String? = null
Daniel Wolf's avatar
Daniel Wolf committed
50
51
    private var filterConfig = QueryLogFilterDialog.FilterConfig.showAllConfig
    private var shortenDomains:Boolean = false
52

53
54
55
56
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return layoutInflater.inflate(R.layout.fragment_querylog_list, container, false)
    }

57
58
59
60
61
62
63
64
    override fun onBackPressed(): Boolean {
        return if(filterConfig != QueryLogFilterDialog.FilterConfig.showAllConfig) {
            clearFilter()
            updateAdapter()
            true
        } else false
    }

65
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Daniel Wolf's avatar
Daniel Wolf committed
66
        launchWithLifecycle {
67
68
            val live = requireContext().getDatabase().dnsQueryDao().getAllLive()
            unfilteredAdapter = createAdapter(LiveDataSource(this@QueryLogListFragment, live, true))
69

Daniel Wolf's avatar
Daniel Wolf committed
70
            launchWithLifecycleUi {
71
72
73
74
75
                list.layoutManager = LinearLayoutManager(requireContext())
                list.adapter = unfilteredAdapter
                progress.visibility = View.GONE
            }
        }
Daniel Wolf's avatar
Daniel Wolf committed
76
77
78
79
80
81
82
83
84
85
        view.filter.setOnClickListener {
            QueryLogFilterDialog(requireContext(), filterConfig, shortenDomains) { config, shortenDomain ->
                val filterChanged = config != filterConfig
                val shortenDomainsChanged = shortenDomains != shortenDomain
                filterConfig = config
                shortenDomains = shortenDomain
                if(filterChanged) updateAdapter()
                else if(shortenDomainsChanged) list.adapter?.notifyDataSetChanged()
            }.show()
        }
86
87
    }

Daniel Wolf's avatar
Daniel Wolf committed
88
89
    private fun createAdapter(forSource:LiveDataSource<DnsQuery>): RecyclerView.Adapter<DefaultViewHolder> {
        return ModelAdapterBuilder.newBuilder(forSource) {
Daniel Wolf's avatar
Daniel Wolf committed
90
            viewBuilder = { parent, _ ->
91
92
93
94
95
96
97
                val createdView = layoutInflater.inflate(R.layout.item_logged_query, parent, false)
                createdView.setOnClickListener {
                    displayQuery(it.tag as DnsQuery)
                }
                createdView
            }
            getItemCount = {
Daniel Wolf's avatar
Daniel Wolf committed
98
                forSource.currentSize()
99
            }
Daniel Wolf's avatar
Daniel Wolf committed
100
            bindModelView = { viewHolder, _, data ->
Daniel Wolf's avatar
Daniel Wolf committed
101
                viewHolder.itemView.findViewById<TextView>(R.id.text).text = if(shortenDomains) data.shortName else data.name
102
                viewHolder.itemView.tag = data
103
104
                viewHolder.itemView.typeImage.setImageResource(
                    when (data.responseSource) {
105
                        QueryListener.Source.UPSTREAM ->
Daniel Wolf's avatar
Daniel Wolf committed
106
                            if (data.isHostBlockedByDnsServer) R.drawable.ic_flag
107
108
109
110
                            else R.drawable.ic_reply
                        QueryListener.Source.CACHE, QueryListener.Source.CACHE_AND_LOCALRESOLVER -> R.drawable.ic_database
                        QueryListener.Source.LOCALRESOLVER -> R.drawable.ic_flag
                        else -> R.drawable.ic_query_question
111
112
113
                    }
                )
                if (isDisplayingQuery(data)) displayQuery(data, false)
114
            }
Daniel Wolf's avatar
Daniel Wolf committed
115
            bindNonModelView = { _, _ ->
116
117
118
119
120
121
122
123

            }
            runOnUiThread = {
                requireActivity().runOnUiThread(it)
            }
        }.build()
    }

Daniel Wolf's avatar
Daniel Wolf committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
    private fun createUpdatedAdapter():RecyclerView.Adapter<*> {
        return if(currentSearchText.isNullOrBlank() && filterConfig.showAll) {
            unfilteredAdapter
        } else {
            val liveData = when {
                currentSearchText.isNullOrBlank() -> requireContext().getDatabase().dnsQueryRepository().getAllWithFilterLive(filterConfig)
                filterConfig.showAll -> requireContext().getDatabase().dnsQueryDao().getAllWithHostLive(currentSearchText!!)
                else -> requireContext().getDatabase().dnsQueryRepository().getAllWithHostAndFilterLive(currentSearchText!!, filterConfig)
            }
            val source = LiveDataSource(this, liveData, true)
            createAdapter(source)
        }
    }

    private fun updateAdapter() {
Daniel Wolf's avatar
Daniel Wolf committed
139
        launchWithLifecycleUi {
Daniel Wolf's avatar
Daniel Wolf committed
140
            view?.progress?.visibility = View.VISIBLE
Daniel Wolf's avatar
Daniel Wolf committed
141
            launchWithLifecycle {
Daniel Wolf's avatar
Daniel Wolf committed
142
                val updatedAdapter = createUpdatedAdapter()
Daniel Wolf's avatar
Daniel Wolf committed
143
                launchWithLifecycleUi {
Daniel Wolf's avatar
Daniel Wolf committed
144
145
146
147
148
149
150
                    view?.progress?.visibility = View.GONE
                    list.adapter = updatedAdapter
                }
            }
        }
    }

151
152
153
154
155
156
157
158
159
    private fun displayQuery(dnsQuery: DnsQuery, switchToDetailView:Boolean = true) {
        (parentFragment as QueryLogFragment).displayQueryDetailed(dnsQuery, switchToDetailView)
    }

    private fun isDisplayingQuery(dnsQuery: DnsQuery):Boolean {
        val parent = parentFragment as QueryLogFragment
        if(!parent.detailFragment.isShowingQuery()) return false
        return parent.detailFragment.currentQuery?.id == dnsQuery.id
    }
160

161
162
163
164
    private fun clearFilter() {
        filterConfig = QueryLogFilterDialog.FilterConfig.showAllConfig
    }

165
    override fun onQueryTextSubmit(query: String?): Boolean {
Daniel Wolf's avatar
Daniel Wolf committed
166
167
168
        if(!query.isNullOrBlank() && query.trim() != currentSearchText) {
            currentSearchText = query.trim()
            updateAdapter()
169
170
        } else if(query.isNullOrBlank() && !currentSearchText.isNullOrBlank()) {
            currentSearchText = null
Daniel Wolf's avatar
Daniel Wolf committed
171
            updateAdapter()
172
173
174
175
        }
        return true
    }

176
177
178
179
180
    override fun onClose(): Boolean {
        clearFilter()
        return false
    }

181
182
183
184
    override fun onQueryTextChange(newText: String?): Boolean {
        if(newText.isNullOrBlank() && !currentSearchText.isNullOrBlank()) onQueryTextSubmit(newText)
        return false
    }
185
}