EditBookmarkFragment.kt 9.57 KB
Newer Older
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
6

package org.mozilla.fenix.library.bookmarks.edit

7
import android.content.DialogInterface
8
9
10
11
12
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
13
import androidx.appcompat.app.AlertDialog
14
import androidx.appcompat.app.AppCompatActivity
15
import androidx.appcompat.widget.Toolbar
16
import androidx.fragment.app.Fragment
17
import androidx.fragment.app.activityViewModels
18
import androidx.lifecycle.ViewModelProvider
19
import androidx.lifecycle.lifecycleScope
20
21
22
23
24
25
26
27
28
29
30
31
import androidx.navigation.Navigation
import com.jakewharton.rxbinding3.widget.textChanges
import com.uber.autodispose.AutoDispose
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.fragment_edit_bookmark.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
32
import kotlinx.coroutines.withContext
33
34
35
36
import mozilla.appservices.places.UrlParseFailed
import mozilla.components.concept.storage.BookmarkInfo
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
37
import mozilla.components.support.ktx.android.content.getColorFromAttr
38
import mozilla.components.support.ktx.android.view.hideKeyboard
39
import org.mozilla.fenix.R
40
import org.mozilla.fenix.components.FenixSnackbar
Colin Lee's avatar
Colin Lee committed
41
import org.mozilla.fenix.components.metrics.Event
42
import org.mozilla.fenix.ext.components
43
import org.mozilla.fenix.ext.getRootView
44
import org.mozilla.fenix.ext.nav
45
import org.mozilla.fenix.ext.requireComponents
46
import org.mozilla.fenix.ext.setToolbarColors
Severin Rudie's avatar
Severin Rudie committed
47
import org.mozilla.fenix.ext.toShortUrl
48
import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
49
import org.mozilla.fenix.library.bookmarks.DesktopFolders
50
51
import java.util.concurrent.TimeUnit

Tiger Oakes's avatar
Tiger Oakes committed
52
53
54
55
/**
 * Menu to edit the name, URL, and location of a bookmark item.
 */
class EditBookmarkFragment : Fragment(R.layout.fragment_edit_bookmark) {
56
57

    private lateinit var guidToEdit: String
58
59
60
    private val sharedViewModel: BookmarksSharedViewModel by activityViewModels {
        ViewModelProvider.NewInstanceFactory() // this is a workaround for #4652
    }
61
62
63
64
65
66
67
68
69
70
    private var bookmarkNode: BookmarkNode? = null
    private var bookmarkParent: BookmarkNode? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)
    }

    override fun onResume() {
        super.onResume()
71

72
        initToolbar()
73
74

        guidToEdit = EditBookmarkFragmentArgs.fromBundle(arguments!!).guidToEdit
75
        lifecycleScope.launch(Main) {
76
            val context = requireContext()
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

            withContext(IO) {
                val bookmarksStorage = context.components.core.bookmarksStorage
                bookmarkNode = bookmarksStorage.getTree(guidToEdit)
                bookmarkParent = sharedViewModel.selectedFolder
                    ?: bookmarkNode?.parentGuid
                        ?.let { bookmarksStorage.getTree(it) }
                        ?.let { DesktopFolders(context, showMobileRoot = true).withRootTitle(it) }
            }

            when (bookmarkNode?.type) {
                BookmarkNodeType.FOLDER -> {
                    activity?.title = getString(R.string.edit_bookmark_folder_fragment_title)
                    bookmarkUrlEdit.visibility = View.GONE
                    bookmarkUrlLabel.visibility = View.GONE
                }
                BookmarkNodeType.ITEM -> {
                    activity?.title = getString(R.string.edit_bookmark_fragment_title)
95
                }
96
97
                else -> throw IllegalArgumentException()
            }
Colin Lee's avatar
Colin Lee committed
98

99
100
101
            bookmarkNode?.let { bookmarkNode ->
                bookmarkNameEdit.setText(bookmarkNode.title)
                bookmarkUrlEdit.setText(bookmarkNode.url)
Colin Lee's avatar
Colin Lee committed
102

103
                if (sharedViewModel.selectedFolder != null && bookmarkNode.title != null) {
104
                    updateBookmarkNode(bookmarkNode.title, bookmarkNode.url)
Colin Lee's avatar
Colin Lee committed
105
                }
106
107
108
            }

            bookmarkParent?.let { node ->
109
110
                    bookmarkParentFolderSelector.text = node.title
                    bookmarkParentFolderSelector.setOnClickListener {
111
                        sharedViewModel.selectedFolder = null
112
113
                        nav(
                            R.id.bookmarkEditFragment,
114
115
116
117
118
119
120
121
122
123
                            EditBookmarkFragmentDirections
                                .actionBookmarkEditFragmentToBookmarkSelectFolderFragment(null)
                        )
                    }
                }
            }

        updateBookmarkFromObservableInput()
    }

