/* This Source Code Form is subject to the terms of the Mozilla Public 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/. */ package org.mozilla.fenix.library.bookmarks.edit import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.os.Bundle import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders 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.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job import kotlinx.coroutines.launch import mozilla.appservices.places.UrlParseFailed import mozilla.components.concept.storage.BookmarkInfo import mozilla.components.concept.storage.BookmarkNode import mozilla.components.concept.storage.BookmarkNodeType import org.mozilla.fenix.R import org.mozilla.fenix.ext.getColorFromAttr import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel import java.lang.IllegalArgumentException import java.util.concurrent.TimeUnit import kotlin.coroutines.CoroutineContext class EditBookmarkFragment : Fragment(), CoroutineScope { private lateinit var sharedViewModel: BookmarksSharedViewModel private lateinit var job: Job private lateinit var guidToEdit: String private var bookmarkNode: BookmarkNode? = null private var bookmarkParent: BookmarkNode? = null override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job() setHasOptionsMenu(true) sharedViewModel = activity?.run { ViewModelProviders.of(this).get(BookmarksSharedViewModel::class.java) }!! } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_edit_bookmark, container, false) } override fun onResume() { super.onResume() (activity as? AppCompatActivity)?.title = getString(R.string.edit_bookmark_fragment_title) (activity as? AppCompatActivity)?.supportActionBar?.show() guidToEdit = EditBookmarkFragmentArgs.fromBundle(arguments!!).guidToEdit launch(IO) { bookmarkNode = requireComponents.core.bookmarksStorage.getTree(guidToEdit) bookmarkParent = sharedViewModel.selectedFolder ?: bookmarkNode?.parentGuid?.let { requireComponents.core.bookmarksStorage.getTree(it) } launch(Main) { when (bookmarkNode?.type) { BookmarkNodeType.FOLDER -> { bookmark_url_edit.visibility = View.GONE bookmark_url_label.visibility = View.GONE } BookmarkNodeType.ITEM -> {} BookmarkNodeType.SEPARATOR -> {} else -> throw IllegalArgumentException() } bookmark_name_edit.setText(bookmarkNode!!.title) bookmark_url_edit.setText(bookmarkNode!!.url) } bookmarkParent?.let { node -> launch(Main) { bookmark_folder_selector.text = node.title bookmark_folder_selector.setOnClickListener { sharedViewModel.selectedFolder = null Navigation.findNavController(requireActivity(), R.id.container).navigate( EditBookmarkFragmentDirections .actionBookmarkEditFragmentToBookmarkSelectFolderFragment(null) ) } } } } updateBookmarkFromObservableInput() } override fun onPause() { updateBookmarkNode(Pair(bookmark_name_edit.text, bookmark_url_edit.text)) super.onPause() } private fun updateBookmarkFromObservableInput() { Observable.combineLatest( bookmark_name_edit.textChanges().skipInitialValue(), bookmark_url_edit.textChanges().skipInitialValue(), BiFunction { name: CharSequence, url: CharSequence -> Pair(name, url) }) .filter { it.first.isNotBlank() && it.second.isNotBlank() } .debounce(debouncePeriodInMs, TimeUnit.MILLISECONDS) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .`as`(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this@EditBookmarkFragment))) .subscribe { try { bookmark_url_edit.error = null updateBookmarkNode(it) } catch (e: UrlParseFailed) { bookmark_url_edit.error = getString(R.string.bookmark_invalid_url_error) } } } override fun onDestroy() { super.onDestroy() job.cancel() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.bookmarks_edit, menu) menu.findItem(R.id.delete_bookmark_button).icon.colorFilter = PorterDuffColorFilter(R.attr.primaryText.getColorFromAttr(context!!), PorterDuff.Mode.SRC_IN) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.delete_bookmark_button -> { launch(IO) { requireComponents.core.bookmarksStorage.deleteNode(guidToEdit) launch(Main) { Navigation.findNavController(requireActivity(), R.id.container).popBackStack() } } true } else -> super.onOptionsItemSelected(item) } } private fun updateBookmarkNode(pair: Pair) { launch(IO) { requireComponents.core.bookmarksStorage.updateNode( guidToEdit, BookmarkInfo( sharedViewModel.selectedFolder?.guid ?: bookmarkNode!!.parentGuid, bookmarkNode!!.position, pair.first.toString(), if (bookmarkNode?.type == BookmarkNodeType.ITEM) pair.second.toString() else null ) ) } } companion object { private const val debouncePeriodInMs = 500L } }