QueryLogListFragment.kt 7.27 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
16
17
18
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
19
import com.frostnerd.smokescreen.dialog.QueryLogFilterDialog
20
21
22
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
23
import kotlinx.android.synthetic.main.fragment_querylog_list.view.*
24
import kotlinx.android.synthetic.main.item_logged_query.view.*
25

Daniel Wolf's avatar
Daniel Wolf committed
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
 * 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.
43
 */
44
45
46
class QueryLogListFragment: Fragment(), SearchView.OnQueryTextListener {
    private lateinit var unfilteredAdapter:RecyclerView.Adapter<*>
    private var currentSearchText:String? = null
Daniel Wolf's avatar
Daniel Wolf committed
47
48
    private var filterConfig = QueryLogFilterDialog.FilterConfig.showAllConfig
    private var shortenDomains:Boolean = false
49

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55
56
57
        launchWithLifecycle(false) {
            val live = requireContext().getDatabase().dnsQueryDao().getAllLive()
            unfilteredAdapter = createAdapter(LiveDataSource(this@QueryLogListFragment, live, true))
58

59
60
61
62
63
64
            launchWithLifecycle(true) {
                list.layoutManager = LinearLayoutManager(requireContext())
                list.adapter = unfilteredAdapter
                progress.visibility = View.GONE
            }
        }
Daniel Wolf's avatar
Daniel Wolf committed
65
66
67
68
69
70
71
72
73
74
        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()
        }
75
76
    }

Daniel Wolf's avatar
Daniel Wolf committed
77
78
    private fun createAdapter(forSource:LiveDataSource<DnsQuery>): RecyclerView.Adapter<DefaultViewHolder> {
        return ModelAdapterBuilder.newBuilder(forSource) {
79
80
81
82
83
84
85
86
            viewBuilder = { parent, viewType ->
                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
87
                forSource.currentSize()
88
89
            }
            bindModelView = { viewHolder, position, data ->
Daniel Wolf's avatar
Daniel Wolf committed
90
                viewHolder.itemView.findViewById<TextView>(R.id.text).text = if(shortenDomains) data.shortName else data.name
91
                viewHolder.itemView.tag = data
92
93
                viewHolder.itemView.typeImage.setImageResource(
                    when (data.responseSource) {
94
                        QueryListener.Source.UPSTREAM ->
Daniel Wolf's avatar
Daniel Wolf committed
95
                            if (data.isHostBlockedByDnsServer) R.drawable.ic_flag
96
97
98
99
                            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
100
101
102
                    }
                )
                if (isDisplayingQuery(data)) displayQuery(data, false)
103
104
105
106
107
108
109
110
111
112
            }
            bindNonModelView = { viewHolder, position ->

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

Daniel Wolf's avatar
Daniel Wolf committed
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
    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() {
        launchWithLifecycle(true) {
            view?.progress?.visibility = View.VISIBLE
            launchWithLifecycle(ui = false) {
                val updatedAdapter = createUpdatedAdapter()
                println("Created new adapter!")
                launchWithLifecycle(true) {
                    view?.progress?.visibility = View.GONE
                    list.adapter = updatedAdapter
                }
            }
        }
    }

141
142
143
144
145
146
147
148
149
    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
    }
150
151

    override fun onQueryTextSubmit(query: String?): Boolean {
Daniel Wolf's avatar
Daniel Wolf committed
152
153
154
        if(!query.isNullOrBlank() && query.trim() != currentSearchText) {
            currentSearchText = query.trim()
            updateAdapter()
155
156
        } else if(query.isNullOrBlank() && !currentSearchText.isNullOrBlank()) {
            currentSearchText = null
Daniel Wolf's avatar
Daniel Wolf committed
157
            updateAdapter()
158
159
160
161
162
163
164
165
        }
        return true
    }

    override fun onQueryTextChange(newText: String?): Boolean {
        if(newText.isNullOrBlank() && !currentSearchText.isNullOrBlank()) onQueryTextSubmit(newText)
        return false
    }
166
}