124
125
126
127
128
129
130
131
132
133
134
135
    private fun initToolbar() {
        val activity = activity as? AppCompatActivity
        val toolbar = activity?.findViewById<Toolbar>(R.id.navigationToolbar)
        context?.let {
            toolbar?.setToolbarColors(
                foreground = it.getColorFromAttr(R.attr.primaryText),
                background = it.getColorFromAttr(R.attr.foundation)
            )
        }
        activity?.supportActionBar?.show()
    }

136
137
    override fun onPause() {
        super.onPause()
138
139
        bookmarkNameEdit.hideKeyboard()
        bookmarkUrlEdit.hideKeyboard()
140
141
    }

142
143
    private fun updateBookmarkFromObservableInput() {
        Observable.combineLatest(
144
145
            bookmarkNameEdit.textChanges().skipInitialValue(),
            bookmarkUrlEdit.textChanges().skipInitialValue(),
146
            BiFunction { name: CharSequence, url: CharSequence ->
147
                Pair(name.toString(), url.toString())
148
            })
149
            .filter { (name) -> name.isNotBlank() }
150
151
152
153
            .debounce(debouncePeriodInMs, TimeUnit.MILLISECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .`as`(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this@EditBookmarkFragment)))
154
155
            .subscribe { (name, url) ->
                updateBookmarkNode(name, url)
156
157
158
159
160
161
162
163
164
165
            }
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.bookmarks_edit, menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.delete_bookmark_button -> {
166
                displayDeleteBookmarkDialog()
167
168
169
170
171
172
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }

173
174
175
176
177
178
179
180
    private fun displayDeleteBookmarkDialog() {
        activity?.let { activity ->
            AlertDialog.Builder(activity).apply {
                setMessage(R.string.bookmark_deletion_confirmation)
                setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _ ->
                    dialog.cancel()
                }
                setPositiveButton(R.string.tab_collection_dialog_positive) { dialog: DialogInterface, _ ->
181
                    lifecycleScope.launch(IO) {
182
183
                        requireComponents.core.bookmarksStorage.deleteNode(guidToEdit)
                        requireComponents.analytics.metrics.track(Event.RemoveBookmark)
184

185
                        launch(Main) {
186
187
                            Navigation.findNavController(requireActivity(), R.id.container)
                                .popBackStack()
188
189

                            bookmarkNode?.let { bookmark ->
190
191
192
193
194
195
                                FenixSnackbar.makeWithToolbarPadding(activity.getRootView()!!)
                                    .setText(
                                        getString(R.string.bookmark_deletion_snackbar_message,
                                            bookmark.url?.toShortUrl(context.components.publicSuffixList)
                                                ?: bookmark.title
                                        )
196
                                    )
197
                                    .show()
198
                            }
199
200
201
202
203
204
205
206
207
                        }
                    }
                    dialog.dismiss()
                }
                create()
            }.show()
        }
    }

208
    private fun updateBookmarkNode(title: String?, url: String?) {
209
        lifecycleScope.launch(IO) {
Colin Lee's avatar
Colin Lee committed
210
            try {
211
212
213
                requireComponents.let { components ->
                    if (title != bookmarkNode?.title || url != bookmarkNode?.url) {
                        components.analytics.metrics.track(Event.EditedBookmark)
Colin Lee's avatar
Colin Lee committed
214
215
                    }
                    if (sharedViewModel.selectedFolder != null) {
216
                        components.analytics.metrics.track(Event.MovedBookmark)
Colin Lee's avatar
Colin Lee committed
217
                    }
218
                    components.core.bookmarksStorage.updateNode(
Colin Lee's avatar
Colin Lee committed
219
220
221
222
                        guidToEdit,
                        BookmarkInfo(
                            sharedViewModel.selectedFolder?.guid ?: bookmarkNode!!.parentGuid,
                            bookmarkNode?.position,
223
224
                            title,
                            if (bookmarkNode?.type == BookmarkNodeType.ITEM) url else null
Colin Lee's avatar
Colin Lee committed
225
226
227
228
                        )
                    )
                }
            } catch (e: UrlParseFailed) {
229
                withContext(Main) {
230
                    bookmarkUrlEdit.error = getString(R.string.bookmark_invalid_url_error)
231
232
                }
            }
233
234
235
236
237
238
239
        }
    }

    companion object {
        private const val debouncePeriodInMs = 500L
    }
}