From 51b8fbf6427b6007188a1a9a2d1e5abd3ba7df59 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 14 Sep 2020 21:25:37 -0400 Subject: [PATCH 001/161] Add named arguments --- .../browser/lightning/search/SuggestionsAdapter.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt index d68f209c7..bc288663b 100644 --- a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt @@ -192,15 +192,15 @@ class SuggestionsAdapter( bookmarksEntries .join( - historyEntries, - { bookmarksEntries }, - { historyEntries } + other = historyEntries, + selectorLeft = { bookmarksEntries }, + selectorRight = { historyEntries } ) { t1, t2 -> Pair(t1, t2) } .compose { bookmarksAndHistory -> bookmarksAndHistory.join( - searchEntries, - { bookmarksAndHistory }, - { searchEntries } + other = searchEntries, + selectorLeft = { bookmarksAndHistory }, + selectorRight = { searchEntries } ) { (bookmarks, history), t2 -> Triple(bookmarks, history, t2) } From 08f4251be8c920fd316b0d053955ea46e70e02a3 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 14 Sep 2020 21:26:24 -0400 Subject: [PATCH 002/161] Initial browser2 rewrite First stage of the browser2 rewrite. partially functional with search, tab and bookmark navigation. --- app/build.gradle | 4 + app/src/main/AndroidManifest.xml | 2 +- .../lightning/_browser2/BrowserActivity.kt | 145 ++++++ .../lightning/_browser2/BrowserContract.kt | 38 ++ .../lightning/_browser2/BrowserPresenter.kt | 325 ++++++++++++++ .../lightning/_browser2/BrowserViewState.kt | 25 ++ .../bookmark/BookmarkRecyclerViewAdapter.kt | 43 ++ .../_browser2/bookmark/BookmarkViewHolder.kt | 31 ++ .../_browser2/search/SearchListener.kt | 40 ++ .../browser/lightning/_browser2/tab/Tab.kt | 69 +++ .../lightning/_browser2/tab/TabAdapter.kt | 106 +++++ .../lightning/_browser2/tab/TabPager.kt | 33 ++ .../_browser2/tab/TabRecyclerViewAdapter.kt | 73 +++ .../lightning/_browser2/tab/TabViewHolder.kt | 45 ++ .../_browser2/tab/TabWebChromeClient.kt | 24 + .../_browser2/tab/TabWebViewClient.kt | 48 ++ .../lightning/_browser2/tab/TabsRepository.kt | 63 +++ .../lightning/_browser2/tab/WebViewFactory.kt | 179 ++++++++ app/src/main/res/layout/browser_activity.xml | 415 ++++++++++++++++++ 19 files changed, 1707 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkRecyclerViewAdapter.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkViewHolder.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/TabViewHolder.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt create mode 100644 app/src/main/res/layout/browser_activity.xml diff --git a/app/build.gradle b/app/build.gradle index 0be4b114d..3374d562b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,6 +22,10 @@ android { lightningLite.setRoot('src/LightningLite') } + buildFeatures { + viewBinding true + } + buildTypes { debug { multiDexEnabled true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 07f408930..6123a3040 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -48,7 +48,7 @@ android:value="2.1"/> + presenter.onSearchSuggestionClicked(suggestionsAdapter.getItem(position) as WebPage) + } + binding.search.setAdapter(suggestionsAdapter) + val searchListener = SearchListener(onConfirm = { + presenter.onSearch(binding.search.text.toString()) + }) + binding.search.setOnEditorActionListener(searchListener) + binding.search.setOnKeyListener(searchListener) + + binding.actionBack.setOnClickListener { presenter.onBackClick() } + binding.actionForward.setOnClickListener { presenter.onForwardClick() } + binding.actionHome.setOnClickListener { presenter.onHomeClick() } + binding.newTabButton.setOnClickListener { presenter.onNewTabClick() } + binding.searchRefresh.setOnClickListener { presenter.onRefreshOrStopClick() } + } + + override fun onDestroy() { + super.onDestroy() + presenter.onViewDetached() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.main, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun renderState(viewState: BrowserViewState) { + binding.actionBack.isEnabled = viewState.isBackEnabled + binding.actionForward.isEnabled = viewState.isForwardEnabled + binding.search.setText(viewState.displayUrl) + binding.searchSslStatus.setImageDrawable(createSslDrawableForState(viewState.sslState)) + binding.searchSslStatus.updateVisibilityForDrawable() + binding.tabCountView.updateCount(viewState.tabs.size) + binding.progressView.progress = viewState.progress + binding.searchRefresh.setImageResource(if (viewState.isRefresh) { + R.drawable.ic_action_refresh + } else { + R.drawable.ic_action_delete + }) + tabsAdapter.submitList(viewState.tabs) + bookmarksAdapter.submitList(viewState.bookmarks) + } + + private fun ImageView.updateVisibilityForDrawable() { + if (drawable == null) { + visibility = View.GONE + } else { + visibility = View.VISIBLE + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt new file mode 100644 index 000000000..32b74a34e --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -0,0 +1,38 @@ +package acr.browser.lightning._browser2 + +import acr.browser.lightning._browser2.tab.TabModel +import io.reactivex.Completable +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single + +/** + * Created by anthonycr on 9/11/20. + */ + + +interface BrowserContract { + + interface View { + + fun renderState(viewState: BrowserViewState) + + } + + interface Model { + + fun deleteTab(id: Int): Completable + + fun createTab(initialUrl: String?): Single + + fun selectTab(id: Int): TabModel + + fun initializeTabs(): Maybe> + + val tabsList: List + + fun tabsListChanges(): Observable> + + } + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt new file mode 100644 index 000000000..1b37cb7c4 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -0,0 +1,325 @@ +package acr.browser.lightning._browser2 + +import acr.browser.lightning._browser2.tab.Tab +import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning.database.Bookmark +import acr.browser.lightning.database.HistoryEntry +import acr.browser.lightning.database.SearchSuggestion +import acr.browser.lightning.database.WebPage +import acr.browser.lightning.database.bookmark.BookmarkRepository +import acr.browser.lightning.di.DatabaseScheduler +import acr.browser.lightning.di.MainScheduler +import acr.browser.lightning.ssl.SslState +import acr.browser.lightning.utils.smartUrlFilter +import io.reactivex.Scheduler +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable +import io.reactivex.rxkotlin.plusAssign + +/** + * Created by anthonycr on 9/11/20. + */ +class BrowserPresenter( + private val model: BrowserContract.Model, + private val bookmarkRepository: BookmarkRepository, + @MainScheduler private val mainScheduler: Scheduler, + @DatabaseScheduler private val databaseScheduler: Scheduler +) { + + private var view: BrowserContract.View? = null + private var viewState: BrowserViewState = BrowserViewState( + displayUrl = "", + isRefresh = true, + sslState = SslState.None, + progress = 0, + tabs = emptyList(), + isForwardEnabled = false, + isBackEnabled = false, + bookmarks = emptyList(), + isBookmarked = false + ) + private var currentTab: TabModel? = null + private var currentFolder: Bookmark.Folder = Bookmark.Folder.Root + + private val compositeDisposable = CompositeDisposable() + private var sslDisposable: Disposable? = null + private var titleDisposable: Disposable? = null + private var urlDisposable: Disposable? = null + private var loadingDisposable: Disposable? = null + private var canGoBackDisposable: Disposable? = null + private var canGoForwardDisposable: Disposable? = null + + fun onViewAttached(view: BrowserContract.View) { + this.view = view + view.updateState(viewState) + + compositeDisposable += bookmarkRepository.getBookmarksFromFolderSorted(folder = null) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribe { list -> + this.view?.updateState(viewState.copy(bookmarks = list)) + } + + compositeDisposable += model.initializeTabs() + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .switchIfEmpty(model.createTab(initialUrl = null).map(::listOf)) + .subscribe { list -> + this.view.updateState(viewState.copy(tabs = list.map { it.asViewState() })) + selectTab(model.selectTab(list.last().id)) + } + + compositeDisposable += model.tabsListChanges() + .observeOn(mainScheduler) + .subscribe { list -> + this.view.updateState(viewState.copy(tabs = list.map { it.asViewState() })) + } + } + + private fun TabModel.asViewState(): Tab = Tab( + id = id, + icon = "", + title = title, + isSelected = isForeground + ) + + private fun List.updateId(id: Int, map: (Tab) -> Tab): List = map { + if (it.id == id) { + map(it) + } else { + it + } + } + + private fun selectTab(tabModel: TabModel?) { + if (currentTab == tabModel) { + return + } + currentTab?.isForeground = false + currentTab = tabModel + currentTab?.isForeground = true + + view.updateState(viewState.copy( + displayUrl = tabModel?.url.orEmpty(), + isForwardEnabled = tabModel?.canGoForward() ?: false, + isBackEnabled = tabModel?.canGoBack() ?: false, + sslState = tabModel?.sslState ?: SslState.None, + progress = tabModel?.loadingProgress ?: 100, + tabs = viewState.tabs.map { + if (it.id == tabModel?.id) { + it.copy(isSelected = true) + } else { + it.copy(isSelected = false) + } + } + )) + + sslDisposable?.dispose() + sslDisposable = tabModel?.sslChanges() + ?.distinctUntilChanged() + ?.observeOn(mainScheduler) + ?.subscribe { + view.updateState(viewState.copy(sslState = it)) + } + + titleDisposable?.dispose() + titleDisposable = tabModel?.titleChanges() + ?.distinctUntilChanged() + ?.observeOn(mainScheduler) + ?.subscribe { title -> + view.updateState(viewState.copy(tabs = viewState.tabs.updateId(tabModel.id) { + it.copy(title = title) + })) + } + + urlDisposable?.dispose() + urlDisposable = tabModel?.urlChanges() + ?.distinctUntilChanged() + ?.observeOn(mainScheduler) + ?.subscribe { + view.updateState(viewState.copy(displayUrl = it)) + } + + loadingDisposable?.dispose() + loadingDisposable = tabModel?.loadingProgress() + ?.distinctUntilChanged() + ?.observeOn(mainScheduler) + ?.subscribe { + view.updateState(viewState.copy(progress = it, isRefresh = it == 100)) + } + + canGoBackDisposable?.dispose() + canGoBackDisposable = tabModel?.canGoBackChanges() + ?.distinctUntilChanged() + ?.observeOn(mainScheduler) + ?.subscribe { + view.updateState(viewState.copy(isBackEnabled = it)) + } + + canGoForwardDisposable?.dispose() + canGoForwardDisposable = tabModel?.canGoForwardChanges() + ?.distinctUntilChanged() + ?.observeOn(mainScheduler) + ?.subscribe { + view.updateState(viewState.copy(isForwardEnabled = it)) + } + } + + fun onViewDetached() { + view = null + compositeDisposable.dispose() + + sslDisposable?.dispose() + titleDisposable?.dispose() + urlDisposable?.dispose() + loadingDisposable?.dispose() + canGoBackDisposable?.dispose() + canGoForwardDisposable?.dispose() + } + + fun onTabClick(index: Int) { + selectTab(model.selectTab(viewState.tabs[index].id)) + } + + private fun List.nextSelected(removedIndex: Int): T? { + val nextIndex = when { + removedIndex > 0 -> removedIndex - 1 + size > removedIndex + 1 -> removedIndex + 1 + else -> -1 + } + return if (nextIndex >= 0) { + this[nextIndex] + } else { + null + } + } + + fun onTabClose(index: Int) { + val nextTab = viewState.tabs.nextSelected(index) + + val needToSelectNextTab = viewState.tabs[index].id == currentTab?.id + + compositeDisposable += model.deleteTab(viewState.tabs[index].id) + .observeOn(mainScheduler) + .subscribe { + if (needToSelectNextTab) { + nextTab?.id?.let { + selectTab(model.selectTab(it)) + } ?: selectTab(tabModel = null) + } + } + } + + fun onBackClick() { + if (currentTab?.canGoBack() == true) { + currentTab?.goBack() + } + } + + fun onForwardClick() { + if (currentTab?.canGoForward() == true) { + currentTab?.goForward() + } + } + + fun onHomeClick() { + currentTab?.loadUrl("https://google.com") + } + + fun onNewTabClick() { + compositeDisposable += model.createTab(initialUrl = null) + .observeOn(mainScheduler) + .subscribe { tab -> + selectTab(model.selectTab(tab.id)) + } + } + + fun onRefreshOrStopClick() { + if (currentTab?.loadingProgress != 100) { + currentTab?.stopLoading() + } else { + currentTab?.reload() + } + } + + fun onSearch(query: String) { + currentTab?.stopLoading() + val url = smartUrlFilter(query, true, SEARCH) + view?.updateState(viewState.copy(displayUrl = url)) + currentTab?.loadUrl(url) + } + + fun onSearchSuggestionClicked(webPage: WebPage) { + val url = when (webPage) { + is HistoryEntry, + is Bookmark.Entry -> webPage.url + is SearchSuggestion -> webPage.title + else -> null + } ?: error("Other types cannot be search suggestions: $webPage") + + onSearch(url) + } + + fun onSslIconClick() { + + } + + fun onBookmarkClick(index: Int) { + when (val bookmark = viewState.bookmarks[index]) { + is Bookmark.Entry -> currentTab?.loadUrl(bookmark.url) + Bookmark.Folder.Root -> TODO() + is Bookmark.Folder.Entry -> { + currentFolder = bookmark + compositeDisposable += bookmarkRepository + .getBookmarksFromFolderSorted(folder = bookmark.title) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribe { list -> + view?.updateState(viewState.copy(bookmarks = list)) + } + } + } + + } + + fun onBookmarkLongClick(index: Int) { + + } + + fun onToolsClick() { + + } + + fun onStarClick() { + + } + + fun onReadingModeClick() { + + } + + fun onTabMenuClick() { + + } + + fun onBookmarkMenuClick() { + if (currentFolder != Bookmark.Folder.Root) { + compositeDisposable += bookmarkRepository + .getBookmarksFromFolderSorted(folder = null) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribe { list -> + view?.updateState(viewState.copy(bookmarks = list)) + } + } + } + + private fun BrowserContract.View?.updateState(state: BrowserViewState) { + viewState = state + this?.renderState(viewState) + } + + private companion object { + private const val SEARCH = "https://www.google.com/search?client=lightning&ie=UTF-8&oe=UTF-8&q=%s" + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt new file mode 100644 index 000000000..5d21feb93 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt @@ -0,0 +1,25 @@ +package acr.browser.lightning._browser2 + +import acr.browser.lightning._browser2.tab.Tab +import acr.browser.lightning.database.Bookmark +import acr.browser.lightning.ssl.SslState + +/** + * Created by anthonycr on 9/11/20. + */ +data class BrowserViewState( + // search bar + val displayUrl: String, + val sslState: SslState, + val isRefresh: Boolean, + val progress: Int, + + // Tabs + val tabs: List, + val isForwardEnabled: Boolean, + val isBackEnabled: Boolean, + + // Bookmarks + val bookmarks: List, + val isBookmarked: Boolean +) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkRecyclerViewAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkRecyclerViewAdapter.kt new file mode 100644 index 000000000..ac95d5ff1 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkRecyclerViewAdapter.kt @@ -0,0 +1,43 @@ +package acr.browser.lightning._browser2.bookmark + +import acr.browser.lightning.R +import acr.browser.lightning.database.Bookmark +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter + +/** + * Created by anthonycr on 9/13/20. + */ +class BookmarkRecyclerViewAdapter( + private val onClick: (Int) -> Unit, + private val onLongClick: (Int) -> Unit +) : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Bookmark, newItem: Bookmark): Boolean = + oldItem == newItem + + override fun areContentsTheSame(oldItem: Bookmark, newItem: Bookmark): Boolean = + oldItem == newItem + } +) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookmarkViewHolder { + val inflater = LayoutInflater.from(parent.context) + val itemView = inflater.inflate(R.layout.bookmark_list_item, parent, false) + + return BookmarkViewHolder( + itemView, + onItemLongClickListener = onLongClick, + onItemClickListener = onClick + ) + } + + override fun onBindViewHolder(holder: BookmarkViewHolder, position: Int) { + val viewModel = getItem(position) + holder.binding.textBookmark.text = viewModel.title + + val url = viewModel.url + holder.binding.faviconBookmark.tag = url + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkViewHolder.kt b/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkViewHolder.kt new file mode 100644 index 000000000..0cefc1add --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkViewHolder.kt @@ -0,0 +1,31 @@ +package acr.browser.lightning._browser2.bookmark + +import acr.browser.lightning.databinding.BookmarkListItemBinding +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +/** + * Created by anthonycr on 9/13/20. + */ +class BookmarkViewHolder( + itemView: View, + private val onItemLongClickListener: (Int) -> Unit, + private val onItemClickListener: (Int) -> Unit +) : RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener { + + val binding = BookmarkListItemBinding.bind(itemView) + + init { + itemView.setOnLongClickListener(this) + itemView.setOnClickListener(this) + } + + override fun onClick(v: View) { + onItemClickListener(adapterPosition) + } + + override fun onLongClick(v: View): Boolean { + onItemLongClickListener(adapterPosition) + return true + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt b/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt new file mode 100644 index 000000000..cf7dc8fe7 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt @@ -0,0 +1,40 @@ +package acr.browser.lightning._browser2.search + +import android.view.KeyEvent +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.TextView + +/** + * Created by anthonycr on 9/14/20. + */ +class SearchListener( + private val onConfirm: () -> Unit, +) : View.OnKeyListener, TextView.OnEditorActionListener { + + override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean { + if (keyEvent.action != KeyEvent.ACTION_UP) { + return false + } + return when (keyCode) { + KeyEvent.KEYCODE_ENTER -> { + onConfirm() + true + } + else -> false + } + } + + override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent?): Boolean { + if (actionId == EditorInfo.IME_ACTION_GO + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT + || actionId == EditorInfo.IME_ACTION_SEND + || actionId == EditorInfo.IME_ACTION_SEARCH + || event?.action == KeyEvent.KEYCODE_ENTER) { + onConfirm() + return true + } + return false + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt new file mode 100644 index 000000000..4b67b5301 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt @@ -0,0 +1,69 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning.ssl.SslState +import io.reactivex.Observable + +/** + * Created by anthonycr on 9/11/20. + */ +data class Tab( + val id: Int, + val icon: String, + val title: String, + val isSelected: Boolean +) + +/* +probably needs to be an interface + */ + + +interface TabModel { + + val id: Int + + // Navigation + + fun loadUrl(url: String) + + fun goBack() + + fun canGoBack(): Boolean + + fun canGoBackChanges(): Observable + + fun goForward() + + fun canGoForward(): Boolean + + fun canGoForwardChanges(): Observable + + fun reload() + + fun stopLoading() + + // Data + + val url: String + + fun urlChanges(): Observable + + val title: String + + fun titleChanges(): Observable + + val sslState: SslState + + fun sslChanges(): Observable + + val loadingProgress: Int + + fun loadingProgress(): Observable + + // Lifecycle + + var isForeground: Boolean + + fun destroy() + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt new file mode 100644 index 000000000..719689a88 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -0,0 +1,106 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning.ssl.SslState +import acr.browser.lightning.view.TabInitializer +import android.webkit.WebView +import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject + +/** + * Created by anthonycr on 9/12/20. + */ +class TabAdapter( + private val tabInitializer: TabInitializer, + private val webView: WebView, +) : TabModel { + + private val urlObservable = PublishSubject.create() + private val sslStateObservable = PublishSubject.create() + private val progressObservable = PublishSubject.create() + private val titleObservable = PublishSubject.create() + private val goBackObservable = PublishSubject.create() + private val goForwardObservable = PublishSubject.create() + private val tabWebViewClient = TabWebViewClient( + urlObservable = urlObservable, + sslStateObservable = sslStateObservable, + goBackObservable = goBackObservable, + goForwardObservable = goForwardObservable + ) + + init { + webView.webViewClient = tabWebViewClient + webView.webChromeClient = TabWebChromeClient( + progressObservable = progressObservable, + titleObservable = titleObservable + ) + tabInitializer.initialize(webView, emptyMap()) + } + + override val id: Int = webView.id + + override fun loadUrl(url: String) { + webView.loadUrl(url) + } + + override fun goBack() { + webView.goBack() + } + + override fun canGoBack(): Boolean = webView.canGoBack() + + override fun canGoBackChanges(): Observable = goBackObservable.hide() + + override fun goForward() { + webView.goForward() + } + + override fun canGoForward(): Boolean = webView.canGoForward() + + override fun canGoForwardChanges(): Observable = goForwardObservable.hide() + + override fun reload() { + webView.reload() + } + + override fun stopLoading() { + webView.stopLoading() + } + + override val url: String + get() = webView.url.orEmpty() + + override fun urlChanges(): Observable = urlObservable.hide() + + override val title: String + get() = webView.title.orEmpty() + + override fun titleChanges(): Observable = titleObservable.hide() + + override val sslState: SslState + get() = tabWebViewClient.sslState + + override fun sslChanges(): Observable = sslStateObservable.hide() + + override val loadingProgress: Int + get() = webView.progress + + override fun loadingProgress(): Observable = progressObservable.hide() + + override var isForeground: Boolean = false + set(value) { + field = value + if (field) { + webView.onResume() + } else { + webView.onPause() + } + } + + override fun destroy() { + webView.stopLoading() + webView.onPause() + webView.clearHistory() + webView.removeAllViews() + webView.destroy() + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt new file mode 100644 index 000000000..128a8cf5b --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt @@ -0,0 +1,33 @@ +package acr.browser.lightning._browser2.tab + +import android.view.ViewGroup +import android.webkit.WebView +import android.widget.FrameLayout + +/** + * Created by anthonycr on 9/12/20. + */ +class TabPager(private val container: FrameLayout) { + + private val webViews: MutableList = mutableListOf() + + fun selectTab(id: Int) { + container.removeAllViews() + container.addView( + webViews.forId(id), + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + } + + fun clearTab() { + container.removeAllViews() + } + + fun addTab(webView: WebView) { + webViews.add(webView) + } + + private fun List.forId(id: Int): WebView = requireNotNull(find { it.id == id }) + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt new file mode 100644 index 000000000..da5cd8234 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt @@ -0,0 +1,73 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning.R +import acr.browser.lightning.extensions.desaturate +import acr.browser.lightning.extensions.inflater +import acr.browser.lightning.view.BackgroundDrawable +import android.graphics.Bitmap +import android.view.ViewGroup +import androidx.core.widget.TextViewCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter + +/** + * Created by anthonycr on 9/12/20. + */ +class TabRecyclerViewAdapter( + private val onClick: (Int) -> Unit, + private val onLongClick: (Int) -> Unit, + private val onCloseClick: (Int) -> Unit, +) : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Tab, newItem: Tab): Boolean = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: Tab, newItem: Tab): Boolean = oldItem == newItem + } +) { + + override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): TabViewHolder { + val view = viewGroup.context.inflater.inflate(R.layout.tab_list_item, viewGroup, false) + view.background = BackgroundDrawable(view.context) + return TabViewHolder(view, onClick = onClick, onLongClick = onLongClick, onCloseClick = onCloseClick) + } + + override fun onBindViewHolder(holder: TabViewHolder, position: Int) { + holder.exitButton.tag = position + + val web = getItem(position) + + holder.txtTitle.text = web.title + updateViewHolderAppearance(holder, null, web.isSelected) + updateViewHolderFavicon(holder, null, web.isSelected) + updateViewHolderBackground(holder, web.isSelected) + } + + private fun updateViewHolderFavicon(viewHolder: TabViewHolder, favicon: Bitmap?, isForeground: Boolean) { + favicon?.let { + if (isForeground) { + viewHolder.favicon.setImageBitmap(it) + } else { + viewHolder.favicon.setImageBitmap(it.desaturate()) + } + } ?: viewHolder.favicon.setImageResource(R.drawable.ic_webpage) + } + + private fun updateViewHolderBackground(viewHolder: TabViewHolder, isForeground: Boolean) { + val verticalBackground = viewHolder.layout.background as BackgroundDrawable + verticalBackground.isCrossFadeEnabled = false + if (isForeground) { + verticalBackground.startTransition(200) + } else { + verticalBackground.reverseTransition(200) + } + } + + private fun updateViewHolderAppearance(viewHolder: TabViewHolder, favicon: Bitmap?, isForeground: Boolean) { + if (isForeground) { + TextViewCompat.setTextAppearance(viewHolder.txtTitle, R.style.boldText) + } else { + TextViewCompat.setTextAppearance(viewHolder.txtTitle, R.style.normalText) + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabViewHolder.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabViewHolder.kt new file mode 100644 index 000000000..a0f661d9f --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabViewHolder.kt @@ -0,0 +1,45 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning.R +import acr.browser.lightning.controller.UIController +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView + +/** + * Created by anthonycr on 9/13/20. + */ + +class TabViewHolder( + view: View, + private val onClick: (Int) -> Unit, + private val onLongClick: (Int) -> Unit, + private val onCloseClick: (Int) -> Unit, +) : RecyclerView.ViewHolder(view), View.OnClickListener, View.OnLongClickListener { + + val txtTitle: TextView = view.findViewById(R.id.textTab) + val favicon: ImageView = view.findViewById(R.id.faviconTab) + val exitButton: View = view.findViewById(R.id.deleteAction) + val layout: LinearLayout = view.findViewById(R.id.tab_item_background) + + init { + exitButton.setOnClickListener(this) + layout.setOnClickListener(this) + layout.setOnLongClickListener(this) + } + + override fun onClick(v: View) { + if (v === exitButton) { + onCloseClick.invoke(adapterPosition) + } else if (v === layout) { + onClick.invoke(adapterPosition) + } + } + + override fun onLongClick(v: View): Boolean { + onLongClick.invoke(adapterPosition) + return true + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt new file mode 100644 index 000000000..9e3e42bd1 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt @@ -0,0 +1,24 @@ +package acr.browser.lightning._browser2.tab + +import android.webkit.WebChromeClient +import android.webkit.WebView +import io.reactivex.subjects.PublishSubject + +/** + * Created by anthonycr on 9/12/20. + */ +class TabWebChromeClient( + private val progressObservable: PublishSubject, + private val titleObservable: PublishSubject +) : WebChromeClient() { + + override fun onProgressChanged(view: WebView, newProgress: Int) { + super.onProgressChanged(view, newProgress) + progressObservable.onNext(newProgress) + } + + override fun onReceivedTitle(view: WebView, title: String) { + super.onReceivedTitle(view, title) + titleObservable.onNext(title) + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt new file mode 100644 index 000000000..d7554050d --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt @@ -0,0 +1,48 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning.ssl.SslState +import android.graphics.Bitmap +import android.net.http.SslError +import android.webkit.SslErrorHandler +import android.webkit.URLUtil +import android.webkit.WebView +import android.webkit.WebViewClient +import io.reactivex.subjects.PublishSubject + +/** + * Created by anthonycr on 9/12/20. + */ +class TabWebViewClient( + private val urlObservable: PublishSubject, + private val sslStateObservable: PublishSubject, + private val goBackObservable: PublishSubject, + private val goForwardObservable: PublishSubject +) : WebViewClient() { + + var sslState: SslState = SslState.None + private set + + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + urlObservable.onNext(url) + sslState = if (URLUtil.isHttpsUrl(url)) { + SslState.Valid + } else { + SslState.None + } + sslStateObservable.onNext(sslState) + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + urlObservable.onNext(url) + goBackObservable.onNext(view.canGoBack()) + goForwardObservable.onNext(view.canGoForward()) + } + + override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { + super.onReceivedSslError(view, handler, error) + sslState = SslState.Invalid(error) + sslStateObservable.onNext(sslState) + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt new file mode 100644 index 000000000..8117ff0f5 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -0,0 +1,63 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning._browser2.BrowserContract +import acr.browser.lightning.view.UrlInitializer +import io.reactivex.Completable +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.subjects.PublishSubject + +/** + * Created by anthonycr on 9/12/20. + */ +class TabsRepository( + private val webViewFactory: WebViewFactory, + private val tabPager: TabPager +) : BrowserContract.Model { + + private var selectedTab: TabModel? = null + private val tabsListObservable = PublishSubject.create>() + + override fun deleteTab(id: Int): Completable = Completable.fromAction { + if (selectedTab?.id == id) { + tabPager.clearTab() + } + val tab = tabsList.forId(id) + tab.destroy() + tabsList -= tab + }.doOnComplete { + tabsListObservable.onNext(tabsList) + } + + override fun createTab(initialUrl: String?): Single = Single.fromCallable { + val webView = webViewFactory.createWebView(isIncognito = false) + tabPager.addTab(webView) + val tabAdapter = TabAdapter(UrlInitializer(initialUrl ?: "https://google.com"), webView) + + tabsList += tabAdapter + + return@fromCallable tabAdapter + }.doOnSuccess { + tabsListObservable.onNext(tabsList) + } + + override fun selectTab(id: Int): TabModel { + val selected = tabsList.forId(id) + selectedTab = selected + tabPager.selectTab(id) + + return selected + } + + override var tabsList = emptyList() + private set + + override fun tabsListChanges(): Observable> = tabsListObservable.hide() + + override fun initializeTabs(): Maybe> = Maybe.empty() + + private fun List.forId(id: Int): TabModel = requireNotNull(find { it.id == id }) + + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt new file mode 100644 index 000000000..a09ac656b --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt @@ -0,0 +1,179 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning.Capabilities +import acr.browser.lightning.isSupported +import acr.browser.lightning.log.Logger +import acr.browser.lightning.preference.UserPreferences +import acr.browser.lightning.preference.userAgent +import android.annotation.SuppressLint +import android.app.Activity +import android.graphics.Color +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import android.view.View +import android.webkit.CookieManager +import android.webkit.WebSettings +import android.webkit.WebView + +/** + * Created by anthonycr on 9/12/20. + */ +class WebViewFactory( + private val activity: Activity, + private val logger: Logger, + private val userPreferences: UserPreferences +) { + + fun createWebView(isIncognito: Boolean): WebView = WebView(activity).apply { + id = View.generateViewId() + isFocusableInTouchMode = true + isFocusable = true + if (VERSION.SDK_INT < VERSION_CODES.M) { + isAnimationCacheEnabled = false + isAlwaysDrawnWithCacheEnabled = false + } + setBackgroundColor(Color.WHITE) + + if (VERSION.SDK_INT >= VERSION_CODES.O) { + importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES + } + + isScrollbarFadingEnabled = true + isSaveEnabled = true + setNetworkAvailable(true) + + settings.apply { + mediaPlaybackRequiresUserGesture = true + + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && !isIncognito) { + mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE + } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW + } + + if (!isIncognito || Capabilities.FULL_INCOGNITO.isSupported) { + domStorageEnabled = true + setAppCacheEnabled(true) + databaseEnabled = true + cacheMode = WebSettings.LOAD_DEFAULT + } else { + domStorageEnabled = false + setAppCacheEnabled(false) + databaseEnabled = false + cacheMode = WebSettings.LOAD_NO_CACHE + } + + setSupportZoom(true) + builtInZoomControls = true + displayZoomControls = false + allowContentAccess = true + allowFileAccess = true + allowFileAccessFromFileURLs = false + allowUniversalAccessFromFileURLs = false + + // TODO: remove? + setAppCachePath(activity.getDir("appcache", 0).path) + setGeolocationDatabasePath(activity.getDir("geolocation", 0).path) + } + + updateForPreferences(userPreferences, isIncognito) + } + + @SuppressLint("SetJavaScriptEnabled") + private fun WebView.updateForPreferences( + userPreferences: UserPreferences, + isIncognito: Boolean + ) { + +// lightningWebClient.updatePreferences() +// + val modifiesHeaders = userPreferences.doNotTrackEnabled + || userPreferences.saveDataEnabled + || userPreferences.removeIdentifyingHeadersEnabled +// +// if (userPreferences.doNotTrackEnabled) { +// requestHeaders[LightningView.HEADER_DNT] = "1" +// } else { +// requestHeaders.remove(LightningView.HEADER_DNT) +// } +// +// if (userPreferences.saveDataEnabled) { +// requestHeaders[LightningView.HEADER_SAVEDATA] = "on" +// } else { +// requestHeaders.remove(LightningView.HEADER_SAVEDATA) +// } +// +// if (userPreferences.removeIdentifyingHeadersEnabled) { +// requestHeaders[LightningView.HEADER_REQUESTED_WITH] = "" +// requestHeaders[LightningView.HEADER_WAP_PROFILE] = "" +// } else { +// requestHeaders.remove(LightningView.HEADER_REQUESTED_WITH) +// requestHeaders.remove(LightningView.HEADER_WAP_PROFILE) +// } + + settings.defaultTextEncodingName = userPreferences.textEncoding +// setColorMode(userPreferences.renderingMode) + + if (!isIncognito) { + settings.setGeolocationEnabled(userPreferences.locationEnabled) + } else { + settings.setGeolocationEnabled(false) + } + + settings.userAgentString = userPreferences.userAgent(activity.application) + + settings.saveFormData = userPreferences.savePasswordsEnabled && !isIncognito + + if (userPreferences.javaScriptEnabled) { + settings.javaScriptEnabled = true + settings.javaScriptCanOpenWindowsAutomatically = true + } else { + settings.javaScriptEnabled = false + settings.javaScriptCanOpenWindowsAutomatically = false + } + + if (userPreferences.textReflowEnabled) { + settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS + try { + settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING + } catch (e: Exception) { + // This shouldn't be necessary, but there are a number + // of KitKat devices that crash trying to set this + logger.log(TAG, "Problem setting LayoutAlgorithm to TEXT_AUTOSIZING") + } + } else { + settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.NORMAL + } + + settings.blockNetworkImage = userPreferences.blockImagesEnabled + // Modifying headers causes SEGFAULTS, so disallow multi window if headers are enabled. + settings.setSupportMultipleWindows(userPreferences.popupsEnabled && !modifiesHeaders) + + settings.useWideViewPort = userPreferences.useWideViewPortEnabled + settings.loadWithOverviewMode = userPreferences.overviewModeEnabled + settings.textZoom = when (userPreferences.textSize) { + 0 -> 200 + 1 -> 150 + 2 -> 125 + 3 -> 100 + 4 -> 75 + 5 -> 50 + else -> throw IllegalArgumentException("Unsupported text size") + } + + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + CookieManager.getInstance().setAcceptThirdPartyCookies(this, + !userPreferences.blockThirdPartyCookiesEnabled) + } + } + + private companion object { + private const val TAG = "WebViewFactory" + + private const val HEADER_REQUESTED_WITH = "X-Requested-With" + private const val HEADER_WAP_PROFILE = "X-Wap-Profile" + private const val HEADER_DNT = "DNT" + private const val HEADER_SAVEDATA = "Save-Data" + } + +} diff --git a/app/src/main/res/layout/browser_activity.xml b/app/src/main/res/layout/browser_activity.xml new file mode 100644 index 000000000..8de767036 --- /dev/null +++ b/app/src/main/res/layout/browser_activity.xml @@ -0,0 +1,415 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9ba9286c820e956a174f4bc4ee4e80090c8f24b4 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 14 Sep 2020 23:18:46 -0400 Subject: [PATCH 003/161] Add history recording --- .../lightning/_browser2/BrowserActivity.kt | 8 +++++++- .../lightning/_browser2/BrowserPresenter.kt | 9 ++++++++- .../_browser2/history/DefaultHistoryRecord.kt | 19 +++++++++++++++++++ .../_browser2/history/HistoryRecord.kt | 10 ++++++++++ .../_browser2/history/NoOpHistoryRecord.kt | 8 ++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/history/DefaultHistoryRecord.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/history/HistoryRecord.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/history/NoOpHistoryRecord.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index b15ef524a..8627f37a6 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning.R import acr.browser.lightning._browser2.bookmark.BookmarkRecyclerViewAdapter +import acr.browser.lightning._browser2.history.DefaultHistoryRecord import acr.browser.lightning._browser2.search.SearchListener import acr.browser.lightning._browser2.tab.TabPager import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter @@ -13,6 +14,7 @@ import acr.browser.lightning.database.HistoryEntry import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage import acr.browser.lightning.database.bookmark.BookmarkDatabase +import acr.browser.lightning.database.history.HistoryDatabase import acr.browser.lightning.databinding.BrowserActivityBinding import acr.browser.lightning.device.ScreenSize import acr.browser.lightning.log.AndroidLogger @@ -60,7 +62,11 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { tabPager = TabPager(binding.contentFrame) ), mainScheduler = AndroidSchedulers.mainThread(), - databaseScheduler = Schedulers.single() + databaseScheduler = Schedulers.single(), + historyRecord = DefaultHistoryRecord( + historyRepository = HistoryDatabase(application), + databaseScheduler = Schedulers.single() + ) ) tabsAdapter = TabRecyclerViewAdapter( diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 1b37cb7c4..185d514e4 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2 +import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.tab.Tab import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning.database.Bookmark @@ -10,6 +11,7 @@ import acr.browser.lightning.database.bookmark.BookmarkRepository import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.MainScheduler import acr.browser.lightning.ssl.SslState +import acr.browser.lightning.utils.isSpecialUrl import acr.browser.lightning.utils.smartUrlFilter import io.reactivex.Scheduler import io.reactivex.disposables.CompositeDisposable @@ -23,7 +25,8 @@ class BrowserPresenter( private val model: BrowserContract.Model, private val bookmarkRepository: BookmarkRepository, @MainScheduler private val mainScheduler: Scheduler, - @DatabaseScheduler private val databaseScheduler: Scheduler + @DatabaseScheduler private val databaseScheduler: Scheduler, + private val historyRecord: HistoryRecord ) { private var view: BrowserContract.View? = null @@ -130,6 +133,10 @@ class BrowserPresenter( view.updateState(viewState.copy(tabs = viewState.tabs.updateId(tabModel.id) { it.copy(title = title) })) + + currentTab?.url?.takeIf { !it.isSpecialUrl() }?.let { + historyRecord.recordVisit(title, it) + } } urlDisposable?.dispose() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/history/DefaultHistoryRecord.kt b/app/src/main/java/acr/browser/lightning/_browser2/history/DefaultHistoryRecord.kt new file mode 100644 index 000000000..5bac8a0e5 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/history/DefaultHistoryRecord.kt @@ -0,0 +1,19 @@ +package acr.browser.lightning._browser2.history + +import acr.browser.lightning.database.history.HistoryRepository +import acr.browser.lightning.di.DatabaseScheduler +import io.reactivex.Scheduler + +/** + * Created by anthonycr on 9/14/20. + */ +class DefaultHistoryRecord( + private val historyRepository: HistoryRepository, + @DatabaseScheduler private val databaseScheduler: Scheduler +) : HistoryRecord { + override fun recordVisit(title: String, url: String) { + historyRepository.visitHistoryEntry(url, title) + .subscribeOn(databaseScheduler) + .subscribe() + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/history/HistoryRecord.kt b/app/src/main/java/acr/browser/lightning/_browser2/history/HistoryRecord.kt new file mode 100644 index 000000000..d0acb43f6 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/history/HistoryRecord.kt @@ -0,0 +1,10 @@ +package acr.browser.lightning._browser2.history + +/** + * Created by anthonycr on 9/14/20. + */ +interface HistoryRecord { + + fun recordVisit(title: String, url: String) + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/history/NoOpHistoryRecord.kt b/app/src/main/java/acr/browser/lightning/_browser2/history/NoOpHistoryRecord.kt new file mode 100644 index 000000000..015b4cb51 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/history/NoOpHistoryRecord.kt @@ -0,0 +1,8 @@ +package acr.browser.lightning._browser2.history + +/** + * Created by anthonycr on 9/14/20. + */ +object NoOpHistoryRecord : HistoryRecord { + override fun recordVisit(title: String, url: String) = Unit +} From 6ce04eda5a9f507683bdc93bd1007d3e2bb3fb47 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 14 Sep 2020 23:25:24 -0400 Subject: [PATCH 004/161] Extract TabModel --- .../browser/lightning/_browser2/tab/Tab.kt | 58 ------------------- .../lightning/_browser2/tab/TabModel.kt | 54 +++++++++++++++++ 2 files changed, 54 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt index 4b67b5301..082c37fc5 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt @@ -1,8 +1,5 @@ package acr.browser.lightning._browser2.tab -import acr.browser.lightning.ssl.SslState -import io.reactivex.Observable - /** * Created by anthonycr on 9/11/20. */ @@ -12,58 +9,3 @@ data class Tab( val title: String, val isSelected: Boolean ) - -/* -probably needs to be an interface - */ - - -interface TabModel { - - val id: Int - - // Navigation - - fun loadUrl(url: String) - - fun goBack() - - fun canGoBack(): Boolean - - fun canGoBackChanges(): Observable - - fun goForward() - - fun canGoForward(): Boolean - - fun canGoForwardChanges(): Observable - - fun reload() - - fun stopLoading() - - // Data - - val url: String - - fun urlChanges(): Observable - - val title: String - - fun titleChanges(): Observable - - val sslState: SslState - - fun sslChanges(): Observable - - val loadingProgress: Int - - fun loadingProgress(): Observable - - // Lifecycle - - var isForeground: Boolean - - fun destroy() - -} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt new file mode 100644 index 000000000..ac146ccf7 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -0,0 +1,54 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning.ssl.SslState +import io.reactivex.Observable + +interface TabModel { + + val id: Int + + // Navigation + + fun loadUrl(url: String) + + fun goBack() + + fun canGoBack(): Boolean + + fun canGoBackChanges(): Observable + + fun goForward() + + fun canGoForward(): Boolean + + fun canGoForwardChanges(): Observable + + fun reload() + + fun stopLoading() + + // Data + + val url: String + + fun urlChanges(): Observable + + val title: String + + fun titleChanges(): Observable + + val sslState: SslState + + fun sslChanges(): Observable + + val loadingProgress: Int + + fun loadingProgress(): Observable + + // Lifecycle + + var isForeground: Boolean + + fun destroy() + +} From bffb31f754822ffe5851624049661ada6d2295a5 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 15 Sep 2020 00:17:49 -0400 Subject: [PATCH 005/161] inject dependencies and create new tabs using home page --- .../lightning/_browser2/BrowserActivity.kt | 44 +++++-------------- .../lightning/_browser2/BrowserContract.kt | 3 +- .../lightning/_browser2/BrowserPresenter.kt | 11 +++-- .../_browser2/di/Browser2Component.kt | 30 +++++++++++++ .../lightning/_browser2/di/Browser2Module.kt | 23 ++++++++++ .../_browser2/history/DefaultHistoryRecord.kt | 3 +- .../lightning/_browser2/tab/TabAdapter.kt | 12 ++++- .../lightning/_browser2/tab/TabPager.kt | 3 +- .../lightning/_browser2/tab/TabsRepository.kt | 8 ++-- .../lightning/_browser2/tab/WebViewFactory.kt | 3 +- .../acr/browser/lightning/di/AppComponent.kt | 10 ++++- 11 files changed, 102 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 8627f37a6..36e2004f8 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -2,23 +2,13 @@ package acr.browser.lightning._browser2 import acr.browser.lightning.R import acr.browser.lightning._browser2.bookmark.BookmarkRecyclerViewAdapter -import acr.browser.lightning._browser2.history.DefaultHistoryRecord import acr.browser.lightning._browser2.search.SearchListener -import acr.browser.lightning._browser2.tab.TabPager import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter -import acr.browser.lightning._browser2.tab.TabsRepository -import acr.browser.lightning._browser2.tab.WebViewFactory import acr.browser.lightning.browser.activity.ThemableBrowserActivity -import acr.browser.lightning.database.Bookmark -import acr.browser.lightning.database.HistoryEntry import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage -import acr.browser.lightning.database.bookmark.BookmarkDatabase -import acr.browser.lightning.database.history.HistoryDatabase import acr.browser.lightning.databinding.BrowserActivityBinding -import acr.browser.lightning.device.ScreenSize -import acr.browser.lightning.log.AndroidLogger -import acr.browser.lightning.preference.UserPreferences +import acr.browser.lightning.di.injector import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState import android.os.Bundle @@ -28,8 +18,7 @@ import android.view.View import android.widget.AdapterView import android.widget.ImageView import androidx.recyclerview.widget.LinearLayoutManager -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers +import javax.inject.Inject /** * Created by anthonycr on 9/11/20. @@ -37,10 +26,12 @@ import io.reactivex.schedulers.Schedulers class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { private lateinit var binding: BrowserActivityBinding - private lateinit var presenter: BrowserPresenter private lateinit var tabsAdapter: TabRecyclerViewAdapter private lateinit var bookmarksAdapter: BookmarkRecyclerViewAdapter + @Inject + internal lateinit var presenter: BrowserPresenter + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = BrowserActivityBinding.inflate(LayoutInflater.from(this)) @@ -48,26 +39,11 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { setContentView(binding.root) setSupportActionBar(binding.toolbar) - presenter = BrowserPresenter( - bookmarkRepository = BookmarkDatabase(application), - model = TabsRepository( - webViewFactory = WebViewFactory( - activity = this, - logger = AndroidLogger(), - userPreferences = UserPreferences( - preferences = application.getSharedPreferences("settings", 0), - screenSize = ScreenSize(this) - ) - ), - tabPager = TabPager(binding.contentFrame) - ), - mainScheduler = AndroidSchedulers.mainThread(), - databaseScheduler = Schedulers.single(), - historyRecord = DefaultHistoryRecord( - historyRepository = HistoryDatabase(application), - databaseScheduler = Schedulers.single() - ) - ) + injector.browser2ComponentBuilder() + .activity(this) + .browserFrame(binding.contentFrame) + .build() + .inject(this) tabsAdapter = TabRecyclerViewAdapter( onClick = presenter::onTabClick, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 32b74a34e..5c30de238 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning.view.TabInitializer import io.reactivex.Completable import io.reactivex.Maybe import io.reactivex.Observable @@ -23,7 +24,7 @@ interface BrowserContract { fun deleteTab(id: Int): Completable - fun createTab(initialUrl: String?): Single + fun createTab(tabInitializer: TabInitializer): Single fun selectTab(id: Int): TabModel diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 185d514e4..b1901e503 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -13,20 +13,23 @@ import acr.browser.lightning.di.MainScheduler import acr.browser.lightning.ssl.SslState import acr.browser.lightning.utils.isSpecialUrl import acr.browser.lightning.utils.smartUrlFilter +import acr.browser.lightning.view.HomePageInitializer import io.reactivex.Scheduler import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.plusAssign +import javax.inject.Inject /** * Created by anthonycr on 9/11/20. */ -class BrowserPresenter( +class BrowserPresenter @Inject constructor( private val model: BrowserContract.Model, private val bookmarkRepository: BookmarkRepository, @MainScheduler private val mainScheduler: Scheduler, @DatabaseScheduler private val databaseScheduler: Scheduler, - private val historyRecord: HistoryRecord + private val historyRecord: HistoryRecord, + private val homePageInitializer: HomePageInitializer ) { private var view: BrowserContract.View? = null @@ -66,7 +69,7 @@ class BrowserPresenter( compositeDisposable += model.initializeTabs() .subscribeOn(databaseScheduler) .observeOn(mainScheduler) - .switchIfEmpty(model.createTab(initialUrl = null).map(::listOf)) + .switchIfEmpty(model.createTab(homePageInitializer).map(::listOf)) .subscribe { list -> this.view.updateState(viewState.copy(tabs = list.map { it.asViewState() })) selectTab(model.selectTab(list.last().id)) @@ -234,7 +237,7 @@ class BrowserPresenter( } fun onNewTabClick() { - compositeDisposable += model.createTab(initialUrl = null) + compositeDisposable += model.createTab(homePageInitializer) .observeOn(mainScheduler) .subscribe { tab -> selectTab(model.selectTab(tab.id)) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt new file mode 100644 index 000000000..ea1921134 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt @@ -0,0 +1,30 @@ +package acr.browser.lightning._browser2.di + +import acr.browser.lightning._browser2.BrowserActivity +import android.app.Activity +import android.widget.FrameLayout +import dagger.BindsInstance +import dagger.Subcomponent + +/** + * Created by anthonycr on 9/15/20. + */ +@Subcomponent(modules = [Browser2Module::class]) +interface Browser2Component { + + @Subcomponent.Builder + interface Builder { + + @BindsInstance + fun activity(activity: Activity): Builder + + @BindsInstance + fun browserFrame(frameLayout: FrameLayout): Builder + + fun build(): Browser2Component + + } + + fun inject(browserActivity: BrowserActivity) + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt new file mode 100644 index 000000000..e486e5281 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -0,0 +1,23 @@ +package acr.browser.lightning._browser2.di + +import acr.browser.lightning._browser2.BrowserContract +import acr.browser.lightning._browser2.history.DefaultHistoryRecord +import acr.browser.lightning._browser2.history.HistoryRecord +import acr.browser.lightning._browser2.tab.TabsRepository +import dagger.Binds +import dagger.Module + +/** + * Created by anthonycr on 9/15/20. + */ +@Module +interface Browser2Module { + + @Binds + fun bindsBrowserModel(tabsRepository: TabsRepository): BrowserContract.Model + + @Binds + fun bindsHistoryRecord(defaultHistoryRecord: DefaultHistoryRecord): HistoryRecord + + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/history/DefaultHistoryRecord.kt b/app/src/main/java/acr/browser/lightning/_browser2/history/DefaultHistoryRecord.kt index 5bac8a0e5..5aa3567b7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/history/DefaultHistoryRecord.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/history/DefaultHistoryRecord.kt @@ -3,11 +3,12 @@ package acr.browser.lightning._browser2.history import acr.browser.lightning.database.history.HistoryRepository import acr.browser.lightning.di.DatabaseScheduler import io.reactivex.Scheduler +import javax.inject.Inject /** * Created by anthonycr on 9/14/20. */ -class DefaultHistoryRecord( +class DefaultHistoryRecord @Inject constructor( private val historyRepository: HistoryRepository, @DatabaseScheduler private val databaseScheduler: Scheduler ) : HistoryRecord { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index 719689a88..4cbe3c4a9 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning.ssl.SslState +import acr.browser.lightning.view.FreezableBundleInitializer import acr.browser.lightning.view.TabInitializer import android.webkit.WebView import io.reactivex.Observable @@ -10,7 +11,7 @@ import io.reactivex.subjects.PublishSubject * Created by anthonycr on 9/12/20. */ class TabAdapter( - private val tabInitializer: TabInitializer, + tabInitializer: TabInitializer, private val webView: WebView, ) : TabModel { @@ -26,6 +27,7 @@ class TabAdapter( goBackObservable = goBackObservable, goForwardObservable = goForwardObservable ) + private var latentInitializer: TabInitializer? = null init { webView.webViewClient = tabWebViewClient @@ -33,7 +35,11 @@ class TabAdapter( progressObservable = progressObservable, titleObservable = titleObservable ) - tabInitializer.initialize(webView, emptyMap()) + if (tabInitializer is FreezableBundleInitializer) { + latentInitializer = tabInitializer + } else { + tabInitializer.initialize(webView, emptyMap()) + } } override val id: Int = webView.id @@ -91,6 +97,8 @@ class TabAdapter( field = value if (field) { webView.onResume() + latentInitializer?.initialize(webView, emptyMap()) + latentInitializer = null } else { webView.onPause() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt index 128a8cf5b..5ae9ae4bf 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt @@ -3,11 +3,12 @@ package acr.browser.lightning._browser2.tab import android.view.ViewGroup import android.webkit.WebView import android.widget.FrameLayout +import javax.inject.Inject /** * Created by anthonycr on 9/12/20. */ -class TabPager(private val container: FrameLayout) { +class TabPager @Inject constructor(private val container: FrameLayout) { private val webViews: MutableList = mutableListOf() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index 8117ff0f5..7dd566ff4 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -1,17 +1,19 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning._browser2.BrowserContract +import acr.browser.lightning.view.TabInitializer import acr.browser.lightning.view.UrlInitializer import io.reactivex.Completable import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single import io.reactivex.subjects.PublishSubject +import javax.inject.Inject /** * Created by anthonycr on 9/12/20. */ -class TabsRepository( +class TabsRepository @Inject constructor( private val webViewFactory: WebViewFactory, private val tabPager: TabPager ) : BrowserContract.Model { @@ -30,10 +32,10 @@ class TabsRepository( tabsListObservable.onNext(tabsList) } - override fun createTab(initialUrl: String?): Single = Single.fromCallable { + override fun createTab(tabInitializer: TabInitializer): Single = Single.fromCallable { val webView = webViewFactory.createWebView(isIncognito = false) tabPager.addTab(webView) - val tabAdapter = TabAdapter(UrlInitializer(initialUrl ?: "https://google.com"), webView) + val tabAdapter = TabAdapter(tabInitializer, webView) tabsList += tabAdapter diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt index a09ac656b..496456e07 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt @@ -14,11 +14,12 @@ import android.view.View import android.webkit.CookieManager import android.webkit.WebSettings import android.webkit.WebView +import javax.inject.Inject /** * Created by anthonycr on 9/12/20. */ -class WebViewFactory( +class WebViewFactory @Inject constructor( private val activity: Activity, private val logger: Logger, private val userPreferences: UserPreferences diff --git a/app/src/main/java/acr/browser/lightning/di/AppComponent.kt b/app/src/main/java/acr/browser/lightning/di/AppComponent.kt index c2987ebc5..69a0b7fcf 100644 --- a/app/src/main/java/acr/browser/lightning/di/AppComponent.kt +++ b/app/src/main/java/acr/browser/lightning/di/AppComponent.kt @@ -1,6 +1,7 @@ package acr.browser.lightning.di import acr.browser.lightning.BrowserApp +import acr.browser.lightning._browser2.di.Browser2Component import acr.browser.lightning.adblock.BloomFilterAdBlocker import acr.browser.lightning.adblock.NoOpAdBlocker import acr.browser.lightning.browser.SearchBoxModel @@ -21,10 +22,11 @@ import acr.browser.lightning.view.LightningWebClient import android.app.Application import dagger.BindsInstance import dagger.Component +import dagger.Module import javax.inject.Singleton @Singleton -@Component(modules = [(AppModule::class), (AppBindsModule::class)]) +@Component(modules = [AppModule::class, AppBindsModule::class, Submodules::class]) interface AppComponent { @Component.Builder @@ -85,4 +87,10 @@ interface AppComponent { fun provideNoOpAdBlocker(): NoOpAdBlocker + fun browser2ComponentBuilder(): Browser2Component.Builder + } + + +@Module(subcomponents = [Browser2Component::class]) +internal class Submodules From 2721cd2a3befdf20c4e808ff30a06e285ff1e233 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 15 Sep 2020 00:28:29 -0400 Subject: [PATCH 006/161] Handle url display --- .../lightning/_browser2/BrowserActivity.kt | 6 ++--- .../lightning/_browser2/BrowserPresenter.kt | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 36e2004f8..b3403a5af 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -118,10 +118,10 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { } private fun ImageView.updateVisibilityForDrawable() { - if (drawable == null) { - visibility = View.GONE + visibility = if (drawable == null) { + View.GONE } else { - visibility = View.VISIBLE + View.VISIBLE } } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index b1901e503..9c723807f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -3,6 +3,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.tab.Tab import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning.browser.SearchBoxModel import acr.browser.lightning.database.Bookmark import acr.browser.lightning.database.HistoryEntry import acr.browser.lightning.database.SearchSuggestion @@ -29,7 +30,8 @@ class BrowserPresenter @Inject constructor( @MainScheduler private val mainScheduler: Scheduler, @DatabaseScheduler private val databaseScheduler: Scheduler, private val historyRecord: HistoryRecord, - private val homePageInitializer: HomePageInitializer + private val homePageInitializer: HomePageInitializer, + private val searchBoxModel: SearchBoxModel ) { private var view: BrowserContract.View? = null @@ -106,7 +108,11 @@ class BrowserPresenter @Inject constructor( currentTab?.isForeground = true view.updateState(viewState.copy( - displayUrl = tabModel?.url.orEmpty(), + displayUrl = searchBoxModel.getDisplayContent( + url = tabModel?.url.orEmpty(), + title = tabModel?.title, + isLoading = (tabModel?.loadingProgress ?: 0) < 100 + ), isForwardEnabled = tabModel?.canGoForward() ?: false, isBackEnabled = tabModel?.canGoBack() ?: false, sslState = tabModel?.sslState ?: SslState.None, @@ -147,7 +153,13 @@ class BrowserPresenter @Inject constructor( ?.distinctUntilChanged() ?.observeOn(mainScheduler) ?.subscribe { - view.updateState(viewState.copy(displayUrl = it)) + view.updateState(viewState.copy( + displayUrl = searchBoxModel.getDisplayContent( + url = tabModel.url, + title = tabModel.title, + isLoading = (tabModel.loadingProgress ?: 0) < 100 + ) + )) } loadingDisposable?.dispose() @@ -255,7 +267,13 @@ class BrowserPresenter @Inject constructor( fun onSearch(query: String) { currentTab?.stopLoading() val url = smartUrlFilter(query, true, SEARCH) - view?.updateState(viewState.copy(displayUrl = url)) + view?.updateState(viewState.copy( + displayUrl = searchBoxModel.getDisplayContent( + url = currentTab?.url.orEmpty(), + title = currentTab?.url, + isLoading = (currentTab?.loadingProgress ?: 0) < 100 + ) + )) currentTab?.loadUrl(url) } From a480079ca09d79f6a3d468854db98de6d46dd27d Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 15 Sep 2020 00:33:56 -0400 Subject: [PATCH 007/161] Support custom search query --- .../browser/lightning/_browser2/BrowserPresenter.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 9c723807f..e868b90f0 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -11,7 +11,9 @@ import acr.browser.lightning.database.WebPage import acr.browser.lightning.database.bookmark.BookmarkRepository import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.MainScheduler +import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.ssl.SslState +import acr.browser.lightning.utils.QUERY_PLACE_HOLDER import acr.browser.lightning.utils.isSpecialUrl import acr.browser.lightning.utils.smartUrlFilter import acr.browser.lightning.view.HomePageInitializer @@ -31,7 +33,8 @@ class BrowserPresenter @Inject constructor( @DatabaseScheduler private val databaseScheduler: Scheduler, private val historyRecord: HistoryRecord, private val homePageInitializer: HomePageInitializer, - private val searchBoxModel: SearchBoxModel + private val searchBoxModel: SearchBoxModel, + private val searchEngineProvider: SearchEngineProvider ) { private var view: BrowserContract.View? = null @@ -266,7 +269,8 @@ class BrowserPresenter @Inject constructor( fun onSearch(query: String) { currentTab?.stopLoading() - val url = smartUrlFilter(query, true, SEARCH) + val searchUrl = searchEngineProvider.provideSearchEngine().queryUrl + QUERY_PLACE_HOLDER + val url = smartUrlFilter(query, true, searchUrl) view?.updateState(viewState.copy( displayUrl = searchBoxModel.getDisplayContent( url = currentTab?.url.orEmpty(), @@ -346,8 +350,4 @@ class BrowserPresenter @Inject constructor( viewState = state this?.renderState(viewState) } - - private companion object { - private const val SEARCH = "https://www.google.com/search?client=lightning&ie=UTF-8&oe=UTF-8&q=%s" - } } From bd832d2b05f7361ad926ff47e51262a3c444718b Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 15 Sep 2020 00:42:00 -0400 Subject: [PATCH 008/161] Adding placeholder docs --- .../lightning/_browser2/BrowserPresenter.kt | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index e868b90f0..14e958e5b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -60,6 +60,9 @@ class BrowserPresenter @Inject constructor( private var canGoBackDisposable: Disposable? = null private var canGoForwardDisposable: Disposable? = null + /** + * TODO + */ fun onViewAttached(view: BrowserContract.View) { this.view = view view.updateState(viewState) @@ -190,6 +193,9 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ fun onViewDetached() { view = null compositeDisposable.dispose() @@ -202,6 +208,9 @@ class BrowserPresenter @Inject constructor( canGoForwardDisposable?.dispose() } + /** + * TODO + */ fun onTabClick(index: Int) { selectTab(model.selectTab(viewState.tabs[index].id)) } @@ -219,6 +228,9 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ fun onTabClose(index: Int) { val nextTab = viewState.tabs.nextSelected(index) @@ -235,22 +247,34 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ fun onBackClick() { if (currentTab?.canGoBack() == true) { currentTab?.goBack() } } + /** + * TODO + */ fun onForwardClick() { if (currentTab?.canGoForward() == true) { currentTab?.goForward() } } + /** + * TODO + */ fun onHomeClick() { currentTab?.loadUrl("https://google.com") } + /** + * TODO + */ fun onNewTabClick() { compositeDisposable += model.createTab(homePageInitializer) .observeOn(mainScheduler) @@ -259,6 +283,9 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ fun onRefreshOrStopClick() { if (currentTab?.loadingProgress != 100) { currentTab?.stopLoading() @@ -267,6 +294,9 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ fun onSearch(query: String) { currentTab?.stopLoading() val searchUrl = searchEngineProvider.provideSearchEngine().queryUrl + QUERY_PLACE_HOLDER @@ -281,6 +311,9 @@ class BrowserPresenter @Inject constructor( currentTab?.loadUrl(url) } + /** + * TODO + */ fun onSearchSuggestionClicked(webPage: WebPage) { val url = when (webPage) { is HistoryEntry, @@ -292,10 +325,16 @@ class BrowserPresenter @Inject constructor( onSearch(url) } + /** + * TODO + */ fun onSslIconClick() { - + // TODO } + /** + * TODO + */ fun onBookmarkClick(index: Int) { when (val bookmark = viewState.bookmarks[index]) { is Bookmark.Entry -> currentTab?.loadUrl(bookmark.url) @@ -314,26 +353,44 @@ class BrowserPresenter @Inject constructor( } + /** + * TODO + */ fun onBookmarkLongClick(index: Int) { } + /** + * TODO + */ fun onToolsClick() { } + /** + * TODO + */ fun onStarClick() { } + /** + * TODO + */ fun onReadingModeClick() { } + /** + * TODO + */ fun onTabMenuClick() { } + /** + * TODO + */ fun onBookmarkMenuClick() { if (currentFolder != Bookmark.Folder.Root) { compositeDisposable += bookmarkRepository From 152f154313a309d488a46fa28d7606f086c8fea0 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 15 Sep 2020 00:47:42 -0400 Subject: [PATCH 009/161] immediately display URL that will be loaded and set title --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 14e958e5b..d1a9a0f2a 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -303,8 +303,8 @@ class BrowserPresenter @Inject constructor( val url = smartUrlFilter(query, true, searchUrl) view?.updateState(viewState.copy( displayUrl = searchBoxModel.getDisplayContent( - url = currentTab?.url.orEmpty(), - title = currentTab?.url, + url = url, + title = currentTab?.title, isLoading = (currentTab?.loadingProgress ?: 0) < 100 ) )) From ddda65c6af0aa5a3023f01e8416a1e9ae5976fed Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 15 Sep 2020 21:31:43 -0400 Subject: [PATCH 010/161] Support loading favicons in tabs and bookmarks --- .../lightning/_browser2/BrowserActivity.kt | 7 +- .../lightning/_browser2/BrowserPresenter.kt | 14 +++- .../bookmark/BookmarkRecyclerViewAdapter.kt | 7 +- .../lightning/_browser2/di/Browser2Module.kt | 5 +- .../_browser2/image/FaviconImageLoader.kt | 74 +++++++++++++++++++ .../lightning/_browser2/image/ImageLoader.kt | 13 ++++ .../browser/lightning/_browser2/tab/Tab.kt | 4 +- .../lightning/_browser2/tab/TabAdapter.kt | 11 ++- .../lightning/_browser2/tab/TabModel.kt | 5 ++ .../_browser2/tab/TabRecyclerViewAdapter.kt | 10 +-- .../_browser2/tab/TabWebChromeClient.kt | 9 ++- 11 files changed, 145 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/image/FaviconImageLoader.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/image/ImageLoader.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index b3403a5af..e97eecbb9 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning.R import acr.browser.lightning._browser2.bookmark.BookmarkRecyclerViewAdapter +import acr.browser.lightning._browser2.image.ImageLoader import acr.browser.lightning._browser2.search.SearchListener import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter import acr.browser.lightning.browser.activity.ThemableBrowserActivity @@ -29,6 +30,9 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { private lateinit var tabsAdapter: TabRecyclerViewAdapter private lateinit var bookmarksAdapter: BookmarkRecyclerViewAdapter + @Inject + internal lateinit var imageLoader: ImageLoader + @Inject internal lateinit var presenter: BrowserPresenter @@ -55,7 +59,8 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { bookmarksAdapter = BookmarkRecyclerViewAdapter( onClick = presenter::onBookmarkClick, - onLongClick = presenter::onBookmarkLongClick + onLongClick = presenter::onBookmarkLongClick, + imageLoader = imageLoader ) binding.bookmarkListView.adapter = bookmarksAdapter binding.bookmarkListView.layoutManager = LinearLayoutManager(this) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index d1a9a0f2a..54d06a081 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -55,6 +55,7 @@ class BrowserPresenter @Inject constructor( private val compositeDisposable = CompositeDisposable() private var sslDisposable: Disposable? = null private var titleDisposable: Disposable? = null + private var faviconDisposable: Disposable? = null private var urlDisposable: Disposable? = null private var loadingDisposable: Disposable? = null private var canGoBackDisposable: Disposable? = null @@ -92,7 +93,7 @@ class BrowserPresenter @Inject constructor( private fun TabModel.asViewState(): Tab = Tab( id = id, - icon = "", + icon = null, title = title, isSelected = isForeground ) @@ -154,6 +155,16 @@ class BrowserPresenter @Inject constructor( } } + faviconDisposable?.dispose() + faviconDisposable = tabModel?.faviconChanges() + ?.observeOn(mainScheduler) + ?.subscribe { favicon -> + view.updateState(viewState.copy(tabs = viewState.tabs.updateId(tabModel.id) { + it.copy(icon = favicon) + })) + } + + urlDisposable?.dispose() urlDisposable = tabModel?.urlChanges() ?.distinctUntilChanged() @@ -202,6 +213,7 @@ class BrowserPresenter @Inject constructor( sslDisposable?.dispose() titleDisposable?.dispose() + faviconDisposable?.dispose() urlDisposable?.dispose() loadingDisposable?.dispose() canGoBackDisposable?.dispose() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkRecyclerViewAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkRecyclerViewAdapter.kt index ac95d5ff1..d9848f747 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkRecyclerViewAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/bookmark/BookmarkRecyclerViewAdapter.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2.bookmark import acr.browser.lightning.R +import acr.browser.lightning._browser2.image.ImageLoader import acr.browser.lightning.database.Bookmark import android.view.LayoutInflater import android.view.ViewGroup @@ -12,7 +13,8 @@ import androidx.recyclerview.widget.ListAdapter */ class BookmarkRecyclerViewAdapter( private val onClick: (Int) -> Unit, - private val onLongClick: (Int) -> Unit + private val onLongClick: (Int) -> Unit, + private val imageLoader: ImageLoader ) : ListAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Bookmark, newItem: Bookmark): Boolean = @@ -37,7 +39,6 @@ class BookmarkRecyclerViewAdapter( val viewModel = getItem(position) holder.binding.textBookmark.text = viewModel.title - val url = viewModel.url - holder.binding.faviconBookmark.tag = url + imageLoader.loadImage(holder.binding.faviconBookmark, viewModel) } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index e486e5281..3a7215826 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -3,6 +3,8 @@ package acr.browser.lightning._browser2.di import acr.browser.lightning._browser2.BrowserContract import acr.browser.lightning._browser2.history.DefaultHistoryRecord import acr.browser.lightning._browser2.history.HistoryRecord +import acr.browser.lightning._browser2.image.FaviconImageLoader +import acr.browser.lightning._browser2.image.ImageLoader import acr.browser.lightning._browser2.tab.TabsRepository import dagger.Binds import dagger.Module @@ -19,5 +21,6 @@ interface Browser2Module { @Binds fun bindsHistoryRecord(defaultHistoryRecord: DefaultHistoryRecord): HistoryRecord - + @Binds + fun bindsFaviconImageLoader(faviconImageLoader: FaviconImageLoader): ImageLoader } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/image/FaviconImageLoader.kt b/app/src/main/java/acr/browser/lightning/_browser2/image/FaviconImageLoader.kt new file mode 100644 index 000000000..58837223b --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/image/FaviconImageLoader.kt @@ -0,0 +1,74 @@ +package acr.browser.lightning._browser2.image + +import acr.browser.lightning.R +import acr.browser.lightning.database.Bookmark +import acr.browser.lightning.di.MainScheduler +import acr.browser.lightning.di.NetworkScheduler +import acr.browser.lightning.extensions.drawable +import acr.browser.lightning.favicon.FaviconModel +import android.app.Application +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.util.LruCache +import android.widget.ImageView +import io.reactivex.Scheduler +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign +import io.reactivex.rxkotlin.subscribeBy +import javax.inject.Inject + +/** + * Created by anthonycr on 9/15/20. + */ +class FaviconImageLoader @Inject constructor( + private val faviconModel: FaviconModel, + application: Application, + @NetworkScheduler private val networkScheduler: Scheduler, + @MainScheduler private val mainScheduler: Scheduler +) : ImageLoader { + + private val lruCache: LruCache = LruCache(1 * 1000 * 1000) + private val folderIcon = application.drawable(R.drawable.ic_folder) + private val webpageIcon = application.drawable(R.drawable.ic_webpage) + private val compositeDisposable = CompositeDisposable() + + override fun loadImage(imageView: ImageView, bookmark: Bookmark) { + imageView.tag = bookmark.url + lruCache[bookmark.url]?.let { + if (it is Bitmap) { + imageView.setImageBitmap(it) + } else if (it is Drawable) { + imageView.setImageDrawable(it) + } + } ?: run { + when (bookmark) { + is Bookmark.Folder -> { + lruCache.put(bookmark.url, folderIcon) + imageView.setImageDrawable(folderIcon) + } + is Bookmark.Entry -> { + lruCache.put(bookmark.url, webpageIcon) + imageView.setImageDrawable(webpageIcon) + compositeDisposable += faviconModel + .faviconForUrl(bookmark.url, bookmark.title) + .subscribeOn(networkScheduler) + .observeOn(mainScheduler) + .subscribeBy( + onSuccess = { bitmap -> + lruCache.put(bookmark.url, bitmap) + if (imageView.tag == bookmark.url) { + imageView.setImageBitmap(bitmap) + } + } + ) + } + } + } + + fun cleanup() { + compositeDisposable.clear() + } + + + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/image/ImageLoader.kt b/app/src/main/java/acr/browser/lightning/_browser2/image/ImageLoader.kt new file mode 100644 index 000000000..034997412 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/image/ImageLoader.kt @@ -0,0 +1,13 @@ +package acr.browser.lightning._browser2.image + +import acr.browser.lightning.database.Bookmark +import android.widget.ImageView + +/** + * Created by anthonycr on 9/15/20. + */ +interface ImageLoader { + + fun loadImage(imageView: ImageView, bookmark: Bookmark) + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt index 082c37fc5..5acf16b5f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt @@ -1,11 +1,13 @@ package acr.browser.lightning._browser2.tab +import android.graphics.Bitmap + /** * Created by anthonycr on 9/11/20. */ data class Tab( val id: Int, - val icon: String, + val icon: Bitmap?, val title: String, val isSelected: Boolean ) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index 4cbe3c4a9..70551bc8f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -3,6 +3,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning.ssl.SslState import acr.browser.lightning.view.FreezableBundleInitializer import acr.browser.lightning.view.TabInitializer +import android.graphics.Bitmap import android.webkit.WebView import io.reactivex.Observable import io.reactivex.subjects.PublishSubject @@ -15,6 +16,7 @@ class TabAdapter( private val webView: WebView, ) : TabModel { + private val faviconObservable = PublishSubject.create() private val urlObservable = PublishSubject.create() private val sslStateObservable = PublishSubject.create() private val progressObservable = PublishSubject.create() @@ -33,7 +35,8 @@ class TabAdapter( webView.webViewClient = tabWebViewClient webView.webChromeClient = TabWebChromeClient( progressObservable = progressObservable, - titleObservable = titleObservable + titleObservable = titleObservable, + faviconObservable = faviconObservable ) if (tabInitializer is FreezableBundleInitializer) { latentInitializer = tabInitializer @@ -72,6 +75,12 @@ class TabAdapter( webView.stopLoading() } + override val favicon: Bitmap? + get() = webView.favicon + + override fun faviconChanges(): Observable = faviconObservable.hide() + + // TODO do we show "new tab" override val url: String get() = webView.url.orEmpty() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt index ac146ccf7..d0c804c6a 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning.ssl.SslState +import android.graphics.Bitmap import io.reactivex.Observable interface TabModel { @@ -29,6 +30,10 @@ interface TabModel { // Data + val favicon: Bitmap? + + fun faviconChanges(): Observable + val url: String fun urlChanges(): Observable diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt index da5cd8234..835d53e48 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt @@ -35,12 +35,12 @@ class TabRecyclerViewAdapter( override fun onBindViewHolder(holder: TabViewHolder, position: Int) { holder.exitButton.tag = position - val web = getItem(position) + val tab = getItem(position) - holder.txtTitle.text = web.title - updateViewHolderAppearance(holder, null, web.isSelected) - updateViewHolderFavicon(holder, null, web.isSelected) - updateViewHolderBackground(holder, web.isSelected) + holder.txtTitle.text = tab.title + updateViewHolderAppearance(holder, null, tab.isSelected) + updateViewHolderFavicon(holder, tab.icon, tab.isSelected) + updateViewHolderBackground(holder, tab.isSelected) } private fun updateViewHolderFavicon(viewHolder: TabViewHolder, favicon: Bitmap?, isForeground: Boolean) { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt index 9e3e42bd1..7bb7d6c54 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2.tab +import android.graphics.Bitmap import android.webkit.WebChromeClient import android.webkit.WebView import io.reactivex.subjects.PublishSubject @@ -9,7 +10,8 @@ import io.reactivex.subjects.PublishSubject */ class TabWebChromeClient( private val progressObservable: PublishSubject, - private val titleObservable: PublishSubject + private val titleObservable: PublishSubject, + private val faviconObservable: PublishSubject ) : WebChromeClient() { override fun onProgressChanged(view: WebView, newProgress: Int) { @@ -21,4 +23,9 @@ class TabWebChromeClient( super.onReceivedTitle(view, title) titleObservable.onNext(title) } + + override fun onReceivedIcon(view: WebView, icon: Bitmap) { + super.onReceivedIcon(view, icon) + faviconObservable.onNext(icon) + } } From 176ab385196cef10862a1a9c8a33f172a7d09e48 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 15 Sep 2020 21:51:32 -0400 Subject: [PATCH 011/161] Support menu clicks --- .../lightning/_browser2/BrowserActivity.kt | 25 ++++++++ .../lightning/_browser2/BrowserContract.kt | 23 ++++++++ .../lightning/_browser2/BrowserNavigator.kt | 24 ++++++++ .../lightning/_browser2/BrowserPresenter.kt | 58 +++++++++++++++---- .../lightning/_browser2/di/Browser2Module.kt | 4 ++ 5 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index e97eecbb9..850505780 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -15,6 +15,7 @@ import acr.browser.lightning.ssl.createSslDrawableForState import android.os.Bundle import android.view.LayoutInflater import android.view.Menu +import android.view.MenuItem import android.view.View import android.widget.AdapterView import android.widget.ImageView @@ -105,6 +106,30 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { return super.onCreateOptionsMenu(menu) } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val menu = when (item.itemId) { + android.R.id.home -> TODO() + R.id.action_back -> TODO() + R.id.action_forward -> TODO() + R.id.action_add_to_homescreen -> BrowserContract.Menu.ADD_TO_HOME + R.id.action_new_tab -> BrowserContract.Menu.NEW_TAB + R.id.action_incognito -> BrowserContract.Menu.NEW_INCOGNITO_TAB + R.id.action_share -> BrowserContract.Menu.SHARE + R.id.action_bookmarks -> BrowserContract.Menu.BOOKMARKS + R.id.action_copy -> BrowserContract.Menu.COPY_LINK + R.id.action_settings -> BrowserContract.Menu.SETTINGS + R.id.action_history -> BrowserContract.Menu.HISTORY + R.id.action_downloads -> BrowserContract.Menu.DOWNLOADS + R.id.action_add_bookmark -> BrowserContract.Menu.ADD_BOOKMARK + R.id.action_find -> BrowserContract.Menu.FIND + R.id.action_reading_mode -> BrowserContract.Menu.READER + else -> null + } + + return menu?.let(presenter::onMenuClick)?.let { true } + ?: super.onOptionsItemSelected(item) + } + override fun renderState(viewState: BrowserViewState) { binding.actionBack.isEnabled = viewState.isBackEnabled binding.actionForward.isEnabled = viewState.isForwardEnabled diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 5c30de238..2563f7aa0 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -36,4 +36,27 @@ interface BrowserContract { } + interface Navigator { + + fun openSettings() + + fun openReaderMode(url: String) + + } + + enum class Menu { + NEW_TAB, + NEW_INCOGNITO_TAB, + SHARE, + HISTORY, + DOWNLOADS, + FIND, + COPY_LINK, + ADD_TO_HOME, + BOOKMARKS, + ADD_BOOKMARK, + READER, + SETTINGS + } + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt new file mode 100644 index 000000000..688ce61e4 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt @@ -0,0 +1,24 @@ +package acr.browser.lightning._browser2 + +import acr.browser.lightning.reading.activity.ReadingActivity +import acr.browser.lightning.settings.activity.SettingsActivity +import android.app.Activity +import android.content.Intent +import javax.inject.Inject + +/** + * Created by anthonycr on 9/15/20. + */ +class BrowserNavigator @Inject constructor( + private val activity: Activity +) : BrowserContract.Navigator { + + override fun openSettings() { + activity.startActivity(Intent(activity, SettingsActivity::class.java)) + } + + override fun openReaderMode(url: String) { + ReadingActivity.launch(activity, url) + } + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 54d06a081..08e122ec1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -16,6 +16,8 @@ import acr.browser.lightning.ssl.SslState import acr.browser.lightning.utils.QUERY_PLACE_HOLDER import acr.browser.lightning.utils.isSpecialUrl import acr.browser.lightning.utils.smartUrlFilter +import acr.browser.lightning.view.DownloadPageInitializer +import acr.browser.lightning.view.HistoryPageInitializer import acr.browser.lightning.view.HomePageInitializer import io.reactivex.Scheduler import io.reactivex.disposables.CompositeDisposable @@ -28,11 +30,14 @@ import javax.inject.Inject */ class BrowserPresenter @Inject constructor( private val model: BrowserContract.Model, + private val navigator: BrowserContract.Navigator, private val bookmarkRepository: BookmarkRepository, @MainScheduler private val mainScheduler: Scheduler, @DatabaseScheduler private val databaseScheduler: Scheduler, private val historyRecord: HistoryRecord, private val homePageInitializer: HomePageInitializer, + private val historyPageInitializer: HistoryPageInitializer, + private val downloadPageInitializer: DownloadPageInitializer, private val searchBoxModel: SearchBoxModel, private val searchEngineProvider: SearchEngineProvider ) { @@ -91,6 +96,22 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ + fun onViewDetached() { + view = null + compositeDisposable.dispose() + + sslDisposable?.dispose() + titleDisposable?.dispose() + faviconDisposable?.dispose() + urlDisposable?.dispose() + loadingDisposable?.dispose() + canGoBackDisposable?.dispose() + canGoForwardDisposable?.dispose() + } + private fun TabModel.asViewState(): Tab = Tab( id = id, icon = null, @@ -207,17 +228,32 @@ class BrowserPresenter @Inject constructor( /** * TODO */ - fun onViewDetached() { - view = null - compositeDisposable.dispose() - - sslDisposable?.dispose() - titleDisposable?.dispose() - faviconDisposable?.dispose() - urlDisposable?.dispose() - loadingDisposable?.dispose() - canGoBackDisposable?.dispose() - canGoForwardDisposable?.dispose() + fun onMenuClick(menuOption: BrowserContract.Menu) { + when (menuOption) { + BrowserContract.Menu.NEW_TAB -> onNewTabClick() + BrowserContract.Menu.NEW_INCOGNITO_TAB -> TODO() + BrowserContract.Menu.SHARE -> TODO() + BrowserContract.Menu.HISTORY -> + compositeDisposable += model.createTab(historyPageInitializer) + .observeOn(mainScheduler) + .subscribe { tab -> + selectTab(model.selectTab(tab.id)) + } + BrowserContract.Menu.DOWNLOADS -> + compositeDisposable += model.createTab(downloadPageInitializer) + .observeOn(mainScheduler) + .subscribe { tab -> + selectTab(model.selectTab(tab.id)) + } + BrowserContract.Menu.FIND -> TODO() + BrowserContract.Menu.COPY_LINK -> TODO() + BrowserContract.Menu.ADD_TO_HOME -> TODO() + BrowserContract.Menu.BOOKMARKS -> TODO() + BrowserContract.Menu.ADD_BOOKMARK -> TODO() + BrowserContract.Menu.READER -> currentTab?.url?.takeIf { !it.isSpecialUrl() } + ?.let(navigator::openReaderMode) + BrowserContract.Menu.SETTINGS -> navigator.openSettings() + } } /** diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index 3a7215826..862c21ade 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2.di import acr.browser.lightning._browser2.BrowserContract +import acr.browser.lightning._browser2.BrowserNavigator import acr.browser.lightning._browser2.history.DefaultHistoryRecord import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.image.FaviconImageLoader @@ -23,4 +24,7 @@ interface Browser2Module { @Binds fun bindsFaviconImageLoader(faviconImageLoader: FaviconImageLoader): ImageLoader + + @Binds + fun bindsBrowserNavigator(browserNavigator: BrowserNavigator): BrowserContract.Navigator } From 921519d5d93cce65f817a48bb1fcfff18ce1b277 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 15 Sep 2020 22:42:46 -0400 Subject: [PATCH 012/161] Adding text changed listener to handle style removal --- .../java/acr/browser/lightning/_browser2/BrowserActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 850505780..2217e84e5 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -5,6 +5,7 @@ import acr.browser.lightning._browser2.bookmark.BookmarkRecyclerViewAdapter import acr.browser.lightning._browser2.image.ImageLoader import acr.browser.lightning._browser2.search.SearchListener import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter +import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher import acr.browser.lightning.browser.activity.ThemableBrowserActivity import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage @@ -88,6 +89,7 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { }) binding.search.setOnEditorActionListener(searchListener) binding.search.setOnKeyListener(searchListener) + binding.search.addTextChangedListener(StyleRemovingTextWatcher()) binding.actionBack.setOnClickListener { presenter.onBackClick() } binding.actionForward.setOnClickListener { presenter.onForwardClick() } From 3f4fdad70230c2499a9c60af8a2b50dc22d6b174 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 16 Sep 2020 08:36:15 -0400 Subject: [PATCH 013/161] Handle keyboard combos and handle menu clicks with the same style of adapter --- .../lightning/_browser2/BrowserActivity.kt | 39 ++++------ .../lightning/_browser2/BrowserContract.kt | 15 +--- .../lightning/_browser2/BrowserPresenter.kt | 56 +++++++++++---- .../lightning/_browser2/keys/KeyCombo.kt | 25 +++++++ .../_browser2/keys/KeyEventAdapter.kt | 71 +++++++++++++++++++ .../_browser2/menu/MenuItemAdapter.kt | 33 +++++++++ .../lightning/_browser2/menu/MenuSelection.kt | 19 +++++ 7 files changed, 206 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/keys/KeyCombo.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/menu/MenuItemAdapter.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/menu/MenuSelection.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 2217e84e5..b958edaa8 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -3,6 +3,8 @@ package acr.browser.lightning._browser2 import acr.browser.lightning.R import acr.browser.lightning._browser2.bookmark.BookmarkRecyclerViewAdapter import acr.browser.lightning._browser2.image.ImageLoader +import acr.browser.lightning._browser2.keys.KeyEventAdapter +import acr.browser.lightning._browser2.menu.MenuItemAdapter import acr.browser.lightning._browser2.search.SearchListener import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher @@ -14,10 +16,7 @@ import acr.browser.lightning.di.injector import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState import android.os.Bundle -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuItem -import android.view.View +import android.view.* import android.widget.AdapterView import android.widget.ImageView import androidx.recyclerview.widget.LinearLayoutManager @@ -35,6 +34,12 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { @Inject internal lateinit var imageLoader: ImageLoader + @Inject + internal lateinit var keyEventAdapter: KeyEventAdapter + + @Inject + internal lateinit var menuItemAdapter: MenuItemAdapter + @Inject internal lateinit var presenter: BrowserPresenter @@ -109,29 +114,15 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { } override fun onOptionsItemSelected(item: MenuItem): Boolean { - val menu = when (item.itemId) { - android.R.id.home -> TODO() - R.id.action_back -> TODO() - R.id.action_forward -> TODO() - R.id.action_add_to_homescreen -> BrowserContract.Menu.ADD_TO_HOME - R.id.action_new_tab -> BrowserContract.Menu.NEW_TAB - R.id.action_incognito -> BrowserContract.Menu.NEW_INCOGNITO_TAB - R.id.action_share -> BrowserContract.Menu.SHARE - R.id.action_bookmarks -> BrowserContract.Menu.BOOKMARKS - R.id.action_copy -> BrowserContract.Menu.COPY_LINK - R.id.action_settings -> BrowserContract.Menu.SETTINGS - R.id.action_history -> BrowserContract.Menu.HISTORY - R.id.action_downloads -> BrowserContract.Menu.DOWNLOADS - R.id.action_add_bookmark -> BrowserContract.Menu.ADD_BOOKMARK - R.id.action_find -> BrowserContract.Menu.FIND - R.id.action_reading_mode -> BrowserContract.Menu.READER - else -> null - } - - return menu?.let(presenter::onMenuClick)?.let { true } + return menuItemAdapter.adaptMenuItem(item)?.let(presenter::onMenuClick)?.let { true } ?: super.onOptionsItemSelected(item) } + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + return keyEventAdapter.adaptKeyEvent(event)?.let(presenter::onKeyComboClick)?.let { true } + ?: super.dispatchKeyEvent(event) + } + override fun renderState(viewState: BrowserViewState) { binding.actionBack.isEnabled = viewState.isBackEnabled binding.actionForward.isEnabled = viewState.isForwardEnabled diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 2563f7aa0..d94ff122e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -44,19 +44,6 @@ interface BrowserContract { } - enum class Menu { - NEW_TAB, - NEW_INCOGNITO_TAB, - SHARE, - HISTORY, - DOWNLOADS, - FIND, - COPY_LINK, - ADD_TO_HOME, - BOOKMARKS, - ADD_BOOKMARK, - READER, - SETTINGS - } + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 08e122ec1..9fbb3d24e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -1,6 +1,8 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.history.HistoryRecord +import acr.browser.lightning._browser2.keys.KeyCombo +import acr.browser.lightning._browser2.menu.MenuSelection import acr.browser.lightning._browser2.tab.Tab import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning.browser.SearchBoxModel @@ -228,31 +230,57 @@ class BrowserPresenter @Inject constructor( /** * TODO */ - fun onMenuClick(menuOption: BrowserContract.Menu) { - when (menuOption) { - BrowserContract.Menu.NEW_TAB -> onNewTabClick() - BrowserContract.Menu.NEW_INCOGNITO_TAB -> TODO() - BrowserContract.Menu.SHARE -> TODO() - BrowserContract.Menu.HISTORY -> + fun onMenuClick(menuSelection: MenuSelection) { + when (menuSelection) { + MenuSelection.NEW_TAB -> onNewTabClick() + MenuSelection.NEW_INCOGNITO_TAB -> TODO() + MenuSelection.SHARE -> TODO() + MenuSelection.HISTORY -> compositeDisposable += model.createTab(historyPageInitializer) .observeOn(mainScheduler) .subscribe { tab -> selectTab(model.selectTab(tab.id)) } - BrowserContract.Menu.DOWNLOADS -> + MenuSelection.DOWNLOADS -> compositeDisposable += model.createTab(downloadPageInitializer) .observeOn(mainScheduler) .subscribe { tab -> selectTab(model.selectTab(tab.id)) } - BrowserContract.Menu.FIND -> TODO() - BrowserContract.Menu.COPY_LINK -> TODO() - BrowserContract.Menu.ADD_TO_HOME -> TODO() - BrowserContract.Menu.BOOKMARKS -> TODO() - BrowserContract.Menu.ADD_BOOKMARK -> TODO() - BrowserContract.Menu.READER -> currentTab?.url?.takeIf { !it.isSpecialUrl() } + MenuSelection.FIND -> TODO() + MenuSelection.COPY_LINK -> TODO() + MenuSelection.ADD_TO_HOME -> TODO() + MenuSelection.BOOKMARKS -> TODO() + MenuSelection.ADD_BOOKMARK -> TODO() + MenuSelection.READER -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let(navigator::openReaderMode) - BrowserContract.Menu.SETTINGS -> navigator.openSettings() + MenuSelection.SETTINGS -> navigator.openSettings() + } + } + + /** + * TODO + */ + fun onKeyComboClick(keyCombo: KeyCombo) { + when (keyCombo) { + KeyCombo.CTRL_F -> TODO() + KeyCombo.CTRL_T -> onNewTabClick() + KeyCombo.CTRL_W -> TODO() + KeyCombo.CTRL_Q -> TODO() + KeyCombo.CTRL_R -> onRefreshOrStopClick() + KeyCombo.CTRL_TAB -> TODO() + KeyCombo.CTRL_SHIFT_TAB -> TODO() + KeyCombo.SEARCH -> TODO() + KeyCombo.ALT_0 -> TODO() + KeyCombo.ALT_1 -> TODO() + KeyCombo.ALT_2 -> TODO() + KeyCombo.ALT_3 -> TODO() + KeyCombo.ALT_4 -> TODO() + KeyCombo.ALT_5 -> TODO() + KeyCombo.ALT_6 -> TODO() + KeyCombo.ALT_7 -> TODO() + KeyCombo.ALT_8 -> TODO() + KeyCombo.ALT_9 -> TODO() } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyCombo.kt b/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyCombo.kt new file mode 100644 index 000000000..5e2bd5eec --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyCombo.kt @@ -0,0 +1,25 @@ +package acr.browser.lightning._browser2.keys + +/** + * Created by anthonycr on 9/16/20. + */ +enum class KeyCombo { + CTRL_F, + CTRL_T, + CTRL_W, + CTRL_Q, + CTRL_R, + CTRL_TAB, + CTRL_SHIFT_TAB, + SEARCH, + ALT_0, + ALT_1, + ALT_2, + ALT_3, + ALT_4, + ALT_5, + ALT_6, + ALT_7, + ALT_8, + ALT_9, +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt new file mode 100644 index 000000000..568037912 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt @@ -0,0 +1,71 @@ +package acr.browser.lightning._browser2.keys + +import android.view.KeyEvent +import javax.inject.Inject + +/** + * Created by anthonycr on 9/16/20. + */ +class KeyEventAdapter @Inject constructor() { + + fun adaptKeyEvent(event: KeyEvent): KeyCombo? { + when { + event.isCtrlPressed -> when (event.keyCode) { + KeyEvent.KEYCODE_F -> { + // Search in page + return KeyCombo.CTRL_F + } + KeyEvent.KEYCODE_T -> { + // New tab + return KeyCombo.CTRL_T + } + KeyEvent.KEYCODE_W -> { + // Close current tab + return KeyCombo.CTRL_W + } + KeyEvent.KEYCODE_Q -> { + // Close browser + return KeyCombo.CTRL_Q + } + KeyEvent.KEYCODE_R -> { + // Refresh + return KeyCombo.CTRL_R + } + KeyEvent.KEYCODE_TAB -> { + return if (event.isShiftPressed) { + // Go back one tab + KeyCombo.CTRL_SHIFT_TAB + } else { + // Go forward one tab + KeyCombo.CTRL_TAB + } + } + } + event.keyCode == KeyEvent.KEYCODE_SEARCH -> { + // Highlight search field + return KeyCombo.SEARCH + } + event.isAltPressed -> { + // Alt + tab number + if (event.keyCode in KeyEvent.KEYCODE_0..KeyEvent.KEYCODE_9) { + // Choose tab by number + return when (event.keyCode) { + KeyEvent.KEYCODE_0 -> KeyCombo.ALT_0 + KeyEvent.KEYCODE_1 -> KeyCombo.ALT_1 + KeyEvent.KEYCODE_2 -> KeyCombo.ALT_2 + KeyEvent.KEYCODE_3 -> KeyCombo.ALT_3 + KeyEvent.KEYCODE_4 -> KeyCombo.ALT_4 + KeyEvent.KEYCODE_5 -> KeyCombo.ALT_5 + KeyEvent.KEYCODE_6 -> KeyCombo.ALT_6 + KeyEvent.KEYCODE_7 -> KeyCombo.ALT_8 + KeyEvent.KEYCODE_8 -> KeyCombo.ALT_8 + KeyEvent.KEYCODE_9 -> KeyCombo.ALT_9 + else -> null + } + } + } + } + return null + } + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuItemAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuItemAdapter.kt new file mode 100644 index 000000000..476f2c53d --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuItemAdapter.kt @@ -0,0 +1,33 @@ +package acr.browser.lightning._browser2.menu + +import acr.browser.lightning.R +import android.view.MenuItem +import javax.inject.Inject + +/** + * Created by anthonycr on 9/16/20. + */ +class MenuItemAdapter @Inject constructor() { + + fun adaptMenuItem(menuItem: MenuItem): MenuSelection? { + return when (menuItem.itemId) { + android.R.id.home -> TODO() + R.id.action_back -> TODO() + R.id.action_forward -> TODO() + R.id.action_add_to_homescreen -> MenuSelection.ADD_TO_HOME + R.id.action_new_tab -> MenuSelection.NEW_TAB + R.id.action_incognito -> MenuSelection.NEW_INCOGNITO_TAB + R.id.action_share -> MenuSelection.SHARE + R.id.action_bookmarks -> MenuSelection.BOOKMARKS + R.id.action_copy -> MenuSelection.COPY_LINK + R.id.action_settings -> MenuSelection.SETTINGS + R.id.action_history -> MenuSelection.HISTORY + R.id.action_downloads -> MenuSelection.DOWNLOADS + R.id.action_add_bookmark -> MenuSelection.ADD_BOOKMARK + R.id.action_find -> MenuSelection.FIND + R.id.action_reading_mode -> MenuSelection.READER + else -> null + } + } + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuSelection.kt b/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuSelection.kt new file mode 100644 index 000000000..27df46bf0 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuSelection.kt @@ -0,0 +1,19 @@ +package acr.browser.lightning._browser2.menu + +/** + * Created by anthonycr on 9/16/20. + */ +enum class MenuSelection { + NEW_TAB, + NEW_INCOGNITO_TAB, + SHARE, + HISTORY, + DOWNLOADS, + FIND, + COPY_LINK, + ADD_TO_HOME, + BOOKMARKS, + ADD_BOOKMARK, + READER, + SETTINGS +} From 430f5bf9de19dc4af2b89957bd566f39175398a6 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 16 Sep 2020 08:47:01 -0400 Subject: [PATCH 014/161] Rename Tab to more descriptive TabViewState --- .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 8 ++++---- .../acr/browser/lightning/_browser2/BrowserViewState.kt | 4 ++-- .../lightning/_browser2/tab/TabRecyclerViewAdapter.kt | 8 ++++---- .../lightning/_browser2/tab/{Tab.kt => TabViewState.kt} | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) rename app/src/main/java/acr/browser/lightning/_browser2/tab/{Tab.kt => TabViewState.kt} (89%) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 9fbb3d24e..090f382ac 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -3,7 +3,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.keys.KeyCombo import acr.browser.lightning._browser2.menu.MenuSelection -import acr.browser.lightning._browser2.tab.Tab +import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning.browser.SearchBoxModel import acr.browser.lightning.database.Bookmark @@ -114,14 +114,14 @@ class BrowserPresenter @Inject constructor( canGoForwardDisposable?.dispose() } - private fun TabModel.asViewState(): Tab = Tab( + private fun TabModel.asViewState(): TabViewState = TabViewState( id = id, - icon = null, + icon = favicon, title = title, isSelected = isForeground ) - private fun List.updateId(id: Int, map: (Tab) -> Tab): List = map { + private fun List.updateId(id: Int, map: (TabViewState) -> TabViewState): List = map { if (it.id == id) { map(it) } else { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt index 5d21feb93..f9ed9f0a7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt @@ -1,6 +1,6 @@ package acr.browser.lightning._browser2 -import acr.browser.lightning._browser2.tab.Tab +import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning.database.Bookmark import acr.browser.lightning.ssl.SslState @@ -15,7 +15,7 @@ data class BrowserViewState( val progress: Int, // Tabs - val tabs: List, + val tabs: List, val isForwardEnabled: Boolean, val isBackEnabled: Boolean, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt index 835d53e48..1626301fc 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt @@ -17,12 +17,12 @@ class TabRecyclerViewAdapter( private val onClick: (Int) -> Unit, private val onLongClick: (Int) -> Unit, private val onCloseClick: (Int) -> Unit, -) : ListAdapter( - object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Tab, newItem: Tab): Boolean = +) : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TabViewState, newItem: TabViewState): Boolean = oldItem.id == newItem.id - override fun areContentsTheSame(oldItem: Tab, newItem: Tab): Boolean = oldItem == newItem + override fun areContentsTheSame(oldItem: TabViewState, newItem: TabViewState): Boolean = oldItem == newItem } ) { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabViewState.kt similarity index 89% rename from app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt rename to app/src/main/java/acr/browser/lightning/_browser2/tab/TabViewState.kt index 5acf16b5f..d02243d58 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/Tab.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabViewState.kt @@ -5,7 +5,7 @@ import android.graphics.Bitmap /** * Created by anthonycr on 9/11/20. */ -data class Tab( +data class TabViewState( val id: Int, val icon: Bitmap?, val title: String, From e29ab821a7b1541e7be7d86624f63f2527f15344 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 16 Sep 2020 22:04:53 -0400 Subject: [PATCH 015/161] Support share and copy link --- .../lightning/_browser2/BrowserContract.kt | 3 +++ .../lightning/_browser2/BrowserNavigator.kt | 17 ++++++++++++++++- .../lightning/_browser2/BrowserPresenter.kt | 7 +++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index d94ff122e..3f1e4d08b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -42,6 +42,9 @@ interface BrowserContract { fun openReaderMode(url: String) + fun sharePage(url: String, title: String?) + + fun copyPageLink(url: String) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt index 688ce61e4..22b663e38 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt @@ -1,8 +1,13 @@ package acr.browser.lightning._browser2 +import acr.browser.lightning.R +import acr.browser.lightning.extensions.copyToClipboard +import acr.browser.lightning.extensions.snackbar import acr.browser.lightning.reading.activity.ReadingActivity import acr.browser.lightning.settings.activity.SettingsActivity +import acr.browser.lightning.utils.IntentUtils import android.app.Activity +import android.content.ClipboardManager import android.content.Intent import javax.inject.Inject @@ -10,7 +15,8 @@ import javax.inject.Inject * Created by anthonycr on 9/15/20. */ class BrowserNavigator @Inject constructor( - private val activity: Activity + private val activity: Activity, + private val clipboardManager: ClipboardManager ) : BrowserContract.Navigator { override fun openSettings() { @@ -21,4 +27,13 @@ class BrowserNavigator @Inject constructor( ReadingActivity.launch(activity, url) } + override fun sharePage(url: String, title: String?) { + IntentUtils(activity).shareUrl(url, title) + } + + override fun copyPageLink(url: String) { + clipboardManager.copyToClipboard(url) + activity.snackbar(R.string.message_link_copied) + } + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 090f382ac..76b5ada0f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -234,7 +234,9 @@ class BrowserPresenter @Inject constructor( when (menuSelection) { MenuSelection.NEW_TAB -> onNewTabClick() MenuSelection.NEW_INCOGNITO_TAB -> TODO() - MenuSelection.SHARE -> TODO() + MenuSelection.SHARE -> currentTab?.url?.takeIf { !it.isSpecialUrl() }?.let { + navigator.sharePage(url = it, title = currentTab?.title) + } MenuSelection.HISTORY -> compositeDisposable += model.createTab(historyPageInitializer) .observeOn(mainScheduler) @@ -248,7 +250,8 @@ class BrowserPresenter @Inject constructor( selectTab(model.selectTab(tab.id)) } MenuSelection.FIND -> TODO() - MenuSelection.COPY_LINK -> TODO() + MenuSelection.COPY_LINK -> currentTab?.url?.takeIf { !it.isSpecialUrl() } + ?.let(navigator::copyPageLink) MenuSelection.ADD_TO_HOME -> TODO() MenuSelection.BOOKMARKS -> TODO() MenuSelection.ADD_BOOKMARK -> TODO() From 1f857a27887b91f67374381e261a568fd6d85462 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 16 Sep 2020 23:23:49 -0400 Subject: [PATCH 016/161] Handle focus issues in the search view --- .../lightning/_browser2/BrowserActivity.kt | 7 ++- .../lightning/_browser2/BrowserPresenter.kt | 54 +++++++++++++++---- .../_browser2/search/SearchListener.kt | 8 ++- .../acr/browser/lightning/view/SearchView.kt | 4 ++ 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index b958edaa8..a990d6ca3 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -17,6 +17,7 @@ import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState import android.os.Bundle import android.view.* +import android.view.inputmethod.InputMethodManager import android.widget.AdapterView import android.widget.ImageView import androidx.recyclerview.widget.LinearLayoutManager @@ -40,6 +41,9 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { @Inject internal lateinit var menuItemAdapter: MenuItemAdapter + @Inject + internal lateinit var inputMethodManager: InputMethodManager + @Inject internal lateinit var presenter: BrowserPresenter @@ -91,10 +95,11 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { binding.search.setAdapter(suggestionsAdapter) val searchListener = SearchListener(onConfirm = { presenter.onSearch(binding.search.text.toString()) - }) + }, inputMethodManager) binding.search.setOnEditorActionListener(searchListener) binding.search.setOnKeyListener(searchListener) binding.search.addTextChangedListener(StyleRemovingTextWatcher()) + binding.search.setOnFocusChangeListener { _, hasFocus -> presenter.onSearchFocusChanged(hasFocus) } binding.actionBack.setOnClickListener { presenter.onBackClick() } binding.actionForward.setOnClickListener { presenter.onForwardClick() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 76b5ada0f..c1d5df345 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -58,6 +58,7 @@ class BrowserPresenter @Inject constructor( ) private var currentTab: TabModel? = null private var currentFolder: Bookmark.Folder = Bookmark.Folder.Root + private var isSearchViewFocused = false private val compositeDisposable = CompositeDisposable() private var sslDisposable: Disposable? = null @@ -161,7 +162,13 @@ class BrowserPresenter @Inject constructor( ?.distinctUntilChanged() ?.observeOn(mainScheduler) ?.subscribe { - view.updateState(viewState.copy(sslState = it)) + view.updateState(viewState.copy( + sslState = if (isSearchViewFocused) { + SslState.None + } else { + it + } + )) } titleDisposable?.dispose() @@ -193,13 +200,15 @@ class BrowserPresenter @Inject constructor( ?.distinctUntilChanged() ?.observeOn(mainScheduler) ?.subscribe { - view.updateState(viewState.copy( - displayUrl = searchBoxModel.getDisplayContent( - url = tabModel.url, - title = tabModel.title, - isLoading = (tabModel.loadingProgress ?: 0) < 100 - ) - )) + if (isSearchViewFocused) { + view.updateState(viewState.copy( + displayUrl = searchBoxModel.getDisplayContent( + url = tabModel.url, + title = tabModel.title, + isLoading = tabModel.loadingProgress < 100 + ) + )) + } } loadingDisposable?.dispose() @@ -207,7 +216,10 @@ class BrowserPresenter @Inject constructor( ?.distinctUntilChanged() ?.observeOn(mainScheduler) ?.subscribe { - view.updateState(viewState.copy(progress = it, isRefresh = it == 100)) + view.updateState(viewState.copy( + progress = it, + isRefresh = it == 100 && !isSearchViewFocused + )) } canGoBackDisposable?.dispose() @@ -366,6 +378,10 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onRefreshOrStopClick() { + if (isSearchViewFocused) { + view?.renderState(viewState.copy(displayUrl = "")) + return + } if (currentTab?.loadingProgress != 100) { currentTab?.stopLoading() } else { @@ -373,6 +389,26 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ + fun onSearchFocusChanged(isFocused: Boolean) { + isSearchViewFocused = isFocused + if (isFocused) { + view?.updateState(viewState.copy(sslState = SslState.None, isRefresh = false)) + } else { + view?.updateState(viewState.copy( + sslState = currentTab?.sslState ?: SslState.None, + isRefresh = (currentTab?.loadingProgress ?: 0) == 100, + displayUrl = searchBoxModel.getDisplayContent( + url = currentTab?.url.orEmpty(), + title = currentTab?.title.orEmpty(), + isLoading = (currentTab?.loadingProgress ?: 0) < 100 + ) + )) + } + } + /** * TODO */ diff --git a/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt b/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt index cf7dc8fe7..be3fecbd1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt @@ -3,6 +3,7 @@ package acr.browser.lightning._browser2.search import android.view.KeyEvent import android.view.View import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager import android.widget.TextView /** @@ -10,6 +11,7 @@ import android.widget.TextView */ class SearchListener( private val onConfirm: () -> Unit, + private val inputMethodManager: InputMethodManager ) : View.OnKeyListener, TextView.OnEditorActionListener { override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean { @@ -18,6 +20,8 @@ class SearchListener( } return when (keyCode) { KeyEvent.KEYCODE_ENTER -> { + view.clearFocus() + inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) onConfirm() true } @@ -25,13 +29,15 @@ class SearchListener( } } - override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent?): Boolean { + override fun onEditorAction(view: TextView, actionId: Int, event: KeyEvent?): Boolean { if (actionId == EditorInfo.IME_ACTION_GO || actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_ACTION_SEND || actionId == EditorInfo.IME_ACTION_SEARCH || event?.action == KeyEvent.KEYCODE_ENTER) { + view.clearFocus() + inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) onConfirm() return true } diff --git a/app/src/main/java/acr/browser/lightning/view/SearchView.kt b/app/src/main/java/acr/browser/lightning/view/SearchView.kt index 5a77221a7..4dceba08b 100644 --- a/app/src/main/java/acr/browser/lightning/view/SearchView.kt +++ b/app/src/main/java/acr/browser/lightning/view/SearchView.kt @@ -22,6 +22,10 @@ class SearchView @JvmOverloads constructor( private var isBeingClicked: Boolean = false private var timePressedNs: Long = 0 + init { + setSelectAllOnFocus(true) + } + override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_DOWN -> { From e2f110602af712ea0a71703775b16bacc065d4cc Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 16 Sep 2020 23:50:48 -0400 Subject: [PATCH 017/161] Fixing search focus display of URL --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index c1d5df345..dba88df10 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -200,7 +200,7 @@ class BrowserPresenter @Inject constructor( ?.distinctUntilChanged() ?.observeOn(mainScheduler) ?.subscribe { - if (isSearchViewFocused) { + if (!isSearchViewFocused) { view.updateState(viewState.copy( displayUrl = searchBoxModel.getDisplayContent( url = tabModel.url, From 3ff5f8ccd9bd04f2cd391dfdbc79f2917b15c31b Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 16 Sep 2020 23:53:06 -0400 Subject: [PATCH 018/161] Create partial state class that allows the diff in the state to be rendered by the view without re-rendering everything --- .../lightning/_browser2/BrowserActivity.kt | 39 ++++++++++-------- .../_browser2/BrowserStateAdapter.kt | 41 +++++++++++++++++++ .../lightning/_browser2/BrowserViewState.kt | 17 ++++++++ 3 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index a990d6ca3..e299ca320 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -26,7 +26,7 @@ import javax.inject.Inject /** * Created by anthonycr on 9/11/20. */ -class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { +class BrowserActivity : ThemableBrowserActivity() { private lateinit var binding: BrowserActivityBinding private lateinit var tabsAdapter: TabRecyclerViewAdapter @@ -76,7 +76,7 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { binding.bookmarkListView.adapter = bookmarksAdapter binding.bookmarkListView.layoutManager = LinearLayoutManager(this) - presenter.onViewAttached(this) + presenter.onViewAttached(BrowserStateAdapter(this)) val suggestionsAdapter = SuggestionsAdapter(this, isIncognito = false).apply { onSuggestionInsertClick = { @@ -128,21 +128,26 @@ class BrowserActivity : ThemableBrowserActivity(), BrowserContract.View { ?: super.dispatchKeyEvent(event) } - override fun renderState(viewState: BrowserViewState) { - binding.actionBack.isEnabled = viewState.isBackEnabled - binding.actionForward.isEnabled = viewState.isForwardEnabled - binding.search.setText(viewState.displayUrl) - binding.searchSslStatus.setImageDrawable(createSslDrawableForState(viewState.sslState)) - binding.searchSslStatus.updateVisibilityForDrawable() - binding.tabCountView.updateCount(viewState.tabs.size) - binding.progressView.progress = viewState.progress - binding.searchRefresh.setImageResource(if (viewState.isRefresh) { - R.drawable.ic_action_refresh - } else { - R.drawable.ic_action_delete - }) - tabsAdapter.submitList(viewState.tabs) - bookmarksAdapter.submitList(viewState.bookmarks) + fun renderState(viewState: PartialBrowserViewState) { + viewState.isBackEnabled?.let { binding.actionBack.isEnabled = it } + viewState.isForwardEnabled?.let { binding.actionForward.isEnabled = it } + viewState.displayUrl?.let(binding.search::setText) + viewState.sslState?.let { + binding.searchSslStatus.setImageDrawable(createSslDrawableForState(it)) + binding.searchSslStatus.updateVisibilityForDrawable() + } + viewState.tabs?.let { binding.tabCountView.updateCount(viewState.tabs.size) } + viewState.progress?.let { binding.progressView.progress = it } + viewState.isRefresh?.let { + binding.searchRefresh.setImageResource(if (viewState.isRefresh) { + R.drawable.ic_action_refresh + } else { + R.drawable.ic_action_delete + }) + } + viewState.tabs?.let { tabsAdapter.submitList(viewState.tabs) } + viewState.bookmarks?.let { bookmarksAdapter.submitList(viewState.bookmarks) } + } private fun ImageView.updateVisibilityForDrawable() { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt new file mode 100644 index 000000000..b6a6b0dd9 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -0,0 +1,41 @@ +package acr.browser.lightning._browser2 + + +/** + * Created by anthonycr on 9/16/20. + */ +class BrowserStateAdapter(private val browserActivity: BrowserActivity) : BrowserContract.View { + + private var currentState: BrowserViewState? = null + + override fun renderState(viewState: BrowserViewState) { + val ( + displayUrl, + sslState, + isRefresh, + progress, + tabs, + isForwardEnabled, + isBackEnabled, + bookmarks, + isBookmarked + ) = viewState + + browserActivity.renderState( + PartialBrowserViewState( + displayUrl = displayUrl.takeIf { it != currentState?.displayUrl }, + sslState = sslState.takeIf { it != currentState?.sslState }, + isRefresh = isRefresh.takeIf { it != currentState?.isRefresh }, + progress = progress.takeIf { it != currentState?.progress }, + tabs = tabs.takeIf { it != currentState?.tabs }, + isForwardEnabled = isForwardEnabled.takeIf { it != currentState?.isForwardEnabled }, + isBackEnabled = isBackEnabled.takeIf { it != currentState?.isBackEnabled }, + bookmarks = bookmarks.takeIf { it != currentState?.bookmarks }, + isBookmarked = isBookmarked.takeIf { it != currentState?.isBookmarked } + ) + ) + + currentState = viewState + } + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt index f9ed9f0a7..5bd26b94a 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt @@ -23,3 +23,20 @@ data class BrowserViewState( val bookmarks: List, val isBookmarked: Boolean ) + +data class PartialBrowserViewState( + // search bar + val displayUrl: String?, + val sslState: SslState?, + val isRefresh: Boolean?, + val progress: Int?, + + // Tabs + val tabs: List?, + val isForwardEnabled: Boolean?, + val isBackEnabled: Boolean?, + + // Bookmarks + val bookmarks: List?, + val isBookmarked: Boolean? +) From bd9e62474c4b99fe44cd93e9689268a0baebc17e Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 17 Sep 2020 21:00:51 -0400 Subject: [PATCH 019/161] Support adblocking --- .../_browser2/di/Browser2BindsModule.kt | 30 +++++++++++ .../_browser2/di/Browser2Component.kt | 2 +- .../lightning/_browser2/di/Browser2Module.kt | 38 +++++++------ .../lightning/_browser2/tab/TabAdapter.kt | 36 ++++--------- .../_browser2/tab/TabWebChromeClient.kt | 10 ++-- .../_browser2/tab/TabWebViewClient.kt | 53 ++++++++++++++++--- .../lightning/_browser2/tab/TabsRepository.kt | 14 +++-- 7 files changed, 120 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt new file mode 100644 index 000000000..cd1105558 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt @@ -0,0 +1,30 @@ +package acr.browser.lightning._browser2.di + +import acr.browser.lightning._browser2.BrowserContract +import acr.browser.lightning._browser2.BrowserNavigator +import acr.browser.lightning._browser2.history.DefaultHistoryRecord +import acr.browser.lightning._browser2.history.HistoryRecord +import acr.browser.lightning._browser2.image.FaviconImageLoader +import acr.browser.lightning._browser2.image.ImageLoader +import acr.browser.lightning._browser2.tab.TabsRepository +import dagger.Binds +import dagger.Module + +/** + * Created by anthonycr on 9/15/20. + */ +@Module +interface Browser2BindsModule { + + @Binds + fun bindsBrowserModel(tabsRepository: TabsRepository): BrowserContract.Model + + @Binds + fun bindsHistoryRecord(defaultHistoryRecord: DefaultHistoryRecord): HistoryRecord + + @Binds + fun bindsFaviconImageLoader(faviconImageLoader: FaviconImageLoader): ImageLoader + + @Binds + fun bindsBrowserNavigator(browserNavigator: BrowserNavigator): BrowserContract.Navigator +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt index ea1921134..77c5e6fa1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt @@ -9,7 +9,7 @@ import dagger.Subcomponent /** * Created by anthonycr on 9/15/20. */ -@Subcomponent(modules = [Browser2Module::class]) +@Subcomponent(modules = [Browser2Module::class, Browser2BindsModule::class]) interface Browser2Component { @Subcomponent.Builder diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index 862c21ade..239182675 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -1,30 +1,28 @@ package acr.browser.lightning._browser2.di -import acr.browser.lightning._browser2.BrowserContract -import acr.browser.lightning._browser2.BrowserNavigator -import acr.browser.lightning._browser2.history.DefaultHistoryRecord -import acr.browser.lightning._browser2.history.HistoryRecord -import acr.browser.lightning._browser2.image.FaviconImageLoader -import acr.browser.lightning._browser2.image.ImageLoader -import acr.browser.lightning._browser2.tab.TabsRepository -import dagger.Binds +import acr.browser.lightning.adblock.AdBlocker +import acr.browser.lightning.adblock.BloomFilterAdBlocker +import acr.browser.lightning.adblock.NoOpAdBlocker +import acr.browser.lightning.preference.UserPreferences import dagger.Module +import dagger.Provides +import javax.inject.Provider /** - * Created by anthonycr on 9/15/20. + * Created by anthonycr on 9/17/20. */ @Module -interface Browser2Module { +class Browser2Module { - @Binds - fun bindsBrowserModel(tabsRepository: TabsRepository): BrowserContract.Model + @Provides + fun providesAdBlocker( + userPreferences: UserPreferences, + bloomFilterAdBlocker: Provider, + noOpAdBlocker: NoOpAdBlocker + ): AdBlocker = if (userPreferences.adBlockEnabled) { + bloomFilterAdBlocker.get() + } else { + noOpAdBlocker + } - @Binds - fun bindsHistoryRecord(defaultHistoryRecord: DefaultHistoryRecord): HistoryRecord - - @Binds - fun bindsFaviconImageLoader(faviconImageLoader: FaviconImageLoader): ImageLoader - - @Binds - fun bindsBrowserNavigator(browserNavigator: BrowserNavigator): BrowserContract.Navigator } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index 70551bc8f..8e3462367 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -6,7 +6,6 @@ import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap import android.webkit.WebView import io.reactivex.Observable -import io.reactivex.subjects.PublishSubject /** * Created by anthonycr on 9/12/20. @@ -14,30 +13,15 @@ import io.reactivex.subjects.PublishSubject class TabAdapter( tabInitializer: TabInitializer, private val webView: WebView, + private val tabWebViewClient: TabWebViewClient, + private val tabWebChromeClient: TabWebChromeClient ) : TabModel { - private val faviconObservable = PublishSubject.create() - private val urlObservable = PublishSubject.create() - private val sslStateObservable = PublishSubject.create() - private val progressObservable = PublishSubject.create() - private val titleObservable = PublishSubject.create() - private val goBackObservable = PublishSubject.create() - private val goForwardObservable = PublishSubject.create() - private val tabWebViewClient = TabWebViewClient( - urlObservable = urlObservable, - sslStateObservable = sslStateObservable, - goBackObservable = goBackObservable, - goForwardObservable = goForwardObservable - ) private var latentInitializer: TabInitializer? = null init { webView.webViewClient = tabWebViewClient - webView.webChromeClient = TabWebChromeClient( - progressObservable = progressObservable, - titleObservable = titleObservable, - faviconObservable = faviconObservable - ) + webView.webChromeClient = tabWebChromeClient if (tabInitializer is FreezableBundleInitializer) { latentInitializer = tabInitializer } else { @@ -57,7 +41,7 @@ class TabAdapter( override fun canGoBack(): Boolean = webView.canGoBack() - override fun canGoBackChanges(): Observable = goBackObservable.hide() + override fun canGoBackChanges(): Observable = tabWebViewClient.goBackObservable.hide() override fun goForward() { webView.goForward() @@ -65,7 +49,7 @@ class TabAdapter( override fun canGoForward(): Boolean = webView.canGoForward() - override fun canGoForwardChanges(): Observable = goForwardObservable.hide() + override fun canGoForwardChanges(): Observable = tabWebViewClient.goForwardObservable.hide() override fun reload() { webView.reload() @@ -78,28 +62,28 @@ class TabAdapter( override val favicon: Bitmap? get() = webView.favicon - override fun faviconChanges(): Observable = faviconObservable.hide() + override fun faviconChanges(): Observable = tabWebChromeClient.faviconObservable.hide() // TODO do we show "new tab" override val url: String get() = webView.url.orEmpty() - override fun urlChanges(): Observable = urlObservable.hide() + override fun urlChanges(): Observable = tabWebViewClient.urlObservable.hide() override val title: String get() = webView.title.orEmpty() - override fun titleChanges(): Observable = titleObservable.hide() + override fun titleChanges(): Observable = tabWebChromeClient.titleObservable.hide() override val sslState: SslState get() = tabWebViewClient.sslState - override fun sslChanges(): Observable = sslStateObservable.hide() + override fun sslChanges(): Observable = tabWebViewClient.sslStateObservable.hide() override val loadingProgress: Int get() = webView.progress - override fun loadingProgress(): Observable = progressObservable.hide() + override fun loadingProgress(): Observable = tabWebChromeClient.progressObservable.hide() override var isForeground: Boolean = false set(value) { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt index 7bb7d6c54..d0f15ebe0 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt @@ -8,11 +8,11 @@ import io.reactivex.subjects.PublishSubject /** * Created by anthonycr on 9/12/20. */ -class TabWebChromeClient( - private val progressObservable: PublishSubject, - private val titleObservable: PublishSubject, - private val faviconObservable: PublishSubject -) : WebChromeClient() { +class TabWebChromeClient : WebChromeClient() { + + val progressObservable: PublishSubject = PublishSubject.create() + val titleObservable: PublishSubject = PublishSubject.create() + val faviconObservable: PublishSubject = PublishSubject.create() override fun onProgressChanged(view: WebView, newProgress: Int) { super.onProgressChanged(view, newProgress) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt index d7554050d..1cc52e7ce 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt @@ -1,29 +1,41 @@ package acr.browser.lightning._browser2.tab +import acr.browser.lightning.adblock.AdBlocker +import acr.browser.lightning.adblock.allowlist.AllowListModel import acr.browser.lightning.ssl.SslState +import android.annotation.TargetApi import android.graphics.Bitmap import android.net.http.SslError -import android.webkit.SslErrorHandler -import android.webkit.URLUtil -import android.webkit.WebView -import android.webkit.WebViewClient +import android.os.Build +import android.webkit.* import io.reactivex.subjects.PublishSubject +import java.io.ByteArrayInputStream /** * Created by anthonycr on 9/12/20. */ class TabWebViewClient( - private val urlObservable: PublishSubject, - private val sslStateObservable: PublishSubject, - private val goBackObservable: PublishSubject, - private val goForwardObservable: PublishSubject + private val adBlocker: AdBlocker, + private val allowListModel: AllowListModel ) : WebViewClient() { + val urlObservable: PublishSubject = PublishSubject.create() + val sslStateObservable: PublishSubject = PublishSubject.create() + val goBackObservable: PublishSubject = PublishSubject.create() + val goForwardObservable: PublishSubject = PublishSubject.create() + var sslState: SslState = SslState.None private set + private var currentUrl: String = "" + + private fun shouldBlockRequest(pageUrl: String, requestUrl: String) = + !allowListModel.isUrlAllowedAds(pageUrl) && + adBlocker.isAd(requestUrl) + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) + currentUrl = url urlObservable.onNext(url) sslState = if (URLUtil.isHttpsUrl(url)) { SslState.Valid @@ -45,4 +57,29 @@ class TabWebViewClient( sslState = SslState.Invalid(error) sslStateObservable.onNext(sslState) } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { + if (shouldBlockRequest(currentUrl, request.url.toString())) { + val empty = ByteArrayInputStream(emptyResponseByteArray) + return WebResourceResponse(BLOCKED_RESPONSE_MIME_TYPE, BLOCKED_RESPONSE_ENCODING, empty) + } + return null + } + + @Suppress("OverridingDeprecatedMember") + override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? { + if (shouldBlockRequest(currentUrl, url)) { + val empty = ByteArrayInputStream(emptyResponseByteArray) + return WebResourceResponse(BLOCKED_RESPONSE_MIME_TYPE, BLOCKED_RESPONSE_ENCODING, empty) + } + return null + } + + companion object { + private val emptyResponseByteArray: ByteArray = byteArrayOf() + + private const val BLOCKED_RESPONSE_MIME_TYPE = "text/plain" + private const val BLOCKED_RESPONSE_ENCODING = "utf-8" + } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index 7dd566ff4..52af0e622 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -1,8 +1,9 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning._browser2.BrowserContract +import acr.browser.lightning.adblock.AdBlocker +import acr.browser.lightning.adblock.allowlist.AllowListModel import acr.browser.lightning.view.TabInitializer -import acr.browser.lightning.view.UrlInitializer import io.reactivex.Completable import io.reactivex.Maybe import io.reactivex.Observable @@ -15,7 +16,9 @@ import javax.inject.Inject */ class TabsRepository @Inject constructor( private val webViewFactory: WebViewFactory, - private val tabPager: TabPager + private val tabPager: TabPager, + private val adBlocker: AdBlocker, + private val allowListModel: AllowListModel ) : BrowserContract.Model { private var selectedTab: TabModel? = null @@ -35,7 +38,12 @@ class TabsRepository @Inject constructor( override fun createTab(tabInitializer: TabInitializer): Single = Single.fromCallable { val webView = webViewFactory.createWebView(isIncognito = false) tabPager.addTab(webView) - val tabAdapter = TabAdapter(tabInitializer, webView) + val tabAdapter = TabAdapter( + tabInitializer, + webView, + TabWebViewClient(adBlocker, allowListModel), + TabWebChromeClient() + ) tabsList += tabAdapter From 1ba137b87c03be4d7721324124f86965bd42414f Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 17 Sep 2020 21:12:35 -0400 Subject: [PATCH 020/161] Fixing bug where search sometimes just reloaded tab, caused by focus clear triggering setText, also filtering out empty searches --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 5 ++++- .../acr/browser/lightning/_browser2/search/SearchListener.kt | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index dba88df10..c221f0414 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -413,9 +413,12 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onSearch(query: String) { + if (query.isEmpty()) { + return + } currentTab?.stopLoading() val searchUrl = searchEngineProvider.provideSearchEngine().queryUrl + QUERY_PLACE_HOLDER - val url = smartUrlFilter(query, true, searchUrl) + val url = smartUrlFilter(query.trim(), true, searchUrl) view?.updateState(viewState.copy( displayUrl = searchBoxModel.getDisplayContent( url = url, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt b/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt index be3fecbd1..85688d4ed 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/search/SearchListener.kt @@ -20,9 +20,9 @@ class SearchListener( } return when (keyCode) { KeyEvent.KEYCODE_ENTER -> { + onConfirm() view.clearFocus() inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) - onConfirm() true } else -> false @@ -36,9 +36,9 @@ class SearchListener( || actionId == EditorInfo.IME_ACTION_SEND || actionId == EditorInfo.IME_ACTION_SEARCH || event?.action == KeyEvent.KEYCODE_ENTER) { + onConfirm() view.clearFocus() inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) - onConfirm() return true } return false From 455f96852bb85b2f9e6b68f3d096e04d11f17b58 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 17 Sep 2020 22:21:01 -0400 Subject: [PATCH 021/161] Support enabling and disabling add bookmark icon, partial bookmark/unbookmark support --- .../lightning/_browser2/BrowserActivity.kt | 4 +- .../lightning/_browser2/BrowserPresenter.kt | 40 +++++++++++++++++-- .../_browser2/BrowserStateAdapter.kt | 6 ++- .../lightning/_browser2/BrowserViewState.kt | 6 ++- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index e299ca320..f4515b66b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -106,6 +106,7 @@ class BrowserActivity : ThemableBrowserActivity() { binding.actionHome.setOnClickListener { presenter.onHomeClick() } binding.newTabButton.setOnClickListener { presenter.onNewTabClick() } binding.searchRefresh.setOnClickListener { presenter.onRefreshOrStopClick() } + binding.actionAddBookmark.setOnClickListener { presenter.onStarClick() } } override fun onDestroy() { @@ -147,7 +148,8 @@ class BrowserActivity : ThemableBrowserActivity() { } viewState.tabs?.let { tabsAdapter.submitList(viewState.tabs) } viewState.bookmarks?.let { bookmarksAdapter.submitList(viewState.bookmarks) } - + viewState.isBookmarked?.let { binding.actionAddBookmark.isSelected = it } + viewState.isBookmarkEnabled?.let { binding.actionAddBookmark.isEnabled = it } } private fun ImageView.updateVisibilityForDrawable() { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index c221f0414..e68f3b1d9 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -54,7 +54,8 @@ class BrowserPresenter @Inject constructor( isForwardEnabled = false, isBackEnabled = false, bookmarks = emptyList(), - isBookmarked = false + isBookmarked = false, + isBookmarkEnabled = true ) private var currentTab: TabModel? = null private var currentFolder: Bookmark.Folder = Bookmark.Folder.Root @@ -197,16 +198,19 @@ class BrowserPresenter @Inject constructor( urlDisposable?.dispose() urlDisposable = tabModel?.urlChanges() + ?.flatMapSingle { url -> bookmarkRepository.isBookmark(url).map { Pair(url, it) } } ?.distinctUntilChanged() ?.observeOn(mainScheduler) - ?.subscribe { + ?.subscribe { (url, isBookmark) -> if (!isSearchViewFocused) { view.updateState(viewState.copy( displayUrl = searchBoxModel.getDisplayContent( url = tabModel.url, title = tabModel.title, isLoading = tabModel.loadingProgress < 100 - ) + ), + isBookmarked = isBookmark, + isBookmarkEnabled = !url.isSpecialUrl() )) } } @@ -489,7 +493,35 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onStarClick() { - + val url = currentTab?.url ?: return + val title = currentTab?.title.orEmpty() + if (url.isSpecialUrl()) { + return + } + compositeDisposable += bookmarkRepository.isBookmark(url) + .flatMap { + if (it) { + bookmarkRepository.deleteBookmark(Bookmark.Entry( + url = url, + title = title, + position = 0, + folder = Bookmark.Folder.Root + )) + } else { + bookmarkRepository.addBookmarkIfNotExists(Bookmark.Entry( + url = url, + title = title, + position = 0, + folder = Bookmark.Folder.Root + )) + } + } + .flatMap { bookmarkRepository.getBookmarksFromFolderSorted(folder = currentFolder.title) } + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribe { list -> + this.view?.updateState(viewState.copy(bookmarks = list)) + } } /** diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index b6a6b0dd9..5265928f2 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -18,7 +18,8 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse isForwardEnabled, isBackEnabled, bookmarks, - isBookmarked + isBookmarked, + isBookmarkEnabled ) = viewState browserActivity.renderState( @@ -31,7 +32,8 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse isForwardEnabled = isForwardEnabled.takeIf { it != currentState?.isForwardEnabled }, isBackEnabled = isBackEnabled.takeIf { it != currentState?.isBackEnabled }, bookmarks = bookmarks.takeIf { it != currentState?.bookmarks }, - isBookmarked = isBookmarked.takeIf { it != currentState?.isBookmarked } + isBookmarked = isBookmarked.takeIf { it != currentState?.isBookmarked }, + isBookmarkEnabled = isBookmarkEnabled.takeIf { it != currentState?.isBookmarkEnabled } ) ) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt index 5bd26b94a..a05650734 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt @@ -21,7 +21,8 @@ data class BrowserViewState( // Bookmarks val bookmarks: List, - val isBookmarked: Boolean + val isBookmarked: Boolean, + val isBookmarkEnabled: Boolean ) data class PartialBrowserViewState( @@ -38,5 +39,6 @@ data class PartialBrowserViewState( // Bookmarks val bookmarks: List?, - val isBookmarked: Boolean? + val isBookmarked: Boolean?, + val isBookmarkEnabled: Boolean? ) From 5faf8f887c4a28ab4488f62c99de6ee654ddc9e6 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Fri, 18 Sep 2020 08:49:41 -0400 Subject: [PATCH 022/161] Add missing click bindings --- .../acr/browser/lightning/_browser2/BrowserActivity.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index f4515b66b..ffbaa33e4 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -107,6 +107,11 @@ class BrowserActivity : ThemableBrowserActivity() { binding.newTabButton.setOnClickListener { presenter.onNewTabClick() } binding.searchRefresh.setOnClickListener { presenter.onRefreshOrStopClick() } binding.actionAddBookmark.setOnClickListener { presenter.onStarClick() } + binding.actionPageTools.setOnClickListener { presenter.onToolsClick() } + binding.tabHeaderButton.setOnClickListener { presenter.onTabMenuClick() } + binding.actionReading.setOnClickListener { presenter.onReadingModeClick() } + binding.bookmarkBackButton.setOnClickListener { presenter.onBookmarkMenuClick() } + binding.searchSslStatus.setOnClickListener { presenter.onSslIconClick() } } override fun onDestroy() { @@ -129,6 +134,9 @@ class BrowserActivity : ThemableBrowserActivity() { ?: super.dispatchKeyEvent(event) } + /** + * TODO + */ fun renderState(viewState: PartialBrowserViewState) { viewState.isBackEnabled?.let { binding.actionBack.isEnabled = it } viewState.isForwardEnabled?.let { binding.actionForward.isEnabled = it } From a94369e77880389032d777763b312a5cb9d86851 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Fri, 18 Sep 2020 21:09:58 -0400 Subject: [PATCH 023/161] Only handle key up scenarios --- .../acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt index 568037912..e18fdae8f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt @@ -9,6 +9,9 @@ import javax.inject.Inject class KeyEventAdapter @Inject constructor() { fun adaptKeyEvent(event: KeyEvent): KeyCombo? { + if (event.action != KeyEvent.ACTION_UP) { + return null + } when { event.isCtrlPressed -> when (event.keyCode) { KeyEvent.KEYCODE_F -> { From d2593984301ae0b16ac3a2199c0792dc1bf55824 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sat, 19 Sep 2020 11:26:33 -0400 Subject: [PATCH 024/161] Simplify key handling --- .../java/acr/browser/lightning/_browser2/BrowserActivity.kt | 4 ++-- .../acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index ffbaa33e4..08d81ddda 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -129,9 +129,9 @@ class BrowserActivity : ThemableBrowserActivity() { ?: super.onOptionsItemSelected(item) } - override fun dispatchKeyEvent(event: KeyEvent): Boolean { + override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { return keyEventAdapter.adaptKeyEvent(event)?.let(presenter::onKeyComboClick)?.let { true } - ?: super.dispatchKeyEvent(event) + ?: super.onKeyUp(keyCode, event) } /** diff --git a/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt index e18fdae8f..568037912 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/keys/KeyEventAdapter.kt @@ -9,9 +9,6 @@ import javax.inject.Inject class KeyEventAdapter @Inject constructor() { fun adaptKeyEvent(event: KeyEvent): KeyCombo? { - if (event.action != KeyEvent.ACTION_UP) { - return null - } when { event.isCtrlPressed -> when (event.keyCode) { KeyEvent.KEYCODE_F -> { From b6e5b80a414ae4fd60adedac8bf00b8dfd7ed1ad Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sat, 19 Sep 2020 12:00:07 -0400 Subject: [PATCH 025/161] Load homepage on home press --- .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 2 +- .../lightning/_browser2/image/FaviconImageLoader.kt | 6 +++--- .../acr/browser/lightning/_browser2/tab/TabAdapter.kt | 8 ++++++-- .../java/acr/browser/lightning/_browser2/tab/TabModel.kt | 3 +++ .../acr/browser/lightning/_browser2/tab/TabsRepository.kt | 1 + 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index e68f3b1d9..4a6123164 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -364,7 +364,7 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onHomeClick() { - currentTab?.loadUrl("https://google.com") + currentTab?.loadFromInitializer(homePageInitializer) } /** diff --git a/app/src/main/java/acr/browser/lightning/_browser2/image/FaviconImageLoader.kt b/app/src/main/java/acr/browser/lightning/_browser2/image/FaviconImageLoader.kt index 58837223b..9a0047533 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/image/FaviconImageLoader.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/image/FaviconImageLoader.kt @@ -29,7 +29,7 @@ class FaviconImageLoader @Inject constructor( private val lruCache: LruCache = LruCache(1 * 1000 * 1000) private val folderIcon = application.drawable(R.drawable.ic_folder) - private val webpageIcon = application.drawable(R.drawable.ic_webpage) + private val webPageIcon = application.drawable(R.drawable.ic_webpage) private val compositeDisposable = CompositeDisposable() override fun loadImage(imageView: ImageView, bookmark: Bookmark) { @@ -47,8 +47,8 @@ class FaviconImageLoader @Inject constructor( imageView.setImageDrawable(folderIcon) } is Bookmark.Entry -> { - lruCache.put(bookmark.url, webpageIcon) - imageView.setImageDrawable(webpageIcon) + lruCache.put(bookmark.url, webPageIcon) + imageView.setImageDrawable(webPageIcon) compositeDisposable += faviconModel .faviconForUrl(bookmark.url, bookmark.title) .subscribeOn(networkScheduler) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index 8e3462367..495179ea2 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -25,7 +25,7 @@ class TabAdapter( if (tabInitializer is FreezableBundleInitializer) { latentInitializer = tabInitializer } else { - tabInitializer.initialize(webView, emptyMap()) + loadFromInitializer(tabInitializer) } } @@ -35,6 +35,10 @@ class TabAdapter( webView.loadUrl(url) } + override fun loadFromInitializer(tabInitializer: TabInitializer) { + tabInitializer.initialize(webView, emptyMap()) + } + override fun goBack() { webView.goBack() } @@ -90,7 +94,7 @@ class TabAdapter( field = value if (field) { webView.onResume() - latentInitializer?.initialize(webView, emptyMap()) + latentInitializer?.let(::loadFromInitializer) latentInitializer = null } else { webView.onPause() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt index d0c804c6a..369818499 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning.ssl.SslState +import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap import io.reactivex.Observable @@ -12,6 +13,8 @@ interface TabModel { fun loadUrl(url: String) + fun loadFromInitializer(tabInitializer: TabInitializer) + fun goBack() fun canGoBack(): Boolean diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index 52af0e622..a93d6ae48 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -3,6 +3,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning._browser2.BrowserContract import acr.browser.lightning.adblock.AdBlocker import acr.browser.lightning.adblock.allowlist.AllowListModel +import acr.browser.lightning.view.HomePageInitializer import acr.browser.lightning.view.TabInitializer import io.reactivex.Completable import io.reactivex.Maybe From 6eabaea3b2acdfd21f8e275253402e432bb22422 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 20 Sep 2020 11:29:24 -0400 Subject: [PATCH 026/161] Handle tab freezing and restoring --- .../lightning/_browser2/BrowserContract.kt | 2 + .../lightning/_browser2/BrowserPresenter.kt | 4 +- .../lightning/_browser2/tab/TabAdapter.kt | 10 +- .../lightning/_browser2/tab/TabModel.kt | 3 + .../lightning/_browser2/tab/TabsRepository.kt | 30 ++++-- .../_browser2/tab/bundle/BundleStore.kt | 91 +++++++++++++++++++ 6 files changed, 127 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 3f1e4d08b..fc88f21a3 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -30,6 +30,8 @@ interface BrowserContract { fun initializeTabs(): Maybe> + fun freeze() + val tabsList: List fun tabsListChanges(): Observable> diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 4a6123164..6558ba2f9 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -85,7 +85,6 @@ class BrowserPresenter @Inject constructor( } compositeDisposable += model.initializeTabs() - .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .switchIfEmpty(model.createTab(homePageInitializer).map(::listOf)) .subscribe { list -> @@ -105,6 +104,9 @@ class BrowserPresenter @Inject constructor( */ fun onViewDetached() { view = null + + model.freeze() + compositeDisposable.dispose() sslDisposable?.dispose() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index 495179ea2..a5695361c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -4,6 +4,7 @@ import acr.browser.lightning.ssl.SslState import acr.browser.lightning.view.FreezableBundleInitializer import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap +import android.os.Bundle import android.webkit.WebView import io.reactivex.Observable @@ -17,7 +18,7 @@ class TabAdapter( private val tabWebChromeClient: TabWebChromeClient ) : TabModel { - private var latentInitializer: TabInitializer? = null + private var latentInitializer: FreezableBundleInitializer? = null init { webView.webViewClient = tabWebViewClient @@ -75,7 +76,7 @@ class TabAdapter( override fun urlChanges(): Observable = tabWebViewClient.urlObservable.hide() override val title: String - get() = webView.title.orEmpty() + get() = latentInitializer?.initialTitle ?: webView.title.orEmpty() override fun titleChanges(): Observable = tabWebChromeClient.titleObservable.hide() @@ -108,4 +109,9 @@ class TabAdapter( webView.removeAllViews() webView.destroy() } + + override fun freeze(): Bundle = latentInitializer?.bundle + ?: Bundle(ClassLoader.getSystemClassLoader()).also { + webView.saveState(it) + } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt index 369818499..111f7e565 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -3,6 +3,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning.ssl.SslState import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap +import android.os.Bundle import io.reactivex.Observable interface TabModel { @@ -59,4 +60,6 @@ interface TabModel { fun destroy() + fun freeze(): Bundle + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index a93d6ae48..0d7468dfc 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -1,14 +1,13 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning._browser2.BrowserContract +import acr.browser.lightning._browser2.tab.bundle.BundleStore import acr.browser.lightning.adblock.AdBlocker import acr.browser.lightning.adblock.allowlist.AllowListModel -import acr.browser.lightning.view.HomePageInitializer -import acr.browser.lightning.view.TabInitializer -import io.reactivex.Completable -import io.reactivex.Maybe -import io.reactivex.Observable -import io.reactivex.Single +import acr.browser.lightning.di.DiskScheduler +import acr.browser.lightning.di.MainScheduler +import acr.browser.lightning.view.* +import io.reactivex.* import io.reactivex.subjects.PublishSubject import javax.inject.Inject @@ -19,7 +18,10 @@ class TabsRepository @Inject constructor( private val webViewFactory: WebViewFactory, private val tabPager: TabPager, private val adBlocker: AdBlocker, - private val allowListModel: AllowListModel + private val allowListModel: AllowListModel, + @DiskScheduler private val diskScheduler: Scheduler, + @MainScheduler private val mainScheduler: Scheduler, + private val bundleStore: BundleStore ) : BrowserContract.Model { private var selectedTab: TabModel? = null @@ -66,9 +68,17 @@ class TabsRepository @Inject constructor( override fun tabsListChanges(): Observable> = tabsListObservable.hide() - override fun initializeTabs(): Maybe> = Maybe.empty() - - private fun List.forId(id: Int): TabModel = requireNotNull(find { it.id == id }) + override fun initializeTabs(): Maybe> = Single.fromCallable(bundleStore::retrieve) + .flatMapObservable { Observable.fromIterable(it) } + .subscribeOn(diskScheduler) + .observeOn(mainScheduler) + .flatMapSingle(::createTab) + .toList() + .filter { it.isNotEmpty() } + override fun freeze() { + bundleStore.save(tabsList) + } + private fun List.forId(id: Int): TabModel = requireNotNull(find { it.id == id }) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt new file mode 100644 index 000000000..a00d2919a --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt @@ -0,0 +1,91 @@ +package acr.browser.lightning._browser2.tab.bundle + +import acr.browser.lightning.R +import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning.di.DiskScheduler +import acr.browser.lightning.utils.* +import acr.browser.lightning.view.* +import android.app.Application +import android.os.Bundle +import io.reactivex.Scheduler +import javax.inject.Inject + +/** + * Created by anthonycr on 9/20/20. + */ +class BundleStore @Inject constructor( + private val application: Application, + private val bookmarkPageInitializer: BookmarkPageInitializer, + private val homePageInitializer: HomePageInitializer, + private val downloadPageInitializer: DownloadPageInitializer, + private val historyPageInitializer: HistoryPageInitializer, + @DiskScheduler private val diskScheduler: Scheduler +) { + + /** + * TODO + */ + fun save(tabs: List) { + val outState = Bundle(ClassLoader.getSystemClassLoader()) + + tabs.withIndex().forEach { (index, tab) -> + if (!tab.url.isSpecialUrl()) { + outState.putBundle(BUNDLE_KEY + index, tab.freeze()) + outState.putString(TAB_TITLE_KEY + index, tab.title) + } else { + outState.putBundle(BUNDLE_KEY + index, Bundle().apply { + putString(URL_KEY, tab.url) + }) + } + } + + FileUtils.writeBundleToStorage(application, outState, BUNDLE_STORAGE) + .subscribeOn(diskScheduler) + .subscribe() + } + + /** + * TODO + */ + fun retrieve(): List = + FileUtils.readBundleFromStorage(application, BUNDLE_STORAGE)?.let { bundle -> + bundle.keySet() + .filter { it.startsWith(BUNDLE_KEY) } + .mapNotNull { bundleKey -> + bundle.getBundle(bundleKey)?.let { + Pair( + it, + bundle.getString(TAB_TITLE_KEY + bundleKey.extractNumberFromEnd()) + ) + } + } + }?.map { (bundle, title) -> + return@map bundle.getString(URL_KEY)?.let { url -> + when { + url.isBookmarkUrl() -> bookmarkPageInitializer + url.isDownloadsUrl() -> downloadPageInitializer + url.isStartPageUrl() -> homePageInitializer + url.isHistoryUrl() -> historyPageInitializer + else -> homePageInitializer + } + } ?: FreezableBundleInitializer(bundle, title + ?: application.getString(R.string.tab_frozen)) + } ?: emptyList() + + private fun String.extractNumberFromEnd(): String { + val underScore = lastIndexOf('_') + return if (underScore in 0 until length) { + substring(underScore + 1) + } else { + "" + } + } + + companion object { + private const val TAG = "TabsRepository" + private const val BUNDLE_KEY = "WEBVIEW_" + private const val TAB_TITLE_KEY = "TITLE_" + private const val URL_KEY = "URL_KEY" + private const val BUNDLE_STORAGE = "SAVED_TABS.parcel" + } +} From 4c9ff6957ca6a3309e515c39109e34acceb8adb0 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 20 Sep 2020 12:39:25 -0400 Subject: [PATCH 027/161] Handle tab updates that occur in the background --- .../lightning/_browser2/BrowserPresenter.kt | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 6558ba2f9..8377e3026 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -62,6 +62,7 @@ class BrowserPresenter @Inject constructor( private var isSearchViewFocused = false private val compositeDisposable = CompositeDisposable() + private val allTabsDisposable = CompositeDisposable() private var sslDisposable: Disposable? = null private var titleDisposable: Disposable? = null private var faviconDisposable: Disposable? = null @@ -96,6 +97,9 @@ class BrowserPresenter @Inject constructor( .observeOn(mainScheduler) .subscribe { list -> this.view.updateState(viewState.copy(tabs = list.map { it.asViewState() })) + + allTabsDisposable.clear() + list.subscribeToUpdates(allTabsDisposable) } } @@ -179,25 +183,17 @@ class BrowserPresenter @Inject constructor( ?.distinctUntilChanged() ?.observeOn(mainScheduler) ?.subscribe { title -> - view.updateState(viewState.copy(tabs = viewState.tabs.updateId(tabModel.id) { - it.copy(title = title) - })) - - currentTab?.url?.takeIf { !it.isSpecialUrl() }?.let { - historyRecord.recordVisit(title, it) + if (!isSearchViewFocused) { + view.updateState(viewState.copy( + displayUrl = searchBoxModel.getDisplayContent( + url = tabModel.url, + title = title, + isLoading = tabModel.loadingProgress < 100 + ) + )) } } - faviconDisposable?.dispose() - faviconDisposable = tabModel?.faviconChanges() - ?.observeOn(mainScheduler) - ?.subscribe { favicon -> - view.updateState(viewState.copy(tabs = viewState.tabs.updateId(tabModel.id) { - it.copy(icon = favicon) - })) - } - - urlDisposable?.dispose() urlDisposable = tabModel?.urlChanges() ?.flatMapSingle { url -> bookmarkRepository.isBookmark(url).map { Pair(url, it) } } @@ -207,7 +203,7 @@ class BrowserPresenter @Inject constructor( if (!isSearchViewFocused) { view.updateState(viewState.copy( displayUrl = searchBoxModel.getDisplayContent( - url = tabModel.url, + url = url, title = tabModel.title, isLoading = tabModel.loadingProgress < 100 ), @@ -245,6 +241,31 @@ class BrowserPresenter @Inject constructor( } } + private fun List.subscribeToUpdates(compositeDisposable: CompositeDisposable) { + forEach { tabModel -> + compositeDisposable += tabModel.titleChanges() + .distinctUntilChanged() + .observeOn(mainScheduler) + .subscribe { title -> + view.updateState(viewState.copy(tabs = viewState.tabs.updateId(tabModel.id) { + it.copy(title = title) + })) + + currentTab?.url?.takeIf { !it.isSpecialUrl() }?.let { + historyRecord.recordVisit(title, it) + } + } + + compositeDisposable += tabModel.faviconChanges() + .observeOn(mainScheduler) + .subscribe { favicon -> + view.updateState(viewState.copy(tabs = viewState.tabs.updateId(tabModel.id) { + it.copy(icon = favicon) + })) + } + } + } + /** * TODO */ From d2f08cb05fe337ee0a431cfb0be270ec2b7e926f Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 20 Sep 2020 12:42:21 -0400 Subject: [PATCH 028/161] Remove unnecessary state render --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 8377e3026..4bc5499ad 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -89,7 +89,6 @@ class BrowserPresenter @Inject constructor( .observeOn(mainScheduler) .switchIfEmpty(model.createTab(homePageInitializer).map(::listOf)) .subscribe { list -> - this.view.updateState(viewState.copy(tabs = list.map { it.asViewState() })) selectTab(model.selectTab(list.last().id)) } From f1ad6c4adeed50752929a6037146fa7d7a9d51cb Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 22 Sep 2020 19:39:20 -0400 Subject: [PATCH 029/161] Support opening links --- .../lightning/_browser2/BrowserActivity.kt | 1 + .../lightning/_browser2/BrowserPresenter.kt | 28 ++++++++++---- .../_browser2/di/Browser2Component.kt | 11 ++++++ .../lightning/_browser2/di/Browser2Module.kt | 9 +++++ .../_browser2/search/IntentExtractor.kt | 37 +++++++++++++++++++ 5 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 08d81ddda..b24b8bbd0 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -57,6 +57,7 @@ class BrowserActivity : ThemableBrowserActivity() { injector.browser2ComponentBuilder() .activity(this) .browserFrame(binding.contentFrame) + .initialIntent(intent) .build() .inject(this) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 4bc5499ad..ff767de47 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2 +import acr.browser.lightning._browser2.di.InitialUrl import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.keys.KeyCombo import acr.browser.lightning._browser2.menu.MenuSelection @@ -21,6 +22,8 @@ import acr.browser.lightning.utils.smartUrlFilter import acr.browser.lightning.view.DownloadPageInitializer import acr.browser.lightning.view.HistoryPageInitializer import acr.browser.lightning.view.HomePageInitializer +import acr.browser.lightning.view.UrlInitializer +import io.reactivex.Maybe import io.reactivex.Scheduler import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable @@ -41,7 +44,8 @@ class BrowserPresenter @Inject constructor( private val historyPageInitializer: HistoryPageInitializer, private val downloadPageInitializer: DownloadPageInitializer, private val searchBoxModel: SearchBoxModel, - private val searchEngineProvider: SearchEngineProvider + private val searchEngineProvider: SearchEngineProvider, + @InitialUrl private val initialUrl: String? ) { private var view: BrowserContract.View? = null @@ -85,13 +89,6 @@ class BrowserPresenter @Inject constructor( this.view?.updateState(viewState.copy(bookmarks = list)) } - compositeDisposable += model.initializeTabs() - .observeOn(mainScheduler) - .switchIfEmpty(model.createTab(homePageInitializer).map(::listOf)) - .subscribe { list -> - selectTab(model.selectTab(list.last().id)) - } - compositeDisposable += model.tabsListChanges() .observeOn(mainScheduler) .subscribe { list -> @@ -100,6 +97,21 @@ class BrowserPresenter @Inject constructor( allTabsDisposable.clear() list.subscribeToUpdates(allTabsDisposable) } + + compositeDisposable += model.initializeTabs() + .observeOn(mainScheduler) + .mergeWith( + Maybe.fromCallable { initialUrl } + .flatMapSingleElement { model.createTab(UrlInitializer(it)) } + .map { listOf(it) } + ) + .toList() + .map { it.flatten() } + .filter { it.isNotEmpty() } + .switchIfEmpty(model.createTab(homePageInitializer).map(::listOf)) + .subscribe { list -> + selectTab(model.selectTab(list.last().id)) + } } /** diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt index 77c5e6fa1..9e793ca28 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt @@ -2,9 +2,11 @@ package acr.browser.lightning._browser2.di import acr.browser.lightning._browser2.BrowserActivity import android.app.Activity +import android.content.Intent import android.widget.FrameLayout import dagger.BindsInstance import dagger.Subcomponent +import javax.inject.Qualifier /** * Created by anthonycr on 9/15/20. @@ -21,6 +23,9 @@ interface Browser2Component { @BindsInstance fun browserFrame(frameLayout: FrameLayout): Builder + @BindsInstance + fun initialIntent(@InitialIntent intent: Intent): Builder + fun build(): Browser2Component } @@ -28,3 +33,9 @@ interface Browser2Component { fun inject(browserActivity: BrowserActivity) } + +@Qualifier +annotation class InitialIntent + +@Qualifier +annotation class InitialUrl diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index 239182675..be9799110 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -1,9 +1,11 @@ package acr.browser.lightning._browser2.di +import acr.browser.lightning._browser2.search.IntentExtractor import acr.browser.lightning.adblock.AdBlocker import acr.browser.lightning.adblock.BloomFilterAdBlocker import acr.browser.lightning.adblock.NoOpAdBlocker import acr.browser.lightning.preference.UserPreferences +import android.content.Intent import dagger.Module import dagger.Provides import javax.inject.Provider @@ -25,4 +27,11 @@ class Browser2Module { noOpAdBlocker } + @Provides + @InitialUrl + fun providesInitialUrl( + @InitialIntent initialIntent: Intent, + intentExtractor: IntentExtractor + ): String? = intentExtractor.extractUrlFromIntent(initialIntent) + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt b/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt new file mode 100644 index 000000000..e048d2bcb --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt @@ -0,0 +1,37 @@ +package acr.browser.lightning._browser2.search + +import acr.browser.lightning.search.SearchEngineProvider +import acr.browser.lightning.utils.QUERY_PLACE_HOLDER +import acr.browser.lightning.utils.smartUrlFilter +import android.app.SearchManager +import android.content.Intent +import javax.inject.Inject + +/** + * Created by anthonycr on 9/20/20. + */ +class IntentExtractor @Inject constructor(private val searchEngineProvider: SearchEngineProvider) { + + /** + * TODO + */ + fun extractUrlFromIntent(intent: Intent): String? { + return if (intent.action == Intent.ACTION_WEB_SEARCH) { + extractSearchFromIntent(intent) + } else { + intent.dataString + } + } + + private fun extractSearchFromIntent(intent: Intent): String? { + val query = intent.getStringExtra(SearchManager.QUERY) + val searchUrl = "${searchEngineProvider.provideSearchEngine().queryUrl}$QUERY_PLACE_HOLDER" + + return if (query?.isNotBlank() == true) { + smartUrlFilter(query, true, searchUrl) + } else { + null + } + } + +} From 29ff9bba87c2a97754d7f8fe78f0fd201643a87f Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 22 Sep 2020 19:47:01 -0400 Subject: [PATCH 030/161] Support new deeplinks --- .../browser/lightning/_browser2/BrowserActivity.kt | 11 +++++++++++ .../browser/lightning/_browser2/BrowserPresenter.kt | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index b24b8bbd0..74b779f81 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -5,6 +5,7 @@ import acr.browser.lightning._browser2.bookmark.BookmarkRecyclerViewAdapter import acr.browser.lightning._browser2.image.ImageLoader import acr.browser.lightning._browser2.keys.KeyEventAdapter import acr.browser.lightning._browser2.menu.MenuItemAdapter +import acr.browser.lightning._browser2.search.IntentExtractor import acr.browser.lightning._browser2.search.SearchListener import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher @@ -15,6 +16,7 @@ import acr.browser.lightning.databinding.BrowserActivityBinding import acr.browser.lightning.di.injector import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState +import android.content.Intent import android.os.Bundle import android.view.* import android.view.inputmethod.InputMethodManager @@ -47,6 +49,9 @@ class BrowserActivity : ThemableBrowserActivity() { @Inject internal lateinit var presenter: BrowserPresenter + @Inject + internal lateinit var intentExtractor: IntentExtractor + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = BrowserActivityBinding.inflate(LayoutInflater.from(this)) @@ -115,6 +120,12 @@ class BrowserActivity : ThemableBrowserActivity() { binding.searchSslStatus.setOnClickListener { presenter.onSslIconClick() } } + override fun onNewIntent(intent: Intent?) { + intent?.let(intentExtractor::extractUrlFromIntent)?.let(presenter::onNewDeepLink) + super.onNewIntent(intent) + + } + override fun onDestroy() { super.onDestroy() presenter.onViewDetached() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index ff767de47..dd16df21a 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -277,6 +277,17 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ + fun onNewDeepLink(url: String) { + compositeDisposable += model.createTab(UrlInitializer(url)) + .observeOn(mainScheduler) + .subscribe { tab -> + selectTab(model.selectTab(tab.id)) + } + } + /** * TODO */ From 816743bb159c7e67b12ac6fabd8c361f741b1e21 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 22 Sep 2020 20:03:49 -0400 Subject: [PATCH 031/161] Fix issue where search didn't lose focus and the wrong text was set --- .../main/java/acr/browser/lightning/_browser2/BrowserActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 74b779f81..71ee3b21c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -96,6 +96,7 @@ class BrowserActivity : ThemableBrowserActivity() { } } binding.search.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> + binding.search.clearFocus() presenter.onSearchSuggestionClicked(suggestionsAdapter.getItem(position) as WebPage) } binding.search.setAdapter(suggestionsAdapter) From f8d869cddf8923abad6224dfe08f2aa1c5e41966 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 22 Sep 2020 23:29:53 -0400 Subject: [PATCH 032/161] Simplifying subscriptions --- .../lightning/_browser2/BrowserPresenter.kt | 172 ++++++------------ 1 file changed, 54 insertions(+), 118 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index dd16df21a..e0570cdc7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -16,18 +16,21 @@ import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.MainScheduler import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.ssl.SslState -import acr.browser.lightning.utils.QUERY_PLACE_HOLDER -import acr.browser.lightning.utils.isSpecialUrl -import acr.browser.lightning.utils.smartUrlFilter +import acr.browser.lightning.utils.* import acr.browser.lightning.view.DownloadPageInitializer import acr.browser.lightning.view.HistoryPageInitializer import acr.browser.lightning.view.HomePageInitializer import acr.browser.lightning.view.UrlInitializer +import android.graphics.Bitmap import io.reactivex.Maybe +import io.reactivex.Observable import io.reactivex.Scheduler import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable +import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.plusAssign +import io.reactivex.rxkotlin.subscribeBy +import java.util.* import javax.inject.Inject /** @@ -67,13 +70,7 @@ class BrowserPresenter @Inject constructor( private val compositeDisposable = CompositeDisposable() private val allTabsDisposable = CompositeDisposable() - private var sslDisposable: Disposable? = null - private var titleDisposable: Disposable? = null - private var faviconDisposable: Disposable? = null - private var urlDisposable: Disposable? = null - private var loadingDisposable: Disposable? = null - private var canGoBackDisposable: Disposable? = null - private var canGoForwardDisposable: Disposable? = null + private var tabDisposable: Disposable? = null /** * TODO @@ -123,14 +120,7 @@ class BrowserPresenter @Inject constructor( model.freeze() compositeDisposable.dispose() - - sslDisposable?.dispose() - titleDisposable?.dispose() - faviconDisposable?.dispose() - urlDisposable?.dispose() - loadingDisposable?.dispose() - canGoBackDisposable?.dispose() - canGoForwardDisposable?.dispose() + tabDisposable?.dispose() } private fun TabModel.asViewState(): TabViewState = TabViewState( @@ -156,127 +146,74 @@ class BrowserPresenter @Inject constructor( currentTab = tabModel currentTab?.isForeground = true - view.updateState(viewState.copy( + val tab = tabModel ?: return view.updateState(viewState.copy( displayUrl = searchBoxModel.getDisplayContent( - url = tabModel?.url.orEmpty(), - title = tabModel?.title, - isLoading = (tabModel?.loadingProgress ?: 0) < 100 + url = "", + title = null, + isLoading = false ), - isForwardEnabled = tabModel?.canGoForward() ?: false, - isBackEnabled = tabModel?.canGoBack() ?: false, - sslState = tabModel?.sslState ?: SslState.None, - progress = tabModel?.loadingProgress ?: 100, + isForwardEnabled = false, + isBackEnabled = false, + sslState = SslState.None, + progress = 100, tabs = viewState.tabs.map { - if (it.id == tabModel?.id) { - it.copy(isSelected = true) - } else { - it.copy(isSelected = false) - } + it.copy(isSelected = false) } )) - sslDisposable?.dispose() - sslDisposable = tabModel?.sslChanges() - ?.distinctUntilChanged() - ?.observeOn(mainScheduler) - ?.subscribe { - view.updateState(viewState.copy( - sslState = if (isSearchViewFocused) { - SslState.None + tabDisposable?.dispose() + tabDisposable = Observables.combineLatest( + tab.sslChanges().startWith(tab.sslState), + tab.titleChanges().startWith(tab.title), + tab.urlChanges().startWith(tab.url), + tab.loadingProgress().startWith(tab.loadingProgress), + tab.canGoBackChanges().startWith(tab.canGoBack()), + tab.canGoForwardChanges().startWith(tab.canGoForward()) + ) { sslState, title, url, progress, canGoBack, canGoForward -> + viewState.copy( + displayUrl = searchBoxModel.getDisplayContent( + url = url, + title = title, + isLoading = progress < 100 + ), + isRefresh = progress == 100, + isForwardEnabled = canGoForward, + isBackEnabled = canGoBack, + sslState = sslState, + progress = progress, + tabs = viewState.tabs.map { + if (it.id == tabModel.id) { + it.copy(isSelected = true) } else { - it + it.copy(isSelected = false) } - )) - } - - titleDisposable?.dispose() - titleDisposable = tabModel?.titleChanges() - ?.distinctUntilChanged() - ?.observeOn(mainScheduler) - ?.subscribe { title -> - if (!isSearchViewFocused) { - view.updateState(viewState.copy( - displayUrl = searchBoxModel.getDisplayContent( - url = tabModel.url, - title = title, - isLoading = tabModel.loadingProgress < 100 - ) - )) } - } - - urlDisposable?.dispose() - urlDisposable = tabModel?.urlChanges() - ?.flatMapSingle { url -> bookmarkRepository.isBookmark(url).map { Pair(url, it) } } - ?.distinctUntilChanged() - ?.observeOn(mainScheduler) - ?.subscribe { (url, isBookmark) -> - if (!isSearchViewFocused) { - view.updateState(viewState.copy( - displayUrl = searchBoxModel.getDisplayContent( - url = url, - title = tabModel.title, - isLoading = tabModel.loadingProgress < 100 - ), - isBookmarked = isBookmark, - isBookmarkEnabled = !url.isSpecialUrl() - )) - } - } - - loadingDisposable?.dispose() - loadingDisposable = tabModel?.loadingProgress() - ?.distinctUntilChanged() - ?.observeOn(mainScheduler) - ?.subscribe { - view.updateState(viewState.copy( - progress = it, - isRefresh = it == 100 && !isSearchViewFocused - )) - } - - canGoBackDisposable?.dispose() - canGoBackDisposable = tabModel?.canGoBackChanges() - ?.distinctUntilChanged() - ?.observeOn(mainScheduler) - ?.subscribe { - view.updateState(viewState.copy(isBackEnabled = it)) - } - - canGoForwardDisposable?.dispose() - canGoForwardDisposable = tabModel?.canGoForwardChanges() - ?.distinctUntilChanged() - ?.observeOn(mainScheduler) - ?.subscribe { - view.updateState(viewState.copy(isForwardEnabled = it)) - } + ) + }.subscribeOn(mainScheduler) + .subscribe { view.updateState(it) } } private fun List.subscribeToUpdates(compositeDisposable: CompositeDisposable) { forEach { tabModel -> - compositeDisposable += tabModel.titleChanges() - .distinctUntilChanged() - .observeOn(mainScheduler) - .subscribe { title -> + compositeDisposable += Observables.combineLatest( + tabModel.titleChanges().startWith(tabModel.title), + tabModel.faviconChanges().optional().startWith(Option.fromNullable(tabModel.favicon)) + ).distinctUntilChanged() + .subscribeOn(mainScheduler) + .subscribeBy { (title, bitmap) -> view.updateState(viewState.copy(tabs = viewState.tabs.updateId(tabModel.id) { - it.copy(title = title) + it.copy(title = title, icon = bitmap.value()) })) - currentTab?.url?.takeIf { !it.isSpecialUrl() }?.let { + tabModel.url.takeIf { !it.isSpecialUrl() }?.let { historyRecord.recordVisit(title, it) } } - - compositeDisposable += tabModel.faviconChanges() - .observeOn(mainScheduler) - .subscribe { favicon -> - view.updateState(viewState.copy(tabs = viewState.tabs.updateId(tabModel.id) { - it.copy(icon = favicon) - })) - } } } + private fun Observable.optional(): Observable> = map { Option.Some(it) } + /** * TODO */ @@ -517,7 +454,6 @@ class BrowserPresenter @Inject constructor( } } } - } /** From b3a8c8a4d7a3194cbeb136638387d421bbfc591b Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 22 Sep 2020 23:36:01 -0400 Subject: [PATCH 033/161] Simplify map --- .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index e0570cdc7..51b92cac0 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -181,13 +181,7 @@ class BrowserPresenter @Inject constructor( isBackEnabled = canGoBack, sslState = sslState, progress = progress, - tabs = viewState.tabs.map { - if (it.id == tabModel.id) { - it.copy(isSelected = true) - } else { - it.copy(isSelected = false) - } - } + tabs = viewState.tabs.map { it.copy(isSelected = it.id == tabModel.id) } ) }.subscribeOn(mainScheduler) .subscribe { view.updateState(it) } From 72275e92a54caec26bfb4f4d8770393f6420aad6 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 24 Sep 2020 21:15:36 -0400 Subject: [PATCH 034/161] Support custom browser headers --- .../lightning/_browser2/BrowserPresenter.kt | 5 ++- .../lightning/_browser2/tab/TabAdapter.kt | 5 ++- .../lightning/_browser2/tab/TabsRepository.kt | 1 + .../lightning/_browser2/tab/WebViewFactory.kt | 45 ++++++++++--------- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 51b92cac0..d090f7dbd 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -130,7 +130,10 @@ class BrowserPresenter @Inject constructor( isSelected = isForeground ) - private fun List.updateId(id: Int, map: (TabViewState) -> TabViewState): List = map { + private fun List.updateId( + id: Int, + map: (TabViewState) -> TabViewState + ): List = map { if (it.id == id) { map(it) } else { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index a5695361c..d3a2551d7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -14,6 +14,7 @@ import io.reactivex.Observable class TabAdapter( tabInitializer: TabInitializer, private val webView: WebView, + private val requestHeaders: Map, private val tabWebViewClient: TabWebViewClient, private val tabWebChromeClient: TabWebChromeClient ) : TabModel { @@ -33,11 +34,11 @@ class TabAdapter( override val id: Int = webView.id override fun loadUrl(url: String) { - webView.loadUrl(url) + webView.loadUrl(url, requestHeaders) } override fun loadFromInitializer(tabInitializer: TabInitializer) { - tabInitializer.initialize(webView, emptyMap()) + tabInitializer.initialize(webView, requestHeaders) } override fun goBack() { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index 0d7468dfc..f12d8d788 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -44,6 +44,7 @@ class TabsRepository @Inject constructor( val tabAdapter = TabAdapter( tabInitializer, webView, + webViewFactory.createRequestHeaders(), TabWebViewClient(adBlocker, allowListModel), TabWebChromeClient() ) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt index 496456e07..e61bb51d9 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt @@ -25,6 +25,31 @@ class WebViewFactory @Inject constructor( private val userPreferences: UserPreferences ) { + fun createRequestHeaders(): Map { + val requestHeaders = mutableMapOf() + if (userPreferences.doNotTrackEnabled) { + requestHeaders[HEADER_DNT] = "1" + } else { + requestHeaders.remove(HEADER_DNT) + } + + if (userPreferences.saveDataEnabled) { + requestHeaders[HEADER_SAVEDATA] = "on" + } else { + requestHeaders.remove(HEADER_SAVEDATA) + } + + if (userPreferences.removeIdentifyingHeadersEnabled) { + requestHeaders[HEADER_REQUESTED_WITH] = "" + requestHeaders[HEADER_WAP_PROFILE] = "" + } else { + requestHeaders.remove(HEADER_REQUESTED_WITH) + requestHeaders.remove(HEADER_WAP_PROFILE) + } + + return requestHeaders + } + fun createWebView(isIncognito: Boolean): WebView = WebView(activity).apply { id = View.generateViewId() isFocusableInTouchMode = true @@ -91,26 +116,6 @@ class WebViewFactory @Inject constructor( val modifiesHeaders = userPreferences.doNotTrackEnabled || userPreferences.saveDataEnabled || userPreferences.removeIdentifyingHeadersEnabled -// -// if (userPreferences.doNotTrackEnabled) { -// requestHeaders[LightningView.HEADER_DNT] = "1" -// } else { -// requestHeaders.remove(LightningView.HEADER_DNT) -// } -// -// if (userPreferences.saveDataEnabled) { -// requestHeaders[LightningView.HEADER_SAVEDATA] = "on" -// } else { -// requestHeaders.remove(LightningView.HEADER_SAVEDATA) -// } -// -// if (userPreferences.removeIdentifyingHeadersEnabled) { -// requestHeaders[LightningView.HEADER_REQUESTED_WITH] = "" -// requestHeaders[LightningView.HEADER_WAP_PROFILE] = "" -// } else { -// requestHeaders.remove(LightningView.HEADER_REQUESTED_WITH) -// requestHeaders.remove(LightningView.HEADER_WAP_PROFILE) -// } settings.defaultTextEncodingName = userPreferences.textEncoding // setColorMode(userPreferences.renderingMode) From 0283085b46591e382dcb40df644107e42ef66d9f Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 24 Sep 2020 21:32:23 -0400 Subject: [PATCH 035/161] Support back press --- .../acr/browser/lightning/_browser2/BrowserActivity.kt | 4 ++++ .../acr/browser/lightning/_browser2/BrowserContract.kt | 4 ++++ .../acr/browser/lightning/_browser2/BrowserNavigator.kt | 8 ++++++++ .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 6 ++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 71ee3b21c..0e948bab7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -132,6 +132,10 @@ class BrowserActivity : ThemableBrowserActivity() { presenter.onViewDetached() } + override fun onBackPressed() { + presenter.onBackClick() + } + override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.main, menu) return super.onCreateOptionsMenu(menu) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index fc88f21a3..be3c77d54 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -47,6 +47,10 @@ interface BrowserContract { fun sharePage(url: String, title: String?) fun copyPageLink(url: String) + + fun closeBrowser() + + fun backgroundBrowser() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt index 22b663e38..a79ea4ba1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt @@ -36,4 +36,12 @@ class BrowserNavigator @Inject constructor( activity.snackbar(R.string.message_link_copied) } + override fun closeBrowser() { + activity.finish() + } + + override fun backgroundBrowser() { + activity.moveTaskToBack(true) + } + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index d090f7dbd..ca157c159 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -325,8 +325,10 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onBackClick() { - if (currentTab?.canGoBack() == true) { - currentTab?.goBack() + when { + currentTab?.canGoBack() == true -> currentTab?.goBack() + currentTab == null -> navigator.closeBrowser() + else -> navigator.backgroundBrowser() } } From 74e5671377a01b3aaf07ce89db520ab8efd1fdac Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 29 Sep 2020 21:27:36 -0400 Subject: [PATCH 036/161] Full support for adding bookmarks --- .../lightning/_browser2/BrowserActivity.kt | 14 ++++ .../lightning/_browser2/BrowserContract.kt | 2 + .../lightning/_browser2/BrowserPresenter.kt | 83 ++++++++++++++----- .../_browser2/BrowserStateAdapter.kt | 5 +- .../dialog/LightningDialogBuilder.kt | 31 +++++++ .../main/res/layout/dialog_edit_bookmark.xml | 1 + 6 files changed, 112 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 0e948bab7..b50c38e63 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -14,6 +14,7 @@ import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage import acr.browser.lightning.databinding.BrowserActivityBinding import acr.browser.lightning.di.injector +import acr.browser.lightning.dialog.LightningDialogBuilder import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState import android.content.Intent @@ -52,6 +53,9 @@ class BrowserActivity : ThemableBrowserActivity() { @Inject internal lateinit var intentExtractor: IntentExtractor + @Inject + internal lateinit var lightningDialogBuilder: LightningDialogBuilder + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = BrowserActivityBinding.inflate(LayoutInflater.from(this)) @@ -177,6 +181,16 @@ class BrowserActivity : ThemableBrowserActivity() { viewState.isBookmarkEnabled?.let { binding.actionAddBookmark.isEnabled = it } } + fun showAddBookmarkDialog(title: String, url: String, folders: List) { + lightningDialogBuilder.showAddBookmarkDialog( + activity = this, + currentTitle = title, + currentUrl = url, + folders = folders, + onSave = presenter::onBookmarkConfirmed + ) + } + private fun ImageView.updateVisibilityForDrawable() { visibility = if (drawable == null) { View.GONE diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index be3c77d54..a46f8cc97 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -18,6 +18,8 @@ interface BrowserContract { fun renderState(viewState: BrowserViewState) + fun showAddBookmarkDialog(title: String, url: String, folders: List) + } interface Model { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index ca157c159..948c713ec 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -4,13 +4,10 @@ import acr.browser.lightning._browser2.di.InitialUrl import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.keys.KeyCombo import acr.browser.lightning._browser2.menu.MenuSelection -import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning.browser.SearchBoxModel -import acr.browser.lightning.database.Bookmark -import acr.browser.lightning.database.HistoryEntry -import acr.browser.lightning.database.SearchSuggestion -import acr.browser.lightning.database.WebPage +import acr.browser.lightning.database.* import acr.browser.lightning.database.bookmark.BookmarkRepository import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.MainScheduler @@ -21,16 +18,15 @@ import acr.browser.lightning.view.DownloadPageInitializer import acr.browser.lightning.view.HistoryPageInitializer import acr.browser.lightning.view.HomePageInitializer import acr.browser.lightning.view.UrlInitializer -import android.graphics.Bitmap import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Scheduler +import io.reactivex.Single import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.subscribeBy -import java.util.* import javax.inject.Inject /** @@ -79,7 +75,7 @@ class BrowserPresenter @Inject constructor( this.view = view view.updateState(viewState) - compositeDisposable += bookmarkRepository.getBookmarksFromFolderSorted(folder = null) + compositeDisposable += bookmarkRepository.bookmarksAndFolders(folder = null) .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> @@ -171,8 +167,10 @@ class BrowserPresenter @Inject constructor( tab.urlChanges().startWith(tab.url), tab.loadingProgress().startWith(tab.loadingProgress), tab.canGoBackChanges().startWith(tab.canGoBack()), - tab.canGoForwardChanges().startWith(tab.canGoForward()) - ) { sslState, title, url, progress, canGoBack, canGoForward -> + tab.canGoForwardChanges().startWith(tab.canGoForward()), + tab.urlChanges().startWith(tab.url).flatMapSingle(bookmarkRepository::isBookmark), + tab.urlChanges().startWith(tab.url).map(String::isSpecialUrl) + ) { sslState, title, url, progress, canGoBack, canGoForward, isBookmark, isSpecialUrl -> viewState.copy( displayUrl = searchBoxModel.getDisplayContent( url = url, @@ -184,7 +182,9 @@ class BrowserPresenter @Inject constructor( isBackEnabled = canGoBack, sslState = sslState, progress = progress, - tabs = viewState.tabs.map { it.copy(isSelected = it.id == tabModel.id) } + tabs = viewState.tabs.map { it.copy(isSelected = it.id == tabModel.id) }, + isBookmarked = isBookmark, + isBookmarkEnabled = !isSpecialUrl ) }.subscribeOn(mainScheduler) .subscribe { view.updateState(it) } @@ -249,7 +249,8 @@ class BrowserPresenter @Inject constructor( ?.let(navigator::copyPageLink) MenuSelection.ADD_TO_HOME -> TODO() MenuSelection.BOOKMARKS -> TODO() - MenuSelection.ADD_BOOKMARK -> TODO() + MenuSelection.ADD_BOOKMARK -> currentTab?.url?.takeIf { !it.isSpecialUrl() } + ?.let { showBookmarkDialog() } MenuSelection.READER -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let(navigator::openReaderMode) MenuSelection.SETTINGS -> navigator.openSettings() @@ -445,7 +446,7 @@ class BrowserPresenter @Inject constructor( is Bookmark.Folder.Entry -> { currentFolder = bookmark compositeDisposable += bookmarkRepository - .getBookmarksFromFolderSorted(folder = bookmark.title) + .bookmarksAndFolders(folder = bookmark) .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> @@ -455,6 +456,18 @@ class BrowserPresenter @Inject constructor( } } + private fun BookmarkRepository.bookmarksAndFolders(folder: Bookmark.Folder?): Single> = + getBookmarksFromFolderSorted(folder = folder?.title) + .concatWith(Single.defer { + if (folder == null) { + getFoldersSorted() + } else { + Single.just(emptyList()) + } + }) + .toList() + .map { it.flatten() } + /** * TODO */ @@ -479,24 +492,47 @@ class BrowserPresenter @Inject constructor( return } compositeDisposable += bookmarkRepository.isBookmark(url) - .flatMap { + .flatMapMaybe { if (it) { bookmarkRepository.deleteBookmark(Bookmark.Entry( url = url, title = title, position = 0, folder = Bookmark.Folder.Root - )) + )).toMaybe() } else { - bookmarkRepository.addBookmarkIfNotExists(Bookmark.Entry( - url = url, - title = title, - position = 0, - folder = Bookmark.Folder.Root - )) + Maybe.empty() } } - .flatMap { bookmarkRepository.getBookmarksFromFolderSorted(folder = currentFolder.title) } + .doOnComplete(::showBookmarkDialog) + .flatMapSingleElement { bookmarkRepository.bookmarksAndFolders(folder = currentFolder) } + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribeBy { list -> + this.view?.updateState(viewState.copy(bookmarks = list)) + } + } + + private fun showBookmarkDialog() { + compositeDisposable += bookmarkRepository.getFolderNames() + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribeBy { + view?.showAddBookmarkDialog( + title = currentTab?.title.orEmpty(), + url = currentTab?.url.orEmpty(), + folders = it + ) + } + } + + fun onBookmarkConfirmed(title: String, url: String, folder: String) { + compositeDisposable += bookmarkRepository.addBookmarkIfNotExists(Bookmark.Entry( + url = url, + title = title, + position = 0, + folder = folder.asFolder() + )).flatMap { bookmarkRepository.bookmarksAndFolders(folder = currentFolder) } .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> @@ -523,8 +559,9 @@ class BrowserPresenter @Inject constructor( */ fun onBookmarkMenuClick() { if (currentFolder != Bookmark.Folder.Root) { + currentFolder = Bookmark.Folder.Root compositeDisposable += bookmarkRepository - .getBookmarksFromFolderSorted(folder = null) + .bookmarksAndFolders(folder = null) .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 5265928f2..c11064124 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -1,6 +1,5 @@ package acr.browser.lightning._browser2 - /** * Created by anthonycr on 9/16/20. */ @@ -40,4 +39,8 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse currentState = viewState } + override fun showAddBookmarkDialog(title: String, url: String, folders: List) { + browserActivity.showAddBookmarkDialog(title, url, folders) + } + } diff --git a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt index 997009258..01c08675c 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt +++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt @@ -9,6 +9,7 @@ import acr.browser.lightning.database.asFolder import acr.browser.lightning.database.bookmark.BookmarkRepository import acr.browser.lightning.database.downloads.DownloadsRepository import acr.browser.lightning.database.history.HistoryRepository +import acr.browser.lightning.databinding.DialogEditBookmarkBinding import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.MainScheduler import acr.browser.lightning.download.DownloadHandler @@ -188,6 +189,36 @@ class LightningDialogBuilder @Inject constructor( } } + fun showAddBookmarkDialog( + activity: Activity, + currentTitle: String, + currentUrl: String, + folders: List, + onSave: (title: String, url: String, folder: String) -> Unit + ) { + val editBookmarkDialog = AlertDialog.Builder(activity) + editBookmarkDialog.setTitle(R.string.action_add_bookmark) + val dialogLayout = View.inflate(activity, R.layout.dialog_edit_bookmark, null) + val binding = DialogEditBookmarkBinding.bind(dialogLayout) + binding.bookmarkTitle.setText(currentTitle) + binding.bookmarkUrl.setText(currentUrl) + binding.bookmarkFolder.setText("") + + val suggestionsAdapter = ArrayAdapter(activity, + android.R.layout.simple_dropdown_item_1line, folders) + binding.bookmarkFolder.setAdapter(suggestionsAdapter) + editBookmarkDialog.setView(dialogLayout) + editBookmarkDialog.setPositiveButton(activity.getString(R.string.action_ok)) { _, _ -> + onSave( + binding.bookmarkTitle.text.toString(), + binding.bookmarkUrl.text.toString(), + binding.bookmarkFolder.text.toString() + ) + } + editBookmarkDialog.setNegativeButton(R.string.action_cancel) { _, _ -> } + editBookmarkDialog.resizeAndShow() + } + private fun showEditBookmarkDialog( activity: Activity, uiController: UIController, diff --git a/app/src/main/res/layout/dialog_edit_bookmark.xml b/app/src/main/res/layout/dialog_edit_bookmark.xml index e84b67a36..22f3fe6c1 100644 --- a/app/src/main/res/layout/dialog_edit_bookmark.xml +++ b/app/src/main/res/layout/dialog_edit_bookmark.xml @@ -29,6 +29,7 @@ android:id="@+id/bookmark_folder" android:layout_width="match_parent" android:layout_height="wrap_content" + android:completionThreshold="1" android:hint="@string/folder" android:maxLines="1" android:singleLine="true" /> From 78260d0f0cbe740fb0f242ea4b8e499fa723d33a Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 30 Sep 2020 22:43:04 -0400 Subject: [PATCH 037/161] Add support for editing bookmark and folder --- .../lightning/_browser2/BrowserActivity.kt | 20 +++++++ .../lightning/_browser2/BrowserContract.kt | 3 + .../lightning/_browser2/BrowserPresenter.kt | 58 ++++++++++++++++--- .../_browser2/BrowserStateAdapter.kt | 8 +++ .../dialog/LightningDialogBuilder.kt | 46 +++++++++++++++ 5 files changed, 126 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index b50c38e63..f119af468 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -10,6 +10,7 @@ import acr.browser.lightning._browser2.search.SearchListener import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher import acr.browser.lightning.browser.activity.ThemableBrowserActivity +import acr.browser.lightning.database.Bookmark import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage import acr.browser.lightning.databinding.BrowserActivityBinding @@ -191,6 +192,25 @@ class BrowserActivity : ThemableBrowserActivity() { ) } + fun showEditBookmarkDialog(title: String, url: String, folder: String, folders: List) { + lightningDialogBuilder.showEditBookmarkDialog( + activity = this, + currentTitle = title, + currentUrl = url, + currentFolder = folder, + folders = folders, + onSave = presenter::onBookmarkEditConfirmed + ) + } + + fun showEditFolderDialog(oldTitle: String) { + lightningDialogBuilder.showRenameFolderDialog( + activity = this, + oldTitle = oldTitle, + onSave = presenter::onBookmarkFolderRenameConfirmed + ) + } + private fun ImageView.updateVisibilityForDrawable() { visibility = if (drawable == null) { View.GONE diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index a46f8cc97..bf32eb348 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -20,6 +20,9 @@ interface BrowserContract { fun showAddBookmarkDialog(title: String, url: String, folders: List) + fun showEditBookmarkDialog(title: String, url: String, folder: String, folders: List) + + fun showEditFolderDialog(title: String) } interface Model { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 948c713ec..ebec5025b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -75,7 +75,8 @@ class BrowserPresenter @Inject constructor( this.view = view view.updateState(viewState) - compositeDisposable += bookmarkRepository.bookmarksAndFolders(folder = null) + currentFolder = Bookmark.Folder.Root + compositeDisposable += bookmarkRepository.bookmarksAndFolders(folder = Bookmark.Folder.Root) .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> @@ -250,7 +251,7 @@ class BrowserPresenter @Inject constructor( MenuSelection.ADD_TO_HOME -> TODO() MenuSelection.BOOKMARKS -> TODO() MenuSelection.ADD_BOOKMARK -> currentTab?.url?.takeIf { !it.isSpecialUrl() } - ?.let { showBookmarkDialog() } + ?.let { showAddBookmarkDialog() } MenuSelection.READER -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let(navigator::openReaderMode) MenuSelection.SETTINGS -> navigator.openSettings() @@ -456,10 +457,10 @@ class BrowserPresenter @Inject constructor( } } - private fun BookmarkRepository.bookmarksAndFolders(folder: Bookmark.Folder?): Single> = - getBookmarksFromFolderSorted(folder = folder?.title) + private fun BookmarkRepository.bookmarksAndFolders(folder: Bookmark.Folder): Single> = + getBookmarksFromFolderSorted(folder = folder.title) .concatWith(Single.defer { - if (folder == null) { + if (folder == Bookmark.Folder.Root) { getFoldersSorted() } else { Single.just(emptyList()) @@ -472,7 +473,15 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onBookmarkLongClick(index: Int) { - + compositeDisposable += bookmarkRepository.getFolderNames() + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribeBy { folders -> + when (val item = viewState.bookmarks[index]) { + is Bookmark.Entry -> view?.showEditBookmarkDialog(item.title, item.url, item.folder.title, folders) + is Bookmark.Folder.Entry -> view?.showEditFolderDialog(item.title) + } + } } /** @@ -504,7 +513,7 @@ class BrowserPresenter @Inject constructor( Maybe.empty() } } - .doOnComplete(::showBookmarkDialog) + .doOnComplete(::showAddBookmarkDialog) .flatMapSingleElement { bookmarkRepository.bookmarksAndFolders(folder = currentFolder) } .subscribeOn(databaseScheduler) .observeOn(mainScheduler) @@ -513,7 +522,7 @@ class BrowserPresenter @Inject constructor( } } - private fun showBookmarkDialog() { + private fun showAddBookmarkDialog() { compositeDisposable += bookmarkRepository.getFolderNames() .subscribeOn(databaseScheduler) .observeOn(mainScheduler) @@ -540,6 +549,37 @@ class BrowserPresenter @Inject constructor( } } + fun onBookmarkEditConfirmed(title: String, url: String, folder: String) { + compositeDisposable += bookmarkRepository.editBookmark( + oldBookmark = Bookmark.Entry( + url, + "", + 0, + Bookmark.Folder.Root + ), + newBookmark = Bookmark.Entry( + url = url, + title = title, + position = 0, + folder = folder.asFolder() + )).andThen(bookmarkRepository.bookmarksAndFolders(folder = currentFolder)) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribe { list -> + this.view?.updateState(viewState.copy(bookmarks = list)) + } + } + + fun onBookmarkFolderRenameConfirmed(oldTitle: String, newTitle: String) { + compositeDisposable += bookmarkRepository.renameFolder(oldTitle, newTitle) + .andThen(bookmarkRepository.bookmarksAndFolders(folder = currentFolder)) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribe { list -> + this.view?.updateState(viewState.copy(bookmarks = list)) + } + } + /** * TODO */ @@ -561,7 +601,7 @@ class BrowserPresenter @Inject constructor( if (currentFolder != Bookmark.Folder.Root) { currentFolder = Bookmark.Folder.Root compositeDisposable += bookmarkRepository - .bookmarksAndFolders(folder = null) + .bookmarksAndFolders(folder = Bookmark.Folder.Root) .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index c11064124..9631be0ee 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -43,4 +43,12 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showAddBookmarkDialog(title, url, folders) } + override fun showEditBookmarkDialog(title: String, url: String, folder: String, folders: List) { + browserActivity.showEditBookmarkDialog(title, url, folder, folders) + } + + override fun showEditFolderDialog(title: String) { + browserActivity.showEditFolderDialog(title) + } + } diff --git a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt index 01c08675c..267f454c7 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt +++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt @@ -260,6 +260,36 @@ class LightningDialogBuilder @Inject constructor( } } + fun showEditBookmarkDialog( + activity: Activity, + currentTitle: String, + currentUrl: String, + currentFolder: String, + folders: List, + onSave: (title: String, url: String, folder: String) -> Unit + ) { + val editBookmarkDialog = AlertDialog.Builder(activity) + editBookmarkDialog.setTitle(R.string.action_add_bookmark) + val dialogLayout = View.inflate(activity, R.layout.dialog_edit_bookmark, null) + val binding = DialogEditBookmarkBinding.bind(dialogLayout) + binding.bookmarkTitle.setText(currentTitle) + binding.bookmarkUrl.setText(currentUrl) + binding.bookmarkFolder.setText(currentFolder) + + val suggestionsAdapter = ArrayAdapter(activity, + android.R.layout.simple_dropdown_item_1line, folders) + binding.bookmarkFolder.setAdapter(suggestionsAdapter) + editBookmarkDialog.setView(dialogLayout) + editBookmarkDialog.setPositiveButton(activity.getString(R.string.action_ok)) { _, _ -> + onSave( + binding.bookmarkTitle.text.toString(), + binding.bookmarkUrl.text.toString(), + binding.bookmarkFolder.text.toString() + ) + } + editBookmarkDialog.resizeAndShow() + } + fun showBookmarkFolderLongPressedDialog( activity: Activity, uiController: UIController, @@ -295,6 +325,22 @@ class LightningDialogBuilder @Inject constructor( } } + fun showRenameFolderDialog( + activity: Activity, + oldTitle: String, + onSave: (oldTitle: String, newTitle: String) -> Unit + ) = BrowserDialog.showEditText( + activity, + R.string.title_rename_folder, + R.string.hint_title, + oldTitle, + R.string.action_ok + ) { text -> + if (text.isNotBlank()) { + onSave(oldTitle, text) + } + } + fun showLongPressedHistoryLinkDialog( activity: Activity, uiController: UIController, From ea3fa166ffbb44796e90d622de5901b26b669d21 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 30 Sep 2020 22:55:07 -0400 Subject: [PATCH 038/161] Show root folder when back is pressed --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index ebec5025b..aca4a0c44 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -328,6 +328,7 @@ class BrowserPresenter @Inject constructor( */ fun onBackClick() { when { + currentFolder != Bookmark.Folder.Root -> onBookmarkMenuClick() currentTab?.canGoBack() == true -> currentTab?.goBack() currentTab == null -> navigator.closeBrowser() else -> navigator.backgroundBrowser() From 5b95d366dbae71f049ff41a4a63b8922f37f9319 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 30 Sep 2020 23:00:49 -0400 Subject: [PATCH 039/161] Handle bookmark back button display --- .../lightning/_browser2/BrowserActivity.kt | 15 +++++++++++---- .../lightning/_browser2/BrowserPresenter.kt | 9 +++++---- .../lightning/_browser2/BrowserStateAdapter.kt | 6 ++++-- .../lightning/_browser2/BrowserViewState.kt | 6 ++++-- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index f119af468..a5725e272 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -167,19 +167,26 @@ class BrowserActivity : ThemableBrowserActivity() { binding.searchSslStatus.setImageDrawable(createSslDrawableForState(it)) binding.searchSslStatus.updateVisibilityForDrawable() } - viewState.tabs?.let { binding.tabCountView.updateCount(viewState.tabs.size) } + viewState.tabs?.let { binding.tabCountView.updateCount(it.size) } viewState.progress?.let { binding.progressView.progress = it } viewState.isRefresh?.let { - binding.searchRefresh.setImageResource(if (viewState.isRefresh) { + binding.searchRefresh.setImageResource(if (it) { R.drawable.ic_action_refresh } else { R.drawable.ic_action_delete }) } - viewState.tabs?.let { tabsAdapter.submitList(viewState.tabs) } - viewState.bookmarks?.let { bookmarksAdapter.submitList(viewState.bookmarks) } + viewState.tabs?.let(tabsAdapter::submitList) + viewState.bookmarks?.let(bookmarksAdapter::submitList) viewState.isBookmarked?.let { binding.actionAddBookmark.isSelected = it } viewState.isBookmarkEnabled?.let { binding.actionAddBookmark.isEnabled = it } + viewState.isRootFolder?.let { + binding.bookmarkBackButton.setImageResource(if (it) { + R.drawable.ic_action_star + } else { + R.drawable.ic_action_back + }) + } } fun showAddBookmarkDialog(title: String, url: String, folders: List) { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index aca4a0c44..87849b516 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -58,7 +58,8 @@ class BrowserPresenter @Inject constructor( isBackEnabled = false, bookmarks = emptyList(), isBookmarked = false, - isBookmarkEnabled = true + isBookmarkEnabled = true, + isRootFolder = true ) private var currentTab: TabModel? = null private var currentFolder: Bookmark.Folder = Bookmark.Folder.Root @@ -80,7 +81,7 @@ class BrowserPresenter @Inject constructor( .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> - this.view?.updateState(viewState.copy(bookmarks = list)) + this.view?.updateState(viewState.copy(bookmarks = list, isRootFolder = true)) } compositeDisposable += model.tabsListChanges() @@ -452,7 +453,7 @@ class BrowserPresenter @Inject constructor( .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> - view?.updateState(viewState.copy(bookmarks = list)) + view?.updateState(viewState.copy(bookmarks = list, isRootFolder = false)) } } } @@ -606,7 +607,7 @@ class BrowserPresenter @Inject constructor( .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> - view?.updateState(viewState.copy(bookmarks = list)) + view?.updateState(viewState.copy(bookmarks = list, isRootFolder = true)) } } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 9631be0ee..307244ebc 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -18,7 +18,8 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse isBackEnabled, bookmarks, isBookmarked, - isBookmarkEnabled + isBookmarkEnabled, + isRootFolder ) = viewState browserActivity.renderState( @@ -32,7 +33,8 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse isBackEnabled = isBackEnabled.takeIf { it != currentState?.isBackEnabled }, bookmarks = bookmarks.takeIf { it != currentState?.bookmarks }, isBookmarked = isBookmarked.takeIf { it != currentState?.isBookmarked }, - isBookmarkEnabled = isBookmarkEnabled.takeIf { it != currentState?.isBookmarkEnabled } + isBookmarkEnabled = isBookmarkEnabled.takeIf { it != currentState?.isBookmarkEnabled }, + isRootFolder = isRootFolder.takeIf { it != currentState?.isRootFolder } ) ) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt index a05650734..c921bb423 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt @@ -22,7 +22,8 @@ data class BrowserViewState( // Bookmarks val bookmarks: List, val isBookmarked: Boolean, - val isBookmarkEnabled: Boolean + val isBookmarkEnabled: Boolean, + val isRootFolder: Boolean ) data class PartialBrowserViewState( @@ -40,5 +41,6 @@ data class PartialBrowserViewState( // Bookmarks val bookmarks: List?, val isBookmarked: Boolean?, - val isBookmarkEnabled: Boolean? + val isBookmarkEnabled: Boolean?, + val isRootFolder: Boolean? ) From 1006a9fcdf4958684bd508ceb317841892997b62 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 30 Sep 2020 23:04:56 -0400 Subject: [PATCH 040/161] Add in long click function --- .../acr/browser/lightning/_browser2/BrowserActivity.kt | 3 +-- .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index a5725e272..58d9d9c52 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -10,7 +10,6 @@ import acr.browser.lightning._browser2.search.SearchListener import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher import acr.browser.lightning.browser.activity.ThemableBrowserActivity -import acr.browser.lightning.database.Bookmark import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage import acr.browser.lightning.databinding.BrowserActivityBinding @@ -74,7 +73,7 @@ class BrowserActivity : ThemableBrowserActivity() { tabsAdapter = TabRecyclerViewAdapter( onClick = presenter::onTabClick, onCloseClick = presenter::onTabClose, - onLongClick = {} + onLongClick = presenter::onTabLongClick ) binding.tabsList.adapter = tabsAdapter binding.tabsList.layoutManager = LinearLayoutManager(this) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 87849b516..e84d3ff23 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -292,6 +292,13 @@ class BrowserPresenter @Inject constructor( selectTab(model.selectTab(viewState.tabs[index].id)) } + /** + * TODO + */ + fun onTabLongClick(index: Int) { + // TODO + } + private fun List.nextSelected(removedIndex: Int): T? { val nextIndex = when { removedIndex > 0 -> removedIndex - 1 From c01cad9dae666ca7b866ffb41e090f224f7152f9 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 14 Oct 2020 21:52:54 -0400 Subject: [PATCH 041/161] Support adding URL to home screen --- .../lightning/_browser2/BrowserContract.kt | 3 ++ .../lightning/_browser2/BrowserNavigator.kt | 18 +++++++++++- .../lightning/_browser2/BrowserPresenter.kt | 9 +++++- .../acr/browser/lightning/utils/Utils.java | 29 +++++++++++++++---- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index bf32eb348..18423a5e1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning.view.TabInitializer +import android.graphics.Bitmap import io.reactivex.Completable import io.reactivex.Maybe import io.reactivex.Observable @@ -55,6 +56,8 @@ interface BrowserContract { fun closeBrowser() + fun addToHomeScreen(url: String, title: String, favicon: Bitmap?) + fun backgroundBrowser() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt index a79ea4ba1..174291aed 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt @@ -1,14 +1,20 @@ package acr.browser.lightning._browser2 import acr.browser.lightning.R +import acr.browser.lightning.browser.activity.BrowserActivity +import acr.browser.lightning.database.HistoryEntry import acr.browser.lightning.extensions.copyToClipboard import acr.browser.lightning.extensions.snackbar +import acr.browser.lightning.log.Logger import acr.browser.lightning.reading.activity.ReadingActivity import acr.browser.lightning.settings.activity.SettingsActivity import acr.browser.lightning.utils.IntentUtils +import acr.browser.lightning.utils.Utils +import acr.browser.lightning.utils.isSpecialUrl import android.app.Activity import android.content.ClipboardManager import android.content.Intent +import android.graphics.Bitmap import javax.inject.Inject /** @@ -16,7 +22,8 @@ import javax.inject.Inject */ class BrowserNavigator @Inject constructor( private val activity: Activity, - private val clipboardManager: ClipboardManager + private val clipboardManager: ClipboardManager, + private val logger: Logger ) : BrowserContract.Navigator { override fun openSettings() { @@ -40,8 +47,17 @@ class BrowserNavigator @Inject constructor( activity.finish() } + override fun addToHomeScreen(url: String, title: String, favicon: Bitmap?) { + Utils.createShortcut(activity, url, title, favicon) + logger.log(TAG, "Creating shortcut: $title $url") + } + override fun backgroundBrowser() { activity.moveTaskToBack(true) } + companion object { + private const val TAG = "BrowserNavigator" + } + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index e84d3ff23..4884e1470 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -249,7 +249,8 @@ class BrowserPresenter @Inject constructor( MenuSelection.FIND -> TODO() MenuSelection.COPY_LINK -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let(navigator::copyPageLink) - MenuSelection.ADD_TO_HOME -> TODO() + MenuSelection.ADD_TO_HOME -> currentTab?.url?.takeIf { !it.isSpecialUrl() } + ?.let { addToHomeScreen() } MenuSelection.BOOKMARKS -> TODO() MenuSelection.ADD_BOOKMARK -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let { showAddBookmarkDialog() } @@ -259,6 +260,12 @@ class BrowserPresenter @Inject constructor( } } + private fun addToHomeScreen() { + currentTab?.let { + navigator.addToHomeScreen(it.url, it.title, it.favicon) + } + } + /** * TODO */ diff --git a/app/src/main/java/acr/browser/lightning/utils/Utils.java b/app/src/main/java/acr/browser/lightning/utils/Utils.java index ef8487950..81bc96c05 100644 --- a/app/src/main/java/acr/browser/lightning/utils/Utils.java +++ b/app/src/main/java/acr/browser/lightning/utils/Utils.java @@ -12,6 +12,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Build; @@ -38,6 +39,8 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableKt; public final class Utils { @@ -228,17 +231,31 @@ public static void close(@Nullable Closeable closeable) { * browser. The icon, URL, and title are used in * the creation of the shortcut. * - * @param activity the activity needed to create - * the intent and show a snackbar message - * @param historyEntry the HistoryEntity to create the shortcut from + * @param activity the activity needed to create + * the intent and show a snackbar message + * @param historyEntry the HistoryEntity to create the shortcut from */ public static void createShortcut(@NonNull Activity activity, @NonNull HistoryEntry historyEntry, @NonNull Bitmap favicon) { + createShortcut(activity, historyEntry.getUrl(), historyEntry.getTitle(), favicon); + } + + public static void createShortcut(@NonNull Activity activity, + @NonNull String url, + @NonNull String unsafeTitle, + @Nullable Bitmap unsafeFavicon) { Intent shortcutIntent = new Intent(Intent.ACTION_VIEW); - shortcutIntent.setData(Uri.parse(historyEntry.getUrl())); + shortcutIntent.setData(Uri.parse(url)); + + final String title = TextUtils.isEmpty(unsafeTitle) ? activity.getString(R.string.untitled) : unsafeTitle; + final Drawable webPageDrawable = ContextCompat.getDrawable(activity, R.drawable.ic_webpage); + Preconditions.checkNonNull(webPageDrawable); + final Bitmap webPageBitmap = DrawableKt.toBitmap(webPageDrawable, + webPageDrawable.getIntrinsicWidth(), + webPageDrawable.getIntrinsicHeight(), null); - final String title = TextUtils.isEmpty(historyEntry.getTitle()) ? activity.getString(R.string.untitled) : historyEntry.getTitle(); + final Bitmap favicon = unsafeFavicon != null ? unsafeFavicon : webPageBitmap; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { Intent addIntent = new Intent(); @@ -252,7 +269,7 @@ public static void createShortcut(@NonNull Activity activity, ShortcutManager shortcutManager = activity.getSystemService(ShortcutManager.class); if (shortcutManager.isRequestPinShortcutSupported()) { ShortcutInfo pinShortcutInfo = - new ShortcutInfo.Builder(activity, "browser-shortcut-" + historyEntry.getUrl().hashCode()) + new ShortcutInfo.Builder(activity, "browser-shortcut-" + url.hashCode()) .setIntent(shortcutIntent) .setIcon(Icon.createWithBitmap(favicon)) .setShortLabel(title) From 127b55593f7ebfebfb4a6f85dc7e8e5aea8e299c Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 18 Nov 2020 21:57:18 -0500 Subject: [PATCH 042/161] Handle back/forward menu items, handle opening tabs and bookmarks --- .../lightning/_browser2/BrowserActivity.kt | 41 +++++++++++++++++++ .../lightning/_browser2/BrowserContract.kt | 4 ++ .../lightning/_browser2/BrowserPresenter.kt | 32 +++++++++++---- .../_browser2/BrowserStateAdapter.kt | 8 ++++ .../lightning/_browser2/di/Browser2Module.kt | 15 +++++++ .../_browser2/menu/MenuItemAdapter.kt | 4 +- .../lightning/_browser2/menu/MenuSelection.kt | 4 +- .../_browser2/ui/BookmarkConfiguration.kt | 9 ++++ .../_browser2/ui/TabConfiguration.kt | 9 ++++ .../lightning/_browser2/ui/UiConfiguration.kt | 9 ++++ .../browser/activity/BrowserActivity.kt | 34 +++++++-------- app/src/main/res/layout/activity_main.xml | 4 +- app/src/main/res/layout/browser_activity.xml | 4 +- 13 files changed, 146 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/ui/BookmarkConfiguration.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/ui/TabConfiguration.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/ui/UiConfiguration.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 58d9d9c52..a161454a1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -8,12 +8,16 @@ import acr.browser.lightning._browser2.menu.MenuItemAdapter import acr.browser.lightning._browser2.search.IntentExtractor import acr.browser.lightning._browser2.search.SearchListener import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter +import acr.browser.lightning._browser2.ui.BookmarkConfiguration +import acr.browser.lightning._browser2.ui.TabConfiguration +import acr.browser.lightning._browser2.ui.UiConfiguration import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher import acr.browser.lightning.browser.activity.ThemableBrowserActivity import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage import acr.browser.lightning.databinding.BrowserActivityBinding import acr.browser.lightning.di.injector +import acr.browser.lightning.dialog.BrowserDialog import acr.browser.lightning.dialog.LightningDialogBuilder import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState @@ -23,6 +27,8 @@ import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.AdapterView import android.widget.ImageView +import androidx.core.view.isVisible +import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.LinearLayoutManager import javax.inject.Inject @@ -56,6 +62,9 @@ class BrowserActivity : ThemableBrowserActivity() { @Inject internal lateinit var lightningDialogBuilder: LightningDialogBuilder + @Inject + internal lateinit var uiConfiguration: UiConfiguration + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = BrowserActivityBinding.inflate(LayoutInflater.from(this)) @@ -70,6 +79,27 @@ class BrowserActivity : ThemableBrowserActivity() { .build() .inject(this) + binding.bookmarkDrawer.layoutParams = (binding.bookmarkDrawer.layoutParams as DrawerLayout.LayoutParams).apply { + gravity = when (uiConfiguration.bookmarkConfiguration) { + BookmarkConfiguration.LEFT -> Gravity.START + BookmarkConfiguration.RIGHT -> Gravity.END + } + } + + binding.tabDrawer.layoutParams = (binding.tabDrawer.layoutParams as DrawerLayout.LayoutParams).apply { + gravity = when (uiConfiguration.bookmarkConfiguration) { + BookmarkConfiguration.LEFT -> Gravity.END + BookmarkConfiguration.RIGHT -> Gravity.START + } + } + + binding.homeImageView.isVisible = uiConfiguration.tabConfiguration == TabConfiguration.DESKTOP + binding.tabCountView.isVisible = uiConfiguration.tabConfiguration == TabConfiguration.DRAWER + + if (uiConfiguration.tabConfiguration == TabConfiguration.DESKTOP) { + binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, binding.tabDrawer) + } + tabsAdapter = TabRecyclerViewAdapter( onClick = presenter::onTabClick, onCloseClick = presenter::onTabClose, @@ -112,6 +142,7 @@ class BrowserActivity : ThemableBrowserActivity() { binding.search.addTextChangedListener(StyleRemovingTextWatcher()) binding.search.setOnFocusChangeListener { _, hasFocus -> presenter.onSearchFocusChanged(hasFocus) } + binding.homeButton.setOnClickListener { presenter.onTabCountViewClick() } binding.actionBack.setOnClickListener { presenter.onBackClick() } binding.actionForward.setOnClickListener { presenter.onForwardClick() } binding.actionHome.setOnClickListener { presenter.onHomeClick() } @@ -217,6 +248,16 @@ class BrowserActivity : ThemableBrowserActivity() { ) } + fun openBookmarkDrawer() { + binding.drawerLayout.closeDrawer(binding.tabDrawer) + binding.drawerLayout.openDrawer(binding.bookmarkDrawer) + } + + fun openTabDrawer() { + binding.drawerLayout.closeDrawer(binding.bookmarkDrawer) + binding.drawerLayout.openDrawer(binding.tabDrawer) + } + private fun ImageView.updateVisibilityForDrawable() { visibility = if (drawable == null) { View.GONE diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 18423a5e1..546ce9673 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -24,6 +24,10 @@ interface BrowserContract { fun showEditBookmarkDialog(title: String, url: String, folder: String, folders: List) fun showEditFolderDialog(title: String) + + fun openBookmarkDrawer() + + fun openTabDrawer() } interface Model { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 4884e1470..d07145e7e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -6,6 +6,8 @@ import acr.browser.lightning._browser2.keys.KeyCombo import acr.browser.lightning._browser2.menu.MenuSelection import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning._browser2.tab.TabViewState +import acr.browser.lightning._browser2.ui.TabConfiguration +import acr.browser.lightning._browser2.ui.UiConfiguration import acr.browser.lightning.browser.SearchBoxModel import acr.browser.lightning.database.* import acr.browser.lightning.database.bookmark.BookmarkRepository @@ -44,7 +46,8 @@ class BrowserPresenter @Inject constructor( private val downloadPageInitializer: DownloadPageInitializer, private val searchBoxModel: SearchBoxModel, private val searchEngineProvider: SearchEngineProvider, - @InitialUrl private val initialUrl: String? + @InitialUrl private val initialUrl: String?, + private val uiConfiguration: UiConfiguration ) { private var view: BrowserContract.View? = null @@ -251,12 +254,14 @@ class BrowserPresenter @Inject constructor( ?.let(navigator::copyPageLink) MenuSelection.ADD_TO_HOME -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let { addToHomeScreen() } - MenuSelection.BOOKMARKS -> TODO() + MenuSelection.BOOKMARKS -> view?.openBookmarkDrawer() MenuSelection.ADD_BOOKMARK -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let { showAddBookmarkDialog() } MenuSelection.READER -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let(navigator::openReaderMode) MenuSelection.SETTINGS -> navigator.openSettings() + MenuSelection.BACK -> onBackClick() + MenuSelection.FORWARD -> onForwardClick() } } @@ -432,6 +437,10 @@ class BrowserPresenter @Inject constructor( currentTab?.loadUrl(url) } + fun onFindInPage(query: String) { + TODO() + } + /** * TODO */ @@ -459,7 +468,7 @@ class BrowserPresenter @Inject constructor( fun onBookmarkClick(index: Int) { when (val bookmark = viewState.bookmarks[index]) { is Bookmark.Entry -> currentTab?.loadUrl(bookmark.url) - Bookmark.Folder.Root -> TODO() + Bookmark.Folder.Root -> error("Cannot click on root folder") is Bookmark.Folder.Entry -> { currentFolder = bookmark compositeDisposable += bookmarkRepository @@ -568,10 +577,10 @@ class BrowserPresenter @Inject constructor( fun onBookmarkEditConfirmed(title: String, url: String, folder: String) { compositeDisposable += bookmarkRepository.editBookmark( oldBookmark = Bookmark.Entry( - url, - "", - 0, - Bookmark.Folder.Root + url = url, + title = "", + position = 0, + folder = Bookmark.Folder.Root ), newBookmark = Bookmark.Entry( url = url, @@ -603,6 +612,15 @@ class BrowserPresenter @Inject constructor( } + /** + * TODO + */ + fun onTabCountViewClick() { + if (uiConfiguration.tabConfiguration == TabConfiguration.DRAWER) { + view?.openTabDrawer() + } + } + /** * TODO */ diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 307244ebc..85a79d6ba 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -53,4 +53,12 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showEditFolderDialog(title) } + override fun openBookmarkDrawer() { + browserActivity.openBookmarkDrawer() + } + + override fun openTabDrawer() { + browserActivity.openTabDrawer() + } + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index be9799110..0c9fefa2a 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -1,6 +1,9 @@ package acr.browser.lightning._browser2.di import acr.browser.lightning._browser2.search.IntentExtractor +import acr.browser.lightning._browser2.ui.BookmarkConfiguration +import acr.browser.lightning._browser2.ui.TabConfiguration +import acr.browser.lightning._browser2.ui.UiConfiguration import acr.browser.lightning.adblock.AdBlocker import acr.browser.lightning.adblock.BloomFilterAdBlocker import acr.browser.lightning.adblock.NoOpAdBlocker @@ -34,4 +37,16 @@ class Browser2Module { intentExtractor: IntentExtractor ): String? = intentExtractor.extractUrlFromIntent(initialIntent) + @Provides + fun providesUiConfiguration( + userPreferences: UserPreferences + ): UiConfiguration = UiConfiguration( + tabConfiguration = TabConfiguration.DRAWER, + bookmarkConfiguration = if (userPreferences.bookmarksAndTabsSwapped) { + BookmarkConfiguration.LEFT + } else { + BookmarkConfiguration.RIGHT + } + ) + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuItemAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuItemAdapter.kt index 476f2c53d..adf5faae0 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuItemAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuItemAdapter.kt @@ -12,8 +12,8 @@ class MenuItemAdapter @Inject constructor() { fun adaptMenuItem(menuItem: MenuItem): MenuSelection? { return when (menuItem.itemId) { android.R.id.home -> TODO() - R.id.action_back -> TODO() - R.id.action_forward -> TODO() + R.id.action_back -> MenuSelection.BACK + R.id.action_forward -> MenuSelection.FORWARD R.id.action_add_to_homescreen -> MenuSelection.ADD_TO_HOME R.id.action_new_tab -> MenuSelection.NEW_TAB R.id.action_incognito -> MenuSelection.NEW_INCOGNITO_TAB diff --git a/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuSelection.kt b/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuSelection.kt index 27df46bf0..83fe0fdf9 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuSelection.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/menu/MenuSelection.kt @@ -15,5 +15,7 @@ enum class MenuSelection { BOOKMARKS, ADD_BOOKMARK, READER, - SETTINGS + SETTINGS, + BACK, + FORWARD } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/ui/BookmarkConfiguration.kt b/app/src/main/java/acr/browser/lightning/_browser2/ui/BookmarkConfiguration.kt new file mode 100644 index 000000000..c90b0a48c --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/ui/BookmarkConfiguration.kt @@ -0,0 +1,9 @@ +package acr.browser.lightning._browser2.ui + +/** + * Created by anthonycr on 11/18/20. + */ +enum class BookmarkConfiguration { + LEFT, + RIGHT +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/ui/TabConfiguration.kt b/app/src/main/java/acr/browser/lightning/_browser2/ui/TabConfiguration.kt new file mode 100644 index 000000000..6afcfc84e --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/ui/TabConfiguration.kt @@ -0,0 +1,9 @@ +package acr.browser.lightning._browser2.ui + +/** + * Created by anthonycr on 11/18/20. + */ +enum class TabConfiguration { + DESKTOP, + DRAWER +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/ui/UiConfiguration.kt b/app/src/main/java/acr/browser/lightning/_browser2/ui/UiConfiguration.kt new file mode 100644 index 000000000..83d0a790c --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/ui/UiConfiguration.kt @@ -0,0 +1,9 @@ +package acr.browser.lightning._browser2.ui + +/** + * Created by anthonycr on 11/18/20. + */ +data class UiConfiguration( + val tabConfiguration: TabConfiguration, + val bookmarkConfiguration: BookmarkConfiguration +) diff --git a/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt index 8804a33e9..c899d4fbe 100644 --- a/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt @@ -247,8 +247,8 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr backgroundDrawable.color = primaryColor // Drawer stutters otherwise - left_drawer.setLayerType(LAYER_TYPE_NONE, null) - right_drawer.setLayerType(LAYER_TYPE_NONE, null) + tab_drawer.setLayerType(LAYER_TYPE_NONE, null) + bookmark_drawer.setLayerType(LAYER_TYPE_NONE, null) setNavigationDrawerWidth() drawer_layout.addDrawerListener(DrawerLocker()) @@ -361,31 +361,31 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr } private fun getBookmarksContainerId(): Int = if (swapBookmarksAndTabs) { - R.id.left_drawer + R.id.tab_drawer } else { - R.id.right_drawer + R.id.bookmark_drawer } private fun getTabsContainerId(): Int = if (shouldShowTabsInDrawer) { if (swapBookmarksAndTabs) { - R.id.right_drawer + R.id.bookmark_drawer } else { - R.id.left_drawer + R.id.tab_drawer } } else { R.id.tabs_toolbar_container } private fun getBookmarkDrawer(): View = if (swapBookmarksAndTabs) { - left_drawer + tab_drawer } else { - right_drawer + bookmark_drawer } private fun getTabDrawer(): View = if (swapBookmarksAndTabs) { - right_drawer + bookmark_drawer } else { - left_drawer + tab_drawer } protected fun panicClean() { @@ -510,14 +510,14 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr val width = resources.displayMetrics.widthPixels - dimen(R.dimen.navigation_drawer_minimum_space) val maxWidth = resources.getDimensionPixelSize(R.dimen.navigation_drawer_max_width) if (width < maxWidth) { - val params = left_drawer.layoutParams as DrawerLayout.LayoutParams + val params = tab_drawer.layoutParams as DrawerLayout.LayoutParams params.width = width - left_drawer.layoutParams = params - left_drawer.requestLayout() - val paramsRight = right_drawer.layoutParams as DrawerLayout.LayoutParams + tab_drawer.layoutParams = params + tab_drawer.requestLayout() + val paramsRight = bookmark_drawer.layoutParams as DrawerLayout.LayoutParams paramsRight.width = width - right_drawer.layoutParams = paramsRight - right_drawer.requestLayout() + bookmark_drawer.layoutParams = paramsRight + bookmark_drawer.requestLayout() } } @@ -1303,7 +1303,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr * @param runnable an optional runnable to run after the drawers are closed. */ protected fun closeDrawers(runnable: (() -> Unit)?) { - if (!drawer_layout.isDrawerOpen(left_drawer) && !drawer_layout.isDrawerOpen(right_drawer)) { + if (!drawer_layout.isDrawerOpen(tab_drawer) && !drawer_layout.isDrawerOpen(bookmark_drawer)) { if (runnable != null) { runnable() return diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 100998a32..4e0ce2687 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -27,7 +27,7 @@ Date: Wed, 18 Nov 2020 22:03:24 -0500 Subject: [PATCH 043/161] Updating gradle --- .../acr/browser/lightning/_browser2/BrowserActivity.kt | 9 ++++++--- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index a161454a1..3dc9594d2 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -134,9 +134,12 @@ class BrowserActivity : ThemableBrowserActivity() { presenter.onSearchSuggestionClicked(suggestionsAdapter.getItem(position) as WebPage) } binding.search.setAdapter(suggestionsAdapter) - val searchListener = SearchListener(onConfirm = { - presenter.onSearch(binding.search.text.toString()) - }, inputMethodManager) + val searchListener = SearchListener( + onConfirm = { + presenter.onSearch(binding.search.text.toString()) + }, + inputMethodManager = inputMethodManager + ) binding.search.setOnEditorActionListener(searchListener) binding.search.setOnKeyListener(searchListener) binding.search.addTextChangedListener(StyleRemovingTextWatcher()) diff --git a/build.gradle b/build.gradle index e08c30b3a..03933cdfd 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:4.1.1' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 44c14da00..9919edb6d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Sep 09 21:23:20 EDT 2020 +#Wed Nov 18 22:00:53 EST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip From ef51569a5aa0ab962af7d6c0672711fd44d6ee0d Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 19 Nov 2020 22:23:00 -0500 Subject: [PATCH 044/161] Handle find in page --- .../lightning/_browser2/BrowserActivity.kt | 22 ++++++++++ .../lightning/_browser2/BrowserContract.kt | 2 + .../lightning/_browser2/BrowserPresenter.kt | 41 ++++++++++++++++--- .../_browser2/BrowserStateAdapter.kt | 10 ++++- .../lightning/_browser2/BrowserViewState.kt | 10 ++++- .../lightning/_browser2/tab/TabAdapter.kt | 23 +++++++++++ .../lightning/_browser2/tab/TabModel.kt | 10 +++++ app/src/main/res/layout/browser_activity.xml | 10 ++--- 8 files changed, 113 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 3dc9594d2..7d710e01f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -145,6 +145,10 @@ class BrowserActivity : ThemableBrowserActivity() { binding.search.addTextChangedListener(StyleRemovingTextWatcher()) binding.search.setOnFocusChangeListener { _, hasFocus -> presenter.onSearchFocusChanged(hasFocus) } + binding.findPrevious.setOnClickListener { presenter.onFindPrevious() } + binding.findNext.setOnClickListener { presenter.onFindNext() } + binding.findQuit.setOnClickListener { presenter.onFindDismiss() } + binding.homeButton.setOnClickListener { presenter.onTabCountViewClick() } binding.actionBack.setOnClickListener { presenter.onBackClick() } binding.actionForward.setOnClickListener { presenter.onForwardClick() } @@ -220,6 +224,14 @@ class BrowserActivity : ThemableBrowserActivity() { R.drawable.ic_action_back }) } + viewState.findInPage?.let { + if (it.isEmpty()) { + binding.findBar.isVisible = false + } else { + binding.findBar.isVisible = true + binding.findQuery.text = it + } + } } fun showAddBookmarkDialog(title: String, url: String, folders: List) { @@ -251,6 +263,16 @@ class BrowserActivity : ThemableBrowserActivity() { ) } + fun showFindInPageDialog() { + BrowserDialog.showEditText( + this, + R.string.action_find, + R.string.search_hint, + R.string.search_hint, + presenter::onFindInPage + ) + } + fun openBookmarkDrawer() { binding.drawerLayout.closeDrawer(binding.tabDrawer) binding.drawerLayout.openDrawer(binding.bookmarkDrawer) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 546ce9673..600a49e2b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -25,6 +25,8 @@ interface BrowserContract { fun showEditFolderDialog(title: String) + fun showFindInPageDialog() + fun openBookmarkDrawer() fun openTabDrawer() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index d07145e7e..7662607fb 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -62,7 +62,8 @@ class BrowserPresenter @Inject constructor( bookmarks = emptyList(), isBookmarked = false, isBookmarkEnabled = true, - isRootFolder = true + isRootFolder = true, + findInPage = "" ) private var currentTab: TabModel? = null private var currentFolder: Bookmark.Folder = Bookmark.Folder.Root @@ -162,7 +163,8 @@ class BrowserPresenter @Inject constructor( progress = 100, tabs = viewState.tabs.map { it.copy(isSelected = false) - } + }, + findInPage = "" )) tabDisposable?.dispose() @@ -187,9 +189,10 @@ class BrowserPresenter @Inject constructor( isBackEnabled = canGoBack, sslState = sslState, progress = progress, - tabs = viewState.tabs.map { it.copy(isSelected = it.id == tabModel.id) }, + tabs = viewState.tabs.map { it.copy(isSelected = it.id == tab.id) }, isBookmarked = isBookmark, - isBookmarkEnabled = !isSpecialUrl + isBookmarkEnabled = !isSpecialUrl, + findInPage = tab.findQuery.orEmpty() ) }.subscribeOn(mainScheduler) .subscribe { view.updateState(it) } @@ -249,7 +252,7 @@ class BrowserPresenter @Inject constructor( .subscribe { tab -> selectTab(model.selectTab(tab.id)) } - MenuSelection.FIND -> TODO() + MenuSelection.FIND -> view?.showFindInPageDialog() MenuSelection.COPY_LINK -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let(navigator::copyPageLink) MenuSelection.ADD_TO_HOME -> currentTab?.url?.takeIf { !it.isSpecialUrl() } @@ -437,8 +440,34 @@ class BrowserPresenter @Inject constructor( currentTab?.loadUrl(url) } + /** + * TODO + */ fun onFindInPage(query: String) { - TODO() + currentTab?.find(query) + view?.updateState(viewState.copy(findInPage = query)) + } + + /** + * TODO + */ + fun onFindNext() { + currentTab?.findNext() + } + + /** + * TODO + */ + fun onFindPrevious() { + currentTab?.findPrevious() + } + + /** + * TODO + */ + fun onFindDismiss() { + currentTab?.clearFindMatches() + view?.updateState(viewState.copy(findInPage = "")) } /** diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 85a79d6ba..0af5718c6 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -19,7 +19,8 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse bookmarks, isBookmarked, isBookmarkEnabled, - isRootFolder + isRootFolder, + findInPage ) = viewState browserActivity.renderState( @@ -34,7 +35,8 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse bookmarks = bookmarks.takeIf { it != currentState?.bookmarks }, isBookmarked = isBookmarked.takeIf { it != currentState?.isBookmarked }, isBookmarkEnabled = isBookmarkEnabled.takeIf { it != currentState?.isBookmarkEnabled }, - isRootFolder = isRootFolder.takeIf { it != currentState?.isRootFolder } + isRootFolder = isRootFolder.takeIf { it != currentState?.isRootFolder }, + findInPage = findInPage.takeIf { it != currentState?.findInPage } ) ) @@ -53,6 +55,10 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showEditFolderDialog(title) } + override fun showFindInPageDialog() { + browserActivity.showFindInPageDialog() + } + override fun openBookmarkDrawer() { browserActivity.openBookmarkDrawer() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt index c921bb423..3ee9224fc 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt @@ -23,7 +23,10 @@ data class BrowserViewState( val bookmarks: List, val isBookmarked: Boolean, val isBookmarkEnabled: Boolean, - val isRootFolder: Boolean + val isRootFolder: Boolean, + + // find + val findInPage: String ) data class PartialBrowserViewState( @@ -42,5 +45,8 @@ data class PartialBrowserViewState( val bookmarks: List?, val isBookmarked: Boolean?, val isBookmarkEnabled: Boolean?, - val isRootFolder: Boolean? + val isRootFolder: Boolean?, + + // find + val findInPage: String? ) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index d3a2551d7..940d67755 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -21,6 +21,8 @@ class TabAdapter( private var latentInitializer: FreezableBundleInitializer? = null + private var findInPageQuery: String? = null + init { webView.webViewClient = tabWebViewClient webView.webChromeClient = tabWebChromeClient @@ -65,6 +67,27 @@ class TabAdapter( webView.stopLoading() } + override fun find(query: String) { + webView.findAllAsync(query) + findInPageQuery = query + } + + override fun findNext() { + webView.findNext(true) + } + + override fun findPrevious() { + webView.findNext(false) + } + + override fun clearFindMatches() { + webView.clearMatches() + findInPageQuery = null + } + + override val findQuery: String? + get() = findInPageQuery + override val favicon: Bitmap? get() = webView.favicon diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt index 111f7e565..a9c6668fa 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -32,6 +32,16 @@ interface TabModel { fun stopLoading() + fun find(query: String) + + fun findNext() + + fun findPrevious() + + fun clearFindMatches() + + val findQuery: String? + // Data val favicon: Bitmap? diff --git a/app/src/main/res/layout/browser_activity.xml b/app/src/main/res/layout/browser_activity.xml index 9a9fbf17d..2dd7ba996 100644 --- a/app/src/main/res/layout/browser_activity.xml +++ b/app/src/main/res/layout/browser_activity.xml @@ -172,7 +172,7 @@ android:clipChildren="true" /> Date: Thu, 19 Nov 2020 22:37:37 -0500 Subject: [PATCH 045/161] Show the ssl state when the icon is clicked --- .../lightning/_browser2/BrowserContract.kt | 3 ++ .../lightning/_browser2/BrowserPresenter.kt | 4 ++- .../_browser2/BrowserStateAdapter.kt | 7 ++++ .../lightning/_browser2/tab/TabAdapter.kt | 13 ++++++++ .../lightning/_browser2/tab/TabModel.kt | 3 ++ .../lightning/ssl/SslCertificateInfo.kt | 15 +++++++++ .../acr/browser/lightning/ssl/SslDialog.kt | 32 +++++++++++++++---- 7 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/ssl/SslCertificateInfo.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 600a49e2b..9de241f83 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap import io.reactivex.Completable @@ -27,6 +28,8 @@ interface BrowserContract { fun showFindInPageDialog() + fun showSslDialog(sslCertificateInfo: SslCertificateInfo) + fun openBookmarkDrawer() fun openTabDrawer() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 7662607fb..0072f3e3d 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -488,7 +488,9 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onSslIconClick() { - // TODO + currentTab?.sslCertificateInfo?.let { + view?.showSslDialog(it) + } } /** diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 0af5718c6..e65ac3766 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -1,5 +1,8 @@ package acr.browser.lightning._browser2 +import acr.browser.lightning.ssl.SslCertificateInfo +import acr.browser.lightning.ssl.showSslDialog + /** * Created by anthonycr on 9/16/20. */ @@ -59,6 +62,10 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showFindInPageDialog() } + override fun showSslDialog(sslCertificateInfo: SslCertificateInfo) { + browserActivity.showSslDialog(sslCertificateInfo) + } + override fun openBookmarkDrawer() { browserActivity.openBookmarkDrawer() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index 940d67755..1b21c4669 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2.tab +import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.SslState import acr.browser.lightning.view.FreezableBundleInitializer import acr.browser.lightning.view.TabInitializer @@ -104,6 +105,18 @@ class TabAdapter( override fun titleChanges(): Observable = tabWebChromeClient.titleObservable.hide() + override val sslCertificateInfo: SslCertificateInfo? + get() = webView.certificate?.let { + SslCertificateInfo( + issuedByCommonName = it.issuedBy.cName, + issuedToCommonName = it.issuedTo.cName, + issuedToOrganizationName = it.issuedTo.oName, + issueDate = it.validNotBeforeDate, + expireDate = it.validNotAfterDate, + sslState = sslState + ) + } + override val sslState: SslState get() = tabWebViewClient.sslState diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt index a9c6668fa..f972b0183 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2.tab +import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.SslState import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap @@ -56,6 +57,8 @@ interface TabModel { fun titleChanges(): Observable + val sslCertificateInfo: SslCertificateInfo? + val sslState: SslState fun sslChanges(): Observable diff --git a/app/src/main/java/acr/browser/lightning/ssl/SslCertificateInfo.kt b/app/src/main/java/acr/browser/lightning/ssl/SslCertificateInfo.kt new file mode 100644 index 000000000..160a60080 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/ssl/SslCertificateInfo.kt @@ -0,0 +1,15 @@ +package acr.browser.lightning.ssl + +import java.util.* + +/** + * Created by anthonycr on 11/19/20. + */ +data class SslCertificateInfo( + val issuedByCommonName: String, + val issuedToCommonName: String, + val issuedToOrganizationName: String?, + val issueDate: Date, + val expireDate: Date, + val sslState: SslState +) diff --git a/app/src/main/java/acr/browser/lightning/ssl/SslDialog.kt b/app/src/main/java/acr/browser/lightning/ssl/SslDialog.kt index 5667f08ff..18392c104 100644 --- a/app/src/main/java/acr/browser/lightning/ssl/SslDialog.kt +++ b/app/src/main/java/acr/browser/lightning/ssl/SslDialog.kt @@ -8,6 +8,7 @@ import android.net.http.SslCertificate import android.text.format.DateFormat import android.widget.TextView import androidx.appcompat.app.AlertDialog +import java.util.* /** * Shows an informative dialog with the provided [SslCertificate] information. @@ -18,21 +19,38 @@ fun Context.showSslDialog(sslCertificate: SslCertificate, sslState: SslState) { val issueDate = sslCertificate.validNotBeforeDate val expireDate = sslCertificate.validNotAfterDate + showSslDialog( + SslCertificateInfo( + issuedByCommonName = by.cName, + issuedToCommonName = to.cName, + issuedToOrganizationName = to.oName, + issueDate = sslCertificate.validNotBeforeDate, + expireDate = sslCertificate.validNotAfterDate, + sslState = sslState + ) + ) +} + +fun Context.showSslDialog(sslCertificateInfo: SslCertificateInfo) { val dateFormat = DateFormat.getDateFormat(applicationContext) val contentView = inflater.inflate(R.layout.dialog_ssl_info, null, false).apply { - findViewById(R.id.ssl_layout_issue_by).text = by.cName - findViewById(R.id.ssl_layout_issue_to).text = to.oName?.takeIf(String::isNotBlank) - ?: to.cName - findViewById(R.id.ssl_layout_issue_date).text = dateFormat.format(issueDate) - findViewById(R.id.ssl_layout_expire_date).text = dateFormat.format(expireDate) + findViewById(R.id.ssl_layout_issue_by).text = + sslCertificateInfo.issuedByCommonName + findViewById(R.id.ssl_layout_issue_to).text = + sslCertificateInfo.issuedToOrganizationName?.takeIf(String::isNotBlank) + ?: sslCertificateInfo.issuedToCommonName + findViewById(R.id.ssl_layout_issue_date).text = + dateFormat.format(sslCertificateInfo.issueDate) + findViewById(R.id.ssl_layout_expire_date).text = + dateFormat.format(sslCertificateInfo.expireDate) } - val icon = createSslDrawableForState(sslState) + val icon = createSslDrawableForState(sslCertificateInfo.sslState) AlertDialog.Builder(this) .setIcon(icon) - .setTitle(to.cName) + .setTitle(sslCertificateInfo.issuedToCommonName) .setView(contentView) .setPositiveButton(R.string.action_ok, null) .resizeAndShow() From 982192f3b0c39ab741030f5b359d91aa728c04c9 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 19 Nov 2020 23:14:04 -0500 Subject: [PATCH 046/161] desktop and drawer tab support --- .../lightning/_browser2/BrowserActivity.kt | 38 +++++-- .../lightning/_browser2/di/Browser2Module.kt | 6 +- .../tab/DesktopTabRecyclerViewAdapter.kt | 104 ++++++++++++++++++ ...ter.kt => DrawerTabRecyclerViewAdapter.kt} | 4 +- .../lightning/browser/tabs/TabsDesktopView.kt | 2 +- .../lightning/browser/tabs/TabsDrawerView.kt | 2 +- app/src/main/res/layout/browser_activity.xml | 19 ++-- app/src/main/res/layout/tab_drawer.xml | 2 +- app/src/main/res/layout/tab_strip.xml | 2 +- 9 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/DesktopTabRecyclerViewAdapter.kt rename app/src/main/java/acr/browser/lightning/_browser2/tab/{TabRecyclerViewAdapter.kt => DrawerTabRecyclerViewAdapter.kt} (97%) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 7d710e01f..5e8a38d8f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -7,7 +7,10 @@ import acr.browser.lightning._browser2.keys.KeyEventAdapter import acr.browser.lightning._browser2.menu.MenuItemAdapter import acr.browser.lightning._browser2.search.IntentExtractor import acr.browser.lightning._browser2.search.SearchListener -import acr.browser.lightning._browser2.tab.TabRecyclerViewAdapter +import acr.browser.lightning._browser2.tab.DrawerTabRecyclerViewAdapter +import acr.browser.lightning._browser2.tab.DesktopTabRecyclerViewAdapter +import acr.browser.lightning._browser2.tab.TabViewHolder +import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning._browser2.ui.BookmarkConfiguration import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration @@ -30,6 +33,8 @@ import android.widget.ImageView import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView import javax.inject.Inject /** @@ -38,7 +43,7 @@ import javax.inject.Inject class BrowserActivity : ThemableBrowserActivity() { private lateinit var binding: BrowserActivityBinding - private lateinit var tabsAdapter: TabRecyclerViewAdapter + private lateinit var tabsAdapter: ListAdapter private lateinit var bookmarksAdapter: BookmarkRecyclerViewAdapter @Inject @@ -100,13 +105,28 @@ class BrowserActivity : ThemableBrowserActivity() { binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, binding.tabDrawer) } - tabsAdapter = TabRecyclerViewAdapter( - onClick = presenter::onTabClick, - onCloseClick = presenter::onTabClose, - onLongClick = presenter::onTabLongClick - ) - binding.tabsList.adapter = tabsAdapter - binding.tabsList.layoutManager = LinearLayoutManager(this) + if (uiConfiguration.tabConfiguration == TabConfiguration.DRAWER) { + tabsAdapter = DrawerTabRecyclerViewAdapter( + onClick = presenter::onTabClick, + onCloseClick = presenter::onTabClose, + onLongClick = presenter::onTabLongClick + ) + binding.drawerTabsList.isVisible = true + binding.drawerTabsList.adapter = tabsAdapter + binding.drawerTabsList.layoutManager = LinearLayoutManager(this) + binding.desktopTabsList.isVisible = false + } else { + tabsAdapter = DesktopTabRecyclerViewAdapter( + context = this, + onClick = presenter::onTabClick, + onCloseClick = presenter::onTabClose, + onLongClick = presenter::onTabLongClick + ) + binding.desktopTabsList.isVisible = true + binding.desktopTabsList.adapter = tabsAdapter + binding.desktopTabsList.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false) + binding.drawerTabsList.isVisible = false + } bookmarksAdapter = BookmarkRecyclerViewAdapter( onClick = presenter::onBookmarkClick, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index 0c9fefa2a..df15cfbec 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -41,7 +41,11 @@ class Browser2Module { fun providesUiConfiguration( userPreferences: UserPreferences ): UiConfiguration = UiConfiguration( - tabConfiguration = TabConfiguration.DRAWER, + tabConfiguration = if (userPreferences.showTabsInDrawer) { + TabConfiguration.DRAWER + } else { + TabConfiguration.DESKTOP + }, bookmarkConfiguration = if (userPreferences.bookmarksAndTabsSwapped) { BookmarkConfiguration.LEFT } else { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/DesktopTabRecyclerViewAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/DesktopTabRecyclerViewAdapter.kt new file mode 100644 index 000000000..40788c209 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/DesktopTabRecyclerViewAdapter.kt @@ -0,0 +1,104 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning.R +import acr.browser.lightning.extensions.desaturate +import acr.browser.lightning.extensions.dimen +import acr.browser.lightning.extensions.drawTrapezoid +import acr.browser.lightning.extensions.inflater +import acr.browser.lightning.utils.ThemeUtils +import acr.browser.lightning.utils.Utils +import acr.browser.lightning.view.BackgroundDrawable +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.view.ViewGroup +import androidx.core.widget.TextViewCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter + +/** + * Created by anthonycr on 9/12/20. + */ +class DesktopTabRecyclerViewAdapter( + context: Context, + private val onClick: (Int) -> Unit, + private val onLongClick: (Int) -> Unit, + private val onCloseClick: (Int) -> Unit, +) : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TabViewState, newItem: TabViewState): Boolean = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: TabViewState, newItem: TabViewState): Boolean = oldItem == newItem + } +) { + private val backgroundTabDrawable: Drawable? + private val foregroundTabDrawable: Drawable? + + init { + val backgroundColor = Utils.mixTwoColors(ThemeUtils.getPrimaryColor(context), Color.BLACK, 0.75f) + val backgroundTabBitmap = Bitmap.createBitmap( + context.dimen(R.dimen.desktop_tab_width), + context.dimen(R.dimen.desktop_tab_height), + Bitmap.Config.ARGB_8888 + ).also { + Canvas(it).drawTrapezoid(backgroundColor, true) + } + backgroundTabDrawable = BitmapDrawable(context.resources, backgroundTabBitmap) + + val foregroundColor = ThemeUtils.getPrimaryColor(context) + val foregroundTabBitmap = Bitmap.createBitmap( + context.dimen(R.dimen.desktop_tab_width), + context.dimen(R.dimen.desktop_tab_height), + Bitmap.Config.ARGB_8888 + ).also { + Canvas(it).drawTrapezoid(foregroundColor, false) + } + foregroundTabDrawable = BitmapDrawable(context.resources, foregroundTabBitmap) + } + + override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): TabViewHolder { + val view = viewGroup.context.inflater.inflate(R.layout.tab_list_item_horizontal, viewGroup, false) + return TabViewHolder(view, onClick = onClick, onLongClick = onLongClick, onCloseClick = onCloseClick) + } + + override fun onBindViewHolder(holder: TabViewHolder, position: Int) { + holder.exitButton.tag = position + + val tab = getItem(position) + + holder.txtTitle.text = tab.title + updateViewHolderAppearance(holder, null, tab.isSelected) + updateViewHolderFavicon(holder, tab.icon, tab.isSelected) + updateViewHolderBackground(holder, tab.isSelected) + } + + private fun updateViewHolderFavicon(viewHolder: TabViewHolder, favicon: Bitmap?, isForeground: Boolean) { + favicon?.let { + if (isForeground) { + viewHolder.favicon.setImageBitmap(it) + } else { + viewHolder.favicon.setImageBitmap(it.desaturate()) + } + } ?: viewHolder.favicon.setImageResource(R.drawable.ic_webpage) + } + + private fun updateViewHolderBackground(viewHolder: TabViewHolder, isForeground: Boolean) { + if (isForeground) { + viewHolder.layout.background = foregroundTabDrawable + } else { + viewHolder.layout.background = backgroundTabDrawable + } + } + + private fun updateViewHolderAppearance(viewHolder: TabViewHolder, favicon: Bitmap?, isForeground: Boolean) { + if (isForeground) { + TextViewCompat.setTextAppearance(viewHolder.txtTitle, R.style.boldText) + } else { + TextViewCompat.setTextAppearance(viewHolder.txtTitle, R.style.normalText) + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/DrawerTabRecyclerViewAdapter.kt similarity index 97% rename from app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt rename to app/src/main/java/acr/browser/lightning/_browser2/tab/DrawerTabRecyclerViewAdapter.kt index 1626301fc..4cc9fc1bd 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabRecyclerViewAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/DrawerTabRecyclerViewAdapter.kt @@ -11,9 +11,9 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter /** - * Created by anthonycr on 9/12/20. + * Created by anthonycr on 11/19/20. */ -class TabRecyclerViewAdapter( +class DrawerTabRecyclerViewAdapter( private val onClick: (Int) -> Unit, private val onLongClick: (Int) -> Unit, private val onCloseClick: (Int) -> Unit, diff --git a/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDesktopView.kt b/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDesktopView.kt index fd6221e49..38408e3a6 100644 --- a/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDesktopView.kt +++ b/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDesktopView.kt @@ -54,7 +54,7 @@ class TabsDesktopView @JvmOverloads constructor( tabsAdapter = TabsDesktopAdapter(context, context.resources, uiController = uiController) - tabList = findViewById(R.id.tabs_list).apply { + tabList = findViewById(R.id.drawer_tabs_list).apply { setLayerType(View.LAYER_TYPE_NONE, null) itemAnimator = animator this.layoutManager = layoutManager diff --git a/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDrawerView.kt b/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDrawerView.kt index a385b67c2..2adedfee5 100644 --- a/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDrawerView.kt +++ b/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDrawerView.kt @@ -44,7 +44,7 @@ class TabsDrawerView @JvmOverloads constructor( moveDuration = 200 } - tabList = findViewById(R.id.tabs_list).apply { + tabList = findViewById(R.id.drawer_tabs_list).apply { setLayerType(View.LAYER_TYPE_NONE, null) itemAnimator = animator layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) diff --git a/app/src/main/res/layout/browser_activity.xml b/app/src/main/res/layout/browser_activity.xml index 2dd7ba996..409374974 100644 --- a/app/src/main/res/layout/browser_activity.xml +++ b/app/src/main/res/layout/browser_activity.xml @@ -28,10 +28,15 @@ android:elevation="3dp" android:fitsSystemWindows="true"> - @@ -47,13 +52,13 @@ app:contentInsetStart="0dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tabs_toolbar_container"> + app:layout_constraintTop_toBottomOf="@id/desktop_tabs_list"> + android:baselineAligned="false" + android:orientation="horizontal"> Date: Fri, 20 Nov 2020 23:04:31 -0500 Subject: [PATCH 047/161] Handle hiding toolbar while scrolling --- .../lightning/_browser2/BrowserActivity.kt | 3 + .../_browser2/di/Browser2Component.kt | 8 + .../lightning/_browser2/tab/TabPager.kt | 15 +- .../view/WebViewScrollCoordinator.kt | 171 ++++++++++++++++++ 4 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 5e8a38d8f..002815869 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -14,6 +14,7 @@ import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning._browser2.ui.BookmarkConfiguration import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration +import acr.browser.lightning._browser2.view.WebViewScrollCoordinator import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher import acr.browser.lightning.browser.activity.ThemableBrowserActivity import acr.browser.lightning.database.SearchSuggestion @@ -80,6 +81,8 @@ class BrowserActivity : ThemableBrowserActivity() { injector.browser2ComponentBuilder() .activity(this) .browserFrame(binding.contentFrame) + .toolbarRoot(binding.uiLayout) + .toolbar(binding.toolbarLayout) .initialIntent(intent) .build() .inject(this) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt index 9e793ca28..a976c8fde 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt @@ -3,7 +3,9 @@ package acr.browser.lightning._browser2.di import acr.browser.lightning._browser2.BrowserActivity import android.app.Activity import android.content.Intent +import android.view.View import android.widget.FrameLayout +import android.widget.LinearLayout import dagger.BindsInstance import dagger.Subcomponent import javax.inject.Qualifier @@ -23,6 +25,12 @@ interface Browser2Component { @BindsInstance fun browserFrame(frameLayout: FrameLayout): Builder + @BindsInstance + fun toolbarRoot(linearLayout: LinearLayout): Builder + + @BindsInstance + fun toolbar(toolbar: View): Builder + @BindsInstance fun initialIntent(@InitialIntent intent: Intent): Builder diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt index 5ae9ae4bf..8acdccf33 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt @@ -1,24 +1,35 @@ package acr.browser.lightning._browser2.tab +import acr.browser.lightning._browser2.view.WebViewScrollCoordinator +import acr.browser.lightning.preference.UserPreferences +import android.view.View import android.view.ViewGroup import android.webkit.WebView import android.widget.FrameLayout +import android.widget.LinearLayout import javax.inject.Inject /** * Created by anthonycr on 9/12/20. */ -class TabPager @Inject constructor(private val container: FrameLayout) { +class TabPager @Inject constructor( + private val container: FrameLayout, + private val webViewScrollCoordinator: WebViewScrollCoordinator +) { private val webViews: MutableList = mutableListOf() fun selectTab(id: Int) { container.removeAllViews() + val webView = webViews.forId(id) container.addView( - webViews.forId(id), + webView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) + + // TODO: coordinator adds views to the container, which can result in UI flashes. + webViewScrollCoordinator.configure(webView) } fun clearTab() { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt new file mode 100644 index 000000000..d2363b361 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt @@ -0,0 +1,171 @@ +package acr.browser.lightning._browser2.view + +import acr.browser.lightning.interpolator.BezierDecelerateInterpolator +import acr.browser.lightning.preference.UserPreferences +import acr.browser.lightning.utils.Utils +import android.annotation.SuppressLint +import android.app.Activity +import android.view.* +import android.view.animation.Animation +import android.view.animation.Transformation +import android.webkit.WebView +import android.widget.FrameLayout +import android.widget.LinearLayout +import javax.inject.Inject + +/** + * TODO + */ +class WebViewScrollCoordinator @Inject constructor( + activity: Activity, + private val browserFrame: FrameLayout, + private val toolbarRoot: LinearLayout, + private val toolbar: View, + private val userPreferences: UserPreferences +) { + + private val gestureListener: CustomGestureListener = CustomGestureListener( + ViewConfiguration.get(activity).scaledMaximumFlingVelocity.toFloat() + ) + + private val touchListener = TouchListener(GestureDetector(activity, gestureListener)) + + /** + * TODO + */ + fun configure(webView: WebView) { + if (userPreferences.fullScreenEnabled) { + (toolbar.parent as ViewGroup?)?.removeView(toolbar) + + browserFrame.addView(toolbar) + + toolbar.translationY = 0f + webView.translationY = toolbar.height.toFloat() + coordinate(toolbar, webView) + } else { + (toolbar.parent as ViewGroup?)?.removeView(toolbar) + + toolbarRoot.addView(toolbar, 0) + + toolbar.translationY = 0f + webView.translationY = 0f + } + } + + @SuppressLint("ClickableViewAccessibility") + private fun coordinate(toolbar: View, webView: WebView) { + webView.setOnTouchListener(touchListener) + + val toggleListener = object : ToggleListener { + override fun hideToolbar() { + val height = toolbar.height + if (toolbar.translationY > -0.01f) { + val hideAnimation = object : Animation() { + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + val trans = interpolatedTime * height + toolbar.translationY = -trans + webView.translationY = height - trans + } + } + hideAnimation.duration = 250 + hideAnimation.interpolator = BezierDecelerateInterpolator() + toolbar.startAnimation(hideAnimation) + } + } + + override fun showToolbar() { + var height = toolbar.height + if (height == 0) { + toolbar.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) + height = toolbar.measuredHeight + } + + val totalHeight = height + if (toolbar.translationY < -(height - 0.01f)) { + val show = object : Animation() { + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + val trans = interpolatedTime * totalHeight + toolbar.translationY = trans - totalHeight + webView.translationY = trans + } + } + show.duration = 250 + show.interpolator = BezierDecelerateInterpolator() + toolbar.startAnimation(show) + } + } + } + + touchListener.toggleListener = toggleListener + gestureListener.toggleListener = toggleListener + } + + interface ToggleListener { + fun hideToolbar() + + fun showToolbar() + } + + private class TouchListener( + private val gestureDetector: GestureDetector + ) : View.OnTouchListener { + + private var location: Float = 0f + private var y: Float = 0f + private var action: Int = 0 + + var toggleListener: ToggleListener? = null + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(view: View?, arg1: MotionEvent): Boolean { + if (view == null) return false + + if (!view.hasFocus()) { + view.requestFocus() + } + action = arg1.action + y = arg1.y + if (action == MotionEvent.ACTION_DOWN) { + location = y + } else if (action == MotionEvent.ACTION_UP) { + val distance = y - location + if (distance > SCROLL_UP_THRESHOLD && view.scrollY < SCROLL_UP_THRESHOLD) { + toggleListener?.showToolbar() + } else if (distance < -SCROLL_UP_THRESHOLD) { + toggleListener?.hideToolbar() + } + location = 0f + } + gestureDetector.onTouchEvent(arg1) + + return false + } + } + + /** + * The SimpleOnGestureListener used by the [TouchListener] + * in order to delegate show/hide events to the action bar when + * the user flings the page. Also handles long press events so + * that we can capture them accurately. + */ + private class CustomGestureListener( + private val maxFling: Float + ) : GestureDetector.SimpleOnGestureListener() { + + var toggleListener: ToggleListener? = null + + override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { + val power = (velocityY * 100 / maxFling).toInt() + if (power < -10) { + toggleListener?.hideToolbar() + } else if (power > 15) { + toggleListener?.showToolbar() + } + return super.onFling(e1, e2, velocityX, velocityY) + } + } + + companion object { + private val SCROLL_UP_THRESHOLD = Utils.dpToPx(10f) + } +} From f335d2bfbca2d341798ef25422dcfc8f6e5bb7fc Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 23 Dec 2020 11:21:17 -0500 Subject: [PATCH 048/161] Updating gradle and dependencies --- app/build.gradle | 21 +++++++++---------- .../acr/browser/lightning/rx/RxExtensions.kt | 2 +- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3374d562b..ad95947a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,24 +105,24 @@ dependencies { debugImplementation 'androidx.multidex:multidex:2.0.1' // test dependencies - testImplementation 'junit:junit:4.13' - testImplementation 'org.assertj:assertj-core:3.17.2' - testImplementation 'org.mockito:mockito-core:3.5.10' + testImplementation 'junit:junit:4.13.1' + testImplementation 'org.assertj:assertj-core:3.18.1' + testImplementation 'org.mockito:mockito-core:3.6.28' testImplementation 'com.nhaarman:mockito-kotlin:1.6.0', { exclude group: 'org.jetbrains.kotlin' } testImplementation 'org.robolectric:robolectric:4.4' // support libraries - implementation "androidx.palette:palette:1.0.0" + implementation "androidx.palette:palette-ktx:1.0.0" implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0" implementation "androidx.appcompat:appcompat:1.2.0" implementation "com.google.android.material:material:1.2.1" implementation "androidx.recyclerview:recyclerview:1.1.0" - implementation "androidx.core:core:1.3.1" - implementation "androidx.constraintlayout:constraintlayout:2.0.1" - implementation "androidx.fragment:fragment:1.2.5" + implementation 'androidx.core:core-ktx:1.5.0-alpha05' + implementation "androidx.constraintlayout:constraintlayout:2.0.4" + implementation "androidx.fragment:fragment-ktx:1.2.5" implementation "androidx.drawerlayout:drawerlayout:1.1.1" // html parsing for reading mode @@ -134,13 +134,13 @@ dependencies { kapt "com.anthonycr.mezzanine:mezzanine-compiler:$mezzanineVersion" // dependency injection - final def daggerVersion = '2.29' + final def daggerVersion = '2.30.1' implementation "com.google.dagger:dagger:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion" compileOnly 'javax.annotation:jsr250-api:1.0' // view binding - final def butterKnifeVersion = '10.2.1' + final def butterKnifeVersion = '10.2.3' implementation "com.jakewharton:butterknife:$butterKnifeVersion" kapt "com.jakewharton:butterknife-compiler:$butterKnifeVersion" @@ -155,7 +155,7 @@ dependencies { // rx implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - implementation 'io.reactivex.rxjava2:rxjava:2.2.19' + implementation 'io.reactivex.rxjava2:rxjava:2.2.20' implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' // tor proxy @@ -172,7 +172,6 @@ dependencies { // kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" - implementation 'androidx.core:core-ktx:1.5.0-alpha02' } kapt { diff --git a/app/src/main/java/acr/browser/lightning/rx/RxExtensions.kt b/app/src/main/java/acr/browser/lightning/rx/RxExtensions.kt index 6c3407fa6..b9f3a0d0d 100644 --- a/app/src/main/java/acr/browser/lightning/rx/RxExtensions.kt +++ b/app/src/main/java/acr/browser/lightning/rx/RxExtensions.kt @@ -16,7 +16,7 @@ import org.reactivestreams.Publisher * type. * @see Observable.join */ -inline fun Flowable.join( +inline fun Flowable.join( other: Flowable, crossinline selectorLeft: (T) -> Publisher, crossinline selectorRight: (R) -> Publisher, diff --git a/build.gradle b/build.gradle index 03933cdfd..4a7d07d26 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlinVersion = '1.4.10' + ext.kotlinVersion = '1.4.21' repositories { google() jcenter() @@ -8,7 +8,7 @@ buildscript { classpath 'com.android.tools.build:gradle:4.1.1' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' + classpath 'com.github.ben-manes:gradle-versions-plugin:0.36.0' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9919edb6d..30f4ee20d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Nov 18 22:00:53 EST 2020 +#Wed Dec 23 10:38:29 EST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip From d856a7368a5d0b94ccd0817a941dea28f763a0bd Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 23 Dec 2020 13:49:27 -0500 Subject: [PATCH 049/161] Creating browser scope that scopes browser instance --- .../browser/lightning/_browser2/BrowserPresenter.kt | 2 ++ .../lightning/_browser2/di/Browser2Component.kt | 1 + .../browser/lightning/_browser2/di/Browser2Scope.kt | 10 ++++++++++ 3 files changed, 13 insertions(+) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Scope.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 0072f3e3d..20c748339 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2 +import acr.browser.lightning._browser2.di.Browser2Scope import acr.browser.lightning._browser2.di.InitialUrl import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.keys.KeyCombo @@ -34,6 +35,7 @@ import javax.inject.Inject /** * Created by anthonycr on 9/11/20. */ +@Browser2Scope class BrowserPresenter @Inject constructor( private val model: BrowserContract.Model, private val navigator: BrowserContract.Navigator, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt index a976c8fde..40afd7aee 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt @@ -13,6 +13,7 @@ import javax.inject.Qualifier /** * Created by anthonycr on 9/15/20. */ +@Browser2Scope @Subcomponent(modules = [Browser2Module::class, Browser2BindsModule::class]) interface Browser2Component { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Scope.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Scope.kt new file mode 100644 index 000000000..a2ff88009 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Scope.kt @@ -0,0 +1,10 @@ +package acr.browser.lightning._browser2.di + +import javax.inject.Scope + +/** + * Created by anthonycr on 12/23/20. + */ +@Scope +@Retention +annotation class Browser2Scope From 9902a14ddf5641322a951224259c08760b8b215b Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 23 Dec 2020 13:49:42 -0500 Subject: [PATCH 050/161] Cleaning up lambdas --- .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 20c748339..73a59bc01 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -104,11 +104,11 @@ class BrowserPresenter @Inject constructor( .mergeWith( Maybe.fromCallable { initialUrl } .flatMapSingleElement { model.createTab(UrlInitializer(it)) } - .map { listOf(it) } + .map(::listOf) ) .toList() - .map { it.flatten() } - .filter { it.isNotEmpty() } + .map(MutableList>::flatten) + .filter(List::isNotEmpty) .switchIfEmpty(model.createTab(homePageInitializer).map(::listOf)) .subscribe { list -> selectTab(model.selectTab(list.last().id)) @@ -525,7 +525,7 @@ class BrowserPresenter @Inject constructor( } }) .toList() - .map { it.flatten() } + .map(MutableList>::flatten) /** * TODO From 5a79d26584673f1ca103192cb6fb72bc4596a996 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 23 Dec 2020 13:49:54 -0500 Subject: [PATCH 051/161] Removing unused imports --- .../main/java/acr/browser/lightning/_browser2/tab/TabPager.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt index 8acdccf33..6112692d2 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt @@ -1,12 +1,9 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning._browser2.view.WebViewScrollCoordinator -import acr.browser.lightning.preference.UserPreferences -import android.view.View import android.view.ViewGroup import android.webkit.WebView import android.widget.FrameLayout -import android.widget.LinearLayout import javax.inject.Inject /** From 76e19d71f26f5a9032ef3d434a49fe0bb33b7bbd Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 25 Jan 2021 20:28:50 -0500 Subject: [PATCH 052/161] Upgrading gradle tools --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 4a7d07d26..47008812f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:4.1.2' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath 'com.github.ben-manes:gradle-versions-plugin:0.36.0' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 30f4ee20d..1ea1674c7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Dec 23 10:38:29 EST 2020 +#Mon Jan 25 20:21:43 EST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip From 3a29b1a6ff7b4a27ce374147ef83bd41b847c5c4 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 25 Jan 2021 21:19:52 -0500 Subject: [PATCH 053/161] Handle long click partially --- .../lightning/_browser2/BrowserActivity.kt | 11 +- .../lightning/_browser2/BrowserPresenter.kt | 8 ++ .../lightning/_browser2/tab/TabPager.kt | 12 +- .../lightning/_browser2/tab/WebViewFactory.kt | 2 + .../_browser2/view/CompositeTouchListener.kt | 20 ++++ .../view/WebViewCompositeTouchExtensions.kt | 14 +++ .../_browser2/view/WebViewLongPressHandler.kt | 113 ++++++++++++++++++ .../view/WebViewScrollCoordinator.kt | 3 +- app/src/main/java/targetUrl/LongPress.kt | 16 +++ 9 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/view/CompositeTouchListener.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/view/WebViewCompositeTouchExtensions.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/view/WebViewLongPressHandler.kt create mode 100644 app/src/main/java/targetUrl/LongPress.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 002815869..d0bdcc85e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -7,14 +7,10 @@ import acr.browser.lightning._browser2.keys.KeyEventAdapter import acr.browser.lightning._browser2.menu.MenuItemAdapter import acr.browser.lightning._browser2.search.IntentExtractor import acr.browser.lightning._browser2.search.SearchListener -import acr.browser.lightning._browser2.tab.DrawerTabRecyclerViewAdapter -import acr.browser.lightning._browser2.tab.DesktopTabRecyclerViewAdapter -import acr.browser.lightning._browser2.tab.TabViewHolder -import acr.browser.lightning._browser2.tab.TabViewState +import acr.browser.lightning._browser2.tab.* import acr.browser.lightning._browser2.ui.BookmarkConfiguration import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration -import acr.browser.lightning._browser2.view.WebViewScrollCoordinator import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher import acr.browser.lightning.browser.activity.ThemableBrowserActivity import acr.browser.lightning.database.SearchSuggestion @@ -62,6 +58,9 @@ class BrowserActivity : ThemableBrowserActivity() { @Inject internal lateinit var presenter: BrowserPresenter + @Inject + internal lateinit var tabPager: TabPager + @Inject internal lateinit var intentExtractor: IntentExtractor @@ -184,6 +183,8 @@ class BrowserActivity : ThemableBrowserActivity() { binding.actionReading.setOnClickListener { presenter.onReadingModeClick() } binding.bookmarkBackButton.setOnClickListener { presenter.onBookmarkMenuClick() } binding.searchSslStatus.setOnClickListener { presenter.onSslIconClick() } + + tabPager.longPressListener = presenter::onPageLongPress } override fun onNewIntent(intent: Intent?) { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 73a59bc01..ce894a949 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -5,6 +5,7 @@ import acr.browser.lightning._browser2.di.InitialUrl import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.keys.KeyCombo import acr.browser.lightning._browser2.menu.MenuSelection +import targetUrl.LongPress import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning._browser2.ui.TabConfiguration @@ -677,6 +678,13 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ + fun onPageLongPress(id: Int, longPress: LongPress) { + + } + private fun BrowserContract.View?.updateState(state: BrowserViewState) { viewState = state this?.renderState(viewState) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt index 6112692d2..de940ff64 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt @@ -1,21 +1,28 @@ package acr.browser.lightning._browser2.tab +import acr.browser.lightning._browser2.di.Browser2Scope +import acr.browser.lightning._browser2.view.WebViewLongPressHandler import acr.browser.lightning._browser2.view.WebViewScrollCoordinator import android.view.ViewGroup import android.webkit.WebView import android.widget.FrameLayout +import targetUrl.LongPress import javax.inject.Inject /** * Created by anthonycr on 9/12/20. */ +@Browser2Scope class TabPager @Inject constructor( private val container: FrameLayout, - private val webViewScrollCoordinator: WebViewScrollCoordinator + private val webViewScrollCoordinator: WebViewScrollCoordinator, + private val webViewLongPressHandler: WebViewLongPressHandler ) { private val webViews: MutableList = mutableListOf() + var longPressListener: ((id: Int, longPress: LongPress) -> Unit)? = null + fun selectTab(id: Int) { container.removeAllViews() val webView = webViews.forId(id) @@ -27,6 +34,9 @@ class TabPager @Inject constructor( // TODO: coordinator adds views to the container, which can result in UI flashes. webViewScrollCoordinator.configure(webView) + webViewLongPressHandler.configure(webView, onLongClick = { + longPressListener?.invoke(id, it) + }) } fun clearTab() { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt index e61bb51d9..249e33384 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning.Capabilities +import acr.browser.lightning._browser2.view.CompositeTouchListener import acr.browser.lightning.isSupported import acr.browser.lightning.log.Logger import acr.browser.lightning.preference.UserPreferences @@ -52,6 +53,7 @@ class WebViewFactory @Inject constructor( fun createWebView(isIncognito: Boolean): WebView = WebView(activity).apply { id = View.generateViewId() + tag = CompositeTouchListener().also(::setOnTouchListener) isFocusableInTouchMode = true isFocusable = true if (VERSION.SDK_INT < VERSION_CODES.M) { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/view/CompositeTouchListener.kt b/app/src/main/java/acr/browser/lightning/_browser2/view/CompositeTouchListener.kt new file mode 100644 index 000000000..0651e5215 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/view/CompositeTouchListener.kt @@ -0,0 +1,20 @@ +package acr.browser.lightning._browser2.view + +import android.annotation.SuppressLint +import android.view.MotionEvent +import android.view.View + +/** + * Created by anthonycr on 12/23/20. + */ +class CompositeTouchListener( + val delegates: MutableMap = mutableMapOf() +) : View.OnTouchListener { + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(v: View, event: MotionEvent): Boolean { + delegates.values.forEach { it?.onTouch(v, event) } + return false + } + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewCompositeTouchExtensions.kt b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewCompositeTouchExtensions.kt new file mode 100644 index 000000000..9a6900138 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewCompositeTouchExtensions.kt @@ -0,0 +1,14 @@ +package acr.browser.lightning._browser2.view + +import android.view.View +import android.webkit.WebView + +/** + * Created by anthonycr on 12/23/20. + */ + + +fun WebView.setCompositeTouchListener(key: String, onTouchListener: View.OnTouchListener?) { + val composite = tag as CompositeTouchListener + composite.delegates[key] = onTouchListener +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewLongPressHandler.kt b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewLongPressHandler.kt new file mode 100644 index 000000000..f1617ab65 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewLongPressHandler.kt @@ -0,0 +1,113 @@ +package acr.browser.lightning._browser2.view + +import targetUrl.LongPress +import android.annotation.SuppressLint +import android.app.Activity +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import android.webkit.WebView +import io.reactivex.functions.Cancellable +import javax.inject.Inject + +/** + * Created by anthonycr on 11/21/20. + */ +class WebViewLongPressHandler @Inject constructor(private val activity: Activity) { + + fun configure( + webView: WebView, + onLongClick: (LongPress) -> Unit + ): Cancellable { + webView.setCompositeTouchListener("long_press", GestureTriggeringTouchListener( + GestureDetector( + activity, + CustomGestureListener( + messageHandler = MessageHandler { + val hitTestResult = webView.hitTestResult + val longPress = LongPress( + targetUrl = it ?: hitTestResult.extra, + hitUrl = hitTestResult.extra, + hitCategory = hitTestResult.type.asLongPressCategory() + ) + onLongClick(longPress) + }, + webView = webView + ) + ) + )) + + return Cancellable { + webView.setCompositeTouchListener("long_press", null) + } + } + + private fun Int.asLongPressCategory(): LongPress.Category = when (this) { + WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE, + WebView.HitTestResult.IMAGE_TYPE -> LongPress.Category.IMAGE + WebView.HitTestResult.UNKNOWN_TYPE -> LongPress.Category.UNKNOWN + else -> LongPress.Category.LINK + } + + private class GestureTriggeringTouchListener( + private val gestureDetector: GestureDetector + ) : View.OnTouchListener { + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(v: View, event: MotionEvent): Boolean { + gestureDetector.onTouchEvent(event) + return false + } + } + + private class MessageHandler( + private val onUrlLongPress: (String?) -> Unit + ) : Handler(Looper.getMainLooper()) { + override fun handleMessage(msg: Message) = onUrlLongPress(msg.data.getString(KEY_URL)) + + companion object { + private const val KEY_URL = "url" + } + } + + private class CustomGestureListener( + private val messageHandler: MessageHandler, + private val webView: WebView + ) : GestureDetector.SimpleOnGestureListener() { + + /** + * Without this, onLongPress is not called when user is zooming using two fingers, but is + * when using only one. + * + * The required behaviour is to avoid triggering this when the user is zooming, it shouldn't + * matter how many fingers the user is using. + */ + private var canTriggerLongPress = true + + override fun onLongPress(e: MotionEvent) { + if (canTriggerLongPress) { + val msg = messageHandler.obtainMessage() + msg.target = messageHandler + webView.requestFocusNodeHref(msg) + } + } + + /** + * Is called when the user is swiping after the doubl tap, which in our case means that they + * are zooming. + */ + override fun onDoubleTapEvent(e: MotionEvent): Boolean { + canTriggerLongPress = false + return false + } + + /** + * Is called when something is starting being pressed, always before onLongPress. + */ + override fun onShowPress(e: MotionEvent) { + canTriggerLongPress = true + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt index d2363b361..7494fd50a 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt @@ -52,9 +52,8 @@ class WebViewScrollCoordinator @Inject constructor( } } - @SuppressLint("ClickableViewAccessibility") private fun coordinate(toolbar: View, webView: WebView) { - webView.setOnTouchListener(touchListener) + webView.setCompositeTouchListener("scroll", touchListener) val toggleListener = object : ToggleListener { override fun hideToolbar() { diff --git a/app/src/main/java/targetUrl/LongPress.kt b/app/src/main/java/targetUrl/LongPress.kt new file mode 100644 index 000000000..7dfba2544 --- /dev/null +++ b/app/src/main/java/targetUrl/LongPress.kt @@ -0,0 +1,16 @@ +package targetUrl + +/** + * Created by anthonycr on 12/23/20. + */ +data class LongPress( + val targetUrl: String?, + val hitUrl: String?, + val hitCategory: Category = Category.UNKNOWN +) { + enum class Category { + IMAGE, + LINK, + UNKNOWN + } +} From 3c4e7e1b49eb75b0d804a15fc05f2c94000d09a2 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 28 Mar 2021 22:00:16 -0400 Subject: [PATCH 054/161] Update gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 47008812f..bc772cd62 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.android.tools.build:gradle:4.1.3' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath 'com.github.ben-manes:gradle-versions-plugin:0.36.0' From 4bd31c10ea3aae19c213163f9b9b787d38f80d76 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 28 Mar 2021 22:36:53 -0400 Subject: [PATCH 055/161] Handle long presses on the webview --- .../lightning/_browser2/BrowserActivity.kt | 51 ++++++++++++ .../lightning/_browser2/BrowserContract.kt | 5 ++ .../lightning/_browser2/BrowserPresenter.kt | 79 ++++++++++++------- .../_browser2/BrowserStateAdapter.kt | 9 +++ .../browser/lightning/browser/BrowserView.kt | 18 +++++ 5 files changed, 135 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index d0bdcc85e..a54460762 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -11,13 +11,16 @@ import acr.browser.lightning._browser2.tab.* import acr.browser.lightning._browser2.ui.BookmarkConfiguration import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration +import acr.browser.lightning.browser.BrowserView import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher import acr.browser.lightning.browser.activity.ThemableBrowserActivity +import acr.browser.lightning.constant.HTTP import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage import acr.browser.lightning.databinding.BrowserActivityBinding import acr.browser.lightning.di.injector import acr.browser.lightning.dialog.BrowserDialog +import acr.browser.lightning.dialog.DialogItem import acr.browser.lightning.dialog.LightningDialogBuilder import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState @@ -32,6 +35,7 @@ import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import targetUrl.LongPress import javax.inject.Inject /** @@ -297,6 +301,53 @@ class BrowserActivity : ThemableBrowserActivity() { ) } + fun showLinkLongPressDialog(longPress: LongPress) { + BrowserDialog.show(this, longPress.targetUrl?.replace(HTTP, ""), + DialogItem(title = R.string.dialog_open_new_tab) { + presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.NEW_TAB) + }, + DialogItem(title = R.string.dialog_open_background_tab) { + presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.BACKGROUND_TAB) + }, + DialogItem( + title = R.string.dialog_open_incognito_tab, + isConditionMet = this is BrowserActivity // TODO: Change for incognito + ) { + presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.INCOGNITO_TAB) + }, + DialogItem(title = R.string.action_share) { + presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.SHARE) + }, + DialogItem(title = R.string.dialog_copy_link) { + presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.COPY_LINK) + }) + } + + fun showImageLongPressDialog(longPress: LongPress) { + BrowserDialog.show(this, longPress.targetUrl?.replace(HTTP, ""), + DialogItem(title = R.string.dialog_open_new_tab) { + presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.NEW_TAB) + }, + DialogItem(title = R.string.dialog_open_background_tab) { + presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.BACKGROUND_TAB) + }, + DialogItem( + title = R.string.dialog_open_incognito_tab, + isConditionMet = this is BrowserActivity // TODO: Change for incognito + ) { + presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.INCOGNITO_TAB) + }, + DialogItem(title = R.string.action_share) { + presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.SHARE) + }, + DialogItem(title = R.string.dialog_copy_link) { + presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.COPY_LINK) + }, + DialogItem(title = R.string.dialog_download_image) { + presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.DOWNLOAD) + }) + } + fun openBookmarkDrawer() { binding.drawerLayout.closeDrawer(binding.tabDrawer) binding.drawerLayout.openDrawer(binding.bookmarkDrawer) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 9de241f83..3d33a819d 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -8,6 +8,7 @@ import io.reactivex.Completable import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single +import targetUrl.LongPress /** * Created by anthonycr on 9/11/20. @@ -28,6 +29,10 @@ interface BrowserContract { fun showFindInPageDialog() + fun showLinkLongPressDialog(longPress: LongPress) + + fun showImageLongPressDialog(longPress: LongPress) + fun showSslDialog(sslCertificateInfo: SslCertificateInfo) fun openBookmarkDrawer() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index ce894a949..922826341 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -10,6 +10,7 @@ import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration +import acr.browser.lightning.browser.BrowserView import acr.browser.lightning.browser.SearchBoxModel import acr.browser.lightning.database.* import acr.browser.lightning.database.bookmark.BookmarkRepository @@ -18,10 +19,7 @@ import acr.browser.lightning.di.MainScheduler import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.ssl.SslState import acr.browser.lightning.utils.* -import acr.browser.lightning.view.DownloadPageInitializer -import acr.browser.lightning.view.HistoryPageInitializer -import acr.browser.lightning.view.HomePageInitializer -import acr.browser.lightning.view.UrlInitializer +import acr.browser.lightning.view.* import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Scheduler @@ -71,6 +69,7 @@ class BrowserPresenter @Inject constructor( private var currentTab: TabModel? = null private var currentFolder: Bookmark.Folder = Bookmark.Folder.Root private var isSearchViewFocused = false + private var pendingLongPressTabId: Int? = null private val compositeDisposable = CompositeDisposable() private val allTabsDisposable = CompositeDisposable() @@ -226,11 +225,7 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onNewDeepLink(url: String) { - compositeDisposable += model.createTab(UrlInitializer(url)) - .observeOn(mainScheduler) - .subscribe { tab -> - selectTab(model.selectTab(tab.id)) - } + createNewTabAndSelect(UrlInitializer(url), shouldSelect = true) } /** @@ -243,18 +238,8 @@ class BrowserPresenter @Inject constructor( MenuSelection.SHARE -> currentTab?.url?.takeIf { !it.isSpecialUrl() }?.let { navigator.sharePage(url = it, title = currentTab?.title) } - MenuSelection.HISTORY -> - compositeDisposable += model.createTab(historyPageInitializer) - .observeOn(mainScheduler) - .subscribe { tab -> - selectTab(model.selectTab(tab.id)) - } - MenuSelection.DOWNLOADS -> - compositeDisposable += model.createTab(downloadPageInitializer) - .observeOn(mainScheduler) - .subscribe { tab -> - selectTab(model.selectTab(tab.id)) - } + MenuSelection.HISTORY -> createNewTabAndSelect(historyPageInitializer, shouldSelect = true) + MenuSelection.DOWNLOADS -> createNewTabAndSelect(downloadPageInitializer, shouldSelect = true) MenuSelection.FIND -> view?.showFindInPageDialog() MenuSelection.COPY_LINK -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let(navigator::copyPageLink) @@ -277,6 +262,16 @@ class BrowserPresenter @Inject constructor( } } + private fun createNewTabAndSelect(tabInitializer: TabInitializer, shouldSelect: Boolean) { + compositeDisposable += model.createTab(tabInitializer) + .observeOn(mainScheduler) + .subscribe { tab -> + if (shouldSelect) { + selectTab(model.selectTab(tab.id)) + } + } + } + /** * TODO */ @@ -381,11 +376,7 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onNewTabClick() { - compositeDisposable += model.createTab(homePageInitializer) - .observeOn(mainScheduler) - .subscribe { tab -> - selectTab(model.selectTab(tab.id)) - } + createNewTabAndSelect(homePageInitializer, shouldSelect = true) } /** @@ -682,7 +673,41 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onPageLongPress(id: Int, longPress: LongPress) { - + pendingLongPressTabId = id + when (longPress.hitCategory) { + LongPress.Category.IMAGE -> view?.showImageLongPressDialog(longPress) + LongPress.Category.LINK -> view?.showLinkLongPressDialog(longPress) + LongPress.Category.UNKNOWN -> Unit // Do nothing + } + } + + fun onLinkLongPressEvent(longPress: LongPress, linkLongPressEvent: BrowserView.LinkLongPressEvent) { + when (linkLongPressEvent) { + BrowserView.LinkLongPressEvent.NEW_TAB -> + longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = true) } + BrowserView.LinkLongPressEvent.BACKGROUND_TAB -> + longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = false) } + BrowserView.LinkLongPressEvent.INCOGNITO_TAB -> TODO() + BrowserView.LinkLongPressEvent.SHARE -> + longPress.targetUrl?.let { navigator.sharePage(url = it, title = null) } + BrowserView.LinkLongPressEvent.COPY_LINK -> + longPress.targetUrl?.let(navigator::copyPageLink) + } + } + + fun onImageLongPressEvent(longPress: LongPress, imageLongPressEvent: BrowserView.ImageLongPressEvent) { + when (imageLongPressEvent) { + BrowserView.ImageLongPressEvent.NEW_TAB -> + longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = true) } + BrowserView.ImageLongPressEvent.BACKGROUND_TAB -> + longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = false) } + BrowserView.ImageLongPressEvent.INCOGNITO_TAB -> TODO() + BrowserView.ImageLongPressEvent.SHARE -> + longPress.targetUrl?.let { navigator.sharePage(url = it, title = null) } + BrowserView.ImageLongPressEvent.COPY_LINK -> + longPress.targetUrl?.let(navigator::copyPageLink) + BrowserView.ImageLongPressEvent.DOWNLOAD -> TODO() + } } private fun BrowserContract.View?.updateState(state: BrowserViewState) { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index e65ac3766..1b215a4fd 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.showSslDialog +import targetUrl.LongPress /** * Created by anthonycr on 9/16/20. @@ -62,6 +63,14 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showFindInPageDialog() } + override fun showLinkLongPressDialog(longPress: LongPress) { + browserActivity.showLinkLongPressDialog(longPress) + } + + override fun showImageLongPressDialog(longPress: LongPress) { + browserActivity.showImageLongPressDialog(longPress) + } + override fun showSslDialog(sslCertificateInfo: SslCertificateInfo) { browserActivity.showSslDialog(sslCertificateInfo) } diff --git a/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt b/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt index e95891c86..ced29db04 100644 --- a/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt +++ b/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt @@ -3,6 +3,7 @@ package acr.browser.lightning.browser import acr.browser.lightning.ssl.SslState import android.view.View import androidx.annotation.StringRes +import targetUrl.LongPress interface BrowserView { @@ -38,4 +39,21 @@ interface BrowserView { fun notifyTabViewInitialized() + enum class LinkLongPressEvent { + NEW_TAB, + BACKGROUND_TAB, + INCOGNITO_TAB, + SHARE, + COPY_LINK + } + + enum class ImageLongPressEvent { + NEW_TAB, + BACKGROUND_TAB, + INCOGNITO_TAB, + SHARE, + COPY_LINK, + DOWNLOAD + } + } From 321e1f102d07f1e4a83e72516dd2383cd832929f Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 28 Mar 2021 22:45:21 -0400 Subject: [PATCH 056/161] Fix lint warnings --- .../acr/browser/lightning/_browser2/tab/TabsRepository.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index f12d8d788..d4c891834 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -33,7 +33,7 @@ class TabsRepository @Inject constructor( } val tab = tabsList.forId(id) tab.destroy() - tabsList -= tab + tabsList = tabsList - tab }.doOnComplete { tabsListObservable.onNext(tabsList) } @@ -49,7 +49,7 @@ class TabsRepository @Inject constructor( TabWebChromeClient() ) - tabsList += tabAdapter + tabsList = tabsList + tabAdapter return@fromCallable tabAdapter }.doOnSuccess { @@ -75,7 +75,7 @@ class TabsRepository @Inject constructor( .observeOn(mainScheduler) .flatMapSingle(::createTab) .toList() - .filter { it.isNotEmpty() } + .filter(MutableList::isNotEmpty) override fun freeze() { bundleStore.save(tabsList) From dc5c7a4f7d7d839905c853ce621659259dfd2c2a Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 28 Mar 2021 22:46:47 -0400 Subject: [PATCH 057/161] Add todo for special URL long clicks --- .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 922826341..c56a0837e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -69,7 +69,6 @@ class BrowserPresenter @Inject constructor( private var currentTab: TabModel? = null private var currentFolder: Bookmark.Folder = Bookmark.Folder.Root private var isSearchViewFocused = false - private var pendingLongPressTabId: Int? = null private val compositeDisposable = CompositeDisposable() private val allTabsDisposable = CompositeDisposable() @@ -673,7 +672,10 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onPageLongPress(id: Int, longPress: LongPress) { - pendingLongPressTabId = id + if (model.tabsList.find { it.id == id }?.url?.isSpecialUrl() == true) { + // TODO handle differently + return + } when (longPress.hitCategory) { LongPress.Category.IMAGE -> view?.showImageLongPressDialog(longPress) LongPress.Category.LINK -> view?.showLinkLongPressDialog(longPress) From 11e265aeae0c841b8164b99a80286b19d572fc38 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 11 Apr 2021 10:38:13 -0400 Subject: [PATCH 058/161] Fix bug where tab freezing wouldn't work due to freezing the state after it was destroyed --- .../acr/browser/lightning/_browser2/BrowserActivity.kt | 5 +++++ .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index a54460762..23e5564a4 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -202,6 +202,11 @@ class BrowserActivity : ThemableBrowserActivity() { presenter.onViewDetached() } + override fun onPause() { + super.onPause() + presenter.onViewHidden() + } + override fun onBackPressed() { presenter.onBackClick() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index c56a0837e..47e329752 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -120,12 +120,17 @@ class BrowserPresenter @Inject constructor( fun onViewDetached() { view = null - model.freeze() - compositeDisposable.dispose() tabDisposable?.dispose() } + /** + * TODO + */ + fun onViewHidden() { + model.freeze() + } + private fun TabModel.asViewState(): TabViewState = TabViewState( id = id, icon = favicon, From 91b7097f78c86a51bde3d4bdc5ae85505f626837 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 11 Apr 2021 10:54:32 -0400 Subject: [PATCH 059/161] Handle panic clean --- .../lightning/_browser2/BrowserActivity.kt | 2 +- .../lightning/_browser2/BrowserContract.kt | 4 +++ .../lightning/_browser2/BrowserPresenter.kt | 29 +++++++++++++++++-- .../_browser2/search/IntentExtractor.kt | 15 ++++++---- .../lightning/_browser2/tab/TabsRepository.kt | 13 +++++++++ .../_browser2/tab/bundle/BundleStore.kt | 8 +++++ .../browser/lightning/browser/BrowserView.kt | 6 ++++ 7 files changed, 68 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 23e5564a4..3eaf93f71 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -192,7 +192,7 @@ class BrowserActivity : ThemableBrowserActivity() { } override fun onNewIntent(intent: Intent?) { - intent?.let(intentExtractor::extractUrlFromIntent)?.let(presenter::onNewDeepLink) + intent?.let(intentExtractor::extractUrlFromIntent)?.let(presenter::onNewAction) super.onNewIntent(intent) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 3d33a819d..9c3ba186c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -44,6 +44,8 @@ interface BrowserContract { fun deleteTab(id: Int): Completable + fun deleteAllTabs(): Completable + fun createTab(tabInitializer: TabInitializer): Single fun selectTab(id: Int): TabModel @@ -52,6 +54,8 @@ interface BrowserContract { fun freeze() + fun clean() + val tabsList: List fun tabsListChanges(): Observable> diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 47e329752..2228a759c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -16,6 +16,7 @@ import acr.browser.lightning.database.* import acr.browser.lightning.database.bookmark.BookmarkRepository import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.MainScheduler +import acr.browser.lightning.html.history.HistoryPageFactory import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.ssl.SslState import acr.browser.lightning.utils.* @@ -30,6 +31,7 @@ import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.subscribeBy import javax.inject.Inject +import kotlin.system.exitProcess /** * Created by anthonycr on 9/11/20. @@ -48,7 +50,8 @@ class BrowserPresenter @Inject constructor( private val searchBoxModel: SearchBoxModel, private val searchEngineProvider: SearchEngineProvider, @InitialUrl private val initialUrl: String?, - private val uiConfiguration: UiConfiguration + private val uiConfiguration: UiConfiguration, + private val historyPageFactory: HistoryPageFactory ) { private var view: BrowserContract.View? = null @@ -228,8 +231,28 @@ class BrowserPresenter @Inject constructor( /** * TODO */ - fun onNewDeepLink(url: String) { - createNewTabAndSelect(UrlInitializer(url), shouldSelect = true) + fun onNewAction(action: BrowserView.Action) { + when (action) { + is BrowserView.Action.LoadUrl -> createNewTabAndSelect( + tabInitializer = UrlInitializer(action.url), + shouldSelect = true + ) + BrowserView.Action.Panic -> panicClean() + } + } + + private fun panicClean() { + createNewTabAndSelect(tabInitializer = NoOpInitializer(), shouldSelect = true) + model.clean() + + historyPageFactory.deleteHistoryPage().subscribe() + model.deleteAllTabs().subscribe() + navigator.closeBrowser() + + // System exit needed in the case of receiving + // the panic intent since finish() isn't completely + // closing the browser + exitProcess(1) } /** diff --git a/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt b/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt index e048d2bcb..9e6efa19b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2.search +import acr.browser.lightning.browser.BrowserView import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.utils.QUERY_PLACE_HOLDER import acr.browser.lightning.utils.smartUrlFilter @@ -15,11 +16,12 @@ class IntentExtractor @Inject constructor(private val searchEngineProvider: Sear /** * TODO */ - fun extractUrlFromIntent(intent: Intent): String? { - return if (intent.action == Intent.ACTION_WEB_SEARCH) { - extractSearchFromIntent(intent) - } else { - intent.dataString + fun extractUrlFromIntent(intent: Intent): BrowserView.Action? { + return when (intent.action) { + INTENT_PANIC_TRIGGER -> BrowserView.Action.Panic + Intent.ACTION_WEB_SEARCH -> + extractSearchFromIntent(intent)?.let(BrowserView.Action::LoadUrl) + else -> intent.dataString?.let(BrowserView.Action::LoadUrl) } } @@ -34,4 +36,7 @@ class IntentExtractor @Inject constructor(private val searchEngineProvider: Sear } } + companion object { + private const val INTENT_PANIC_TRIGGER = "info.guardianproject.panic.action.TRIGGER" + } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index d4c891834..5a02e4f6a 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -38,6 +38,15 @@ class TabsRepository @Inject constructor( tabsListObservable.onNext(tabsList) } + override fun deleteAllTabs(): Completable = Completable.fromAction { + tabPager.clearTab() + + tabsList.forEach(TabModel::destroy) + tabsList = emptyList() + }.doOnComplete { + tabsListObservable.onNext(tabsList) + } + override fun createTab(tabInitializer: TabInitializer): Single = Single.fromCallable { val webView = webViewFactory.createWebView(isIncognito = false) tabPager.addTab(webView) @@ -81,5 +90,9 @@ class TabsRepository @Inject constructor( bundleStore.save(tabsList) } + override fun clean() { + bundleStore.deleteAll() + } + private fun List.forId(id: Int): TabModel = requireNotNull(find { it.id == id }) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt index a00d2919a..f51d5526d 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2.tab.bundle import acr.browser.lightning.R import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning.browser.TabsManager import acr.browser.lightning.di.DiskScheduler import acr.browser.lightning.utils.* import acr.browser.lightning.view.* @@ -72,6 +73,13 @@ class BundleStore @Inject constructor( ?: application.getString(R.string.tab_frozen)) } ?: emptyList() + /** + * Synchronously delete all stored tabs. + */ + fun deleteAll() { + FileUtils.deleteBundleInStorage(application, BUNDLE_STORAGE) + } + private fun String.extractNumberFromEnd(): String { val underScore = lastIndexOf('_') return if (underScore in 0 until length) { diff --git a/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt b/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt index ced29db04..00d8e50d5 100644 --- a/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt +++ b/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt @@ -56,4 +56,10 @@ interface BrowserView { DOWNLOAD } + sealed class Action { + data class LoadUrl(val url: String) : Action() + + object Panic : Action() + } + } From 67c62d4b92c0f91154cf9e2ab0d03263e3b1b082 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 11 Apr 2021 11:27:18 -0400 Subject: [PATCH 060/161] Open another app when navigating to URLs that can be handled outside the app --- .../lightning/_browser2/di/Browser2Module.kt | 10 +- .../_browser2/tab/TabWebViewClient.kt | 16 +- .../lightning/_browser2/tab/TabsRepository.kt | 8 +- .../lightning/_browser2/tab/UrlHandler.kt | 137 ++++++++++++++++++ 4 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/UrlHandler.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index df15cfbec..eb5fde5d5 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -7,7 +7,10 @@ import acr.browser.lightning._browser2.ui.UiConfiguration import acr.browser.lightning.adblock.AdBlocker import acr.browser.lightning.adblock.BloomFilterAdBlocker import acr.browser.lightning.adblock.NoOpAdBlocker +import acr.browser.lightning.browser.BrowserView import acr.browser.lightning.preference.UserPreferences +import acr.browser.lightning.utils.IntentUtils +import android.app.Activity import android.content.Intent import dagger.Module import dagger.Provides @@ -30,12 +33,17 @@ class Browser2Module { noOpAdBlocker } + // TODO: dont force cast @Provides @InitialUrl fun providesInitialUrl( @InitialIntent initialIntent: Intent, intentExtractor: IntentExtractor - ): String? = intentExtractor.extractUrlFromIntent(initialIntent) + ): String? = (intentExtractor.extractUrlFromIntent(initialIntent) as? BrowserView.Action.LoadUrl)?.url + + // TODO: auto inject intent utils + @Provides + fun providesIntentUtils(activity: Activity): IntentUtils = IntentUtils(activity) @Provides fun providesUiConfiguration( diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt index 1cc52e7ce..9c175bad7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebViewClient.kt @@ -8,6 +8,7 @@ import android.graphics.Bitmap import android.net.http.SslError import android.os.Build import android.webkit.* +import androidx.annotation.RequiresApi import io.reactivex.subjects.PublishSubject import java.io.ByteArrayInputStream @@ -16,7 +17,9 @@ import java.io.ByteArrayInputStream */ class TabWebViewClient( private val adBlocker: AdBlocker, - private val allowListModel: AllowListModel + private val allowListModel: AllowListModel, + private val urlHandler: UrlHandler, + private val headers: Map ) : WebViewClient() { val urlObservable: PublishSubject = PublishSubject.create() @@ -58,6 +61,17 @@ class TabWebViewClient( sslStateObservable.onNext(sslState) } + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + return urlHandler.shouldOverrideLoading(view, url, headers) || + super.shouldOverrideUrlLoading(view, url) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + return urlHandler.shouldOverrideLoading(view, request.url.toString(), headers) || + super.shouldOverrideUrlLoading(view, request) + } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { if (shouldBlockRequest(currentUrl, request.url.toString())) { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index 5a02e4f6a..dbf52c6dd 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -21,7 +21,8 @@ class TabsRepository @Inject constructor( private val allowListModel: AllowListModel, @DiskScheduler private val diskScheduler: Scheduler, @MainScheduler private val mainScheduler: Scheduler, - private val bundleStore: BundleStore + private val bundleStore: BundleStore, + private val urlHandler: UrlHandler ) : BrowserContract.Model { private var selectedTab: TabModel? = null @@ -49,12 +50,13 @@ class TabsRepository @Inject constructor( override fun createTab(tabInitializer: TabInitializer): Single = Single.fromCallable { val webView = webViewFactory.createWebView(isIncognito = false) + val headers = webViewFactory.createRequestHeaders() tabPager.addTab(webView) val tabAdapter = TabAdapter( tabInitializer, webView, - webViewFactory.createRequestHeaders(), - TabWebViewClient(adBlocker, allowListModel), + headers, + TabWebViewClient(adBlocker, allowListModel, urlHandler, headers), TabWebChromeClient() ) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/UrlHandler.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/UrlHandler.kt new file mode 100644 index 000000000..e52960f67 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/UrlHandler.kt @@ -0,0 +1,137 @@ +package acr.browser.lightning._browser2.tab + +import acr.browser.lightning.BuildConfig +import acr.browser.lightning.R +import acr.browser.lightning.constant.FILE +import acr.browser.lightning.extensions.snackbar +import acr.browser.lightning.log.Logger +import acr.browser.lightning.utils.IntentUtils +import acr.browser.lightning.utils.Utils +import acr.browser.lightning.utils.isSpecialUrl +import acr.browser.lightning.view.LightningWebClient +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.MailTo +import android.webkit.MimeTypeMap +import android.webkit.URLUtil +import android.webkit.WebView +import androidx.core.content.FileProvider +import java.io.File +import java.net.URISyntaxException +import javax.inject.Inject + +/** + * Created by anthonycr on 9/24/20. + */ +class UrlHandler @Inject constructor( + private val activity: Activity, + private val logger: Logger, + private val intentUtils: IntentUtils +) { + + fun shouldOverrideLoading( + view: WebView, + url: String, + headers: Map + ): Boolean { + // TODO: handle proxy +// // Check if configured proxy is available +// if (!proxyUtils.isProxyReady(activity)) { +// // User has been notified +// return true +// } + + // TODO: handle incognito +// if (lightningView.isIncognito) { +// // If we are in incognito, immediately load, we don't want the url to leave the app +// return continueLoadingUrl(view, url, headers) +// } + if (URLUtil.isAboutUrl(url)) { + // If this is an about page, immediately load, we don't need to leave the app + return continueLoadingUrl(view, url, headers) + } + + return if (isMailOrIntent(url, view) || intentUtils.startActivityForUrl(view, url)) { + // If it was a mailto: link, or an intent, or could be launched elsewhere, do that + true + } else { + // If none of the special conditions was met, continue with loading the url + continueLoadingUrl(view, url, headers) + } + } + + private fun continueLoadingUrl(webView: WebView, url: String, headers: Map): Boolean { + if (!URLUtil.isNetworkUrl(url) + && !URLUtil.isFileUrl(url) + && !URLUtil.isAboutUrl(url) + && !URLUtil.isDataUrl(url) + && !URLUtil.isJavaScriptUrl(url)) { + webView.stopLoading() + return true + } + return when { + headers.isEmpty() -> false + else -> { + webView.loadUrl(url, headers) + true + } + } + } + + private fun isMailOrIntent(url: String, view: WebView): Boolean { + if (url.startsWith("mailto:")) { + val mailTo = MailTo.parse(url) + val i = Utils.newEmailIntent(mailTo.to, mailTo.subject, mailTo.body, mailTo.cc) + activity.startActivity(i) + view.reload() + return true + } else if (url.startsWith("intent://")) { + val intent = try { + Intent.parseUri(url, Intent.URI_INTENT_SCHEME) + } catch (ignored: URISyntaxException) { + null + } + + if (intent != null) { + intent.addCategory(Intent.CATEGORY_BROWSABLE) + intent.component = null + intent.selector = null + try { + activity.startActivity(intent) + } catch (e: ActivityNotFoundException) { + logger.log(TAG, "ActivityNotFoundException") + } + + return true + } + } else if (URLUtil.isFileUrl(url) && !url.isSpecialUrl()) { + val file = File(url.replace(FILE, "")) + + if (file.exists()) { + val newMimeType = MimeTypeMap.getSingleton() + .getMimeTypeFromExtension(Utils.guessFileExtension(file.toString())) + + val intent = Intent(Intent.ACTION_VIEW) + intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + val contentUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileprovider", file) + intent.setDataAndType(contentUri, newMimeType) + + try { + activity.startActivity(intent) + } catch (e: Exception) { + println("LightningWebClient: cannot open downloaded file") + } + + } else { + activity.snackbar(R.string.message_open_download_fail) + } + return true + } + return false + } + + companion object { + private const val TAG = "UrlHandler" + } +} From adc12308f44bcadde60906c403f94c67dcebcfda Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 18 Apr 2021 19:20:32 -0400 Subject: [PATCH 061/161] Handling tab long press and tab closing dialog --- .../lightning/_browser2/BrowserActivity.kt | 14 +++++++++++ .../lightning/_browser2/BrowserContract.kt | 2 ++ .../lightning/_browser2/BrowserPresenter.kt | 24 +++++++++++++++++-- .../_browser2/BrowserStateAdapter.kt | 4 ++++ .../browser/lightning/browser/BrowserView.kt | 6 +++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 3eaf93f71..5e08a6580 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -26,6 +26,7 @@ import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState import android.content.Intent import android.os.Bundle +import android.os.Handler import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.AdapterView @@ -353,6 +354,19 @@ class BrowserActivity : ThemableBrowserActivity() { }) } + fun showCloseBrowserDialog(id: Int) { + BrowserDialog.show(this, R.string.dialog_title_close_browser, + DialogItem(title = R.string.close_tab) { + presenter.onCloseBrowserEvent(id, BrowserView.CloseTabEvent.CLOSE_CURRENT) + }, + DialogItem(title = R.string.close_other_tabs) { + presenter.onCloseBrowserEvent(id, BrowserView.CloseTabEvent.CLOSE_OTHERS) + }, + DialogItem(title = R.string.close_all_tabs, onClick = { + presenter.onCloseBrowserEvent(id, BrowserView.CloseTabEvent.CLOSE_ALL) + })) + } + fun openBookmarkDrawer() { binding.drawerLayout.closeDrawer(binding.tabDrawer) binding.drawerLayout.openDrawer(binding.bookmarkDrawer) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 9c3ba186c..ffb18019e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -35,6 +35,8 @@ interface BrowserContract { fun showSslDialog(sslCertificateInfo: SslCertificateInfo) + fun showCloseBrowserDialog(id: Int) + fun openBookmarkDrawer() fun openTabDrawer() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 2228a759c..352b8e113 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -30,6 +30,7 @@ import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.subscribeBy +import io.reactivex.rxkotlin.toObservable import javax.inject.Inject import kotlin.system.exitProcess @@ -336,7 +337,7 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onTabLongClick(index: Int) { - // TODO + view?.showCloseBrowserDialog(viewState.tabs[index].id) } private fun List.nextSelected(removedIndex: Int): T? { @@ -677,7 +678,9 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onTabMenuClick() { - + currentTab?.let { + view?.showCloseBrowserDialog(it.id) + } } /** @@ -711,6 +714,23 @@ class BrowserPresenter @Inject constructor( } } + fun onCloseBrowserEvent(id: Int, closeTabEvent: BrowserView.CloseTabEvent) { + when (closeTabEvent) { + BrowserView.CloseTabEvent.CLOSE_CURRENT -> + onTabClose(viewState.tabs.indexOfFirst { it.id == id }) + BrowserView.CloseTabEvent.CLOSE_OTHERS -> model.tabsList + .filter { it.id != id } + .toObservable() + .flatMapCompletable { model.deleteTab(it.id) } + .subscribeOn(mainScheduler) + .subscribe() + BrowserView.CloseTabEvent.CLOSE_ALL -> { + model.deleteAllTabs().subscribeOn(mainScheduler) + .subscribeBy(onComplete = navigator::closeBrowser) + } + } + } + fun onLinkLongPressEvent(longPress: LongPress, linkLongPressEvent: BrowserView.LinkLongPressEvent) { when (linkLongPressEvent) { BrowserView.LinkLongPressEvent.NEW_TAB -> diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 1b215a4fd..edf2f20d4 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -75,6 +75,10 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showSslDialog(sslCertificateInfo) } + override fun showCloseBrowserDialog(id: Int) { + browserActivity.showCloseBrowserDialog(id) + } + override fun openBookmarkDrawer() { browserActivity.openBookmarkDrawer() } diff --git a/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt b/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt index 00d8e50d5..31e6e531a 100644 --- a/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt +++ b/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt @@ -39,6 +39,12 @@ interface BrowserView { fun notifyTabViewInitialized() + enum class CloseTabEvent { + CLOSE_CURRENT, + CLOSE_OTHERS, + CLOSE_ALL + } + enum class LinkLongPressEvent { NEW_TAB, BACKGROUND_TAB, From f7dfa1c06562926109ffbc436014208fbc7d1821 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 18 Apr 2021 21:47:45 -0400 Subject: [PATCH 062/161] Moving enums to correct contract location --- .../lightning/_browser2/BrowserActivity.kt | 28 ++++++------- .../lightning/_browser2/BrowserContract.kt | 27 +++++++++++++ .../lightning/_browser2/BrowserPresenter.kt | 40 +++++++++---------- .../lightning/_browser2/di/Browser2Module.kt | 3 +- .../_browser2/search/IntentExtractor.kt | 10 ++--- .../browser/lightning/browser/BrowserView.kt | 29 -------------- 6 files changed, 68 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 5e08a6580..9afd27b88 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -310,60 +310,60 @@ class BrowserActivity : ThemableBrowserActivity() { fun showLinkLongPressDialog(longPress: LongPress) { BrowserDialog.show(this, longPress.targetUrl?.replace(HTTP, ""), DialogItem(title = R.string.dialog_open_new_tab) { - presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.NEW_TAB) + presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.NEW_TAB) }, DialogItem(title = R.string.dialog_open_background_tab) { - presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.BACKGROUND_TAB) + presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.BACKGROUND_TAB) }, DialogItem( title = R.string.dialog_open_incognito_tab, isConditionMet = this is BrowserActivity // TODO: Change for incognito ) { - presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.INCOGNITO_TAB) + presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.INCOGNITO_TAB) }, DialogItem(title = R.string.action_share) { - presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.SHARE) + presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.SHARE) }, DialogItem(title = R.string.dialog_copy_link) { - presenter.onLinkLongPressEvent(longPress, BrowserView.LinkLongPressEvent.COPY_LINK) + presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.COPY_LINK) }) } fun showImageLongPressDialog(longPress: LongPress) { BrowserDialog.show(this, longPress.targetUrl?.replace(HTTP, ""), DialogItem(title = R.string.dialog_open_new_tab) { - presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.NEW_TAB) + presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.NEW_TAB) }, DialogItem(title = R.string.dialog_open_background_tab) { - presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.BACKGROUND_TAB) + presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.BACKGROUND_TAB) }, DialogItem( title = R.string.dialog_open_incognito_tab, isConditionMet = this is BrowserActivity // TODO: Change for incognito ) { - presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.INCOGNITO_TAB) + presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.INCOGNITO_TAB) }, DialogItem(title = R.string.action_share) { - presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.SHARE) + presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.SHARE) }, DialogItem(title = R.string.dialog_copy_link) { - presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.COPY_LINK) + presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.COPY_LINK) }, DialogItem(title = R.string.dialog_download_image) { - presenter.onImageLongPressEvent(longPress, BrowserView.ImageLongPressEvent.DOWNLOAD) + presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.DOWNLOAD) }) } fun showCloseBrowserDialog(id: Int) { BrowserDialog.show(this, R.string.dialog_title_close_browser, DialogItem(title = R.string.close_tab) { - presenter.onCloseBrowserEvent(id, BrowserView.CloseTabEvent.CLOSE_CURRENT) + presenter.onCloseBrowserEvent(id, BrowserContract.CloseTabEvent.CLOSE_CURRENT) }, DialogItem(title = R.string.close_other_tabs) { - presenter.onCloseBrowserEvent(id, BrowserView.CloseTabEvent.CLOSE_OTHERS) + presenter.onCloseBrowserEvent(id, BrowserContract.CloseTabEvent.CLOSE_OTHERS) }, DialogItem(title = R.string.close_all_tabs, onClick = { - presenter.onCloseBrowserEvent(id, BrowserView.CloseTabEvent.CLOSE_ALL) + presenter.onCloseBrowserEvent(id, BrowserContract.CloseTabEvent.CLOSE_ALL) })) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index ffb18019e..101d5ff4e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -81,6 +81,33 @@ interface BrowserContract { fun backgroundBrowser() } + enum class CloseTabEvent { + CLOSE_CURRENT, + CLOSE_OTHERS, + CLOSE_ALL + } + + enum class LinkLongPressEvent { + NEW_TAB, + BACKGROUND_TAB, + INCOGNITO_TAB, + SHARE, + COPY_LINK + } + enum class ImageLongPressEvent { + NEW_TAB, + BACKGROUND_TAB, + INCOGNITO_TAB, + SHARE, + COPY_LINK, + DOWNLOAD + } + + sealed class Action { + data class LoadUrl(val url: String) : Action() + + object Panic : Action() + } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 352b8e113..0d3dbb305 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -232,13 +232,13 @@ class BrowserPresenter @Inject constructor( /** * TODO */ - fun onNewAction(action: BrowserView.Action) { + fun onNewAction(action: BrowserContract.Action) { when (action) { - is BrowserView.Action.LoadUrl -> createNewTabAndSelect( + is BrowserContract.Action.LoadUrl -> createNewTabAndSelect( tabInitializer = UrlInitializer(action.url), shouldSelect = true ) - BrowserView.Action.Panic -> panicClean() + BrowserContract.Action.Panic -> panicClean() } } @@ -714,49 +714,49 @@ class BrowserPresenter @Inject constructor( } } - fun onCloseBrowserEvent(id: Int, closeTabEvent: BrowserView.CloseTabEvent) { + fun onCloseBrowserEvent(id: Int, closeTabEvent: BrowserContract.CloseTabEvent) { when (closeTabEvent) { - BrowserView.CloseTabEvent.CLOSE_CURRENT -> + BrowserContract.CloseTabEvent.CLOSE_CURRENT -> onTabClose(viewState.tabs.indexOfFirst { it.id == id }) - BrowserView.CloseTabEvent.CLOSE_OTHERS -> model.tabsList + BrowserContract.CloseTabEvent.CLOSE_OTHERS -> model.tabsList .filter { it.id != id } .toObservable() .flatMapCompletable { model.deleteTab(it.id) } .subscribeOn(mainScheduler) .subscribe() - BrowserView.CloseTabEvent.CLOSE_ALL -> { + BrowserContract.CloseTabEvent.CLOSE_ALL -> { model.deleteAllTabs().subscribeOn(mainScheduler) .subscribeBy(onComplete = navigator::closeBrowser) } } } - fun onLinkLongPressEvent(longPress: LongPress, linkLongPressEvent: BrowserView.LinkLongPressEvent) { + fun onLinkLongPressEvent(longPress: LongPress, linkLongPressEvent: BrowserContract.LinkLongPressEvent) { when (linkLongPressEvent) { - BrowserView.LinkLongPressEvent.NEW_TAB -> + BrowserContract.LinkLongPressEvent.NEW_TAB -> longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = true) } - BrowserView.LinkLongPressEvent.BACKGROUND_TAB -> + BrowserContract.LinkLongPressEvent.BACKGROUND_TAB -> longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = false) } - BrowserView.LinkLongPressEvent.INCOGNITO_TAB -> TODO() - BrowserView.LinkLongPressEvent.SHARE -> + BrowserContract.LinkLongPressEvent.INCOGNITO_TAB -> TODO() + BrowserContract.LinkLongPressEvent.SHARE -> longPress.targetUrl?.let { navigator.sharePage(url = it, title = null) } - BrowserView.LinkLongPressEvent.COPY_LINK -> + BrowserContract.LinkLongPressEvent.COPY_LINK -> longPress.targetUrl?.let(navigator::copyPageLink) } } - fun onImageLongPressEvent(longPress: LongPress, imageLongPressEvent: BrowserView.ImageLongPressEvent) { + fun onImageLongPressEvent(longPress: LongPress, imageLongPressEvent: BrowserContract.ImageLongPressEvent) { when (imageLongPressEvent) { - BrowserView.ImageLongPressEvent.NEW_TAB -> + BrowserContract.ImageLongPressEvent.NEW_TAB -> longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = true) } - BrowserView.ImageLongPressEvent.BACKGROUND_TAB -> + BrowserContract.ImageLongPressEvent.BACKGROUND_TAB -> longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = false) } - BrowserView.ImageLongPressEvent.INCOGNITO_TAB -> TODO() - BrowserView.ImageLongPressEvent.SHARE -> + BrowserContract.ImageLongPressEvent.INCOGNITO_TAB -> TODO() + BrowserContract.ImageLongPressEvent.SHARE -> longPress.targetUrl?.let { navigator.sharePage(url = it, title = null) } - BrowserView.ImageLongPressEvent.COPY_LINK -> + BrowserContract.ImageLongPressEvent.COPY_LINK -> longPress.targetUrl?.let(navigator::copyPageLink) - BrowserView.ImageLongPressEvent.DOWNLOAD -> TODO() + BrowserContract.ImageLongPressEvent.DOWNLOAD -> TODO() } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index eb5fde5d5..d86a19011 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2.di +import acr.browser.lightning._browser2.BrowserContract import acr.browser.lightning._browser2.search.IntentExtractor import acr.browser.lightning._browser2.ui.BookmarkConfiguration import acr.browser.lightning._browser2.ui.TabConfiguration @@ -39,7 +40,7 @@ class Browser2Module { fun providesInitialUrl( @InitialIntent initialIntent: Intent, intentExtractor: IntentExtractor - ): String? = (intentExtractor.extractUrlFromIntent(initialIntent) as? BrowserView.Action.LoadUrl)?.url + ): String? = (intentExtractor.extractUrlFromIntent(initialIntent) as? BrowserContract.Action.LoadUrl)?.url // TODO: auto inject intent utils @Provides diff --git a/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt b/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt index 9e6efa19b..294205e78 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/search/IntentExtractor.kt @@ -1,6 +1,6 @@ package acr.browser.lightning._browser2.search -import acr.browser.lightning.browser.BrowserView +import acr.browser.lightning._browser2.BrowserContract import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.utils.QUERY_PLACE_HOLDER import acr.browser.lightning.utils.smartUrlFilter @@ -16,12 +16,12 @@ class IntentExtractor @Inject constructor(private val searchEngineProvider: Sear /** * TODO */ - fun extractUrlFromIntent(intent: Intent): BrowserView.Action? { + fun extractUrlFromIntent(intent: Intent): BrowserContract.Action? { return when (intent.action) { - INTENT_PANIC_TRIGGER -> BrowserView.Action.Panic + INTENT_PANIC_TRIGGER -> BrowserContract.Action.Panic Intent.ACTION_WEB_SEARCH -> - extractSearchFromIntent(intent)?.let(BrowserView.Action::LoadUrl) - else -> intent.dataString?.let(BrowserView.Action::LoadUrl) + extractSearchFromIntent(intent)?.let(BrowserContract.Action::LoadUrl) + else -> intent.dataString?.let(BrowserContract.Action::LoadUrl) } } diff --git a/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt b/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt index 31e6e531a..3b9f76a30 100644 --- a/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt +++ b/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt @@ -39,33 +39,4 @@ interface BrowserView { fun notifyTabViewInitialized() - enum class CloseTabEvent { - CLOSE_CURRENT, - CLOSE_OTHERS, - CLOSE_ALL - } - - enum class LinkLongPressEvent { - NEW_TAB, - BACKGROUND_TAB, - INCOGNITO_TAB, - SHARE, - COPY_LINK - } - - enum class ImageLongPressEvent { - NEW_TAB, - BACKGROUND_TAB, - INCOGNITO_TAB, - SHARE, - COPY_LINK, - DOWNLOAD - } - - sealed class Action { - data class LoadUrl(val url: String) : Action() - - object Panic : Action() - } - } From 2aa4137a2eb173587cdfbf747c41961a1befbbf3 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 18 Apr 2021 22:19:03 -0400 Subject: [PATCH 063/161] Add option support to folders --- .../lightning/_browser2/BrowserActivity.kt | 21 ++++++- .../lightning/_browser2/BrowserContract.kt | 20 ++++++ .../lightning/_browser2/BrowserPresenter.kt | 62 +++++++++++++++---- .../_browser2/BrowserStateAdapter.kt | 9 +++ .../dialog/LightningDialogBuilder.kt | 41 ++++++++++++ 5 files changed, 140 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 9afd27b88..1e208ea1f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -11,10 +11,10 @@ import acr.browser.lightning._browser2.tab.* import acr.browser.lightning._browser2.ui.BookmarkConfiguration import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration -import acr.browser.lightning.browser.BrowserView import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher import acr.browser.lightning.browser.activity.ThemableBrowserActivity import acr.browser.lightning.constant.HTTP +import acr.browser.lightning.database.Bookmark import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage import acr.browser.lightning.databinding.BrowserActivityBinding @@ -26,7 +26,6 @@ import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState import android.content.Intent import android.os.Bundle -import android.os.Handler import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.AdapterView @@ -278,6 +277,15 @@ class BrowserActivity : ThemableBrowserActivity() { ) } + fun showBookmarkOptionsDialog(bookmark: Bookmark.Entry) { + lightningDialogBuilder.showLongPressedDialogForBookmarkUrl( + activity = this, + onClick = { + presenter.onBookmarkOptionClick(bookmark, it) + } + ) + } + fun showEditBookmarkDialog(title: String, url: String, folder: String, folders: List) { lightningDialogBuilder.showEditBookmarkDialog( activity = this, @@ -289,6 +297,15 @@ class BrowserActivity : ThemableBrowserActivity() { ) } + fun showFolderOptionsDialog(folder: Bookmark.Folder) { + lightningDialogBuilder.showBookmarkFolderLongPressedDialog( + activity = this, + onClick = { + presenter.onFolderOptionClick(folder, it) + } + ) + } + fun showEditFolderDialog(oldTitle: String) { lightningDialogBuilder.showRenameFolderDialog( activity = this, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 101d5ff4e..9ff3080b8 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning.database.Bookmark import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap @@ -23,8 +24,12 @@ interface BrowserContract { fun showAddBookmarkDialog(title: String, url: String, folders: List) + fun showBookmarkOptionsDialog(bookmark: Bookmark.Entry) + fun showEditBookmarkDialog(title: String, url: String, folder: String, folders: List) + fun showFolderOptionsDialog(folder: Bookmark.Folder) + fun showEditFolderDialog(title: String) fun showFindInPageDialog() @@ -87,6 +92,21 @@ interface BrowserContract { CLOSE_ALL } + enum class BookmarkOptionEvent { + NEW_TAB, + BACKGROUND_TAB, + INCOGNITO_TAB, + SHARE, + COPY_LINK, + REMOVE, + EDIT + } + + enum class FolderOptionEvent { + RENAME, + REMOVE + } + enum class LinkLongPressEvent { NEW_TAB, BACKGROUND_TAB, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 0d3dbb305..5a44ae565 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -5,12 +5,10 @@ import acr.browser.lightning._browser2.di.InitialUrl import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.keys.KeyCombo import acr.browser.lightning._browser2.menu.MenuSelection -import targetUrl.LongPress import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration -import acr.browser.lightning.browser.BrowserView import acr.browser.lightning.browser.SearchBoxModel import acr.browser.lightning.database.* import acr.browser.lightning.database.bookmark.BookmarkRepository @@ -31,6 +29,7 @@ import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.toObservable +import targetUrl.LongPress import javax.inject.Inject import kotlin.system.exitProcess @@ -551,15 +550,10 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onBookmarkLongClick(index: Int) { - compositeDisposable += bookmarkRepository.getFolderNames() - .subscribeOn(databaseScheduler) - .observeOn(mainScheduler) - .subscribeBy { folders -> - when (val item = viewState.bookmarks[index]) { - is Bookmark.Entry -> view?.showEditBookmarkDialog(item.title, item.url, item.folder.title, folders) - is Bookmark.Folder.Entry -> view?.showEditFolderDialog(item.title) - } - } + when (val item = viewState.bookmarks[index]) { + is Bookmark.Entry -> view?.showBookmarkOptionsDialog(item) + is Bookmark.Folder.Entry -> view?.showFolderOptionsDialog(item) + } } /** @@ -658,6 +652,52 @@ class BrowserPresenter @Inject constructor( } } + /** + * TODO + */ + fun onBookmarkOptionClick(bookmark: Bookmark.Entry, option: BrowserContract.BookmarkOptionEvent) { + when (option) { + BrowserContract.BookmarkOptionEvent.NEW_TAB -> + createNewTabAndSelect(UrlInitializer(bookmark.url), shouldSelect = true) + BrowserContract.BookmarkOptionEvent.BACKGROUND_TAB -> + createNewTabAndSelect(UrlInitializer(bookmark.url), shouldSelect = false) + BrowserContract.BookmarkOptionEvent.INCOGNITO_TAB -> TODO() + BrowserContract.BookmarkOptionEvent.SHARE -> + navigator.sharePage(url = bookmark.url, title = bookmark.title) + BrowserContract.BookmarkOptionEvent.COPY_LINK -> + navigator.copyPageLink(bookmark.url) + BrowserContract.BookmarkOptionEvent.REMOVE -> + compositeDisposable += bookmarkRepository.deleteBookmark(bookmark) + .flatMap { bookmarkRepository.bookmarksAndFolders(folder = currentFolder) } + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribe { list -> + view?.updateState(viewState.copy(bookmarks = list)) + } + BrowserContract.BookmarkOptionEvent.EDIT -> + compositeDisposable += bookmarkRepository.getFolderNames() + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribeBy { folders -> + view?.showEditBookmarkDialog(bookmark.title, bookmark.url, bookmark.folder.title, folders) + } + } + } + + fun onFolderOptionClick(folder: Bookmark.Folder, option: BrowserContract.FolderOptionEvent) { + when (option) { + BrowserContract.FolderOptionEvent.RENAME -> view?.showEditFolderDialog(folder.title) + BrowserContract.FolderOptionEvent.REMOVE -> + compositeDisposable += bookmarkRepository.deleteFolder(folder.title) + .andThen(bookmarkRepository.bookmarksAndFolders(folder = currentFolder)) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribe { list -> + view?.updateState(viewState.copy(bookmarks = list)) + } + } + } + /** * TODO */ diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index edf2f20d4..a224647a0 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2 +import acr.browser.lightning.database.Bookmark import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.showSslDialog import targetUrl.LongPress @@ -51,10 +52,18 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showAddBookmarkDialog(title, url, folders) } + override fun showBookmarkOptionsDialog(bookmark: Bookmark.Entry) { + browserActivity.showBookmarkOptionsDialog(bookmark) + } + override fun showEditBookmarkDialog(title: String, url: String, folder: String, folders: List) { browserActivity.showEditBookmarkDialog(title, url, folder, folders) } + override fun showFolderOptionsDialog(folder: Bookmark.Folder) { + browserActivity.showFolderOptionsDialog(folder) + } + override fun showEditFolderDialog(title: String) { browserActivity.showEditFolderDialog(title) } diff --git a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt index 267f454c7..64d78dfbd 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt +++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt @@ -2,6 +2,7 @@ package acr.browser.lightning.dialog import acr.browser.lightning.MainActivity import acr.browser.lightning.R +import acr.browser.lightning._browser2.BrowserContract import acr.browser.lightning.constant.HTTP import acr.browser.lightning.controller.UIController import acr.browser.lightning.database.Bookmark @@ -120,6 +121,35 @@ class LightningDialogBuilder @Inject constructor( showEditBookmarkDialog(activity, uiController, entry) }) + fun showLongPressedDialogForBookmarkUrl( + activity: Activity, + onClick: (BrowserContract.BookmarkOptionEvent) -> Unit + ) = BrowserDialog.show(activity, R.string.action_bookmarks, + DialogItem(title = R.string.dialog_open_new_tab) { + onClick(BrowserContract.BookmarkOptionEvent.NEW_TAB) + }, + DialogItem(title = R.string.dialog_open_background_tab) { + onClick(BrowserContract.BookmarkOptionEvent.BACKGROUND_TAB) + }, + DialogItem( + title = R.string.dialog_open_incognito_tab, + isConditionMet = activity is MainActivity + ) { + onClick(BrowserContract.BookmarkOptionEvent.INCOGNITO_TAB) + }, + DialogItem(title = R.string.action_share) { + onClick(BrowserContract.BookmarkOptionEvent.SHARE) + }, + DialogItem(title = R.string.dialog_copy_link) { + onClick(BrowserContract.BookmarkOptionEvent.COPY_LINK) + }, + DialogItem(title = R.string.dialog_remove_bookmark) { + onClick(BrowserContract.BookmarkOptionEvent.REMOVE) + }, + DialogItem(title = R.string.dialog_edit_bookmark) { + onClick(BrowserContract.BookmarkOptionEvent.EDIT) + }) + /** * Show the appropriated dialog for the long pressed link. * @@ -307,6 +337,17 @@ class LightningDialogBuilder @Inject constructor( } }) + fun showBookmarkFolderLongPressedDialog( + activity: Activity, + onClick: (BrowserContract.FolderOptionEvent) -> Unit + ) = BrowserDialog.show(activity, R.string.action_folder, + DialogItem(title = R.string.dialog_rename_folder) { + onClick(BrowserContract.FolderOptionEvent.RENAME) + }, + DialogItem(title = R.string.dialog_remove_folder) { + onClick(BrowserContract.FolderOptionEvent.REMOVE) + }) + private fun showRenameFolderDialog( activity: Activity, uiController: UIController, From 524c712586d097e7bbdad2e5c2d3bd8f90fd0908 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 25 Jul 2021 14:07:55 -0400 Subject: [PATCH 064/161] Formatting --- .../lightning/_browser2/BrowserActivity.kt | 116 ++++++++++++------ 1 file changed, 81 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 1e208ea1f..b6d8c8963 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -90,25 +90,31 @@ class BrowserActivity : ThemableBrowserActivity() { .build() .inject(this) - binding.bookmarkDrawer.layoutParams = (binding.bookmarkDrawer.layoutParams as DrawerLayout.LayoutParams).apply { - gravity = when (uiConfiguration.bookmarkConfiguration) { - BookmarkConfiguration.LEFT -> Gravity.START - BookmarkConfiguration.RIGHT -> Gravity.END + binding.bookmarkDrawer.layoutParams = + (binding.bookmarkDrawer.layoutParams as DrawerLayout.LayoutParams).apply { + gravity = when (uiConfiguration.bookmarkConfiguration) { + BookmarkConfiguration.LEFT -> Gravity.START + BookmarkConfiguration.RIGHT -> Gravity.END + } } - } - binding.tabDrawer.layoutParams = (binding.tabDrawer.layoutParams as DrawerLayout.LayoutParams).apply { - gravity = when (uiConfiguration.bookmarkConfiguration) { - BookmarkConfiguration.LEFT -> Gravity.END - BookmarkConfiguration.RIGHT -> Gravity.START + binding.tabDrawer.layoutParams = + (binding.tabDrawer.layoutParams as DrawerLayout.LayoutParams).apply { + gravity = when (uiConfiguration.bookmarkConfiguration) { + BookmarkConfiguration.LEFT -> Gravity.END + BookmarkConfiguration.RIGHT -> Gravity.START + } } - } - binding.homeImageView.isVisible = uiConfiguration.tabConfiguration == TabConfiguration.DESKTOP + binding.homeImageView.isVisible = + uiConfiguration.tabConfiguration == TabConfiguration.DESKTOP binding.tabCountView.isVisible = uiConfiguration.tabConfiguration == TabConfiguration.DRAWER if (uiConfiguration.tabConfiguration == TabConfiguration.DESKTOP) { - binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, binding.tabDrawer) + binding.drawerLayout.setDrawerLockMode( + DrawerLayout.LOCK_MODE_LOCKED_CLOSED, + binding.tabDrawer + ) } if (uiConfiguration.tabConfiguration == TabConfiguration.DRAWER) { @@ -130,7 +136,8 @@ class BrowserActivity : ThemableBrowserActivity() { ) binding.desktopTabsList.isVisible = true binding.desktopTabsList.adapter = tabsAdapter - binding.desktopTabsList.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false) + binding.desktopTabsList.layoutManager = + LinearLayoutManager(this, RecyclerView.HORIZONTAL, false) binding.drawerTabsList.isVisible = false } @@ -169,7 +176,11 @@ class BrowserActivity : ThemableBrowserActivity() { binding.search.setOnEditorActionListener(searchListener) binding.search.setOnKeyListener(searchListener) binding.search.addTextChangedListener(StyleRemovingTextWatcher()) - binding.search.setOnFocusChangeListener { _, hasFocus -> presenter.onSearchFocusChanged(hasFocus) } + binding.search.setOnFocusChangeListener { _, hasFocus -> + presenter.onSearchFocusChanged( + hasFocus + ) + } binding.findPrevious.setOnClickListener { presenter.onFindPrevious() } binding.findNext.setOnClickListener { presenter.onFindNext() } @@ -240,22 +251,26 @@ class BrowserActivity : ThemableBrowserActivity() { viewState.tabs?.let { binding.tabCountView.updateCount(it.size) } viewState.progress?.let { binding.progressView.progress = it } viewState.isRefresh?.let { - binding.searchRefresh.setImageResource(if (it) { - R.drawable.ic_action_refresh - } else { - R.drawable.ic_action_delete - }) + binding.searchRefresh.setImageResource( + if (it) { + R.drawable.ic_action_refresh + } else { + R.drawable.ic_action_delete + } + ) } viewState.tabs?.let(tabsAdapter::submitList) viewState.bookmarks?.let(bookmarksAdapter::submitList) viewState.isBookmarked?.let { binding.actionAddBookmark.isSelected = it } viewState.isBookmarkEnabled?.let { binding.actionAddBookmark.isEnabled = it } viewState.isRootFolder?.let { - binding.bookmarkBackButton.setImageResource(if (it) { - R.drawable.ic_action_star - } else { - R.drawable.ic_action_back - }) + binding.bookmarkBackButton.setImageResource( + if (it) { + R.drawable.ic_action_star + } else { + R.drawable.ic_action_back + } + ) } viewState.findInPage?.let { if (it.isEmpty()) { @@ -327,47 +342,77 @@ class BrowserActivity : ThemableBrowserActivity() { fun showLinkLongPressDialog(longPress: LongPress) { BrowserDialog.show(this, longPress.targetUrl?.replace(HTTP, ""), DialogItem(title = R.string.dialog_open_new_tab) { - presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.NEW_TAB) + presenter.onLinkLongPressEvent( + longPress, + BrowserContract.LinkLongPressEvent.NEW_TAB + ) }, DialogItem(title = R.string.dialog_open_background_tab) { - presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.BACKGROUND_TAB) + presenter.onLinkLongPressEvent( + longPress, + BrowserContract.LinkLongPressEvent.BACKGROUND_TAB + ) }, DialogItem( title = R.string.dialog_open_incognito_tab, isConditionMet = this is BrowserActivity // TODO: Change for incognito ) { - presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.INCOGNITO_TAB) + presenter.onLinkLongPressEvent( + longPress, + BrowserContract.LinkLongPressEvent.INCOGNITO_TAB + ) }, DialogItem(title = R.string.action_share) { presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.SHARE) }, DialogItem(title = R.string.dialog_copy_link) { - presenter.onLinkLongPressEvent(longPress, BrowserContract.LinkLongPressEvent.COPY_LINK) + presenter.onLinkLongPressEvent( + longPress, + BrowserContract.LinkLongPressEvent.COPY_LINK + ) }) } fun showImageLongPressDialog(longPress: LongPress) { BrowserDialog.show(this, longPress.targetUrl?.replace(HTTP, ""), DialogItem(title = R.string.dialog_open_new_tab) { - presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.NEW_TAB) + presenter.onImageLongPressEvent( + longPress, + BrowserContract.ImageLongPressEvent.NEW_TAB + ) }, DialogItem(title = R.string.dialog_open_background_tab) { - presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.BACKGROUND_TAB) + presenter.onImageLongPressEvent( + longPress, + BrowserContract.ImageLongPressEvent.BACKGROUND_TAB + ) }, DialogItem( title = R.string.dialog_open_incognito_tab, isConditionMet = this is BrowserActivity // TODO: Change for incognito ) { - presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.INCOGNITO_TAB) + presenter.onImageLongPressEvent( + longPress, + BrowserContract.ImageLongPressEvent.INCOGNITO_TAB + ) }, DialogItem(title = R.string.action_share) { - presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.SHARE) + presenter.onImageLongPressEvent( + longPress, + BrowserContract.ImageLongPressEvent.SHARE + ) }, DialogItem(title = R.string.dialog_copy_link) { - presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.COPY_LINK) + presenter.onImageLongPressEvent( + longPress, + BrowserContract.ImageLongPressEvent.COPY_LINK + ) }, DialogItem(title = R.string.dialog_download_image) { - presenter.onImageLongPressEvent(longPress, BrowserContract.ImageLongPressEvent.DOWNLOAD) + presenter.onImageLongPressEvent( + longPress, + BrowserContract.ImageLongPressEvent.DOWNLOAD + ) }) } @@ -381,7 +426,8 @@ class BrowserActivity : ThemableBrowserActivity() { }, DialogItem(title = R.string.close_all_tabs, onClick = { presenter.onCloseBrowserEvent(id, BrowserContract.CloseTabEvent.CLOSE_ALL) - })) + }) + ) } fun openBookmarkDrawer() { From 2798feb1e04887643617e062a51ff0801a0d3069 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 25 Jul 2021 14:13:58 -0400 Subject: [PATCH 065/161] Close bookmark and tab drawers after clicking on a tab or bookmark --- .../lightning/_browser2/BrowserActivity.kt | 11 +- .../lightning/_browser2/BrowserContract.kt | 11 +- .../lightning/_browser2/BrowserPresenter.kt | 164 ++++++++++++------ .../_browser2/BrowserStateAdapter.kt | 15 +- 4 files changed, 144 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index b6d8c8963..1b140767a 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -417,7 +417,8 @@ class BrowserActivity : ThemableBrowserActivity() { } fun showCloseBrowserDialog(id: Int) { - BrowserDialog.show(this, R.string.dialog_title_close_browser, + BrowserDialog.show( + this, R.string.dialog_title_close_browser, DialogItem(title = R.string.close_tab) { presenter.onCloseBrowserEvent(id, BrowserContract.CloseTabEvent.CLOSE_CURRENT) }, @@ -435,11 +436,19 @@ class BrowserActivity : ThemableBrowserActivity() { binding.drawerLayout.openDrawer(binding.bookmarkDrawer) } + fun closeBookmarkDrawer() { + binding.drawerLayout.closeDrawer(binding.bookmarkDrawer) + } + fun openTabDrawer() { binding.drawerLayout.closeDrawer(binding.bookmarkDrawer) binding.drawerLayout.openDrawer(binding.tabDrawer) } + fun closeTabDrawer() { + binding.drawerLayout.closeDrawer(binding.tabDrawer) + } + private fun ImageView.updateVisibilityForDrawable() { visibility = if (drawable == null) { View.GONE diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 9ff3080b8..959520286 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -26,7 +26,12 @@ interface BrowserContract { fun showBookmarkOptionsDialog(bookmark: Bookmark.Entry) - fun showEditBookmarkDialog(title: String, url: String, folder: String, folders: List) + fun showEditBookmarkDialog( + title: String, + url: String, + folder: String, + folders: List + ) fun showFolderOptionsDialog(folder: Bookmark.Folder) @@ -44,7 +49,11 @@ interface BrowserContract { fun openBookmarkDrawer() + fun closeBookmarkDrawer() + fun openTabDrawer() + + fun closeTabDrawer() } interface Model { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 5a44ae565..068af65e9 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -160,21 +160,23 @@ class BrowserPresenter @Inject constructor( currentTab = tabModel currentTab?.isForeground = true - val tab = tabModel ?: return view.updateState(viewState.copy( - displayUrl = searchBoxModel.getDisplayContent( - url = "", - title = null, - isLoading = false - ), - isForwardEnabled = false, - isBackEnabled = false, - sslState = SslState.None, - progress = 100, - tabs = viewState.tabs.map { - it.copy(isSelected = false) - }, - findInPage = "" - )) + val tab = tabModel ?: return view.updateState( + viewState.copy( + displayUrl = searchBoxModel.getDisplayContent( + url = "", + title = null, + isLoading = false + ), + isForwardEnabled = false, + isBackEnabled = false, + sslState = SslState.None, + progress = 100, + tabs = viewState.tabs.map { + it.copy(isSelected = false) + }, + findInPage = "" + ) + ) tabDisposable?.dispose() tabDisposable = Observables.combineLatest( @@ -211,7 +213,8 @@ class BrowserPresenter @Inject constructor( forEach { tabModel -> compositeDisposable += Observables.combineLatest( tabModel.titleChanges().startWith(tabModel.title), - tabModel.faviconChanges().optional().startWith(Option.fromNullable(tabModel.favicon)) + tabModel.faviconChanges().optional() + .startWith(Option.fromNullable(tabModel.favicon)) ).distinctUntilChanged() .subscribeOn(mainScheduler) .subscribeBy { (title, bitmap) -> @@ -265,8 +268,14 @@ class BrowserPresenter @Inject constructor( MenuSelection.SHARE -> currentTab?.url?.takeIf { !it.isSpecialUrl() }?.let { navigator.sharePage(url = it, title = currentTab?.title) } - MenuSelection.HISTORY -> createNewTabAndSelect(historyPageInitializer, shouldSelect = true) - MenuSelection.DOWNLOADS -> createNewTabAndSelect(downloadPageInitializer, shouldSelect = true) + MenuSelection.HISTORY -> createNewTabAndSelect( + historyPageInitializer, + shouldSelect = true + ) + MenuSelection.DOWNLOADS -> createNewTabAndSelect( + downloadPageInitializer, + shouldSelect = true + ) MenuSelection.FIND -> view?.showFindInPageDialog() MenuSelection.COPY_LINK -> currentTab?.url?.takeIf { !it.isSpecialUrl() } ?.let(navigator::copyPageLink) @@ -330,6 +339,7 @@ class BrowserPresenter @Inject constructor( */ fun onTabClick(index: Int) { selectTab(model.selectTab(viewState.tabs[index].id)) + view?.closeTabDrawer() } /** @@ -429,15 +439,17 @@ class BrowserPresenter @Inject constructor( if (isFocused) { view?.updateState(viewState.copy(sslState = SslState.None, isRefresh = false)) } else { - view?.updateState(viewState.copy( - sslState = currentTab?.sslState ?: SslState.None, - isRefresh = (currentTab?.loadingProgress ?: 0) == 100, - displayUrl = searchBoxModel.getDisplayContent( - url = currentTab?.url.orEmpty(), - title = currentTab?.title.orEmpty(), - isLoading = (currentTab?.loadingProgress ?: 0) < 100 + view?.updateState( + viewState.copy( + sslState = currentTab?.sslState ?: SslState.None, + isRefresh = (currentTab?.loadingProgress ?: 0) == 100, + displayUrl = searchBoxModel.getDisplayContent( + url = currentTab?.url.orEmpty(), + title = currentTab?.title.orEmpty(), + isLoading = (currentTab?.loadingProgress ?: 0) < 100 + ) ) - )) + ) } } @@ -451,13 +463,15 @@ class BrowserPresenter @Inject constructor( currentTab?.stopLoading() val searchUrl = searchEngineProvider.provideSearchEngine().queryUrl + QUERY_PLACE_HOLDER val url = smartUrlFilter(query.trim(), true, searchUrl) - view?.updateState(viewState.copy( - displayUrl = searchBoxModel.getDisplayContent( - url = url, - title = currentTab?.title, - isLoading = (currentTab?.loadingProgress ?: 0) < 100 + view?.updateState( + viewState.copy( + displayUrl = searchBoxModel.getDisplayContent( + url = url, + title = currentTab?.title, + isLoading = (currentTab?.loadingProgress ?: 0) < 100 + ) ) - )) + ) currentTab?.loadUrl(url) } @@ -519,7 +533,10 @@ class BrowserPresenter @Inject constructor( */ fun onBookmarkClick(index: Int) { when (val bookmark = viewState.bookmarks[index]) { - is Bookmark.Entry -> currentTab?.loadUrl(bookmark.url) + is Bookmark.Entry -> { + currentTab?.loadUrl(bookmark.url) + view?.closeBookmarkDrawer() + } Bookmark.Folder.Root -> error("Cannot click on root folder") is Bookmark.Folder.Entry -> { currentFolder = bookmark @@ -575,12 +592,14 @@ class BrowserPresenter @Inject constructor( compositeDisposable += bookmarkRepository.isBookmark(url) .flatMapMaybe { if (it) { - bookmarkRepository.deleteBookmark(Bookmark.Entry( - url = url, - title = title, - position = 0, - folder = Bookmark.Folder.Root - )).toMaybe() + bookmarkRepository.deleteBookmark( + Bookmark.Entry( + url = url, + title = title, + position = 0, + folder = Bookmark.Folder.Root + ) + ).toMaybe() } else { Maybe.empty() } @@ -608,12 +627,14 @@ class BrowserPresenter @Inject constructor( } fun onBookmarkConfirmed(title: String, url: String, folder: String) { - compositeDisposable += bookmarkRepository.addBookmarkIfNotExists(Bookmark.Entry( - url = url, - title = title, - position = 0, - folder = folder.asFolder() - )).flatMap { bookmarkRepository.bookmarksAndFolders(folder = currentFolder) } + compositeDisposable += bookmarkRepository.addBookmarkIfNotExists( + Bookmark.Entry( + url = url, + title = title, + position = 0, + folder = folder.asFolder() + ) + ).flatMap { bookmarkRepository.bookmarksAndFolders(folder = currentFolder) } .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> @@ -634,7 +655,8 @@ class BrowserPresenter @Inject constructor( title = title, position = 0, folder = folder.asFolder() - )).andThen(bookmarkRepository.bookmarksAndFolders(folder = currentFolder)) + ) + ).andThen(bookmarkRepository.bookmarksAndFolders(folder = currentFolder)) .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { list -> @@ -655,7 +677,10 @@ class BrowserPresenter @Inject constructor( /** * TODO */ - fun onBookmarkOptionClick(bookmark: Bookmark.Entry, option: BrowserContract.BookmarkOptionEvent) { + fun onBookmarkOptionClick( + bookmark: Bookmark.Entry, + option: BrowserContract.BookmarkOptionEvent + ) { when (option) { BrowserContract.BookmarkOptionEvent.NEW_TAB -> createNewTabAndSelect(UrlInitializer(bookmark.url), shouldSelect = true) @@ -679,7 +704,12 @@ class BrowserPresenter @Inject constructor( .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribeBy { folders -> - view?.showEditBookmarkDialog(bookmark.title, bookmark.url, bookmark.folder.title, folders) + view?.showEditBookmarkDialog( + bookmark.title, + bookmark.url, + bookmark.folder.title, + folders + ) } } } @@ -771,12 +801,25 @@ class BrowserPresenter @Inject constructor( } } - fun onLinkLongPressEvent(longPress: LongPress, linkLongPressEvent: BrowserContract.LinkLongPressEvent) { + fun onLinkLongPressEvent( + longPress: LongPress, + linkLongPressEvent: BrowserContract.LinkLongPressEvent + ) { when (linkLongPressEvent) { BrowserContract.LinkLongPressEvent.NEW_TAB -> - longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = true) } + longPress.targetUrl?.let { + createNewTabAndSelect( + UrlInitializer(it), + shouldSelect = true + ) + } BrowserContract.LinkLongPressEvent.BACKGROUND_TAB -> - longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = false) } + longPress.targetUrl?.let { + createNewTabAndSelect( + UrlInitializer(it), + shouldSelect = false + ) + } BrowserContract.LinkLongPressEvent.INCOGNITO_TAB -> TODO() BrowserContract.LinkLongPressEvent.SHARE -> longPress.targetUrl?.let { navigator.sharePage(url = it, title = null) } @@ -785,12 +828,25 @@ class BrowserPresenter @Inject constructor( } } - fun onImageLongPressEvent(longPress: LongPress, imageLongPressEvent: BrowserContract.ImageLongPressEvent) { + fun onImageLongPressEvent( + longPress: LongPress, + imageLongPressEvent: BrowserContract.ImageLongPressEvent + ) { when (imageLongPressEvent) { BrowserContract.ImageLongPressEvent.NEW_TAB -> - longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = true) } + longPress.targetUrl?.let { + createNewTabAndSelect( + UrlInitializer(it), + shouldSelect = true + ) + } BrowserContract.ImageLongPressEvent.BACKGROUND_TAB -> - longPress.targetUrl?.let { createNewTabAndSelect(UrlInitializer(it), shouldSelect = false) } + longPress.targetUrl?.let { + createNewTabAndSelect( + UrlInitializer(it), + shouldSelect = false + ) + } BrowserContract.ImageLongPressEvent.INCOGNITO_TAB -> TODO() BrowserContract.ImageLongPressEvent.SHARE -> longPress.targetUrl?.let { navigator.sharePage(url = it, title = null) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index a224647a0..2d5097cd0 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -56,7 +56,12 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showBookmarkOptionsDialog(bookmark) } - override fun showEditBookmarkDialog(title: String, url: String, folder: String, folders: List) { + override fun showEditBookmarkDialog( + title: String, + url: String, + folder: String, + folders: List + ) { browserActivity.showEditBookmarkDialog(title, url, folder, folders) } @@ -92,8 +97,16 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.openBookmarkDrawer() } + override fun closeBookmarkDrawer() { + browserActivity.closeBookmarkDrawer() + } + override fun openTabDrawer() { browserActivity.openTabDrawer() } + override fun closeTabDrawer() { + browserActivity.closeTabDrawer() + } + } From a62f5cf72df256e3298348c8e7706218a10a9143 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 25 Jul 2021 14:24:33 -0400 Subject: [PATCH 066/161] Upgrade dependencies --- app/build.gradle | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ad95947a3..49efb5c4e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,9 +105,9 @@ dependencies { debugImplementation 'androidx.multidex:multidex:2.0.1' // test dependencies - testImplementation 'junit:junit:4.13.1' - testImplementation 'org.assertj:assertj-core:3.18.1' - testImplementation 'org.mockito:mockito-core:3.6.28' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.assertj:assertj-core:3.20.2' + testImplementation 'org.mockito:mockito-core:3.11.2' testImplementation 'com.nhaarman:mockito-kotlin:1.6.0', { exclude group: 'org.jetbrains.kotlin' } @@ -115,14 +115,14 @@ dependencies { // support libraries implementation "androidx.palette:palette-ktx:1.0.0" - implementation "androidx.annotation:annotation:1.1.0" + implementation "androidx.annotation:annotation:1.2.0" implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0" - implementation "androidx.appcompat:appcompat:1.2.0" - implementation "com.google.android.material:material:1.2.1" - implementation "androidx.recyclerview:recyclerview:1.1.0" - implementation 'androidx.core:core-ktx:1.5.0-alpha05' + implementation "androidx.appcompat:appcompat:1.3.1" + implementation "com.google.android.material:material:1.4.0" + implementation "androidx.recyclerview:recyclerview:1.2.1" + implementation 'androidx.core:core-ktx:1.7.0-alpha01' implementation "androidx.constraintlayout:constraintlayout:2.0.4" - implementation "androidx.fragment:fragment-ktx:1.2.5" + implementation "androidx.fragment:fragment-ktx:1.3.6" implementation "androidx.drawerlayout:drawerlayout:1.1.1" // html parsing for reading mode @@ -134,7 +134,7 @@ dependencies { kapt "com.anthonycr.mezzanine:mezzanine-compiler:$mezzanineVersion" // dependency injection - final def daggerVersion = '2.30.1' + final def daggerVersion = '2.38' implementation "com.google.dagger:dagger:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion" compileOnly 'javax.annotation:jsr250-api:1.0' @@ -155,7 +155,7 @@ dependencies { // rx implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - implementation 'io.reactivex.rxjava2:rxjava:2.2.20' + implementation 'io.reactivex.rxjava2:rxjava:2.2.21' implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' // tor proxy From 874e3c1bdd9bf92b3a1329e66cccc735d0720897 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 25 Jul 2021 15:21:01 -0400 Subject: [PATCH 067/161] Properly display dialog for bookmark and folder page and reload the page --- .../lightning/_browser2/BrowserPresenter.kt | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 068af65e9..14b3b007c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -13,12 +13,15 @@ import acr.browser.lightning.browser.SearchBoxModel import acr.browser.lightning.database.* import acr.browser.lightning.database.bookmark.BookmarkRepository import acr.browser.lightning.di.DatabaseScheduler +import acr.browser.lightning.di.DiskScheduler import acr.browser.lightning.di.MainScheduler +import acr.browser.lightning.html.bookmark.BookmarkPageFactory import acr.browser.lightning.html.history.HistoryPageFactory import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.ssl.SslState import acr.browser.lightning.utils.* import acr.browser.lightning.view.* +import androidx.core.net.toUri import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Scheduler @@ -41,9 +44,11 @@ class BrowserPresenter @Inject constructor( private val model: BrowserContract.Model, private val navigator: BrowserContract.Navigator, private val bookmarkRepository: BookmarkRepository, + @DiskScheduler private val diskScheduler: Scheduler, @MainScheduler private val mainScheduler: Scheduler, @DatabaseScheduler private val databaseScheduler: Scheduler, private val historyRecord: HistoryRecord, + private val bookmarkPageFactory: BookmarkPageFactory, private val homePageInitializer: HomePageInitializer, private val historyPageInitializer: HistoryPageInitializer, private val downloadPageInitializer: DownloadPageInitializer, @@ -426,6 +431,28 @@ class BrowserPresenter @Inject constructor( } if (currentTab?.loadingProgress != 100) { currentTab?.stopLoading() + } else { + reload() + } + } + + private fun reload() { + val currentUrl = currentTab?.url + if (currentUrl?.isSpecialUrl() == true) { + when { + currentUrl.isBookmarkUrl() -> + compositeDisposable += bookmarkPageFactory.buildPage() + .subscribeOn(diskScheduler) + .observeOn(mainScheduler) + .subscribeBy { + currentTab?.reload() + } + currentUrl.isDownloadsUrl() -> + currentTab?.loadFromInitializer(downloadPageInitializer) + currentUrl.isHistoryUrl() -> + currentTab?.loadFromInitializer(historyPageInitializer) + else -> currentTab?.reload() + } } else { currentTab?.reload() } @@ -661,6 +688,9 @@ class BrowserPresenter @Inject constructor( .observeOn(mainScheduler) .subscribe { list -> this.view?.updateState(viewState.copy(bookmarks = list)) + if (currentTab?.url?.isBookmarkUrl() == true) { + reload() + } } } @@ -671,6 +701,9 @@ class BrowserPresenter @Inject constructor( .observeOn(mainScheduler) .subscribe { list -> this.view?.updateState(viewState.copy(bookmarks = list)) + if (currentTab?.url?.isBookmarkUrl() == true) { + reload() + } } } @@ -773,8 +806,32 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onPageLongPress(id: Int, longPress: LongPress) { - if (model.tabsList.find { it.id == id }?.url?.isSpecialUrl() == true) { - // TODO handle differently + val pageUrl = model.tabsList.find { it.id == id }?.url + if (pageUrl?.isSpecialUrl() == true) { + if (pageUrl.isBookmarkUrl()) { + val url = longPress.targetUrl ?: return + if (url.isBookmarkUrl()) { + val filename = requireNotNull(longPress.targetUrl.toUri().lastPathSegment) { + "Last segment should always exist for bookmark file" + } + val folderTitle = filename.substring( + 0, + filename.length - BookmarkPageFactory.FILENAME.length - 1 + ) + view?.showFolderOptionsDialog(folderTitle.asFolder()) + } else { + compositeDisposable += bookmarkRepository.findBookmarkForUrl(url) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribe { bookmarkEntry -> + view?.showBookmarkOptionsDialog(bookmarkEntry) + } + } + } else if (pageUrl.isDownloadsUrl()) { + // TODO + } else if (pageUrl.isHistoryUrl()) { + // TODO + } return } when (longPress.hitCategory) { From d3918ce5ccea68a6684438f395f5fc26ea98986d Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 25 Jul 2021 15:44:10 -0400 Subject: [PATCH 068/161] Support deleting downloads on download page --- .../lightning/_browser2/BrowserActivity.kt | 10 ++++ .../lightning/_browser2/BrowserContract.kt | 8 +++ .../lightning/_browser2/BrowserPresenter.kt | 42 ++++++++++++-- .../_browser2/BrowserStateAdapter.kt | 5 ++ .../dialog/LightningDialogBuilder.kt | 58 ++++++++++++++----- 5 files changed, 106 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 1b140767a..d06d938f3 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -17,6 +17,7 @@ import acr.browser.lightning.constant.HTTP import acr.browser.lightning.database.Bookmark import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage +import acr.browser.lightning.database.downloads.DownloadEntry import acr.browser.lightning.databinding.BrowserActivityBinding import acr.browser.lightning.di.injector import acr.browser.lightning.dialog.BrowserDialog @@ -329,6 +330,15 @@ class BrowserActivity : ThemableBrowserActivity() { ) } + fun showDownloadOptionsDialog(download: DownloadEntry) { + lightningDialogBuilder.showLongPressedDialogForDownloadUrl( + activity = this, + onClick = { + presenter.onDownloadOptionClick(download, it) + } + ) + } + fun showFindInPageDialog() { BrowserDialog.showEditText( this, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 959520286..93bde45de 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning.database.Bookmark +import acr.browser.lightning.database.downloads.DownloadEntry import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap @@ -37,6 +38,8 @@ interface BrowserContract { fun showEditFolderDialog(title: String) + fun showDownloadOptionsDialog(download: DownloadEntry) + fun showFindInPageDialog() fun showLinkLongPressDialog(longPress: LongPress) @@ -111,6 +114,11 @@ interface BrowserContract { EDIT } + enum class DownloadOptionEvent { + DELETE, + DELETE_ALL + } + enum class FolderOptionEvent { RENAME, REMOVE diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 14b3b007c..497637b2f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -12,6 +12,8 @@ import acr.browser.lightning._browser2.ui.UiConfiguration import acr.browser.lightning.browser.SearchBoxModel import acr.browser.lightning.database.* import acr.browser.lightning.database.bookmark.BookmarkRepository +import acr.browser.lightning.database.downloads.DownloadEntry +import acr.browser.lightning.database.downloads.DownloadsRepository import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.DiskScheduler import acr.browser.lightning.di.MainScheduler @@ -44,6 +46,7 @@ class BrowserPresenter @Inject constructor( private val model: BrowserContract.Model, private val navigator: BrowserContract.Navigator, private val bookmarkRepository: BookmarkRepository, + private val downloadsRepository: DownloadsRepository, @DiskScheduler private val diskScheduler: Scheduler, @MainScheduler private val mainScheduler: Scheduler, @DatabaseScheduler private val databaseScheduler: Scheduler, @@ -761,6 +764,32 @@ class BrowserPresenter @Inject constructor( } } + fun onDownloadOptionClick( + download: DownloadEntry, + option: BrowserContract.DownloadOptionEvent + ) { + when (option) { + BrowserContract.DownloadOptionEvent.DELETE -> + compositeDisposable += downloadsRepository.deleteAllDownloads() + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribeBy { + if (currentTab?.url?.isDownloadsUrl() == true) { + reload() + } + } + BrowserContract.DownloadOptionEvent.DELETE_ALL -> + compositeDisposable += downloadsRepository.deleteDownload(download.url) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribeBy { + if (currentTab?.url?.isDownloadsUrl() == true) { + reload() + } + } + } + } + /** * TODO */ @@ -808,8 +837,8 @@ class BrowserPresenter @Inject constructor( fun onPageLongPress(id: Int, longPress: LongPress) { val pageUrl = model.tabsList.find { it.id == id }?.url if (pageUrl?.isSpecialUrl() == true) { + val url = longPress.targetUrl ?: return if (pageUrl.isBookmarkUrl()) { - val url = longPress.targetUrl ?: return if (url.isBookmarkUrl()) { val filename = requireNotNull(longPress.targetUrl.toUri().lastPathSegment) { "Last segment should always exist for bookmark file" @@ -823,12 +852,17 @@ class BrowserPresenter @Inject constructor( compositeDisposable += bookmarkRepository.findBookmarkForUrl(url) .subscribeOn(databaseScheduler) .observeOn(mainScheduler) - .subscribe { bookmarkEntry -> - view?.showBookmarkOptionsDialog(bookmarkEntry) + .subscribeBy { + view?.showBookmarkOptionsDialog(it) } } } else if (pageUrl.isDownloadsUrl()) { - // TODO + compositeDisposable += downloadsRepository.findDownloadForUrl(url) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribeBy { + view?.showDownloadOptionsDialog(it) + } } else if (pageUrl.isHistoryUrl()) { // TODO } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 2d5097cd0..6bda1f441 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning.database.Bookmark +import acr.browser.lightning.database.downloads.DownloadEntry import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.showSslDialog import targetUrl.LongPress @@ -73,6 +74,10 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showEditFolderDialog(title) } + override fun showDownloadOptionsDialog(download: DownloadEntry) { + browserActivity.showDownloadOptionsDialog(download) + } + override fun showFindInPageDialog() { browserActivity.showFindInPageDialog() } diff --git a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt index 64d78dfbd..cf52138b6 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt +++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt @@ -70,8 +70,10 @@ class LightningDialogBuilder @Inject constructor( if (url.isBookmarkUrl()) { // TODO hacky, make a better bookmark mechanism in the future val uri = url.toUri() - val filename = requireNotNull(uri.lastPathSegment) { "Last segment should always exist for bookmark file" } - val folderTitle = filename.substring(0, filename.length - BookmarkPageFactory.FILENAME.length - 1) + val filename = + requireNotNull(uri.lastPathSegment) { "Last segment should always exist for bookmark file" } + val folderTitle = + filename.substring(0, filename.length - BookmarkPageFactory.FILENAME.length - 1) showBookmarkFolderLongPressedDialog(activity, uiController, folderTitle.asFolder()) } else { bookmarkManager.findBookmarkForUrl(url) @@ -169,6 +171,18 @@ class LightningDialogBuilder @Inject constructor( .subscribe(uiController::handleDownloadDeleted) }) + fun showLongPressedDialogForDownloadUrl( + activity: Activity, + onClick: (BrowserContract.DownloadOptionEvent) -> Unit + ) = BrowserDialog.show(activity, R.string.action_downloads, + DialogItem(title = R.string.dialog_delete_all_downloads) { + onClick(BrowserContract.DownloadOptionEvent.DELETE_ALL) + }, + DialogItem(title = R.string.dialog_delete_all_downloads) { + onClick(BrowserContract.DownloadOptionEvent.DELETE) + } + ) + /** * Show the add bookmark dialog. Shows a dialog with the title and URL pre-populated. */ @@ -192,8 +206,10 @@ class LightningDialogBuilder @Inject constructor( .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { folders -> - val suggestionsAdapter = ArrayAdapter(activity, - android.R.layout.simple_dropdown_item_1line, folders) + val suggestionsAdapter = ArrayAdapter( + activity, + android.R.layout.simple_dropdown_item_1line, folders + ) getFolder.threshold = 1 getFolder.setAdapter(suggestionsAdapter) editBookmarkDialog.setView(dialogLayout) @@ -234,8 +250,10 @@ class LightningDialogBuilder @Inject constructor( binding.bookmarkUrl.setText(currentUrl) binding.bookmarkFolder.setText("") - val suggestionsAdapter = ArrayAdapter(activity, - android.R.layout.simple_dropdown_item_1line, folders) + val suggestionsAdapter = ArrayAdapter( + activity, + android.R.layout.simple_dropdown_item_1line, folders + ) binding.bookmarkFolder.setAdapter(suggestionsAdapter) editBookmarkDialog.setView(dialogLayout) editBookmarkDialog.setPositiveButton(activity.getString(R.string.action_ok)) { _, _ -> @@ -269,8 +287,10 @@ class LightningDialogBuilder @Inject constructor( .subscribeOn(databaseScheduler) .observeOn(mainScheduler) .subscribe { folders -> - val suggestionsAdapter = ArrayAdapter(activity, - android.R.layout.simple_dropdown_item_1line, folders) + val suggestionsAdapter = ArrayAdapter( + activity, + android.R.layout.simple_dropdown_item_1line, folders + ) getFolder.threshold = 1 getFolder.setAdapter(suggestionsAdapter) editBookmarkDialog.setView(dialogLayout) @@ -306,8 +326,10 @@ class LightningDialogBuilder @Inject constructor( binding.bookmarkUrl.setText(currentUrl) binding.bookmarkFolder.setText(currentFolder) - val suggestionsAdapter = ArrayAdapter(activity, - android.R.layout.simple_dropdown_item_1line, folders) + val suggestionsAdapter = ArrayAdapter( + activity, + android.R.layout.simple_dropdown_item_1line, folders + ) binding.bookmarkFolder.setAdapter(suggestionsAdapter) editBookmarkDialog.setView(dialogLayout) editBookmarkDialog.setPositiveButton(activity.getString(R.string.action_ok)) { _, _ -> @@ -352,11 +374,13 @@ class LightningDialogBuilder @Inject constructor( activity: Activity, uiController: UIController, folder: Bookmark.Folder - ) = BrowserDialog.showEditText(activity, + ) = BrowserDialog.showEditText( + activity, R.string.title_rename_folder, R.string.hint_title, folder.title, - R.string.action_ok) { text -> + R.string.action_ok + ) { text -> if (text.isNotBlank()) { val oldTitle = folder.title bookmarkManager.renameFolder(oldTitle, text) @@ -438,7 +462,15 @@ class LightningDialogBuilder @Inject constructor( clipboardManager.copyToClipboard(url) }, DialogItem(title = R.string.dialog_download_image) { - downloadHandler.onDownloadStart(activity, userPreferences, url, userAgent, "attachment", null, "") + downloadHandler.onDownloadStart( + activity, + userPreferences, + url, + userAgent, + "attachment", + null, + "" + ) }) fun showLongPressLinkDialog( From 30e348bd1ee2e84c4af20a94fca311734aee6665 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 25 Jul 2021 16:03:46 -0400 Subject: [PATCH 069/161] Support showing long press options for history page --- .../lightning/_browser2/BrowserActivity.kt | 10 +++++ .../lightning/_browser2/BrowserContract.kt | 12 ++++++ .../lightning/_browser2/BrowserPresenter.kt | 43 ++++++++++++++++++- .../_browser2/BrowserStateAdapter.kt | 5 +++ .../dialog/LightningDialogBuilder.kt | 26 +++++++++++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index d06d938f3..cd57a0314 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -15,6 +15,7 @@ import acr.browser.lightning.browser.activity.StyleRemovingTextWatcher import acr.browser.lightning.browser.activity.ThemableBrowserActivity import acr.browser.lightning.constant.HTTP import acr.browser.lightning.database.Bookmark +import acr.browser.lightning.database.HistoryEntry import acr.browser.lightning.database.SearchSuggestion import acr.browser.lightning.database.WebPage import acr.browser.lightning.database.downloads.DownloadEntry @@ -339,6 +340,15 @@ class BrowserActivity : ThemableBrowserActivity() { ) } + fun showHistoryOptionsDialog(historyEntry: HistoryEntry) { + lightningDialogBuilder.showLongPressedHistoryLinkDialog( + activity = this, + onClick = { + presenter.onHistoryOptionClick(historyEntry, it) + } + ) + } + fun showFindInPageDialog() { BrowserDialog.showEditText( this, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 93bde45de..2966eaabf 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning.database.Bookmark +import acr.browser.lightning.database.HistoryEntry import acr.browser.lightning.database.downloads.DownloadEntry import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.view.TabInitializer @@ -40,6 +41,8 @@ interface BrowserContract { fun showDownloadOptionsDialog(download: DownloadEntry) + fun showHistoryOptionsDialog(historyEntry: HistoryEntry) + fun showFindInPageDialog() fun showLinkLongPressDialog(longPress: LongPress) @@ -114,6 +117,15 @@ interface BrowserContract { EDIT } + enum class HistoryOptionEvent { + NEW_TAB, + BACKGROUND_TAB, + INCOGNITO_TAB, + SHARE, + COPY_LINK, + REMOVE, + } + enum class DownloadOptionEvent { DELETE, DELETE_ALL diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 497637b2f..1819da62b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -14,6 +14,7 @@ import acr.browser.lightning.database.* import acr.browser.lightning.database.bookmark.BookmarkRepository import acr.browser.lightning.database.downloads.DownloadEntry import acr.browser.lightning.database.downloads.DownloadsRepository +import acr.browser.lightning.database.history.HistoryRepository import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.DiskScheduler import acr.browser.lightning.di.MainScheduler @@ -47,6 +48,7 @@ class BrowserPresenter @Inject constructor( private val navigator: BrowserContract.Navigator, private val bookmarkRepository: BookmarkRepository, private val downloadsRepository: DownloadsRepository, + private val historyRepository: HistoryRepository, @DiskScheduler private val diskScheduler: Scheduler, @MainScheduler private val mainScheduler: Scheduler, @DatabaseScheduler private val databaseScheduler: Scheduler, @@ -734,6 +736,9 @@ class BrowserPresenter @Inject constructor( .observeOn(mainScheduler) .subscribe { list -> view?.updateState(viewState.copy(bookmarks = list)) + if (currentTab?.url?.isBookmarkUrl() == true) { + reload() + } } BrowserContract.BookmarkOptionEvent.EDIT -> compositeDisposable += bookmarkRepository.getFolderNames() @@ -760,6 +765,9 @@ class BrowserPresenter @Inject constructor( .observeOn(mainScheduler) .subscribe { list -> view?.updateState(viewState.copy(bookmarks = list)) + if (currentTab?.url?.isBookmarkUrl() == true) { + reload() + } } } } @@ -790,6 +798,31 @@ class BrowserPresenter @Inject constructor( } } + fun onHistoryOptionClick( + historyEntry: HistoryEntry, + option: BrowserContract.HistoryOptionEvent + ) { + when (option) { + BrowserContract.HistoryOptionEvent.NEW_TAB -> + createNewTabAndSelect(UrlInitializer(historyEntry.url), shouldSelect = true) + BrowserContract.HistoryOptionEvent.BACKGROUND_TAB -> + createNewTabAndSelect(UrlInitializer(historyEntry.url), shouldSelect = false) + BrowserContract.HistoryOptionEvent.INCOGNITO_TAB -> TODO() + BrowserContract.HistoryOptionEvent.SHARE -> + navigator.sharePage(url = historyEntry.url, title = historyEntry.title) + BrowserContract.HistoryOptionEvent.COPY_LINK -> navigator.copyPageLink(historyEntry.url) + BrowserContract.HistoryOptionEvent.REMOVE -> + compositeDisposable += historyRepository.deleteHistoryEntry(historyEntry.url) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribeBy { + if (currentTab?.url?.isHistoryUrl() == true) { + reload() + } + } + } + } + /** * TODO */ @@ -864,7 +897,15 @@ class BrowserPresenter @Inject constructor( view?.showDownloadOptionsDialog(it) } } else if (pageUrl.isHistoryUrl()) { - // TODO + compositeDisposable += historyRepository.findHistoryEntriesContaining(url) + .subscribeOn(databaseScheduler) + .observeOn(mainScheduler) + .subscribeBy { entries -> + entries.firstOrNull()?.let { + view?.showHistoryOptionsDialog(it) + } ?: view?.showHistoryOptionsDialog(HistoryEntry(url = url, title = "")) + } + } return } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 6bda1f441..a982424da 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning.database.Bookmark +import acr.browser.lightning.database.HistoryEntry import acr.browser.lightning.database.downloads.DownloadEntry import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.showSslDialog @@ -78,6 +79,10 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showDownloadOptionsDialog(download) } + override fun showHistoryOptionsDialog(historyEntry: HistoryEntry) { + browserActivity.showHistoryOptionsDialog(historyEntry) + } + override fun showFindInPageDialog() { browserActivity.showFindInPageDialog() } diff --git a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt index cf52138b6..b4406b27b 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt +++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt @@ -436,6 +436,32 @@ class LightningDialogBuilder @Inject constructor( .subscribe(uiController::handleHistoryChange) }) + fun showLongPressedHistoryLinkDialog( + activity: Activity, + onClick: (BrowserContract.HistoryOptionEvent) -> Unit + ) = BrowserDialog.show(activity, R.string.action_history, + DialogItem(title = R.string.dialog_open_new_tab) { + onClick(BrowserContract.HistoryOptionEvent.NEW_TAB) + }, + DialogItem(title = R.string.dialog_open_background_tab) { + onClick(BrowserContract.HistoryOptionEvent.BACKGROUND_TAB) + }, + DialogItem( + title = R.string.dialog_open_incognito_tab, + isConditionMet = activity is MainActivity + ) { + onClick(BrowserContract.HistoryOptionEvent.INCOGNITO_TAB) + }, + DialogItem(title = R.string.action_share) { + onClick(BrowserContract.HistoryOptionEvent.SHARE) + }, + DialogItem(title = R.string.dialog_copy_link) { + onClick(BrowserContract.HistoryOptionEvent.COPY_LINK) + }, + DialogItem(title = R.string.dialog_remove_from_history) { + onClick(BrowserContract.HistoryOptionEvent.REMOVE) + }) + // TODO There should be a way in which we do not need an activity reference to dowload a file fun showLongPressImageDialog( activity: Activity, From e7757c1e8b2bfb204131e18b6cc567d55228b3e1 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 25 Jul 2021 21:07:04 -0400 Subject: [PATCH 070/161] Support downloads --- .../lightning/_browser2/BrowserContract.kt | 3 + .../lightning/_browser2/BrowserNavigator.kt | 16 ++- .../lightning/_browser2/BrowserPresenter.kt | 21 +++- .../download/DownloadPermissionsHelper.kt | 110 ++++++++++++++++++ .../_browser2/download/PendingDownload.kt | 12 ++ .../lightning/_browser2/tab/TabAdapter.kt | 23 +++- .../lightning/_browser2/tab/TabModel.kt | 4 + .../lightning/download/DownloadHandler.java | 37 +++--- 8 files changed, 203 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/download/DownloadPermissionsHelper.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/download/PendingDownload.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 2966eaabf..583e544ab 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2 +import acr.browser.lightning._browser2.download.PendingDownload import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning.database.Bookmark import acr.browser.lightning.database.HistoryEntry @@ -98,6 +99,8 @@ interface BrowserContract { fun addToHomeScreen(url: String, title: String, favicon: Bitmap?) + fun download(pendingDownload: PendingDownload) + fun backgroundBrowser() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt index 174291aed..69880029e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt @@ -1,6 +1,8 @@ package acr.browser.lightning._browser2 import acr.browser.lightning.R +import acr.browser.lightning._browser2.download.DownloadPermissionsHelper +import acr.browser.lightning._browser2.download.PendingDownload import acr.browser.lightning.browser.activity.BrowserActivity import acr.browser.lightning.database.HistoryEntry import acr.browser.lightning.extensions.copyToClipboard @@ -23,7 +25,8 @@ import javax.inject.Inject class BrowserNavigator @Inject constructor( private val activity: Activity, private val clipboardManager: ClipboardManager, - private val logger: Logger + private val logger: Logger, + private val downloadPermissionsHelper: DownloadPermissionsHelper ) : BrowserContract.Navigator { override fun openSettings() { @@ -52,6 +55,17 @@ class BrowserNavigator @Inject constructor( logger.log(TAG, "Creating shortcut: $title $url") } + override fun download(pendingDownload: PendingDownload) { + downloadPermissionsHelper.download( + activity = activity, + url = pendingDownload.url, + userAgent = pendingDownload.userAgent, + contentDisposition = pendingDownload.contentDisposition, + mimeType = pendingDownload.mimeType, + contentLength = pendingDownload.contentLength + ) + } + override fun backgroundBrowser() { activity.moveTaskToBack(true) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 1819da62b..daf79c31c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.di.Browser2Scope import acr.browser.lightning._browser2.di.InitialUrl +import acr.browser.lightning._browser2.download.PendingDownload import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.keys.KeyCombo import acr.browser.lightning._browser2.menu.MenuSelection @@ -85,7 +86,7 @@ class BrowserPresenter @Inject constructor( private val compositeDisposable = CompositeDisposable() private val allTabsDisposable = CompositeDisposable() - private var tabDisposable: Disposable? = null + private var tabDisposable: CompositeDisposable = CompositeDisposable() /** * TODO @@ -188,8 +189,9 @@ class BrowserPresenter @Inject constructor( ) ) - tabDisposable?.dispose() - tabDisposable = Observables.combineLatest( + tabDisposable.dispose() + tabDisposable = CompositeDisposable() + tabDisposable += Observables.combineLatest( tab.sslChanges().startWith(tab.sslState), tab.titleChanges().startWith(tab.title), tab.urlChanges().startWith(tab.url), @@ -217,6 +219,9 @@ class BrowserPresenter @Inject constructor( ) }.subscribeOn(mainScheduler) .subscribe { view.updateState(it) } + tabDisposable += tab.downloadRequests() + .subscribeOn(mainScheduler) + .subscribeBy(onNext = navigator::download) } private fun List.subscribeToUpdates(compositeDisposable: CompositeDisposable) { @@ -984,7 +989,15 @@ class BrowserPresenter @Inject constructor( longPress.targetUrl?.let { navigator.sharePage(url = it, title = null) } BrowserContract.ImageLongPressEvent.COPY_LINK -> longPress.targetUrl?.let(navigator::copyPageLink) - BrowserContract.ImageLongPressEvent.DOWNLOAD -> TODO() + BrowserContract.ImageLongPressEvent.DOWNLOAD -> navigator.download( + PendingDownload( + url = longPress.targetUrl.orEmpty(), + userAgent = null, + contentDisposition = "attachment", + mimeType = null, + contentLength = 0 + ) + ) } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/download/DownloadPermissionsHelper.kt b/app/src/main/java/acr/browser/lightning/_browser2/download/DownloadPermissionsHelper.kt new file mode 100644 index 000000000..968646416 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/download/DownloadPermissionsHelper.kt @@ -0,0 +1,110 @@ +package acr.browser.lightning._browser2.download + +import acr.browser.lightning.R +import acr.browser.lightning.database.downloads.DownloadEntry +import acr.browser.lightning.database.downloads.DownloadsRepository +import acr.browser.lightning.di.DatabaseScheduler +import acr.browser.lightning.dialog.BrowserDialog.setDialogSize +import acr.browser.lightning.download.DownloadHandler +import acr.browser.lightning.log.Logger +import acr.browser.lightning.preference.UserPreferences +import android.Manifest +import android.app.Activity +import android.app.Dialog +import android.content.DialogInterface +import android.text.format.Formatter +import android.webkit.URLUtil +import androidx.appcompat.app.AlertDialog +import com.anthonycr.grant.PermissionsManager +import com.anthonycr.grant.PermissionsResultAction +import io.reactivex.Scheduler +import io.reactivex.rxkotlin.subscribeBy +import javax.inject.Inject + +/** + * Created by anthonycr on 7/25/21. + */ +class DownloadPermissionsHelper @Inject constructor( + private val downloadHandler: DownloadHandler, + private val userPreferences: UserPreferences, + private val logger: Logger, + private val downloadsRepository: DownloadsRepository, + @DatabaseScheduler private val databaseScheduler: Scheduler +) { + + fun download( + activity: Activity, + url: String, + userAgent: String?, + contentDisposition: String?, + mimeType: String?, + contentLength: Long + ) { + PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(activity, arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ), + object : PermissionsResultAction() { + override fun onGranted() { + val fileName = URLUtil.guessFileName(url, contentDisposition, mimeType) + val downloadSize: String = if (contentLength > 0) { + Formatter.formatFileSize(activity, contentLength) + } else { + activity.getString(R.string.unknown_size) + } + val dialogClickListener = DialogInterface.OnClickListener { _, which: Int -> + when (which) { + DialogInterface.BUTTON_POSITIVE -> { + downloadHandler.onDownloadStart( + activity, + userPreferences, + url, + userAgent, + contentDisposition, + mimeType, + downloadSize + ) + downloadsRepository.addDownloadIfNotExists( + DownloadEntry( + url = url, + title = fileName, + contentSize = downloadSize + ) + ).subscribeOn(databaseScheduler) + .subscribeBy { + if (!it) { + logger.log(TAG, "error saving download to database") + } + } + } + DialogInterface.BUTTON_NEGATIVE -> { + } + } + } + val builder = AlertDialog.Builder(activity) // dialog + val message: String = activity.getString(R.string.dialog_download, downloadSize) + val dialog: Dialog = builder.setTitle(fileName) + .setMessage(message) + .setPositiveButton( + activity.resources.getString(R.string.action_download), + dialogClickListener + ) + .setNegativeButton( + activity.resources.getString(R.string.action_cancel), + dialogClickListener + ).show() + setDialogSize(activity, dialog) + logger.log(TAG, "Downloading: $fileName") + } + + override fun onDenied(permission: String) { + //TODO show message + logger.log(TAG, "Download permission denied") + } + }) + } + + companion object { + private const val TAG = "DownloadPermissionsHelper" + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/download/PendingDownload.kt b/app/src/main/java/acr/browser/lightning/_browser2/download/PendingDownload.kt new file mode 100644 index 000000000..acde59b4e --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/download/PendingDownload.kt @@ -0,0 +1,12 @@ +package acr.browser.lightning._browser2.download + +/** + * Created by anthonycr on 7/25/21. + */ +data class PendingDownload( + val url: String, + val userAgent: String?, + val contentDisposition: String?, + val mimeType: String?, + val contentLength: Long +) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index 1b21c4669..c422c7dd7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2.tab +import acr.browser.lightning._browser2.download.PendingDownload import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.SslState import acr.browser.lightning.view.FreezableBundleInitializer @@ -8,6 +9,7 @@ import android.graphics.Bitmap import android.os.Bundle import android.webkit.WebView import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject /** * Created by anthonycr on 9/12/20. @@ -23,10 +25,22 @@ class TabAdapter( private var latentInitializer: FreezableBundleInitializer? = null private var findInPageQuery: String? = null + private val downloadsSubject = PublishSubject.create() init { webView.webViewClient = tabWebViewClient webView.webChromeClient = tabWebChromeClient + webView.setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength -> + downloadsSubject.onNext( + PendingDownload( + url = url, + userAgent = userAgent, + contentDisposition = contentDisposition, + mimeType = mimetype, + contentLength = contentLength + ) + ) + } if (tabInitializer is FreezableBundleInitializer) { latentInitializer = tabInitializer } else { @@ -58,7 +72,8 @@ class TabAdapter( override fun canGoForward(): Boolean = webView.canGoForward() - override fun canGoForwardChanges(): Observable = tabWebViewClient.goForwardObservable.hide() + override fun canGoForwardChanges(): Observable = + tabWebViewClient.goForwardObservable.hide() override fun reload() { webView.reload() @@ -127,6 +142,8 @@ class TabAdapter( override fun loadingProgress(): Observable = tabWebChromeClient.progressObservable.hide() + override fun downloadRequests(): Observable = downloadsSubject.hide() + override var isForeground: Boolean = false set(value) { field = value @@ -148,7 +165,5 @@ class TabAdapter( } override fun freeze(): Bundle = latentInitializer?.bundle - ?: Bundle(ClassLoader.getSystemClassLoader()).also { - webView.saveState(it) - } + ?: Bundle(ClassLoader.getSystemClassLoader()).also(webView::saveState) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt index f972b0183..18640bc94 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -1,5 +1,6 @@ package acr.browser.lightning._browser2.tab +import acr.browser.lightning._browser2.download.PendingDownload import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.SslState import acr.browser.lightning.view.TabInitializer @@ -67,6 +68,8 @@ interface TabModel { fun loadingProgress(): Observable + fun downloadRequests(): Observable + // Lifecycle var isForeground: Boolean @@ -75,4 +78,5 @@ interface TabModel { fun freeze(): Bundle + } diff --git a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java index b55f4e059..3d4dced27 100644 --- a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java +++ b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java @@ -89,8 +89,12 @@ public DownloadHandler(DownloadsRepository downloadsRepository, * @param mimeType The mimeType of the content reported by the server * @param contentSize The size of the content */ - public void onDownloadStart(@NonNull Activity context, @NonNull UserPreferences manager, @NonNull String url, String userAgent, - @Nullable String contentDisposition, String mimeType, @NonNull String contentSize) { + public void onDownloadStart(@NonNull Activity context, + @NonNull UserPreferences manager, + @NonNull String url, String userAgent, + @Nullable String contentDisposition, + @Nullable String mimeType, + @NonNull String contentSize) { logger.log(TAG, "DOWNLOAD: Trying to download from URL: " + url); logger.log(TAG, "DOWNLOAD: Content disposition: " + contentDisposition); @@ -174,9 +178,12 @@ private static String encodePath(@NonNull String path) { * @param contentSize The size of the content */ /* package */ - private void onDownloadStartNoStream(@NonNull final Activity context, @NonNull UserPreferences preferences, + private void onDownloadStartNoStream(@NonNull final Activity context, + @NonNull UserPreferences preferences, @NonNull String url, String userAgent, - String contentDisposition, @Nullable String mimetype, @NonNull String contentSize) { + @Nullable String contentDisposition, + @Nullable String mimetype, + @NonNull String contentSize) { final String filename = URLUtil.guessFileName(url, contentDisposition, mimetype); // Check to see if we have an SDCard @@ -293,17 +300,19 @@ private void onDownloadStartNoStream(@NonNull final Activity context, @NonNull U } // save download in database - UIController browserActivity = (UIController) context; - LightningView view = browserActivity.getTabModel().getCurrentTab(); + if (context instanceof UIController) { + UIController browserActivity = (UIController) context; + LightningView view = browserActivity.getTabModel().getCurrentTab(); - if (view != null && !view.isIncognito()) { - downloadsRepository.addDownloadIfNotExists(new DownloadEntry(url, filename, contentSize)) - .subscribeOn(databaseScheduler) - .subscribe(aBoolean -> { - if (!aBoolean) { - logger.log(TAG, "error saving download to database"); - } - }); + if (view != null && !view.isIncognito()) { + downloadsRepository.addDownloadIfNotExists(new DownloadEntry(url, filename, contentSize)) + .subscribeOn(databaseScheduler) + .subscribe(aBoolean -> { + if (!aBoolean) { + logger.log(TAG, "error saving download to database"); + } + }); + } } } From 13c95c4413c426b1cf9e89aade5a09c819fc2c53 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sat, 23 Jul 2022 20:03:44 -0400 Subject: [PATCH 071/161] Upgrading gradle --- app/build.gradle | 24 ++++++++---------------- build.gradle | 3 +-- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 49efb5c4e..864504475 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,13 +2,11 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' -apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'jacoco' apply plugin: 'com.github.ben-manes.versions' android { compileSdkVersion project.targetSdkVersion - buildToolsVersion project.buildToolsVersion defaultConfig { minSdkVersion project.minSdkVersion @@ -74,32 +72,26 @@ android { versionCode 102 } } - - lintOptions { - abortOnError true - } - packagingOptions { - exclude '.readme' + resources { + excludes += ['.readme'] + } } + + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + lint { + abortOnError true + } } jacoco { toolVersion = '0.7.9' // See http://www.eclemma.org/jacoco/ } -dexcount { - includeClasses = false - includeFieldCount = false - format = "tree" - orderByMethodCount = true - verbose = false -} - dependencies { // multidex debug debugImplementation 'androidx.multidex:multidex:2.0.1' diff --git a/build.gradle b/build.gradle index bc772cd62..0d6d8e0c5 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' - classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.3' + classpath 'com.android.tools.build:gradle:7.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath 'com.github.ben-manes:gradle-versions-plugin:0.36.0' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1ea1674c7..a9b83217e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip From fdf68b3a84df7b2c34430be735a90d90a4348acd Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 26 Jul 2022 11:58:53 -0400 Subject: [PATCH 072/161] Updating gradle --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 0d6d8e0c5..cce635885 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:7.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath 'com.github.ben-manes:gradle-versions-plugin:0.36.0' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a9b83217e..80fd5b4e4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip From a0499f4e1eae239d2755bb8bd714ff903e01c946 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 26 Jul 2022 11:59:12 -0400 Subject: [PATCH 073/161] Fixing issue where tab close can be triggered on tabs that are in the process of closing --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index daf79c31c..bfd9c5ba1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -381,6 +381,9 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onTabClose(index: Int) { + if (index == -1) { + return + } val nextTab = viewState.tabs.nextSelected(index) val needToSelectNextTab = viewState.tabs[index].id == currentTab?.id From 04f430baaec0fc779936b58dcf1a2cf42547e20c Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 26 Jul 2022 12:15:37 -0400 Subject: [PATCH 074/161] Properly adding subscription to disposable --- .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index bfd9c5ba1..4d107b9b1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -135,7 +135,7 @@ class BrowserPresenter @Inject constructor( view = null compositeDisposable.dispose() - tabDisposable?.dispose() + tabDisposable.dispose() } /** @@ -934,10 +934,9 @@ class BrowserPresenter @Inject constructor( .flatMapCompletable { model.deleteTab(it.id) } .subscribeOn(mainScheduler) .subscribe() - BrowserContract.CloseTabEvent.CLOSE_ALL -> { - model.deleteAllTabs().subscribeOn(mainScheduler) + BrowserContract.CloseTabEvent.CLOSE_ALL -> + compositeDisposable += model.deleteAllTabs().subscribeOn(mainScheduler) .subscribeBy(onComplete = navigator::closeBrowser) - } } } From 52727388bcfb9d7d7d648ed2d41447274792a3f7 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 26 Jul 2022 12:17:53 -0400 Subject: [PATCH 075/161] Disable overscroll on webviews since it causes distortion of the webpages and looks weird --- .../java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt index 249e33384..33825e121 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt @@ -68,6 +68,7 @@ class WebViewFactory @Inject constructor( isScrollbarFadingEnabled = true isSaveEnabled = true + overScrollMode = View.OVER_SCROLL_NEVER setNetworkAvailable(true) settings.apply { From e69fca049a0d04fac55a2ff7fc3d3ce5a50914c8 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 26 Jul 2022 15:51:22 -0400 Subject: [PATCH 076/161] Adjusting dark theme search bar background --- app/src/main/res/drawable/card_bg.xml | 4 ++-- app/src/main/res/drawable/card_bg_elevate.xml | 4 ++-- app/src/main/res/layout/browser_activity.xml | 2 +- app/src/main/res/values/attr.xml | 7 ++++--- app/src/main/res/values/styles.xml | 3 +++ 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/drawable/card_bg.xml b/app/src/main/res/drawable/card_bg.xml index f758b275c..11ed27470 100644 --- a/app/src/main/res/drawable/card_bg.xml +++ b/app/src/main/res/drawable/card_bg.xml @@ -7,8 +7,8 @@ android:shape="rectangle" > - + - \ No newline at end of file + diff --git a/app/src/main/res/drawable/card_bg_elevate.xml b/app/src/main/res/drawable/card_bg_elevate.xml index 4f00a5950..7d5fad698 100644 --- a/app/src/main/res/drawable/card_bg_elevate.xml +++ b/app/src/main/res/drawable/card_bg_elevate.xml @@ -16,8 +16,8 @@ android:shape="rectangle" > - + - \ No newline at end of file + diff --git a/app/src/main/res/layout/browser_activity.xml b/app/src/main/res/layout/browser_activity.xml index 409374974..ee809993a 100644 --- a/app/src/main/res/layout/browser_activity.xml +++ b/app/src/main/res/layout/browser_activity.xml @@ -91,7 +91,7 @@ + @@ -15,8 +16,8 @@ - - - + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 733e5baea..bfd298e39 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -52,6 +52,7 @@ @color/icon_light_theme @color/state_color_icons_light + @color/white @color/black @color/hint_text_light_theme @@ -81,6 +82,7 @@ @color/icon_dark_theme @color/state_color_icons_dark + @color/divider_dark @color/white @color/hint_text_dark_theme @@ -109,6 +111,7 @@ @color/icon_dark_theme @color/state_color_icons_dark + @color/gray_dark @color/white @color/hint_text_dark_theme From ef6c45f4d575b9d1bb67bd015f459b08eacc335d Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 26 Jul 2022 17:52:16 -0400 Subject: [PATCH 077/161] Support the tools menu in the browser --- .../lightning/_browser2/BrowserActivity.kt | 28 +++++++++ .../lightning/_browser2/BrowserContract.kt | 2 + .../lightning/_browser2/BrowserPresenter.kt | 24 +++++++- .../_browser2/BrowserStateAdapter.kt | 4 ++ .../lightning/_browser2/di/Browser2Module.kt | 12 +++- .../_browser2/tab/DefaultUserAgent.kt | 9 +++ .../lightning/_browser2/tab/TabAdapter.kt | 19 +++++- .../lightning/_browser2/tab/TabModel.kt | 2 + .../lightning/_browser2/tab/TabsRepository.kt | 59 +++++++++++-------- .../preference/UserPreferencesExtensions.kt | 9 +++ 10 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/DefaultUserAgent.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index cd57a0314..cfce483a3 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -24,8 +24,12 @@ import acr.browser.lightning.di.injector import acr.browser.lightning.dialog.BrowserDialog import acr.browser.lightning.dialog.DialogItem import acr.browser.lightning.dialog.LightningDialogBuilder +import acr.browser.lightning.extensions.color +import acr.browser.lightning.extensions.drawable import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState +import acr.browser.lightning.utils.isSpecialUrl +import android.content.Context import android.content.Intent import android.os.Bundle import android.view.* @@ -469,6 +473,30 @@ class BrowserActivity : ThemableBrowserActivity() { binding.drawerLayout.closeDrawer(binding.tabDrawer) } + fun showToolsDialog(areAdsAllowed: Boolean, shouldShowAdBlockOption: Boolean) { + val whitelistString = if (areAdsAllowed) { + R.string.dialog_adblock_enable_for_site + } else { + R.string.dialog_adblock_disable_for_site + } + + BrowserDialog.showWithIcons( + this, getString(R.string.dialog_tools_title), + DialogItem( + icon = drawable(R.drawable.ic_action_desktop), + title = R.string.dialog_toggle_desktop, + onClick = presenter::onToggleDesktopAgent + ), + DialogItem( + icon = drawable(R.drawable.ic_block), + colorTint = color(R.color.error_red).takeIf { areAdsAllowed }, + title = whitelistString, + isConditionMet = shouldShowAdBlockOption, + onClick = presenter::onToggleAdBlocking + ) + ) + } + private fun ImageView.updateVisibilityForDrawable() { visibility = if (drawable == null) { View.GONE diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 583e544ab..0c5e2ed1c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -61,6 +61,8 @@ interface BrowserContract { fun openTabDrawer() fun closeTabDrawer() + + fun showToolsDialog(areAdsAllowed: Boolean, shouldShowAdBlockOption: Boolean) } interface Model { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 4d107b9b1..fa940aa66 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -10,6 +10,7 @@ import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration +import acr.browser.lightning.adblock.allowlist.AllowListModel import acr.browser.lightning.browser.SearchBoxModel import acr.browser.lightning.database.* import acr.browser.lightning.database.bookmark.BookmarkRepository @@ -62,7 +63,8 @@ class BrowserPresenter @Inject constructor( private val searchEngineProvider: SearchEngineProvider, @InitialUrl private val initialUrl: String?, private val uiConfiguration: UiConfiguration, - private val historyPageFactory: HistoryPageFactory + private val historyPageFactory: HistoryPageFactory, + private val allowListModel: AllowListModel ) { private var view: BrowserContract.View? = null @@ -610,6 +612,7 @@ class BrowserPresenter @Inject constructor( when (val item = viewState.bookmarks[index]) { is Bookmark.Entry -> view?.showBookmarkOptionsDialog(item) is Bookmark.Folder.Entry -> view?.showFolderOptionsDialog(item) + Bookmark.Folder.Root -> Unit // Root is not clickable } } @@ -617,7 +620,26 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onToolsClick() { + val currentUrl = currentTab?.url ?: return + view?.showToolsDialog( + areAdsAllowed = allowListModel.isUrlAllowedAds(currentUrl), + shouldShowAdBlockOption = !currentUrl.isSpecialUrl() + ) + } + + fun onToggleDesktopAgent() { + currentTab?.toggleDesktopAgent() + currentTab?.reload() + } + fun onToggleAdBlocking() { + val currentUrl = currentTab?.url ?: return + if (allowListModel.isUrlAllowedAds(currentUrl)) { + allowListModel.removeUrlFromAllowList(currentUrl) + } else { + allowListModel.addUrlToAllowList(currentUrl) + } + currentTab?.reload() } /** diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index a982424da..84155be4e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -119,4 +119,8 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.closeTabDrawer() } + override fun showToolsDialog(areAdsAllowed: Boolean, shouldShowAdBlockOption: Boolean) { + browserActivity.showToolsDialog(areAdsAllowed, shouldShowAdBlockOption) + } + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index d86a19011..416c5b5f7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -2,6 +2,8 @@ package acr.browser.lightning._browser2.di import acr.browser.lightning._browser2.BrowserContract import acr.browser.lightning._browser2.search.IntentExtractor +import acr.browser.lightning._browser2.tab.DefaultUserAgent +import acr.browser.lightning._browser2.tab.WebViewFactory import acr.browser.lightning._browser2.ui.BookmarkConfiguration import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration @@ -12,7 +14,9 @@ import acr.browser.lightning.browser.BrowserView import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.utils.IntentUtils import android.app.Activity +import android.app.Application import android.content.Intent +import android.webkit.WebSettings import dagger.Module import dagger.Provides import javax.inject.Provider @@ -40,7 +44,8 @@ class Browser2Module { fun providesInitialUrl( @InitialIntent initialIntent: Intent, intentExtractor: IntentExtractor - ): String? = (intentExtractor.extractUrlFromIntent(initialIntent) as? BrowserContract.Action.LoadUrl)?.url + ): String? = + (intentExtractor.extractUrlFromIntent(initialIntent) as? BrowserContract.Action.LoadUrl)?.url // TODO: auto inject intent utils @Provides @@ -62,4 +67,9 @@ class Browser2Module { } ) + @DefaultUserAgent + @Provides + fun providesDefaultUserAgent(application: Application): String = + WebSettings.getDefaultUserAgent(application) + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/DefaultUserAgent.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/DefaultUserAgent.kt new file mode 100644 index 000000000..078f65e6d --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/DefaultUserAgent.kt @@ -0,0 +1,9 @@ +package acr.browser.lightning._browser2.tab + +import javax.inject.Qualifier + +/** + * The default user agent marker. + */ +@Qualifier +annotation class DefaultUserAgent diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index c422c7dd7..b5f1fdb1d 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -1,6 +1,9 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning._browser2.download.PendingDownload +import acr.browser.lightning.constant.DESKTOP_USER_AGENT +import acr.browser.lightning.preference.UserPreferences +import acr.browser.lightning.preference.userAgent import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.SslState import acr.browser.lightning.view.FreezableBundleInitializer @@ -19,12 +22,15 @@ class TabAdapter( private val webView: WebView, private val requestHeaders: Map, private val tabWebViewClient: TabWebViewClient, - private val tabWebChromeClient: TabWebChromeClient + private val tabWebChromeClient: TabWebChromeClient, + private val userPreferences: UserPreferences, + private val defaultUserAgent: String ) : TabModel { private var latentInitializer: FreezableBundleInitializer? = null private var findInPageQuery: String? = null + private var toggleDesktop: Boolean = false private val downloadsSubject = PublishSubject.create() init { @@ -75,6 +81,17 @@ class TabAdapter( override fun canGoForwardChanges(): Observable = tabWebViewClient.goForwardObservable.hide() + override fun toggleDesktopAgent() { + if (!toggleDesktop) { + webView.settings.userAgentString = DESKTOP_USER_AGENT + } else { + webView.settings.userAgentString = userPreferences.userAgent(defaultUserAgent) + + } + + toggleDesktop = !toggleDesktop + } + override fun reload() { webView.reload() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt index 18640bc94..043561e3c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -30,6 +30,8 @@ interface TabModel { fun canGoForwardChanges(): Observable + fun toggleDesktopAgent() + fun reload() fun stopLoading() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index dbf52c6dd..a2571be14 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -6,6 +6,7 @@ import acr.browser.lightning.adblock.AdBlocker import acr.browser.lightning.adblock.allowlist.AllowListModel import acr.browser.lightning.di.DiskScheduler import acr.browser.lightning.di.MainScheduler +import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.view.* import io.reactivex.* import io.reactivex.subjects.PublishSubject @@ -22,7 +23,9 @@ class TabsRepository @Inject constructor( @DiskScheduler private val diskScheduler: Scheduler, @MainScheduler private val mainScheduler: Scheduler, private val bundleStore: BundleStore, - private val urlHandler: UrlHandler + private val urlHandler: UrlHandler, + private val userPreferences: UserPreferences, + @DefaultUserAgent private val defaultUserAgent: String ) : BrowserContract.Model { private var selectedTab: TabModel? = null @@ -48,24 +51,27 @@ class TabsRepository @Inject constructor( tabsListObservable.onNext(tabsList) } - override fun createTab(tabInitializer: TabInitializer): Single = Single.fromCallable { - val webView = webViewFactory.createWebView(isIncognito = false) - val headers = webViewFactory.createRequestHeaders() - tabPager.addTab(webView) - val tabAdapter = TabAdapter( - tabInitializer, - webView, - headers, - TabWebViewClient(adBlocker, allowListModel, urlHandler, headers), - TabWebChromeClient() - ) - - tabsList = tabsList + tabAdapter - - return@fromCallable tabAdapter - }.doOnSuccess { - tabsListObservable.onNext(tabsList) - } + override fun createTab(tabInitializer: TabInitializer): Single = + Single.fromCallable { + val webView = webViewFactory.createWebView(isIncognito = false) + val headers = webViewFactory.createRequestHeaders() + tabPager.addTab(webView) + val tabAdapter = TabAdapter( + tabInitializer, + webView, + headers, + TabWebViewClient(adBlocker, allowListModel, urlHandler, headers), + TabWebChromeClient(), + userPreferences, + defaultUserAgent + ) + + tabsList = tabsList + tabAdapter + + return@fromCallable tabAdapter + }.doOnSuccess { + tabsListObservable.onNext(tabsList) + } override fun selectTab(id: Int): TabModel { val selected = tabsList.forId(id) @@ -80,13 +86,14 @@ class TabsRepository @Inject constructor( override fun tabsListChanges(): Observable> = tabsListObservable.hide() - override fun initializeTabs(): Maybe> = Single.fromCallable(bundleStore::retrieve) - .flatMapObservable { Observable.fromIterable(it) } - .subscribeOn(diskScheduler) - .observeOn(mainScheduler) - .flatMapSingle(::createTab) - .toList() - .filter(MutableList::isNotEmpty) + override fun initializeTabs(): Maybe> = + Single.fromCallable(bundleStore::retrieve) + .flatMapObservable { Observable.fromIterable(it) } + .subscribeOn(diskScheduler) + .observeOn(mainScheduler) + .flatMapSingle(::createTab) + .toList() + .filter(MutableList::isNotEmpty) override fun freeze() { bundleStore.save(tabsList) diff --git a/app/src/main/java/acr/browser/lightning/preference/UserPreferencesExtensions.kt b/app/src/main/java/acr/browser/lightning/preference/UserPreferencesExtensions.kt index 8c8e6a24c..8ddcc17fe 100644 --- a/app/src/main/java/acr/browser/lightning/preference/UserPreferencesExtensions.kt +++ b/app/src/main/java/acr/browser/lightning/preference/UserPreferencesExtensions.kt @@ -16,3 +16,12 @@ fun UserPreferences.userAgent(application: Application): String = 4 -> userAgentString.takeIf(String::isNotEmpty) ?: " " else -> throw UnsupportedOperationException("Unknown userAgentChoice: $choice") } + +fun UserPreferences.userAgent(defaultUserAgent: String): String = + when (val choice = userAgentChoice) { + 1 -> defaultUserAgent + 2 -> DESKTOP_USER_AGENT + 3 -> MOBILE_USER_AGENT + 4 -> userAgentString.takeIf(String::isNotEmpty) ?: " " + else -> throw UnsupportedOperationException("Unknown userAgentChoice: $choice") + } From c6f218a38ce5cab359c5ce3acdcda83f831f81b9 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 26 Jul 2022 19:40:02 -0400 Subject: [PATCH 078/161] Fixe reading mode button not working --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index fa940aa66..dfdbce3ad 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -857,7 +857,8 @@ class BrowserPresenter @Inject constructor( * TODO */ fun onReadingModeClick() { - + currentTab?.url?.takeIf { !it.isSpecialUrl() } + ?.let(navigator::openReaderMode) } /** From e2ff9e5ee1cb2122b739477a83e703685e1d5718 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 11:23:11 -0400 Subject: [PATCH 079/161] Implementing incognito mode --- app/src/main/AndroidManifest.xml | 8 +- .../lightning/_browser2/BrowserActivity.kt | 38 ++++++-- .../lightning/_browser2/BrowserContract.kt | 2 + .../lightning/_browser2/BrowserNavigator.kt | 4 + .../lightning/_browser2/BrowserPresenter.kt | 30 ++++-- .../_browser2/DefaultBrowserActivity.kt | 14 +++ .../_browser2/IncognitoBrowserActivity.kt | 31 +++++++ .../_browser2/data/CookieAdministrator.kt | 13 +++ .../data/DefaultCookieAdministrator.kt | 24 +++++ .../data/IncognitoCookieAdministrator.kt | 30 ++++++ .../_browser2/di/Browser2BindsModule.kt | 3 - .../_browser2/di/Browser2Component.kt | 6 ++ .../lightning/_browser2/di/Browser2Module.kt | 54 +++++++++++ .../notification/DefaultTabCountNotifier.kt | 8 ++ .../notification/IncognitoTabCountNotifier.kt | 19 ++++ .../notification/TabCountNotifier.kt | 10 ++ .../lightning/_browser2/tab/TabsRepository.kt | 2 +- .../lightning/_browser2/tab/UrlHandler.kt | 28 ++++-- .../lightning/_browser2/tab/WebViewFactory.kt | 18 ++-- .../_browser2/tab/bundle/BundleStore.kt | 92 ++----------------- .../tab/bundle/DefaultBundleStore.kt | 91 ++++++++++++++++++ .../tab/bundle/IncognitoBundleStore.kt | 15 +++ .../notifications/IncognitoNotification.kt | 32 ++++--- 23 files changed, 432 insertions(+), 140 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/DefaultBrowserActivity.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/IncognitoBrowserActivity.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/data/CookieAdministrator.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/data/DefaultCookieAdministrator.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/data/IncognitoCookieAdministrator.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/notification/DefaultTabCountNotifier.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/notification/IncognitoTabCountNotifier.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/notification/TabCountNotifier.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/DefaultBundleStore.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/IncognitoBundleStore.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6123a3040..029224013 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -48,12 +48,11 @@ android:value="2.1"/> @@ -139,14 +138,13 @@ diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index cfce483a3..8f15e6860 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -28,14 +28,14 @@ import acr.browser.lightning.extensions.color import acr.browser.lightning.extensions.drawable import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState -import acr.browser.lightning.utils.isSpecialUrl -import android.content.Context import android.content.Intent import android.os.Bundle import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.AdapterView import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.annotation.MenuRes import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.LinearLayoutManager @@ -47,7 +47,7 @@ import javax.inject.Inject /** * Created by anthonycr on 9/11/20. */ -class BrowserActivity : ThemableBrowserActivity() { +abstract class BrowserActivity : ThemableBrowserActivity() { private lateinit var binding: BrowserActivityBinding private lateinit var tabsAdapter: ListAdapter @@ -80,6 +80,23 @@ class BrowserActivity : ThemableBrowserActivity() { @Inject internal lateinit var uiConfiguration: UiConfiguration + /** + * True if the activity is operating in incognito mode, false otherwise. + */ + abstract fun isIncognito(): Boolean + + /** + * Provide the menu used by the browser instance. + */ + @MenuRes + abstract fun menu(): Int + + /** + * Provide the home icon used by the browser instance. + */ + @DrawableRes + abstract fun homeIcon(): Int + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = BrowserActivityBinding.inflate(LayoutInflater.from(this)) @@ -93,6 +110,7 @@ class BrowserActivity : ThemableBrowserActivity() { .toolbarRoot(binding.uiLayout) .toolbar(binding.toolbarLayout) .initialIntent(intent) + .incognitoMode(isIncognito()) .build() .inject(this) @@ -113,8 +131,10 @@ class BrowserActivity : ThemableBrowserActivity() { } binding.homeImageView.isVisible = - uiConfiguration.tabConfiguration == TabConfiguration.DESKTOP - binding.tabCountView.isVisible = uiConfiguration.tabConfiguration == TabConfiguration.DRAWER + uiConfiguration.tabConfiguration == TabConfiguration.DESKTOP || isIncognito() + binding.homeImageView.setImageResource(homeIcon()) + binding.tabCountView.isVisible = + uiConfiguration.tabConfiguration == TabConfiguration.DRAWER && !isIncognito() if (uiConfiguration.tabConfiguration == TabConfiguration.DESKTOP) { binding.drawerLayout.setDrawerLockMode( @@ -157,7 +177,7 @@ class BrowserActivity : ThemableBrowserActivity() { presenter.onViewAttached(BrowserStateAdapter(this)) - val suggestionsAdapter = SuggestionsAdapter(this, isIncognito = false).apply { + val suggestionsAdapter = SuggestionsAdapter(this, isIncognito = isIncognito()).apply { onSuggestionInsertClick = { if (it is SearchSuggestion) { binding.search.setText(it.title) @@ -229,7 +249,7 @@ class BrowserActivity : ThemableBrowserActivity() { } override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.main, menu) + menuInflater.inflate(menu(), menu) return super.onCreateOptionsMenu(menu) } @@ -379,7 +399,7 @@ class BrowserActivity : ThemableBrowserActivity() { }, DialogItem( title = R.string.dialog_open_incognito_tab, - isConditionMet = this is BrowserActivity // TODO: Change for incognito + isConditionMet = !isIncognito() ) { presenter.onLinkLongPressEvent( longPress, @@ -413,7 +433,7 @@ class BrowserActivity : ThemableBrowserActivity() { }, DialogItem( title = R.string.dialog_open_incognito_tab, - isConditionMet = this is BrowserActivity // TODO: Change for incognito + isConditionMet = !isIncognito() ) { presenter.onImageLongPressEvent( longPress, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 0c5e2ed1c..b99e29e62 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -104,6 +104,8 @@ interface BrowserContract { fun download(pendingDownload: PendingDownload) fun backgroundBrowser() + + fun launchIncognito(url: String?) } enum class CloseTabEvent { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt index 69880029e..7a43e7a4c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserNavigator.kt @@ -70,6 +70,10 @@ class BrowserNavigator @Inject constructor( activity.moveTaskToBack(true) } + override fun launchIncognito(url: String?) { + IncognitoBrowserActivity.launch(activity, url) + } + companion object { private const val TAG = "BrowserNavigator" } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index dfdbce3ad..723c31c6c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -1,11 +1,14 @@ package acr.browser.lightning._browser2 +import acr.browser.lightning._browser2.data.CookieAdministrator import acr.browser.lightning._browser2.di.Browser2Scope +import acr.browser.lightning._browser2.di.IncognitoMode import acr.browser.lightning._browser2.di.InitialUrl import acr.browser.lightning._browser2.download.PendingDownload import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.keys.KeyCombo import acr.browser.lightning._browser2.menu.MenuSelection +import acr.browser.lightning._browser2.notification.TabCountNotifier import acr.browser.lightning._browser2.tab.TabModel import acr.browser.lightning._browser2.tab.TabViewState import acr.browser.lightning._browser2.ui.TabConfiguration @@ -32,7 +35,6 @@ import io.reactivex.Observable import io.reactivex.Scheduler import io.reactivex.Single import io.reactivex.disposables.CompositeDisposable -import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.subscribeBy @@ -64,7 +66,10 @@ class BrowserPresenter @Inject constructor( @InitialUrl private val initialUrl: String?, private val uiConfiguration: UiConfiguration, private val historyPageFactory: HistoryPageFactory, - private val allowListModel: AllowListModel + private val allowListModel: AllowListModel, + @IncognitoMode private val incognitoMode: Boolean, + private val cookieAdministrator: CookieAdministrator, + private val tabCountNotifier: TabCountNotifier ) { private var view: BrowserContract.View? = null @@ -97,6 +102,8 @@ class BrowserPresenter @Inject constructor( this.view = view view.updateState(viewState) + cookieAdministrator.adjustCookieSettings() + currentFolder = Bookmark.Folder.Root compositeDisposable += bookmarkRepository.bookmarksAndFolders(folder = Bookmark.Folder.Root) .subscribeOn(databaseScheduler) @@ -112,6 +119,8 @@ class BrowserPresenter @Inject constructor( allTabsDisposable.clear() list.subscribeToUpdates(allTabsDisposable) + + tabCountNotifier.notifyTabCountChange(list.size) } compositeDisposable += model.initializeTabs() @@ -281,7 +290,7 @@ class BrowserPresenter @Inject constructor( fun onMenuClick(menuSelection: MenuSelection) { when (menuSelection) { MenuSelection.NEW_TAB -> onNewTabClick() - MenuSelection.NEW_INCOGNITO_TAB -> TODO() + MenuSelection.NEW_INCOGNITO_TAB -> navigator.launchIncognito(url = null) MenuSelection.SHARE -> currentTab?.url?.takeIf { !it.isSpecialUrl() }?.let { navigator.sharePage(url = it, title = currentTab?.title) } @@ -409,7 +418,11 @@ class BrowserPresenter @Inject constructor( currentFolder != Bookmark.Folder.Root -> onBookmarkMenuClick() currentTab?.canGoBack() == true -> currentTab?.goBack() currentTab == null -> navigator.closeBrowser() - else -> navigator.backgroundBrowser() + else -> if (incognitoMode) { + navigator.closeBrowser() + } else { + navigator.backgroundBrowser() + } } } @@ -754,7 +767,7 @@ class BrowserPresenter @Inject constructor( createNewTabAndSelect(UrlInitializer(bookmark.url), shouldSelect = true) BrowserContract.BookmarkOptionEvent.BACKGROUND_TAB -> createNewTabAndSelect(UrlInitializer(bookmark.url), shouldSelect = false) - BrowserContract.BookmarkOptionEvent.INCOGNITO_TAB -> TODO() + BrowserContract.BookmarkOptionEvent.INCOGNITO_TAB -> navigator.launchIncognito(bookmark.url) BrowserContract.BookmarkOptionEvent.SHARE -> navigator.sharePage(url = bookmark.url, title = bookmark.title) BrowserContract.BookmarkOptionEvent.COPY_LINK -> @@ -837,7 +850,8 @@ class BrowserPresenter @Inject constructor( createNewTabAndSelect(UrlInitializer(historyEntry.url), shouldSelect = true) BrowserContract.HistoryOptionEvent.BACKGROUND_TAB -> createNewTabAndSelect(UrlInitializer(historyEntry.url), shouldSelect = false) - BrowserContract.HistoryOptionEvent.INCOGNITO_TAB -> TODO() + BrowserContract.HistoryOptionEvent.INCOGNITO_TAB -> + navigator.launchIncognito(historyEntry.url) BrowserContract.HistoryOptionEvent.SHARE -> navigator.sharePage(url = historyEntry.url, title = historyEntry.title) BrowserContract.HistoryOptionEvent.COPY_LINK -> navigator.copyPageLink(historyEntry.url) @@ -982,7 +996,7 @@ class BrowserPresenter @Inject constructor( shouldSelect = false ) } - BrowserContract.LinkLongPressEvent.INCOGNITO_TAB -> TODO() + BrowserContract.LinkLongPressEvent.INCOGNITO_TAB -> longPress.targetUrl?.let(navigator::launchIncognito) BrowserContract.LinkLongPressEvent.SHARE -> longPress.targetUrl?.let { navigator.sharePage(url = it, title = null) } BrowserContract.LinkLongPressEvent.COPY_LINK -> @@ -1009,7 +1023,7 @@ class BrowserPresenter @Inject constructor( shouldSelect = false ) } - BrowserContract.ImageLongPressEvent.INCOGNITO_TAB -> TODO() + BrowserContract.ImageLongPressEvent.INCOGNITO_TAB -> longPress.targetUrl?.let(navigator::launchIncognito) BrowserContract.ImageLongPressEvent.SHARE -> longPress.targetUrl?.let { navigator.sharePage(url = it, title = null) } BrowserContract.ImageLongPressEvent.COPY_LINK -> diff --git a/app/src/main/java/acr/browser/lightning/_browser2/DefaultBrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/DefaultBrowserActivity.kt new file mode 100644 index 000000000..03227c107 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/DefaultBrowserActivity.kt @@ -0,0 +1,14 @@ +package acr.browser.lightning._browser2 + +import acr.browser.lightning.R + +/** + * Created by anthonycr on 7/26/22. + */ +class DefaultBrowserActivity : BrowserActivity() { + override fun isIncognito(): Boolean = false + + override fun menu(): Int = R.menu.main + + override fun homeIcon(): Int = R.drawable.ic_action_home +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/IncognitoBrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/IncognitoBrowserActivity.kt new file mode 100644 index 000000000..a42f152c8 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/IncognitoBrowserActivity.kt @@ -0,0 +1,31 @@ +package acr.browser.lightning._browser2 + +import acr.browser.lightning.R +import android.app.Activity +import android.content.Intent +import androidx.core.net.toUri + +/** + * Created by anthonycr on 7/26/22. + */ +class IncognitoBrowserActivity : BrowserActivity() { + + override fun provideThemeOverride(): Int = R.style.Theme_DarkTheme + + override fun isIncognito(): Boolean = true + + override fun menu(): Int = R.menu.incognito + + override fun homeIcon(): Int = R.drawable.incognito_mode + + companion object { + fun intent(activity: Activity, url: String? = null): Intent = + Intent(activity, IncognitoBrowserActivity::class.java).apply { + data = url?.let(String::toUri) + } + + fun launch(activity: Activity, url: String?) { + activity.startActivity(intent(activity, url)) + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/data/CookieAdministrator.kt b/app/src/main/java/acr/browser/lightning/_browser2/data/CookieAdministrator.kt new file mode 100644 index 000000000..d500cfb2b --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/data/CookieAdministrator.kt @@ -0,0 +1,13 @@ +package acr.browser.lightning._browser2.data + +/** + * The administrator used to manage browser cookie preferences. + */ +interface CookieAdministrator { + + /** + * Adjust the cookie setting based on the current preferences. + */ + fun adjustCookieSettings() + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/data/DefaultCookieAdministrator.kt b/app/src/main/java/acr/browser/lightning/_browser2/data/DefaultCookieAdministrator.kt new file mode 100644 index 000000000..994e5477e --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/data/DefaultCookieAdministrator.kt @@ -0,0 +1,24 @@ +package acr.browser.lightning._browser2.data + +import acr.browser.lightning.preference.UserPreferences +import android.app.Activity +import android.os.Build +import android.webkit.CookieManager +import android.webkit.CookieSyncManager +import javax.inject.Inject + +/** + * The default cookie administrator that sets cookie preferences for the default browser instance. + */ +class DefaultCookieAdministrator @Inject constructor( + private val activity: Activity, + private val userPreferences: UserPreferences +) : CookieAdministrator { + override fun adjustCookieSettings() { + val cookieManager = CookieManager.getInstance() + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CookieSyncManager.createInstance(activity) + } + cookieManager.setAcceptCookie(userPreferences.cookiesEnabled) + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/data/IncognitoCookieAdministrator.kt b/app/src/main/java/acr/browser/lightning/_browser2/data/IncognitoCookieAdministrator.kt new file mode 100644 index 000000000..87c0ad101 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/data/IncognitoCookieAdministrator.kt @@ -0,0 +1,30 @@ +package acr.browser.lightning._browser2.data + +import acr.browser.lightning.Capabilities +import acr.browser.lightning.isSupported +import acr.browser.lightning.preference.UserPreferences +import android.app.Activity +import android.os.Build +import android.webkit.CookieManager +import android.webkit.CookieSyncManager +import javax.inject.Inject + +/** + * The cookie administrator used to set cookie preferences for the incognito instance. + */ +class IncognitoCookieAdministrator @Inject constructor( + private val activity: Activity, + private val userPreferences: UserPreferences +) : CookieAdministrator { + override fun adjustCookieSettings() { + val cookieManager = CookieManager.getInstance() + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CookieSyncManager.createInstance(activity) + } + if (Capabilities.FULL_INCOGNITO.isSupported) { + cookieManager.setAcceptCookie(userPreferences.cookiesEnabled) + } else { + cookieManager.setAcceptCookie(userPreferences.incognitoCookiesEnabled) + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt index cd1105558..705e7183c 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt @@ -19,9 +19,6 @@ interface Browser2BindsModule { @Binds fun bindsBrowserModel(tabsRepository: TabsRepository): BrowserContract.Model - @Binds - fun bindsHistoryRecord(defaultHistoryRecord: DefaultHistoryRecord): HistoryRecord - @Binds fun bindsFaviconImageLoader(faviconImageLoader: FaviconImageLoader): ImageLoader diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt index 40afd7aee..6fa476d83 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Component.kt @@ -35,6 +35,9 @@ interface Browser2Component { @BindsInstance fun initialIntent(@InitialIntent intent: Intent): Builder + @BindsInstance + fun incognitoMode(@IncognitoMode incognitoMode: Boolean): Builder + fun build(): Browser2Component } @@ -48,3 +51,6 @@ annotation class InitialIntent @Qualifier annotation class InitialUrl + +@Qualifier +annotation class IncognitoMode diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index 416c5b5f7..a7814b963 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -1,9 +1,20 @@ package acr.browser.lightning._browser2.di import acr.browser.lightning._browser2.BrowserContract +import acr.browser.lightning._browser2.data.CookieAdministrator +import acr.browser.lightning._browser2.data.DefaultCookieAdministrator +import acr.browser.lightning._browser2.history.DefaultHistoryRecord +import acr.browser.lightning._browser2.history.HistoryRecord +import acr.browser.lightning._browser2.history.NoOpHistoryRecord +import acr.browser.lightning._browser2.notification.DefaultTabCountNotifier +import acr.browser.lightning._browser2.notification.IncognitoTabCountNotifier +import acr.browser.lightning._browser2.notification.TabCountNotifier import acr.browser.lightning._browser2.search.IntentExtractor import acr.browser.lightning._browser2.tab.DefaultUserAgent import acr.browser.lightning._browser2.tab.WebViewFactory +import acr.browser.lightning._browser2.tab.bundle.BundleStore +import acr.browser.lightning._browser2.tab.bundle.DefaultBundleStore +import acr.browser.lightning._browser2.tab.bundle.IncognitoBundleStore import acr.browser.lightning._browser2.ui.BookmarkConfiguration import acr.browser.lightning._browser2.ui.TabConfiguration import acr.browser.lightning._browser2.ui.UiConfiguration @@ -17,6 +28,7 @@ import android.app.Activity import android.app.Application import android.content.Intent import android.webkit.WebSettings +import dagger.Binds import dagger.Module import dagger.Provides import javax.inject.Provider @@ -72,4 +84,46 @@ class Browser2Module { fun providesDefaultUserAgent(application: Application): String = WebSettings.getDefaultUserAgent(application) + + @Provides + fun providesHistoryRecord( + @IncognitoMode incognitoMode: Boolean, + defaultHistoryRecord: DefaultHistoryRecord + ): HistoryRecord = if (incognitoMode) { + NoOpHistoryRecord + } else { + defaultHistoryRecord + } + + @Provides + fun providesCookieAdministrator( + @IncognitoMode incognitoMode: Boolean, + defaultCookieAdministrator: DefaultCookieAdministrator, + incognitoCookieAdministrator: DefaultCookieAdministrator + ): CookieAdministrator = if (incognitoMode) { + incognitoCookieAdministrator + } else { + defaultCookieAdministrator + } + + @Provides + fun providesTabCountNotifier( + @IncognitoMode incognitoMode: Boolean, + incognitoTabCountNotifier: IncognitoTabCountNotifier + ): TabCountNotifier = if (incognitoMode) { + incognitoTabCountNotifier + } else { + DefaultTabCountNotifier + } + + @Provides + fun providesBundleStore( + @IncognitoMode incognitoMode: Boolean, + defaultBundleStore: DefaultBundleStore + ): BundleStore = if (incognitoMode) { + IncognitoBundleStore + } else { + defaultBundleStore + } + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/notification/DefaultTabCountNotifier.kt b/app/src/main/java/acr/browser/lightning/_browser2/notification/DefaultTabCountNotifier.kt new file mode 100644 index 000000000..84f0d1241 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/notification/DefaultTabCountNotifier.kt @@ -0,0 +1,8 @@ +package acr.browser.lightning._browser2.notification + +/** + * Created by anthonycr on 7/27/22. + */ +object DefaultTabCountNotifier : TabCountNotifier { + override fun notifyTabCountChange(total: Int) = Unit +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/notification/IncognitoTabCountNotifier.kt b/app/src/main/java/acr/browser/lightning/_browser2/notification/IncognitoTabCountNotifier.kt new file mode 100644 index 000000000..fbc46b89f --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/notification/IncognitoTabCountNotifier.kt @@ -0,0 +1,19 @@ +package acr.browser.lightning._browser2.notification + +import acr.browser.lightning.notifications.IncognitoNotification +import javax.inject.Inject + +/** + * Created by anthonycr on 7/27/22. + */ +class IncognitoTabCountNotifier @Inject constructor( + private val incognitoNotification: IncognitoNotification +) : TabCountNotifier { + override fun notifyTabCountChange(total: Int) { + if (total > 0) { + incognitoNotification.show(total) + } else { + incognitoNotification.hide() + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/notification/TabCountNotifier.kt b/app/src/main/java/acr/browser/lightning/_browser2/notification/TabCountNotifier.kt new file mode 100644 index 000000000..411e8631e --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/notification/TabCountNotifier.kt @@ -0,0 +1,10 @@ +package acr.browser.lightning._browser2.notification + +/** + * Created by anthonycr on 7/27/22. + */ +interface TabCountNotifier { + + fun notifyTabCountChange(total: Int) + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index a2571be14..a5491d806 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -53,7 +53,7 @@ class TabsRepository @Inject constructor( override fun createTab(tabInitializer: TabInitializer): Single = Single.fromCallable { - val webView = webViewFactory.createWebView(isIncognito = false) + val webView = webViewFactory.createWebView() val headers = webViewFactory.createRequestHeaders() tabPager.addTab(webView) val tabAdapter = TabAdapter( diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/UrlHandler.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/UrlHandler.kt index e52960f67..09674477d 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/UrlHandler.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/UrlHandler.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning.BuildConfig import acr.browser.lightning.R +import acr.browser.lightning._browser2.di.IncognitoMode import acr.browser.lightning.constant.FILE import acr.browser.lightning.extensions.snackbar import acr.browser.lightning.log.Logger @@ -27,7 +28,8 @@ import javax.inject.Inject class UrlHandler @Inject constructor( private val activity: Activity, private val logger: Logger, - private val intentUtils: IntentUtils + private val intentUtils: IntentUtils, + @IncognitoMode private val incognitoMode: Boolean ) { fun shouldOverrideLoading( @@ -42,11 +44,10 @@ class UrlHandler @Inject constructor( // return true // } - // TODO: handle incognito -// if (lightningView.isIncognito) { -// // If we are in incognito, immediately load, we don't want the url to leave the app -// return continueLoadingUrl(view, url, headers) -// } + if (incognitoMode) { + // If we are in incognito, immediately load, we don't want the url to leave the app + return continueLoadingUrl(view, url, headers) + } if (URLUtil.isAboutUrl(url)) { // If this is an about page, immediately load, we don't need to leave the app return continueLoadingUrl(view, url, headers) @@ -61,12 +62,17 @@ class UrlHandler @Inject constructor( } } - private fun continueLoadingUrl(webView: WebView, url: String, headers: Map): Boolean { + private fun continueLoadingUrl( + webView: WebView, + url: String, + headers: Map + ): Boolean { if (!URLUtil.isNetworkUrl(url) && !URLUtil.isFileUrl(url) && !URLUtil.isAboutUrl(url) && !URLUtil.isDataUrl(url) - && !URLUtil.isJavaScriptUrl(url)) { + && !URLUtil.isJavaScriptUrl(url) + ) { webView.stopLoading() return true } @@ -114,7 +120,11 @@ class UrlHandler @Inject constructor( val intent = Intent(Intent.ACTION_VIEW) intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION - val contentUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileprovider", file) + val contentUri = FileProvider.getUriForFile( + activity, + BuildConfig.APPLICATION_ID + ".fileprovider", + file + ) intent.setDataAndType(contentUri, newMimeType) try { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt index 33825e121..ff9ca4a15 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/WebViewFactory.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning.Capabilities +import acr.browser.lightning._browser2.di.IncognitoMode import acr.browser.lightning._browser2.view.CompositeTouchListener import acr.browser.lightning.isSupported import acr.browser.lightning.log.Logger @@ -23,7 +24,8 @@ import javax.inject.Inject class WebViewFactory @Inject constructor( private val activity: Activity, private val logger: Logger, - private val userPreferences: UserPreferences + private val userPreferences: UserPreferences, + @IncognitoMode private val incognitoMode: Boolean ) { fun createRequestHeaders(): Map { @@ -51,7 +53,7 @@ class WebViewFactory @Inject constructor( return requestHeaders } - fun createWebView(isIncognito: Boolean): WebView = WebView(activity).apply { + fun createWebView(): WebView = WebView(activity).apply { id = View.generateViewId() tag = CompositeTouchListener().also(::setOnTouchListener) isFocusableInTouchMode = true @@ -74,13 +76,13 @@ class WebViewFactory @Inject constructor( settings.apply { mediaPlaybackRequiresUserGesture = true - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && !isIncognito) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && !incognitoMode) { mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW } - if (!isIncognito || Capabilities.FULL_INCOGNITO.isSupported) { + if (!incognitoMode || Capabilities.FULL_INCOGNITO.isSupported) { domStorageEnabled = true setAppCacheEnabled(true) databaseEnabled = true @@ -105,7 +107,7 @@ class WebViewFactory @Inject constructor( setGeolocationDatabasePath(activity.getDir("geolocation", 0).path) } - updateForPreferences(userPreferences, isIncognito) + updateForPreferences(userPreferences, incognitoMode) } @SuppressLint("SetJavaScriptEnabled") @@ -171,8 +173,10 @@ class WebViewFactory @Inject constructor( } if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - CookieManager.getInstance().setAcceptThirdPartyCookies(this, - !userPreferences.blockThirdPartyCookiesEnabled) + CookieManager.getInstance().setAcceptThirdPartyCookies( + this, + !userPreferences.blockThirdPartyCookiesEnabled + ) } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt index f51d5526d..231a5c34b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/BundleStore.kt @@ -1,99 +1,19 @@ package acr.browser.lightning._browser2.tab.bundle -import acr.browser.lightning.R import acr.browser.lightning._browser2.tab.TabModel -import acr.browser.lightning.browser.TabsManager -import acr.browser.lightning.di.DiskScheduler -import acr.browser.lightning.utils.* -import acr.browser.lightning.view.* -import android.app.Application -import android.os.Bundle -import io.reactivex.Scheduler -import javax.inject.Inject +import acr.browser.lightning.view.TabInitializer /** - * Created by anthonycr on 9/20/20. + * Created by anthonycr on 7/27/22. */ -class BundleStore @Inject constructor( - private val application: Application, - private val bookmarkPageInitializer: BookmarkPageInitializer, - private val homePageInitializer: HomePageInitializer, - private val downloadPageInitializer: DownloadPageInitializer, - private val historyPageInitializer: HistoryPageInitializer, - @DiskScheduler private val diskScheduler: Scheduler -) { +interface BundleStore { - /** - * TODO - */ - fun save(tabs: List) { - val outState = Bundle(ClassLoader.getSystemClassLoader()) - - tabs.withIndex().forEach { (index, tab) -> - if (!tab.url.isSpecialUrl()) { - outState.putBundle(BUNDLE_KEY + index, tab.freeze()) - outState.putString(TAB_TITLE_KEY + index, tab.title) - } else { - outState.putBundle(BUNDLE_KEY + index, Bundle().apply { - putString(URL_KEY, tab.url) - }) - } - } - - FileUtils.writeBundleToStorage(application, outState, BUNDLE_STORAGE) - .subscribeOn(diskScheduler) - .subscribe() - } + fun save(tabs: List) - /** - * TODO - */ - fun retrieve(): List = - FileUtils.readBundleFromStorage(application, BUNDLE_STORAGE)?.let { bundle -> - bundle.keySet() - .filter { it.startsWith(BUNDLE_KEY) } - .mapNotNull { bundleKey -> - bundle.getBundle(bundleKey)?.let { - Pair( - it, - bundle.getString(TAB_TITLE_KEY + bundleKey.extractNumberFromEnd()) - ) - } - } - }?.map { (bundle, title) -> - return@map bundle.getString(URL_KEY)?.let { url -> - when { - url.isBookmarkUrl() -> bookmarkPageInitializer - url.isDownloadsUrl() -> downloadPageInitializer - url.isStartPageUrl() -> homePageInitializer - url.isHistoryUrl() -> historyPageInitializer - else -> homePageInitializer - } - } ?: FreezableBundleInitializer(bundle, title - ?: application.getString(R.string.tab_frozen)) - } ?: emptyList() + fun retrieve(): List /** * Synchronously delete all stored tabs. */ - fun deleteAll() { - FileUtils.deleteBundleInStorage(application, BUNDLE_STORAGE) - } - - private fun String.extractNumberFromEnd(): String { - val underScore = lastIndexOf('_') - return if (underScore in 0 until length) { - substring(underScore + 1) - } else { - "" - } - } - - companion object { - private const val TAG = "TabsRepository" - private const val BUNDLE_KEY = "WEBVIEW_" - private const val TAB_TITLE_KEY = "TITLE_" - private const val URL_KEY = "URL_KEY" - private const val BUNDLE_STORAGE = "SAVED_TABS.parcel" - } + fun deleteAll() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/DefaultBundleStore.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/DefaultBundleStore.kt new file mode 100644 index 000000000..b721c85bd --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/DefaultBundleStore.kt @@ -0,0 +1,91 @@ +package acr.browser.lightning._browser2.tab.bundle + +import acr.browser.lightning.R +import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning.browser.TabsManager +import acr.browser.lightning.di.DiskScheduler +import acr.browser.lightning.utils.* +import acr.browser.lightning.view.* +import android.app.Application +import android.os.Bundle +import io.reactivex.Scheduler +import javax.inject.Inject + +/** + * Created by anthonycr on 9/20/20. + */ +class DefaultBundleStore @Inject constructor( + private val application: Application, + private val bookmarkPageInitializer: BookmarkPageInitializer, + private val homePageInitializer: HomePageInitializer, + private val downloadPageInitializer: DownloadPageInitializer, + private val historyPageInitializer: HistoryPageInitializer, + @DiskScheduler private val diskScheduler: Scheduler +) : BundleStore { + + override fun save(tabs: List) { + val outState = Bundle(ClassLoader.getSystemClassLoader()) + + tabs.withIndex().forEach { (index, tab) -> + if (!tab.url.isSpecialUrl()) { + outState.putBundle(BUNDLE_KEY + index, tab.freeze()) + outState.putString(TAB_TITLE_KEY + index, tab.title) + } else { + outState.putBundle(BUNDLE_KEY + index, Bundle().apply { + putString(URL_KEY, tab.url) + }) + } + } + + FileUtils.writeBundleToStorage(application, outState, BUNDLE_STORAGE) + .subscribeOn(diskScheduler) + .subscribe() + } + + override fun retrieve(): List = + FileUtils.readBundleFromStorage(application, BUNDLE_STORAGE)?.let { bundle -> + bundle.keySet() + .filter { it.startsWith(BUNDLE_KEY) } + .mapNotNull { bundleKey -> + bundle.getBundle(bundleKey)?.let { + Pair( + it, + bundle.getString(TAB_TITLE_KEY + bundleKey.extractNumberFromEnd()) + ) + } + } + }?.map { (bundle, title) -> + return@map bundle.getString(URL_KEY)?.let { url -> + when { + url.isBookmarkUrl() -> bookmarkPageInitializer + url.isDownloadsUrl() -> downloadPageInitializer + url.isStartPageUrl() -> homePageInitializer + url.isHistoryUrl() -> historyPageInitializer + else -> homePageInitializer + } + } ?: FreezableBundleInitializer( + bundle, title ?: application.getString(R.string.tab_frozen) + ) + } ?: emptyList() + + override fun deleteAll() { + FileUtils.deleteBundleInStorage(application, BUNDLE_STORAGE) + } + + private fun String.extractNumberFromEnd(): String { + val underScore = lastIndexOf('_') + return if (underScore in 0 until length) { + substring(underScore + 1) + } else { + "" + } + } + + companion object { + private const val TAG = "TabsRepository" + private const val BUNDLE_KEY = "WEBVIEW_" + private const val TAB_TITLE_KEY = "TITLE_" + private const val URL_KEY = "URL_KEY" + private const val BUNDLE_STORAGE = "SAVED_TABS.parcel" + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/IncognitoBundleStore.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/IncognitoBundleStore.kt new file mode 100644 index 000000000..9e2e431f7 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/bundle/IncognitoBundleStore.kt @@ -0,0 +1,15 @@ +package acr.browser.lightning._browser2.tab.bundle + +import acr.browser.lightning._browser2.tab.TabModel +import acr.browser.lightning.view.TabInitializer + +/** + * A bundle store implementation that no-ops for for incognito mode. + */ +object IncognitoBundleStore : BundleStore { + override fun save(tabs: List) = Unit + + override fun retrieve(): List = emptyList() + + override fun deleteAll() = Unit +} diff --git a/app/src/main/java/acr/browser/lightning/notifications/IncognitoNotification.kt b/app/src/main/java/acr/browser/lightning/notifications/IncognitoNotification.kt index 8655a8be1..44629a5ab 100644 --- a/app/src/main/java/acr/browser/lightning/notifications/IncognitoNotification.kt +++ b/app/src/main/java/acr/browser/lightning/notifications/IncognitoNotification.kt @@ -1,23 +1,24 @@ package acr.browser.lightning.notifications -import acr.browser.lightning.IncognitoActivity import acr.browser.lightning.R +import acr.browser.lightning._browser2.IncognitoBrowserActivity import acr.browser.lightning.utils.ThemeUtils import android.annotation.TargetApi +import android.app.Activity import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent -import android.content.Context import android.os.Build import androidx.core.app.NotificationCompat +import javax.inject.Inject /** * A notification helper that displays the current number of tabs open in a notification as a * warning. When the notification is pressed, the incognito browser will open. */ -class IncognitoNotification( - private val context: Context, +class IncognitoNotification @Inject constructor( + private val activity: Activity, private val notificationManager: NotificationManager ) { @@ -32,8 +33,9 @@ class IncognitoNotification( @TargetApi(Build.VERSION_CODES.O) private fun createNotificationChannel() { - val channelName = context.getString(R.string.notification_incognito_running_description) - val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW) + val channelName = activity.getString(R.string.notification_incognito_running_description) + val channel = + NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW) channel.enableVibration(false) notificationManager.createNotificationChannel(channel) } @@ -46,15 +48,21 @@ class IncognitoNotification( */ fun show(number: Int) { require(number > 0) - val incognitoIntent = IncognitoActivity.createIntent(context) + val incognitoIntent = IncognitoBrowserActivity.intent(activity) - val incognitoNotification = NotificationCompat.Builder(context, channelId) + val incognitoNotification = NotificationCompat.Builder(activity, channelId) .setSmallIcon(R.drawable.ic_notification_incognito) - .setContentTitle(context.resources.getQuantityString(R.plurals.notification_incognito_running_title, number, number)) - .setContentIntent(PendingIntent.getActivity(context, 0, incognitoIntent, 0)) - .setContentText(context.getString(R.string.notification_incognito_running_message)) + .setContentTitle( + activity.resources.getQuantityString( + R.plurals.notification_incognito_running_title, + number, + number + ) + ) + .setContentIntent(PendingIntent.getActivity(activity, 0, incognitoIntent, 0)) + .setContentText(activity.getString(R.string.notification_incognito_running_message)) .setAutoCancel(false) - .setColor(ThemeUtils.getAccentColor(context)) + .setColor(ThemeUtils.getAccentColor(activity)) .setOngoing(true) .build() From e1d3d5864a41978ebc620c1f26849da2e43007ca Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 11:45:29 -0400 Subject: [PATCH 080/161] Need to support going home in certain configurations --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 723c31c6c..54de7ba2b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -881,6 +881,8 @@ class BrowserPresenter @Inject constructor( fun onTabCountViewClick() { if (uiConfiguration.tabConfiguration == TabConfiguration.DRAWER) { view?.openTabDrawer() + } else { + // TODO: GO HOME } } From 853c3fc18e287897b2566c3844328575a8a87836 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 14:59:01 -0400 Subject: [PATCH 081/161] Fix issue where new tabs would be opened before old tabs --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 54de7ba2b..e2d516825 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -125,7 +125,7 @@ class BrowserPresenter @Inject constructor( compositeDisposable += model.initializeTabs() .observeOn(mainScheduler) - .mergeWith( + .concatWith( Maybe.fromCallable { initialUrl } .flatMapSingleElement { model.createTab(UrlInitializer(it)) } .map(::listOf) From a00a9d54afb0251ea204790454a89178ff44d596 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 16:13:23 -0400 Subject: [PATCH 082/161] Fix UI flashing when deleting a tab --- .../browser/lightning/_browser2/tab/TabPager.kt | 10 +++++++--- .../_browser2/view/WebViewScrollCoordinator.kt | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt index de940ff64..26a11fd0b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt @@ -6,6 +6,7 @@ import acr.browser.lightning._browser2.view.WebViewScrollCoordinator import android.view.ViewGroup import android.webkit.WebView import android.widget.FrameLayout +import androidx.core.view.children import targetUrl.LongPress import javax.inject.Inject @@ -24,7 +25,7 @@ class TabPager @Inject constructor( var longPressListener: ((id: Int, longPress: LongPress) -> Unit)? = null fun selectTab(id: Int) { - container.removeAllViews() + container.removeWebViews() val webView = webViews.forId(id) container.addView( webView, @@ -32,7 +33,6 @@ class TabPager @Inject constructor( ViewGroup.LayoutParams.MATCH_PARENT ) - // TODO: coordinator adds views to the container, which can result in UI flashes. webViewScrollCoordinator.configure(webView) webViewLongPressHandler.configure(webView, onLongClick = { longPressListener?.invoke(id, it) @@ -40,13 +40,17 @@ class TabPager @Inject constructor( } fun clearTab() { - container.removeAllViews() + container.removeWebViews() } fun addTab(webView: WebView) { webViews.add(webView) } + private fun FrameLayout.removeWebViews() { + children.filterIsInstance().forEach(container::removeView) + } + private fun List.forId(id: Int): WebView = requireNotNull(find { it.id == id }) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt index 7494fd50a..106e4f14e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt @@ -60,7 +60,10 @@ class WebViewScrollCoordinator @Inject constructor( val height = toolbar.height if (toolbar.translationY > -0.01f) { val hideAnimation = object : Animation() { - override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + override fun applyTransformation( + interpolatedTime: Float, + t: Transformation + ) { val trans = interpolatedTime * height toolbar.translationY = -trans webView.translationY = height - trans @@ -82,7 +85,10 @@ class WebViewScrollCoordinator @Inject constructor( val totalHeight = height if (toolbar.translationY < -(height - 0.01f)) { val show = object : Animation() { - override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + override fun applyTransformation( + interpolatedTime: Float, + t: Transformation + ) { val trans = interpolatedTime * totalHeight toolbar.translationY = trans - totalHeight webView.translationY = trans @@ -153,7 +159,12 @@ class WebViewScrollCoordinator @Inject constructor( var toggleListener: ToggleListener? = null - override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { + override fun onFling( + e1: MotionEvent, + e2: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { val power = (velocityY * 100 / maxFling).toInt() if (power < -10) { toggleListener?.hideToolbar() From def1192a14697bc1ede1227d9607628a81d61010 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 16:14:25 -0400 Subject: [PATCH 083/161] Properly handle the back button in the browser Mostly implement proper back button handling. Currently missing handling for search bar focus and custom view hiding --- .../lightning/_browser2/BrowserActivity.kt | 21 +++++- .../lightning/_browser2/BrowserPresenter.kt | 66 +++++++++++++++---- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 8f15e6860..921e2f167 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -114,6 +114,25 @@ abstract class BrowserActivity : ThemableBrowserActivity() { .build() .inject(this) + binding.drawerLayout.addDrawerListener(object : DrawerLayout.SimpleDrawerListener() { + + override fun onDrawerOpened(drawerView: View) { + if (drawerView == binding.tabDrawer) { + presenter.onTabDrawerMoved(isOpen = true) + } else if (drawerView == binding.bookmarkDrawer) { + presenter.onBookmarkDrawerMoved(isOpen = true) + } + } + + override fun onDrawerClosed(drawerView: View) { + if (drawerView == binding.tabDrawer) { + presenter.onTabDrawerMoved(isOpen = false) + } else if (drawerView == binding.bookmarkDrawer) { + presenter.onBookmarkDrawerMoved(isOpen = false) + } + } + }) + binding.bookmarkDrawer.layoutParams = (binding.bookmarkDrawer.layoutParams as DrawerLayout.LayoutParams).apply { gravity = when (uiConfiguration.bookmarkConfiguration) { @@ -245,7 +264,7 @@ abstract class BrowserActivity : ThemableBrowserActivity() { } override fun onBackPressed() { - presenter.onBackClick() + presenter.onNavigateBack() } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index e2d516825..47598479d 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -67,7 +67,6 @@ class BrowserPresenter @Inject constructor( private val uiConfiguration: UiConfiguration, private val historyPageFactory: HistoryPageFactory, private val allowListModel: AllowListModel, - @IncognitoMode private val incognitoMode: Boolean, private val cookieAdministrator: CookieAdministrator, private val tabCountNotifier: TabCountNotifier ) { @@ -89,7 +88,10 @@ class BrowserPresenter @Inject constructor( ) private var currentTab: TabModel? = null private var currentFolder: Bookmark.Folder = Bookmark.Folder.Root + private var isTabDrawerOpen = false + private var isBookmarkDrawerOpen = false private var isSearchViewFocused = false + private var tabIdOpenedFromAction = -1 private val compositeDisposable = CompositeDisposable() private val allTabsDisposable = CompositeDisposable() @@ -154,6 +156,7 @@ class BrowserPresenter @Inject constructor( */ fun onViewHidden() { model.freeze() + tabIdOpenedFromAction = -1 } private fun TabModel.asViewState(): TabViewState = TabViewState( @@ -264,7 +267,8 @@ class BrowserPresenter @Inject constructor( when (action) { is BrowserContract.Action.LoadUrl -> createNewTabAndSelect( tabInitializer = UrlInitializer(action.url), - shouldSelect = true + shouldSelect = true, + markAsOpenedFromAction = true ) BrowserContract.Action.Panic -> panicClean() } @@ -324,12 +328,19 @@ class BrowserPresenter @Inject constructor( } } - private fun createNewTabAndSelect(tabInitializer: TabInitializer, shouldSelect: Boolean) { + private fun createNewTabAndSelect( + tabInitializer: TabInitializer, + shouldSelect: Boolean, + markAsOpenedFromAction: Boolean = false + ) { compositeDisposable += model.createTab(tabInitializer) .observeOn(mainScheduler) .subscribe { tab -> if (shouldSelect) { selectTab(model.selectTab(tab.id)) + if (markAsOpenedFromAction) { + tabIdOpenedFromAction = tab.id + } } } } @@ -397,7 +408,8 @@ class BrowserPresenter @Inject constructor( } val nextTab = viewState.tabs.nextSelected(index) - val needToSelectNextTab = viewState.tabs[index].id == currentTab?.id + val currentTabId = currentTab?.id + val needToSelectNextTab = viewState.tabs[index].id == currentTabId compositeDisposable += model.deleteTab(viewState.tabs[index].id) .observeOn(mainScheduler) @@ -405,29 +417,55 @@ class BrowserPresenter @Inject constructor( if (needToSelectNextTab) { nextTab?.id?.let { selectTab(model.selectTab(it)) - } ?: selectTab(tabModel = null) + if (tabIdOpenedFromAction == currentTabId) { + tabIdOpenedFromAction = -1 + navigator.backgroundBrowser() + } + } ?: run { + selectTab(tabModel = null) + navigator.closeBrowser() + } + } } } + fun onTabDrawerMoved(isOpen: Boolean) { + isTabDrawerOpen = isOpen + } + + fun onBookmarkDrawerMoved(isOpen: Boolean) { + isBookmarkDrawerOpen = isOpen + } + /** - * TODO + * Called when the user clicks on the device back button or swipes to go back. Differentiated + * from [onBackClick] which is called when the user presses the browser's back button. */ - fun onBackClick() { + fun onNavigateBack() { when { - currentFolder != Bookmark.Folder.Root -> onBookmarkMenuClick() - currentTab?.canGoBack() == true -> currentTab?.goBack() - currentTab == null -> navigator.closeBrowser() - else -> if (incognitoMode) { - navigator.closeBrowser() + isTabDrawerOpen -> view?.closeTabDrawer() + isBookmarkDrawerOpen -> if (currentFolder != Bookmark.Folder.Root) { + onBookmarkMenuClick() } else { - navigator.backgroundBrowser() + view?.closeBookmarkDrawer() } + currentTab?.canGoBack() == true -> currentTab?.goBack() + currentTab?.canGoBack() == false -> onTabClose(viewState.tabs.indexOfFirst { it.id == currentTab?.id }) } } /** - * TODO + * Called when the user presses the browser's back button. + */ + fun onBackClick() { + if (currentTab?.canGoBack() == true) { + currentTab?.goBack() + } + } + + /** + * Called when the user presses the browser's forward button. */ fun onForwardClick() { if (currentTab?.canGoForward() == true) { From db494dcdc1bfa894f7806f0dae1634822038bc6a Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 17:32:37 -0400 Subject: [PATCH 084/161] Hide the keyboard when a search suggestion is clicked --- .../main/java/acr/browser/lightning/_browser2/BrowserActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 921e2f167..def0632d7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -210,6 +210,7 @@ abstract class BrowserActivity : ThemableBrowserActivity() { binding.search.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> binding.search.clearFocus() presenter.onSearchSuggestionClicked(suggestionsAdapter.getItem(position) as WebPage) + inputMethodManager.hideSoftInputFromWindow(binding.root.windowToken, 0) } binding.search.setAdapter(suggestionsAdapter) val searchListener = SearchListener( From 2fcc624de8cea08d13394064c5e1106f01d54b57 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 18:19:22 -0400 Subject: [PATCH 085/161] Close tab drawers whenever the current tab is changed --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 47598479d..acefb1094 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -203,6 +203,8 @@ class BrowserPresenter @Inject constructor( ) ) + view?.closeTabDrawer() + tabDisposable.dispose() tabDisposable = CompositeDisposable() tabDisposable += Observables.combineLatest( @@ -376,7 +378,6 @@ class BrowserPresenter @Inject constructor( */ fun onTabClick(index: Int) { selectTab(model.selectTab(viewState.tabs[index].id)) - view?.closeTabDrawer() } /** From a0236fd0106b58db44b81745900c63ed36c4784b Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 18:24:11 -0400 Subject: [PATCH 086/161] Go home when the home button his hit in desktop configuration mode --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index acefb1094..028f77108 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -2,7 +2,6 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.data.CookieAdministrator import acr.browser.lightning._browser2.di.Browser2Scope -import acr.browser.lightning._browser2.di.IncognitoMode import acr.browser.lightning._browser2.di.InitialUrl import acr.browser.lightning._browser2.download.PendingDownload import acr.browser.lightning._browser2.history.HistoryRecord @@ -921,7 +920,7 @@ class BrowserPresenter @Inject constructor( if (uiConfiguration.tabConfiguration == TabConfiguration.DRAWER) { view?.openTabDrawer() } else { - // TODO: GO HOME + currentTab?.loadFromInitializer(homePageInitializer) } } From f6a7ef2ac99214fb83caa0b3467d349341b4b12e Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 22:13:58 -0400 Subject: [PATCH 087/161] Hide keyboard when the webview is focused --- .../lightning/_browser2/view/WebViewScrollCoordinator.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt index 106e4f14e..310eee438 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt @@ -8,6 +8,7 @@ import android.app.Activity import android.view.* import android.view.animation.Animation import android.view.animation.Transformation +import android.view.inputmethod.InputMethodManager import android.webkit.WebView import android.widget.FrameLayout import android.widget.LinearLayout @@ -21,7 +22,8 @@ class WebViewScrollCoordinator @Inject constructor( private val browserFrame: FrameLayout, private val toolbarRoot: LinearLayout, private val toolbar: View, - private val userPreferences: UserPreferences + private val userPreferences: UserPreferences, + private val inputMethodManager: InputMethodManager ) { private val gestureListener: CustomGestureListener = CustomGestureListener( @@ -34,6 +36,11 @@ class WebViewScrollCoordinator @Inject constructor( * TODO */ fun configure(webView: WebView) { + webView.setOnFocusChangeListener { v, hasFocus -> + if (hasFocus) { + inputMethodManager.hideSoftInputFromWindow(v.windowToken, 0) + } + } if (userPreferences.fullScreenEnabled) { (toolbar.parent as ViewGroup?)?.removeView(toolbar) From eb6368d70d326440ce5a3ed782b7a9537066ca7d Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 27 Jul 2022 22:37:22 -0400 Subject: [PATCH 088/161] Properly show the toolbar when switching tabs and when the url changes --- .../lightning/_browser2/BrowserActivity.kt | 4 ++++ .../lightning/_browser2/BrowserContract.kt | 2 ++ .../lightning/_browser2/BrowserPresenter.kt | 7 ++++++ .../_browser2/BrowserStateAdapter.kt | 4 ++++ .../lightning/_browser2/tab/TabPager.kt | 4 ++++ .../view/WebViewScrollCoordinator.kt | 24 +++++++++++++++---- 6 files changed, 40 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index def0632d7..8d83f43fc 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -513,6 +513,10 @@ abstract class BrowserActivity : ThemableBrowserActivity() { binding.drawerLayout.closeDrawer(binding.tabDrawer) } + fun showToolbar() { + tabPager.showToolbar() + } + fun showToolsDialog(areAdsAllowed: Boolean, shouldShowAdBlockOption: Boolean) { val whitelistString = if (areAdsAllowed) { R.string.dialog_adblock_enable_for_site diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index b99e29e62..3a3a8bae7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -62,6 +62,8 @@ interface BrowserContract { fun closeTabDrawer() + fun showToolbar() + fun showToolsDialog(areAdsAllowed: Boolean, shouldShowAdBlockOption: Boolean) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 028f77108..99abf1c7f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -202,6 +202,7 @@ class BrowserPresenter @Inject constructor( ) ) + view?.showToolbar() view?.closeTabDrawer() tabDisposable.dispose() @@ -234,9 +235,15 @@ class BrowserPresenter @Inject constructor( ) }.subscribeOn(mainScheduler) .subscribe { view.updateState(it) } + tabDisposable += tab.downloadRequests() .subscribeOn(mainScheduler) .subscribeBy(onNext = navigator::download) + + tabDisposable += tab.urlChanges() + .distinctUntilChanged() + .subscribeOn(mainScheduler) + .subscribeBy { view?.showToolbar() } } private fun List.subscribeToUpdates(compositeDisposable: CompositeDisposable) { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 84155be4e..46184139f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -119,6 +119,10 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.closeTabDrawer() } + override fun showToolbar() { + browserActivity.showToolbar() + } + override fun showToolsDialog(areAdsAllowed: Boolean, shouldShowAdBlockOption: Boolean) { browserActivity.showToolsDialog(areAdsAllowed, shouldShowAdBlockOption) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt index 26a11fd0b..e8962ebfe 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabPager.kt @@ -47,6 +47,10 @@ class TabPager @Inject constructor( webViews.add(webView) } + fun showToolbar() { + webViewScrollCoordinator.showToolbar() + } + private fun FrameLayout.removeWebViews() { children.filterIsInstance().forEach(container::removeView) } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt index 310eee438..62991d493 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/view/WebViewScrollCoordinator.kt @@ -32,6 +32,8 @@ class WebViewScrollCoordinator @Inject constructor( private val touchListener = TouchListener(GestureDetector(activity, gestureListener)) + private var currentToggleListener: ToggleListener? = null + /** * TODO */ @@ -42,23 +44,34 @@ class WebViewScrollCoordinator @Inject constructor( } } if (userPreferences.fullScreenEnabled) { - (toolbar.parent as ViewGroup?)?.removeView(toolbar) + if (toolbar.parent != browserFrame) { + (toolbar.parent as ViewGroup?)?.removeView(toolbar) - browserFrame.addView(toolbar) + browserFrame.addView(toolbar) + } + + currentToggleListener?.showToolbar() ?: run { + toolbar.translationY = 0f + } - toolbar.translationY = 0f webView.translationY = toolbar.height.toFloat() coordinate(toolbar, webView) } else { - (toolbar.parent as ViewGroup?)?.removeView(toolbar) + if (toolbar.parent != toolbarRoot) { + (toolbar.parent as ViewGroup?)?.removeView(toolbar) - toolbarRoot.addView(toolbar, 0) + toolbarRoot.addView(toolbar, 0) + } toolbar.translationY = 0f webView.translationY = 0f } } + fun showToolbar() { + currentToggleListener?.showToolbar() + } + private fun coordinate(toolbar: View, webView: WebView) { webView.setCompositeTouchListener("scroll", touchListener) @@ -110,6 +123,7 @@ class WebViewScrollCoordinator @Inject constructor( touchListener.toggleListener = toggleListener gestureListener.toggleListener = toggleListener + currentToggleListener = toggleListener } interface ToggleListener { From 96632f2e96e24d6708e2b0a473dab6b8c9379dc5 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 10:41:50 -0400 Subject: [PATCH 089/161] Properly handle the back button when the tab can no longer "go back" --- .../lightning/_browser2/BrowserPresenter.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 99abf1c7f..508425f29 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -2,6 +2,7 @@ package acr.browser.lightning._browser2 import acr.browser.lightning._browser2.data.CookieAdministrator import acr.browser.lightning._browser2.di.Browser2Scope +import acr.browser.lightning._browser2.di.IncognitoMode import acr.browser.lightning._browser2.di.InitialUrl import acr.browser.lightning._browser2.download.PendingDownload import acr.browser.lightning._browser2.history.HistoryRecord @@ -67,7 +68,8 @@ class BrowserPresenter @Inject constructor( private val historyPageFactory: HistoryPageFactory, private val allowListModel: AllowListModel, private val cookieAdministrator: CookieAdministrator, - private val tabCountNotifier: TabCountNotifier + private val tabCountNotifier: TabCountNotifier, + @IncognitoMode private val incognitoMode: Boolean ) { private var view: BrowserContract.View? = null @@ -458,7 +460,15 @@ class BrowserPresenter @Inject constructor( view?.closeBookmarkDrawer() } currentTab?.canGoBack() == true -> currentTab?.goBack() - currentTab?.canGoBack() == false -> onTabClose(viewState.tabs.indexOfFirst { it.id == currentTab?.id }) + currentTab?.canGoBack() == false -> if (incognitoMode) { + currentTab?.id?.let { + view?.showCloseBrowserDialog(it) + } + } else if (tabIdOpenedFromAction == currentTab?.id) { + onTabClose(viewState.tabs.indexOfFirst { it.id == currentTab?.id }) + } else { + navigator.backgroundBrowser() + } } } From 67e9f660a81e7ee15e8dc67f4989bf58d823dd88 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 11:46:53 -0400 Subject: [PATCH 090/161] Fixing behavior issue where the favicon would be incorrect if the page didn't have an icon --- .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 4 +--- .../java/acr/browser/lightning/_browser2/tab/TabAdapter.kt | 6 ++++-- .../java/acr/browser/lightning/_browser2/tab/TabModel.kt | 3 ++- .../browser/lightning/_browser2/tab/TabWebChromeClient.kt | 7 +++++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 508425f29..bc379bca9 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -252,7 +252,7 @@ class BrowserPresenter @Inject constructor( forEach { tabModel -> compositeDisposable += Observables.combineLatest( tabModel.titleChanges().startWith(tabModel.title), - tabModel.faviconChanges().optional() + tabModel.faviconChanges() .startWith(Option.fromNullable(tabModel.favicon)) ).distinctUntilChanged() .subscribeOn(mainScheduler) @@ -268,8 +268,6 @@ class BrowserPresenter @Inject constructor( } } - private fun Observable.optional(): Observable> = map { Option.Some(it) } - /** * TODO */ diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index b5f1fdb1d..1cda97833 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -6,6 +6,8 @@ import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.preference.userAgent import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.SslState +import acr.browser.lightning.utils.Option +import acr.browser.lightning.utils.value import acr.browser.lightning.view.FreezableBundleInitializer import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap @@ -122,9 +124,9 @@ class TabAdapter( get() = findInPageQuery override val favicon: Bitmap? - get() = webView.favicon + get() = tabWebChromeClient.faviconObservable.value?.value() - override fun faviconChanges(): Observable = tabWebChromeClient.faviconObservable.hide() + override fun faviconChanges(): Observable> = tabWebChromeClient.faviconObservable // TODO do we show "new tab" override val url: String diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt index 043561e3c..014310a4f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -3,6 +3,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning._browser2.download.PendingDownload import acr.browser.lightning.ssl.SslCertificateInfo import acr.browser.lightning.ssl.SslState +import acr.browser.lightning.utils.Option import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap import android.os.Bundle @@ -50,7 +51,7 @@ interface TabModel { val favicon: Bitmap? - fun faviconChanges(): Observable + fun faviconChanges(): Observable> val url: String diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt index d0f15ebe0..7df37a8f5 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt @@ -1,8 +1,10 @@ package acr.browser.lightning._browser2.tab +import acr.browser.lightning.utils.Option import android.graphics.Bitmap import android.webkit.WebChromeClient import android.webkit.WebView +import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.PublishSubject /** @@ -12,7 +14,7 @@ class TabWebChromeClient : WebChromeClient() { val progressObservable: PublishSubject = PublishSubject.create() val titleObservable: PublishSubject = PublishSubject.create() - val faviconObservable: PublishSubject = PublishSubject.create() + val faviconObservable: BehaviorSubject> = BehaviorSubject.create() override fun onProgressChanged(view: WebView, newProgress: Int) { super.onProgressChanged(view, newProgress) @@ -22,10 +24,11 @@ class TabWebChromeClient : WebChromeClient() { override fun onReceivedTitle(view: WebView, title: String) { super.onReceivedTitle(view, title) titleObservable.onNext(title) + faviconObservable.onNext(Option.None) } override fun onReceivedIcon(view: WebView, icon: Bitmap) { super.onReceivedIcon(view, icon) - faviconObservable.onNext(icon) + faviconObservable.onNext(Option.Some(icon)) } } From 7b960a87d20c330adc3ab3cd5005120fe1a0203d Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 11:56:17 -0400 Subject: [PATCH 091/161] Fixing issues with search bar focus and the URL displayed --- .../acr/browser/lightning/_browser2/BrowserActivity.kt | 9 +++------ .../acr/browser/lightning/_browser2/BrowserPresenter.kt | 8 +++++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 8d83f43fc..5bccf94f4 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -214,18 +214,15 @@ abstract class BrowserActivity : ThemableBrowserActivity() { } binding.search.setAdapter(suggestionsAdapter) val searchListener = SearchListener( - onConfirm = { - presenter.onSearch(binding.search.text.toString()) - }, + onConfirm = { presenter.onSearch(binding.search.text.toString()) }, inputMethodManager = inputMethodManager ) binding.search.setOnEditorActionListener(searchListener) binding.search.setOnKeyListener(searchListener) binding.search.addTextChangedListener(StyleRemovingTextWatcher()) binding.search.setOnFocusChangeListener { _, hasFocus -> - presenter.onSearchFocusChanged( - hasFocus - ) + binding.search.selectAll() + presenter.onSearchFocusChanged(hasFocus) } binding.findPrevious.setOnClickListener { presenter.onFindPrevious() } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index bc379bca9..4bf8f2ac1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -545,7 +545,13 @@ class BrowserPresenter @Inject constructor( fun onSearchFocusChanged(isFocused: Boolean) { isSearchViewFocused = isFocused if (isFocused) { - view?.updateState(viewState.copy(sslState = SslState.None, isRefresh = false)) + view?.updateState( + viewState.copy( + sslState = SslState.None, + isRefresh = false, + displayUrl = currentTab?.url?.takeIf { !it.isSpecialUrl() }.orEmpty() + ) + ) } else { view?.updateState( viewState.copy( From 50667a68b503f2511a80c1b8309e56bfc00b36f8 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 12:59:10 -0400 Subject: [PATCH 092/161] Show the freeze icon as the tab icon when the tab is frozen --- .../browser/lightning/_browser2/di/Browser2Module.kt | 10 ++++++++++ .../browser/lightning/_browser2/image/IconFreeze.kt | 9 +++++++++ .../acr/browser/lightning/_browser2/tab/TabAdapter.kt | 6 ++++-- .../browser/lightning/_browser2/tab/TabsRepository.kt | 8 ++++++-- 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/image/IconFreeze.kt diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt index a7814b963..c36856d4a 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2Module.kt @@ -1,11 +1,13 @@ package acr.browser.lightning._browser2.di +import acr.browser.lightning.R import acr.browser.lightning._browser2.BrowserContract import acr.browser.lightning._browser2.data.CookieAdministrator import acr.browser.lightning._browser2.data.DefaultCookieAdministrator import acr.browser.lightning._browser2.history.DefaultHistoryRecord import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.history.NoOpHistoryRecord +import acr.browser.lightning._browser2.image.IconFreeze import acr.browser.lightning._browser2.notification.DefaultTabCountNotifier import acr.browser.lightning._browser2.notification.IncognitoTabCountNotifier import acr.browser.lightning._browser2.notification.TabCountNotifier @@ -22,12 +24,15 @@ import acr.browser.lightning.adblock.AdBlocker import acr.browser.lightning.adblock.BloomFilterAdBlocker import acr.browser.lightning.adblock.NoOpAdBlocker import acr.browser.lightning.browser.BrowserView +import acr.browser.lightning.extensions.drawable import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.utils.IntentUtils import android.app.Activity import android.app.Application import android.content.Intent +import android.graphics.Bitmap import android.webkit.WebSettings +import androidx.core.graphics.drawable.toBitmap import dagger.Binds import dagger.Module import dagger.Provides @@ -126,4 +131,9 @@ class Browser2Module { defaultBundleStore } + @IconFreeze + @Provides + fun providesFrozenIcon(activity: Activity): Bitmap = + activity.drawable(R.drawable.ic_frozen).toBitmap() + } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/image/IconFreeze.kt b/app/src/main/java/acr/browser/lightning/_browser2/image/IconFreeze.kt new file mode 100644 index 000000000..f4dfea41c --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/image/IconFreeze.kt @@ -0,0 +1,9 @@ +package acr.browser.lightning._browser2.image + +import javax.inject.Qualifier + +/** + * The frozen icon qualifier + */ +@Qualifier +annotation class IconFreeze diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index 1cda97833..f1cac65c7 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -26,7 +26,8 @@ class TabAdapter( private val tabWebViewClient: TabWebViewClient, private val tabWebChromeClient: TabWebChromeClient, private val userPreferences: UserPreferences, - private val defaultUserAgent: String + private val defaultUserAgent: String, + private val iconFreeze: Bitmap ) : TabModel { private var latentInitializer: FreezableBundleInitializer? = null @@ -124,7 +125,8 @@ class TabAdapter( get() = findInPageQuery override val favicon: Bitmap? - get() = tabWebChromeClient.faviconObservable.value?.value() + get() = latentInitializer?.let { iconFreeze } + ?: tabWebChromeClient.faviconObservable.value?.value() override fun faviconChanges(): Observable> = tabWebChromeClient.faviconObservable diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index a5491d806..7cff2ebf5 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -1,6 +1,7 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning._browser2.BrowserContract +import acr.browser.lightning._browser2.image.IconFreeze import acr.browser.lightning._browser2.tab.bundle.BundleStore import acr.browser.lightning.adblock.AdBlocker import acr.browser.lightning.adblock.allowlist.AllowListModel @@ -8,6 +9,7 @@ import acr.browser.lightning.di.DiskScheduler import acr.browser.lightning.di.MainScheduler import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.view.* +import android.graphics.Bitmap import io.reactivex.* import io.reactivex.subjects.PublishSubject import javax.inject.Inject @@ -25,7 +27,8 @@ class TabsRepository @Inject constructor( private val bundleStore: BundleStore, private val urlHandler: UrlHandler, private val userPreferences: UserPreferences, - @DefaultUserAgent private val defaultUserAgent: String + @DefaultUserAgent private val defaultUserAgent: String, + @IconFreeze private val iconFreeze: Bitmap ) : BrowserContract.Model { private var selectedTab: TabModel? = null @@ -63,7 +66,8 @@ class TabsRepository @Inject constructor( TabWebViewClient(adBlocker, allowListModel, urlHandler, headers), TabWebChromeClient(), userPreferences, - defaultUserAgent + defaultUserAgent, + iconFreeze ) tabsList = tabsList + tabAdapter From c29f77af7a71312ac1bbdc5d71393ea4afb0ba2b Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 13:57:29 -0400 Subject: [PATCH 093/161] Enable and disable menu items based on current URL --- .../lightning/_browser2/BrowserActivity.kt | 18 ++++++++++++++++++ .../lightning/_browser2/BrowserPresenter.kt | 3 +++ .../lightning/_browser2/BrowserStateAdapter.kt | 2 ++ .../lightning/_browser2/BrowserViewState.kt | 3 +++ 4 files changed, 26 insertions(+) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 5bccf94f4..0c5c6aad1 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -53,6 +53,12 @@ abstract class BrowserActivity : ThemableBrowserActivity() { private lateinit var tabsAdapter: ListAdapter private lateinit var bookmarksAdapter: BookmarkRecyclerViewAdapter + private var menuItemShare: MenuItem? = null + private var menuItemCopyLink: MenuItem? = null + private var menuItemAddToHome: MenuItem? = null + private var menuItemAddBookmark: MenuItem? = null + private var menuItemReaderMode: MenuItem? = null + @Inject internal lateinit var imageLoader: ImageLoader @@ -267,6 +273,11 @@ abstract class BrowserActivity : ThemableBrowserActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(menu(), menu) + menuItemShare = menu.findItem(R.id.action_share) + menuItemCopyLink = menu.findItem(R.id.action_copy) + menuItemAddToHome = menu.findItem(R.id.action_add_to_homescreen) + menuItemAddBookmark = menu.findItem(R.id.action_add_bookmark) + menuItemReaderMode = menu.findItem(R.id.action_reading_mode) return super.onCreateOptionsMenu(menu) } @@ -291,6 +302,13 @@ abstract class BrowserActivity : ThemableBrowserActivity() { binding.searchSslStatus.setImageDrawable(createSslDrawableForState(it)) binding.searchSslStatus.updateVisibilityForDrawable() } + viewState.enableFullMenu?.let { + menuItemShare?.isVisible = it + menuItemCopyLink?.isVisible = it + menuItemAddToHome?.isVisible = it + menuItemAddBookmark?.isVisible = it + menuItemReaderMode?.isVisible = it + } viewState.tabs?.let { binding.tabCountView.updateCount(it.size) } viewState.progress?.let { binding.progressView.progress = it } viewState.isRefresh?.let { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 4bf8f2ac1..397448c13 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -78,6 +78,7 @@ class BrowserPresenter @Inject constructor( isRefresh = true, sslState = SslState.None, progress = 0, + enableFullMenu = true, tabs = emptyList(), isForwardEnabled = false, isBackEnabled = false, @@ -193,6 +194,7 @@ class BrowserPresenter @Inject constructor( title = null, isLoading = false ), + enableFullMenu = false, isForwardEnabled = false, isBackEnabled = false, sslState = SslState.None, @@ -225,6 +227,7 @@ class BrowserPresenter @Inject constructor( title = title, isLoading = progress < 100 ), + enableFullMenu = !url.isSpecialUrl(), isRefresh = progress == 100, isForwardEnabled = canGoForward, isBackEnabled = canGoBack, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 46184139f..7667b38cf 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -20,6 +20,7 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse sslState, isRefresh, progress, + enableFullMenu, tabs, isForwardEnabled, isBackEnabled, @@ -36,6 +37,7 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse sslState = sslState.takeIf { it != currentState?.sslState }, isRefresh = isRefresh.takeIf { it != currentState?.isRefresh }, progress = progress.takeIf { it != currentState?.progress }, + enableFullMenu = enableFullMenu.takeIf { it != currentState?.enableFullMenu }, tabs = tabs.takeIf { it != currentState?.tabs }, isForwardEnabled = isForwardEnabled.takeIf { it != currentState?.isForwardEnabled }, isBackEnabled = isBackEnabled.takeIf { it != currentState?.isBackEnabled }, diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt index 3ee9224fc..616aaaa9e 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserViewState.kt @@ -13,6 +13,7 @@ data class BrowserViewState( val sslState: SslState, val isRefresh: Boolean, val progress: Int, + val enableFullMenu: Boolean, // Tabs val tabs: List, @@ -27,6 +28,7 @@ data class BrowserViewState( // find val findInPage: String + ) data class PartialBrowserViewState( @@ -35,6 +37,7 @@ data class PartialBrowserViewState( val sslState: SslState?, val isRefresh: Boolean?, val progress: Int?, + val enableFullMenu: Boolean?, // Tabs val tabs: List?, From f09d18748ef87d5cc2e082d4f5ab8f47cd5f4760 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 14:11:00 -0400 Subject: [PATCH 094/161] Properly cache favicons for use elsewhere --- .../lightning/_browser2/tab/TabWebChromeClient.kt | 11 ++++++++++- .../browser/lightning/_browser2/tab/TabsRepository.kt | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt index 7df37a8f5..45b1254a2 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt @@ -1,16 +1,21 @@ package acr.browser.lightning._browser2.tab +import acr.browser.lightning.favicon.FaviconModel import acr.browser.lightning.utils.Option import android.graphics.Bitmap import android.webkit.WebChromeClient import android.webkit.WebView +import io.reactivex.Scheduler import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.PublishSubject /** * Created by anthonycr on 9/12/20. */ -class TabWebChromeClient : WebChromeClient() { +class TabWebChromeClient( + private val faviconModel: FaviconModel, + private val diskScheduler: Scheduler +) : WebChromeClient() { val progressObservable: PublishSubject = PublishSubject.create() val titleObservable: PublishSubject = PublishSubject.create() @@ -30,5 +35,9 @@ class TabWebChromeClient : WebChromeClient() { override fun onReceivedIcon(view: WebView, icon: Bitmap) { super.onReceivedIcon(view, icon) faviconObservable.onNext(Option.Some(icon)) + val url = view.url ?: return + faviconModel.cacheFaviconForUrl(icon, url) + .subscribeOn(diskScheduler) + .subscribe() } } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt index 7cff2ebf5..d32b151cb 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabsRepository.kt @@ -7,6 +7,7 @@ import acr.browser.lightning.adblock.AdBlocker import acr.browser.lightning.adblock.allowlist.AllowListModel import acr.browser.lightning.di.DiskScheduler import acr.browser.lightning.di.MainScheduler +import acr.browser.lightning.favicon.FaviconModel import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.view.* import android.graphics.Bitmap @@ -22,6 +23,7 @@ class TabsRepository @Inject constructor( private val tabPager: TabPager, private val adBlocker: AdBlocker, private val allowListModel: AllowListModel, + private val faviconModel: FaviconModel, @DiskScheduler private val diskScheduler: Scheduler, @MainScheduler private val mainScheduler: Scheduler, private val bundleStore: BundleStore, @@ -64,7 +66,7 @@ class TabsRepository @Inject constructor( webView, headers, TabWebViewClient(adBlocker, allowListModel, urlHandler, headers), - TabWebChromeClient(), + TabWebChromeClient(faviconModel, diskScheduler), userPreferences, defaultUserAgent, iconFreeze From ce44cdb24a10719ef811af690bde40d5b2b5ee42 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 14:11:37 -0400 Subject: [PATCH 095/161] Avoid subscribing to the bookmark repo on the main thread --- .../java/acr/browser/lightning/_browser2/BrowserPresenter.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 397448c13..0f7c07fba 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -218,7 +218,8 @@ class BrowserPresenter @Inject constructor( tab.loadingProgress().startWith(tab.loadingProgress), tab.canGoBackChanges().startWith(tab.canGoBack()), tab.canGoForwardChanges().startWith(tab.canGoForward()), - tab.urlChanges().startWith(tab.url).flatMapSingle(bookmarkRepository::isBookmark), + tab.urlChanges().startWith(tab.url).observeOn(diskScheduler) + .flatMapSingle(bookmarkRepository::isBookmark), tab.urlChanges().startWith(tab.url).map(String::isSpecialUrl) ) { sslState, title, url, progress, canGoBack, canGoForward, isBookmark, isSpecialUrl -> viewState.copy( @@ -238,7 +239,7 @@ class BrowserPresenter @Inject constructor( isBookmarkEnabled = !isSpecialUrl, findInPage = tab.findQuery.orEmpty() ) - }.subscribeOn(mainScheduler) + }.observeOn(mainScheduler) .subscribe { view.updateState(it) } tabDisposable += tab.downloadRequests() From 2656cc03952bd9a881fca2de639b15adb0cbc533 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 15:27:17 -0400 Subject: [PATCH 096/161] Implementing most key combos --- .../lightning/_browser2/BrowserPresenter.kt | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 0f7c07fba..58005fcde 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -362,24 +362,24 @@ class BrowserPresenter @Inject constructor( */ fun onKeyComboClick(keyCombo: KeyCombo) { when (keyCombo) { - KeyCombo.CTRL_F -> TODO() + KeyCombo.CTRL_F -> view?.showFindInPageDialog() KeyCombo.CTRL_T -> onNewTabClick() - KeyCombo.CTRL_W -> TODO() - KeyCombo.CTRL_Q -> TODO() + KeyCombo.CTRL_W -> onTabClose(viewState.tabs.indexOfFirst { it.id == currentTab?.id }) + KeyCombo.CTRL_Q -> view?.showCloseBrowserDialog(viewState.tabs.indexOfFirst { it.id == currentTab?.id }) KeyCombo.CTRL_R -> onRefreshOrStopClick() KeyCombo.CTRL_TAB -> TODO() KeyCombo.CTRL_SHIFT_TAB -> TODO() KeyCombo.SEARCH -> TODO() - KeyCombo.ALT_0 -> TODO() - KeyCombo.ALT_1 -> TODO() - KeyCombo.ALT_2 -> TODO() - KeyCombo.ALT_3 -> TODO() - KeyCombo.ALT_4 -> TODO() - KeyCombo.ALT_5 -> TODO() - KeyCombo.ALT_6 -> TODO() - KeyCombo.ALT_7 -> TODO() - KeyCombo.ALT_8 -> TODO() - KeyCombo.ALT_9 -> TODO() + KeyCombo.ALT_0 -> onTabClick(0.coerceAtMost(viewState.tabs.size - 1)) + KeyCombo.ALT_1 -> onTabClick(1.coerceAtMost(viewState.tabs.size - 1)) + KeyCombo.ALT_2 -> onTabClick(2.coerceAtMost(viewState.tabs.size - 1)) + KeyCombo.ALT_3 -> onTabClick(3.coerceAtMost(viewState.tabs.size - 1)) + KeyCombo.ALT_4 -> onTabClick(4.coerceAtMost(viewState.tabs.size - 1)) + KeyCombo.ALT_5 -> onTabClick(5.coerceAtMost(viewState.tabs.size - 1)) + KeyCombo.ALT_6 -> onTabClick(6.coerceAtMost(viewState.tabs.size - 1)) + KeyCombo.ALT_7 -> onTabClick(7.coerceAtMost(viewState.tabs.size - 1)) + KeyCombo.ALT_8 -> onTabClick(8.coerceAtMost(viewState.tabs.size - 1)) + KeyCombo.ALT_9 -> onTabClick(9.coerceAtMost(viewState.tabs.size - 1)) } } From f73d17c216de087ab58c12f72705fc027fc11e53 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 16:52:30 -0400 Subject: [PATCH 097/161] Prevent local file URLs from being unilaterally loaded by the browser without user consent --- .../lightning/_browser2/BrowserActivity.kt | 17 ++++++++++ .../lightning/_browser2/BrowserContract.kt | 2 ++ .../lightning/_browser2/BrowserPresenter.kt | 34 ++++++++++++++++--- .../_browser2/BrowserStateAdapter.kt | 4 +++ .../acr/browser/lightning/utils/UrlUtils.kt | 7 +++- 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt index 0c5c6aad1..631003687 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserActivity.kt @@ -26,6 +26,7 @@ import acr.browser.lightning.dialog.DialogItem import acr.browser.lightning.dialog.LightningDialogBuilder import acr.browser.lightning.extensions.color import acr.browser.lightning.extensions.drawable +import acr.browser.lightning.extensions.resizeAndShow import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.ssl.createSslDrawableForState import android.content.Intent @@ -36,6 +37,7 @@ import android.widget.AdapterView import android.widget.ImageView import androidx.annotation.DrawableRes import androidx.annotation.MenuRes +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.LinearLayoutManager @@ -556,6 +558,21 @@ abstract class BrowserActivity : ThemableBrowserActivity() { ) } + fun showLocalFileBlockedDialog() { + AlertDialog.Builder(this) + .setCancelable(true) + .setTitle(R.string.title_warning) + .setMessage(R.string.message_blocked_local) + .setNegativeButton(android.R.string.cancel) { _, _ -> + presenter.onConfirmOpenLocalFile(allow = false) + } + .setPositiveButton(R.string.action_open) { _, _ -> + presenter.onConfirmOpenLocalFile(allow = true) + } + .setOnCancelListener { presenter.onConfirmOpenLocalFile(allow = false) } + .resizeAndShow() + } + private fun ImageView.updateVisibilityForDrawable() { visibility = if (drawable == null) { View.GONE diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt index 3a3a8bae7..8ecf7e6e8 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserContract.kt @@ -65,6 +65,8 @@ interface BrowserContract { fun showToolbar() fun showToolsDialog(areAdsAllowed: Boolean, shouldShowAdBlockOption: Boolean) + + fun showLocalFileBlockedDialog() } interface Model { diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 58005fcde..bb0b00f81 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -94,6 +94,7 @@ class BrowserPresenter @Inject constructor( private var isBookmarkDrawerOpen = false private var isSearchViewFocused = false private var tabIdOpenedFromAction = -1 + private var pendingAction: BrowserContract.Action.LoadUrl? = null private val compositeDisposable = CompositeDisposable() private val allTabsDisposable = CompositeDisposable() @@ -277,15 +278,38 @@ class BrowserPresenter @Inject constructor( */ fun onNewAction(action: BrowserContract.Action) { when (action) { - is BrowserContract.Action.LoadUrl -> createNewTabAndSelect( - tabInitializer = UrlInitializer(action.url), - shouldSelect = true, - markAsOpenedFromAction = true - ) + is BrowserContract.Action.LoadUrl -> if (action.url.isSpecialUrl()) { + view?.showLocalFileBlockedDialog() + pendingAction = action + } else { + createNewTabAndSelect( + tabInitializer = UrlInitializer(action.url), + shouldSelect = true, + markAsOpenedFromAction = true + ) + } BrowserContract.Action.Panic -> panicClean() } } + /** + * Call when the user confirms that they do or do not want to allow a local file to be opened + * in the browser. This is a security gate to prevent malicious local files from being opened + * in the browser without the user's knowledge. + */ + fun onConfirmOpenLocalFile(allow: Boolean) { + if (allow) { + pendingAction?.let { + createNewTabAndSelect( + tabInitializer = UrlInitializer(it.url), + shouldSelect = true, + markAsOpenedFromAction = true + ) + } + } + pendingAction = null + } + private fun panicClean() { createNewTabAndSelect(tabInitializer = NoOpInitializer(), shouldSelect = true) model.clean() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt index 7667b38cf..5fbb1478b 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserStateAdapter.kt @@ -129,4 +129,8 @@ class BrowserStateAdapter(private val browserActivity: BrowserActivity) : Browse browserActivity.showToolsDialog(areAdsAllowed, shouldShowAdBlockOption) } + override fun showLocalFileBlockedDialog() { + browserActivity.showLocalFileBlockedDialog() + } + } diff --git a/app/src/main/java/acr/browser/lightning/utils/UrlUtils.kt b/app/src/main/java/acr/browser/lightning/utils/UrlUtils.kt index 2651dc7c8..d7c174eea 100644 --- a/app/src/main/java/acr/browser/lightning/utils/UrlUtils.kt +++ b/app/src/main/java/acr/browser/lightning/utils/UrlUtils.kt @@ -67,6 +67,10 @@ fun smartUrlFilter(url: String, canBeSearch: Boolean, searchUrl: String): String } } +/** + * True if the URL is a file URL, false otherwise. + */ +fun String?.isFileUrl(): Boolean = this != null && this.startsWith(FILE) /** * Returns whether the given url is the bookmarks/history page or a normal website @@ -111,6 +115,7 @@ fun String?.isHistoryUrl(): Boolean = fun String?.isStartPageUrl(): Boolean = this != null && this.startsWith(FILE) && this.endsWith(HomePageFactory.FILENAME) -private val ACCEPTED_URI_SCHEMA = Pattern.compile("(?i)((?:http|https|file)://|(?:inline|data|about|javascript):|(?:.*:.*@))(.*)") +private val ACCEPTED_URI_SCHEMA = + Pattern.compile("(?i)((?:http|https|file)://|(?:inline|data|about|javascript):|(?:.*:.*@))(.*)") const val QUERY_PLACE_HOLDER = "%s" private const val URL_ENCODED_SPACE = "%20" From 7c731b37cf4c8641b53a295993fa0db2e48031ff Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 28 Jul 2022 22:39:21 -0400 Subject: [PATCH 098/161] Support create and close window requests --- .../lightning/_browser2/BrowserPresenter.kt | 19 ++++++++++++++++--- .../lightning/_browser2/tab/TabAdapter.kt | 6 ++++++ .../lightning/_browser2/tab/TabModel.kt | 4 ++++ .../_browser2/tab/TabWebChromeClient.kt | 19 +++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index bb0b00f81..85d7444ce 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -251,6 +251,14 @@ class BrowserPresenter @Inject constructor( .distinctUntilChanged() .subscribeOn(mainScheduler) .subscribeBy { view?.showToolbar() } + + tabDisposable += tab.createWindowRequests() + .subscribeOn(mainScheduler) + .subscribeBy { createNewTabAndSelect(it, shouldSelect = true) } + + tabDisposable += tab.closeWindowRequests() + .subscribeOn(mainScheduler) + .subscribeBy { onTabClose(viewState.indexOfCurrentTab()) } } private fun List.subscribeToUpdates(compositeDisposable: CompositeDisposable) { @@ -381,6 +389,11 @@ class BrowserPresenter @Inject constructor( } } + private fun BrowserViewState.tabIndexForId(id: Int?): Int = + tabs.indexOfFirst { it.id == id } + + private fun BrowserViewState.indexOfCurrentTab(): Int = tabIndexForId(currentTab?.id) + /** * TODO */ @@ -388,7 +401,7 @@ class BrowserPresenter @Inject constructor( when (keyCombo) { KeyCombo.CTRL_F -> view?.showFindInPageDialog() KeyCombo.CTRL_T -> onNewTabClick() - KeyCombo.CTRL_W -> onTabClose(viewState.tabs.indexOfFirst { it.id == currentTab?.id }) + KeyCombo.CTRL_W -> onTabClose(viewState.indexOfCurrentTab()) KeyCombo.CTRL_Q -> view?.showCloseBrowserDialog(viewState.tabs.indexOfFirst { it.id == currentTab?.id }) KeyCombo.CTRL_R -> onRefreshOrStopClick() KeyCombo.CTRL_TAB -> TODO() @@ -491,7 +504,7 @@ class BrowserPresenter @Inject constructor( view?.showCloseBrowserDialog(it) } } else if (tabIdOpenedFromAction == currentTab?.id) { - onTabClose(viewState.tabs.indexOfFirst { it.id == currentTab?.id }) + onTabClose(viewState.indexOfCurrentTab()) } else { navigator.backgroundBrowser() } @@ -1053,7 +1066,7 @@ class BrowserPresenter @Inject constructor( fun onCloseBrowserEvent(id: Int, closeTabEvent: BrowserContract.CloseTabEvent) { when (closeTabEvent) { BrowserContract.CloseTabEvent.CLOSE_CURRENT -> - onTabClose(viewState.tabs.indexOfFirst { it.id == id }) + onTabClose(viewState.tabIndexForId(id)) BrowserContract.CloseTabEvent.CLOSE_OTHERS -> model.tabsList .filter { it.id != id } .toObservable() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt index f1cac65c7..3b8858510 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabAdapter.kt @@ -165,6 +165,12 @@ class TabAdapter( override fun downloadRequests(): Observable = downloadsSubject.hide() + override fun createWindowRequests(): Observable = + tabWebChromeClient.createWindowObservable.hide() + + override fun closeWindowRequests(): Observable = + tabWebChromeClient.closeWindowObservable.hide() + override var isForeground: Boolean = false set(value) { field = value diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt index 014310a4f..088e560be 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabModel.kt @@ -75,6 +75,10 @@ interface TabModel { // Lifecycle + fun createWindowRequests(): Observable + + fun closeWindowRequests(): Observable + var isForeground: Boolean fun destroy() diff --git a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt index 45b1254a2..4738bf086 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/tab/TabWebChromeClient.kt @@ -2,7 +2,10 @@ package acr.browser.lightning._browser2.tab import acr.browser.lightning.favicon.FaviconModel import acr.browser.lightning.utils.Option +import acr.browser.lightning.view.ResultMessageInitializer +import acr.browser.lightning.view.TabInitializer import android.graphics.Bitmap +import android.os.Message import android.webkit.WebChromeClient import android.webkit.WebView import io.reactivex.Scheduler @@ -20,6 +23,22 @@ class TabWebChromeClient( val progressObservable: PublishSubject = PublishSubject.create() val titleObservable: PublishSubject = PublishSubject.create() val faviconObservable: BehaviorSubject> = BehaviorSubject.create() + val createWindowObservable: PublishSubject = PublishSubject.create() + val closeWindowObservable: PublishSubject = PublishSubject.create() + + override fun onCreateWindow( + view: WebView, + isDialog: Boolean, + isUserGesture: Boolean, + resultMsg: Message + ): Boolean { + createWindowObservable.onNext(ResultMessageInitializer(resultMsg)) + return true + } + + override fun onCloseWindow(window: WebView) { + closeWindowObservable.onNext(Unit) + } override fun onProgressChanged(view: WebView, newProgress: Int) { super.onProgressChanged(view, newProgress) From 04a69b76f73b0ab620e4c2248515f67c966b15b7 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sat, 30 Jul 2022 22:46:37 -0400 Subject: [PATCH 099/161] Fixing issue where subscribing to the url changes from two different subscriptions caused the tabs to be incorrectly updated --- .../lightning/_browser2/BrowserPresenter.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt index 85d7444ce..445cd3e06 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/BrowserPresenter.kt @@ -31,7 +31,6 @@ import acr.browser.lightning.utils.* import acr.browser.lightning.view.* import androidx.core.net.toUri import io.reactivex.Maybe -import io.reactivex.Observable import io.reactivex.Scheduler import io.reactivex.Single import io.reactivex.disposables.CompositeDisposable @@ -210,6 +209,8 @@ class BrowserPresenter @Inject constructor( view?.showToolbar() view?.closeTabDrawer() + view?.updateState(viewState.copy(tabs = viewState.tabs.map { it.copy(isSelected = it.id == tab.id ) })) + tabDisposable.dispose() tabDisposable = CompositeDisposable() tabDisposable += Observables.combineLatest( @@ -220,9 +221,11 @@ class BrowserPresenter @Inject constructor( tab.canGoBackChanges().startWith(tab.canGoBack()), tab.canGoForwardChanges().startWith(tab.canGoForward()), tab.urlChanges().startWith(tab.url).observeOn(diskScheduler) - .flatMapSingle(bookmarkRepository::isBookmark), - tab.urlChanges().startWith(tab.url).map(String::isSpecialUrl) - ) { sslState, title, url, progress, canGoBack, canGoForward, isBookmark, isSpecialUrl -> + .flatMapSingle(bookmarkRepository::isBookmark).observeOn(mainScheduler), + tab.urlChanges().startWith(tab.url).map(String::isSpecialUrl), + tab.faviconChanges().startWith(Option.fromNullable(tab.favicon)) + ) { sslState, title, url, progress, canGoBack, canGoForward, isBookmark, isSpecialUrl, icon -> + viewState.copy( displayUrl = searchBoxModel.getDisplayContent( url = url, @@ -235,7 +238,9 @@ class BrowserPresenter @Inject constructor( isBackEnabled = canGoBack, sslState = sslState, progress = progress, - tabs = viewState.tabs.map { it.copy(isSelected = it.id == tab.id) }, + tabs = viewState.tabs.updateId(tab.id) { + it.copy(title = title, icon = icon.value()) + }, isBookmarked = isBookmark, isBookmarkEnabled = !isSpecialUrl, findInPage = tab.findQuery.orEmpty() From 8b81521deb594958b706b89dfc1d330d0e7035ba Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 31 Jul 2022 22:55:11 -0400 Subject: [PATCH 100/161] Theming the bookmark page --- app/src/main/html/bookmarks.html | 25 ++++++----- .../_browser2/di/Browser2BindsModule.kt | 5 +++ .../_browser2/theme/DefaultThemeProvider.kt | 14 ++++++ .../_browser2/theme/LegacyThemeProvider.kt | 44 +++++++++++++++++++ .../_browser2/theme/ThemeProvider.kt | 17 +++++++ .../browser/lightning/di/AppBindsModule.kt | 5 +++ .../html/bookmark/BookmarkPageFactory.kt | 29 ++++++++++-- .../lightning/html/jsoup/JsoupExtensions.kt | 9 ++++ 8 files changed, 135 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/theme/DefaultThemeProvider.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/theme/LegacyThemeProvider.kt create mode 100644 app/src/main/java/acr/browser/lightning/_browser2/theme/ThemeProvider.kt diff --git a/app/src/main/html/bookmarks.html b/app/src/main/html/bookmarks.html index cbfc0e4c6..4c2ae7aec 100644 --- a/app/src/main/html/bookmarks.html +++ b/app/src/main/html/bookmarks.html @@ -11,13 +11,19 @@ diff --git a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt index 705e7183c..ac78a6ad8 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/di/Browser2BindsModule.kt @@ -7,6 +7,8 @@ import acr.browser.lightning._browser2.history.HistoryRecord import acr.browser.lightning._browser2.image.FaviconImageLoader import acr.browser.lightning._browser2.image.ImageLoader import acr.browser.lightning._browser2.tab.TabsRepository +import acr.browser.lightning._browser2.theme.LegacyThemeProvider +import acr.browser.lightning._browser2.theme.ThemeProvider import dagger.Binds import dagger.Module @@ -24,4 +26,7 @@ interface Browser2BindsModule { @Binds fun bindsBrowserNavigator(browserNavigator: BrowserNavigator): BrowserContract.Navigator + + @Binds + fun bindsThemeProvider(legacyThemeProvider: LegacyThemeProvider): ThemeProvider } diff --git a/app/src/main/java/acr/browser/lightning/_browser2/theme/DefaultThemeProvider.kt b/app/src/main/java/acr/browser/lightning/_browser2/theme/DefaultThemeProvider.kt new file mode 100644 index 000000000..3d4ba326e --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/theme/DefaultThemeProvider.kt @@ -0,0 +1,14 @@ +package acr.browser.lightning._browser2.theme + +import acr.browser.lightning.utils.ThemeUtils +import android.app.Activity + +/** + * The default theme attribute provider that delegates to the activity. + */ +class DefaultThemeProvider(private val activity: Activity) : ThemeProvider { + + override fun color(attrRes: Int): Int = + ThemeUtils.getColor(activity, attrRes) + +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/theme/LegacyThemeProvider.kt b/app/src/main/java/acr/browser/lightning/_browser2/theme/LegacyThemeProvider.kt new file mode 100644 index 000000000..3f993a929 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/theme/LegacyThemeProvider.kt @@ -0,0 +1,44 @@ +package acr.browser.lightning._browser2.theme + +import acr.browser.lightning.AppTheme +import acr.browser.lightning.R +import acr.browser.lightning.extensions.color +import acr.browser.lightning.preference.UserPreferences +import android.app.Application +import javax.inject.Inject + +/** + * The theme provider that should be used until [DefaultThemeProvider] can be injected safely + * throughout the codebase. + */ +class LegacyThemeProvider @Inject constructor( + private val application: Application, + userPreferences: UserPreferences +) : ThemeProvider { + + val theme = userPreferences.useTheme + + override fun color(attrRes: Int): Int = when (attrRes) { + R.attr.colorPrimary -> when (theme) { + AppTheme.LIGHT -> application.color(R.color.primary_color) + AppTheme.DARK -> application.color(R.color.primary_color_dark) + AppTheme.BLACK -> application.color(R.color.black) + } + R.attr.drawerBackground -> when (theme) { + AppTheme.LIGHT -> application.color(R.color.drawer_background) + AppTheme.DARK -> application.color(R.color.drawer_background_dark) + AppTheme.BLACK -> application.color(R.color.black) + } + R.attr.autoCompleteBackgroundColor -> when (theme) { + AppTheme.LIGHT -> application.color(R.color.white) + AppTheme.DARK -> application.color(R.color.divider_dark) + AppTheme.BLACK -> application.color(R.color.gray_dark) + } + R.attr.autoCompleteTitleColor -> when (theme) { + AppTheme.LIGHT -> application.color(R.color.black) + AppTheme.DARK -> application.color(R.color.white) + AppTheme.BLACK -> application.color(R.color.white) + } + else -> error("Unsupported color") + } +} diff --git a/app/src/main/java/acr/browser/lightning/_browser2/theme/ThemeProvider.kt b/app/src/main/java/acr/browser/lightning/_browser2/theme/ThemeProvider.kt new file mode 100644 index 000000000..9b91c1f1a --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/_browser2/theme/ThemeProvider.kt @@ -0,0 +1,17 @@ +package acr.browser.lightning._browser2.theme + +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt + +/** + * Provides themed attributes. + */ +interface ThemeProvider { + + /** + * Provide a themed color attribute. + */ + @ColorInt + fun color(@AttrRes attrRes: Int): Int + +} diff --git a/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt b/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt index 152f58cf2..86d356cac 100644 --- a/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt +++ b/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt @@ -1,5 +1,7 @@ package acr.browser.lightning.di +import acr.browser.lightning._browser2.theme.LegacyThemeProvider +import acr.browser.lightning._browser2.theme.ThemeProvider import acr.browser.lightning.adblock.allowlist.AllowListModel import acr.browser.lightning.adblock.allowlist.SessionAllowListModel import acr.browser.lightning.adblock.source.AssetsHostsDataSource @@ -58,4 +60,7 @@ interface AppBindsModule { @Binds fun bindsHostsDataSourceProvider(preferencesHostsDataSourceProvider: PreferencesHostsDataSourceProvider): HostsDataSourceProvider + + @Binds + fun bindsThemeProvider(legacyThemeProvider: LegacyThemeProvider): ThemeProvider } diff --git a/app/src/main/java/acr/browser/lightning/html/bookmark/BookmarkPageFactory.kt b/app/src/main/java/acr/browser/lightning/html/bookmark/BookmarkPageFactory.kt index e4b01d31d..8002cda21 100644 --- a/app/src/main/java/acr/browser/lightning/html/bookmark/BookmarkPageFactory.kt +++ b/app/src/main/java/acr/browser/lightning/html/bookmark/BookmarkPageFactory.kt @@ -1,6 +1,7 @@ package acr.browser.lightning.html.bookmark import acr.browser.lightning.R +import acr.browser.lightning._browser2.theme.ThemeProvider import acr.browser.lightning.constant.FILE import acr.browser.lightning.database.Bookmark import acr.browser.lightning.database.bookmark.BookmarkRepository @@ -12,12 +13,14 @@ import acr.browser.lightning.favicon.toValidUri import acr.browser.lightning.html.HtmlPageFactory import acr.browser.lightning.html.jsoup.* import acr.browser.lightning.utils.ThemeUtils +import android.app.Activity import android.app.Application import android.graphics.Bitmap import androidx.core.net.toUri import dagger.Reusable import io.reactivex.Scheduler import io.reactivex.Single +import org.jsoup.nodes.DataNode import java.io.File import java.io.FileOutputStream import java.io.FileWriter @@ -33,13 +36,24 @@ class BookmarkPageFactory @Inject constructor( private val faviconModel: FaviconModel, @DatabaseScheduler private val databaseScheduler: Scheduler, @DiskScheduler private val diskScheduler: Scheduler, - private val bookmarkPageReader: BookmarkPageReader + private val bookmarkPageReader: BookmarkPageReader, + themeProvider: ThemeProvider ) : HtmlPageFactory { private val title = application.getString(R.string.action_bookmarks) private val folderIconFile by lazy { File(application.cacheDir, FOLDER_ICON) } private val defaultIconFile by lazy { File(application.cacheDir, DEFAULT_ICON) } + private fun Int.toColor(): String { + val string = Integer.toHexString(this) + + return string.substring(2) + string.substring(0, 2) + } + + private val backgroundColor = themeProvider.color(R.attr.colorPrimary).toColor() + private val cardColor = themeProvider.color(R.attr.autoCompleteBackgroundColor).toColor() + private val textColor = themeProvider.color(R.attr.autoCompleteTitleColor).toColor() + override fun buildPage(): Single = bookmarkModel .getAllBookmarksSorted() .flattenAsObservable { it } @@ -50,7 +64,8 @@ class BookmarkPageFactory @Inject constructor( .toList() .concatWith( if (folder == Bookmark.Folder.Root) { - bookmarkModel.getFoldersSorted().map { it.filterIsInstance() } + bookmarkModel.getFoldersSorted() + .map { it.filterIsInstance() } } else { Single.just(emptyList()) } @@ -70,7 +85,10 @@ class BookmarkPageFactory @Inject constructor( } .ignoreElements() .toSingle { - cacheIcon(ThemeUtils.createThemedBitmap(application, R.drawable.ic_folder, false), folderIconFile) + cacheIcon( + ThemeUtils.createThemedBitmap(application, R.drawable.ic_folder, false), + folderIconFile + ) cacheIcon(faviconModel.createDefaultBitmapForTitle(null), defaultIconFile) "$FILE${createBookmarkPage(null)}" @@ -84,6 +102,11 @@ class BookmarkPageFactory @Inject constructor( private fun construct(list: List): String { return parse(bookmarkPageReader.provideHtml()) andBuild { title { title } + style { content -> + content.replace("--body-bg: {COLOR}", "--body-bg: #$backgroundColor;") + .replace("--box-bg: {COLOR}", "--box-bg: #$cardColor;") + .replace("--box-txt: {COLOR}", "--box-txt: #$textColor;") + } body { val repeatableElement = id("repeated").removeElement() id("content") { diff --git a/app/src/main/java/acr/browser/lightning/html/jsoup/JsoupExtensions.kt b/app/src/main/java/acr/browser/lightning/html/jsoup/JsoupExtensions.kt index 16f5b7020..e7475564a 100644 --- a/app/src/main/java/acr/browser/lightning/html/jsoup/JsoupExtensions.kt +++ b/app/src/main/java/acr/browser/lightning/html/jsoup/JsoupExtensions.kt @@ -3,6 +3,7 @@ package acr.browser.lightning.html.jsoup import org.jsoup.Jsoup +import org.jsoup.nodes.DataNode import org.jsoup.nodes.Document import org.jsoup.nodes.Element @@ -17,6 +18,14 @@ inline fun Document.title(provide: () -> String) { this.title(provide()) } +inline fun Document.style(mutate: (String) -> String) { + head().getElementsByTag("style").first().apply { + childNodes().filterIsInstance().first().apply { + wholeData = mutate(wholeData) + } + } +} + inline fun Document.body(build: Element.() -> Unit) { build(body()) } From 188fb3d597d515de59b00859b70f6662ed057c2d Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 31 Jul 2022 23:06:26 -0400 Subject: [PATCH 101/161] Theming download and history pages --- app/src/main/html/list.html | 37 +++++++++++-------- .../_browser2/theme/LegacyThemeProvider.kt | 5 +++ .../html/download/DownloadPageFactory.kt | 21 ++++++++++- .../html/history/HistoryPageFactory.kt | 21 ++++++++++- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/app/src/main/html/list.html b/app/src/main/html/list.html index d51ad66b9..0567e8208 100644 --- a/app/src/main/html/list.html +++ b/app/src/main/html/list.html @@ -2,18 +2,27 @@ - - - + + + -
- -
- -

${TITLE}

-

${URL}

-
+
+
+ +

${TITLE}

+

${URL}

+ +
diff --git a/app/src/main/java/acr/browser/lightning/_browser2/theme/LegacyThemeProvider.kt b/app/src/main/java/acr/browser/lightning/_browser2/theme/LegacyThemeProvider.kt index 3f993a929..a5b5b018f 100644 --- a/app/src/main/java/acr/browser/lightning/_browser2/theme/LegacyThemeProvider.kt +++ b/app/src/main/java/acr/browser/lightning/_browser2/theme/LegacyThemeProvider.kt @@ -39,6 +39,11 @@ class LegacyThemeProvider @Inject constructor( AppTheme.DARK -> application.color(R.color.white) AppTheme.BLACK -> application.color(R.color.white) } + R.attr.autoCompleteUrlColor -> when (theme) { + AppTheme.LIGHT -> application.color(R.color.hint_text_light_theme) + AppTheme.DARK -> application.color(R.color.hint_text_dark_theme) + AppTheme.BLACK -> application.color(R.color.hint_text_dark_theme) + } else -> error("Unsupported color") } } diff --git a/app/src/main/java/acr/browser/lightning/html/download/DownloadPageFactory.kt b/app/src/main/java/acr/browser/lightning/html/download/DownloadPageFactory.kt index f2f075e8b..3afb6fb1a 100644 --- a/app/src/main/java/acr/browser/lightning/html/download/DownloadPageFactory.kt +++ b/app/src/main/java/acr/browser/lightning/html/download/DownloadPageFactory.kt @@ -1,6 +1,7 @@ package acr.browser.lightning.html.download import acr.browser.lightning.R +import acr.browser.lightning._browser2.theme.ThemeProvider import acr.browser.lightning.constant.FILE import acr.browser.lightning.database.downloads.DownloadEntry import acr.browser.lightning.database.downloads.DownloadsRepository @@ -23,14 +24,32 @@ class DownloadPageFactory @Inject constructor( private val application: Application, private val userPreferences: UserPreferences, private val manager: DownloadsRepository, - private val listPageReader: ListPageReader + private val listPageReader: ListPageReader, + themeProvider: ThemeProvider ) : HtmlPageFactory { + private fun Int.toColor(): String { + val string = Integer.toHexString(this) + + return string.substring(2) + string.substring(0, 2) + } + + private val backgroundColor = themeProvider.color(R.attr.colorPrimary).toColor() + private val dividerColor = themeProvider.color(R.attr.autoCompleteBackgroundColor).toColor() + private val textColor = themeProvider.color(R.attr.autoCompleteTitleColor).toColor() + private val subtitleColor = themeProvider.color(R.attr.autoCompleteUrlColor).toColor() + override fun buildPage(): Single = manager .getAllDownloads() .map { list -> parse(listPageReader.provideHtml()) andBuild { title { application.getString(R.string.action_downloads) } + style { content -> + content.replace("--body-bg: {COLOR}", "--body-bg: #$backgroundColor;") + .replace("--divider-color: {COLOR}", "--divider-color: #$dividerColor;") + .replace("--title-color: {COLOR}", "--title-color: #$textColor;") + .replace("--subtitle-color: {COLOR}", "--subtitle-color: #$subtitleColor;") + } body { val repeatableElement = id("repeated").removeElement() id("content") { diff --git a/app/src/main/java/acr/browser/lightning/html/history/HistoryPageFactory.kt b/app/src/main/java/acr/browser/lightning/html/history/HistoryPageFactory.kt index 197509fdd..3cf4ff9b4 100644 --- a/app/src/main/java/acr/browser/lightning/html/history/HistoryPageFactory.kt +++ b/app/src/main/java/acr/browser/lightning/html/history/HistoryPageFactory.kt @@ -1,6 +1,7 @@ package acr.browser.lightning.html.history import acr.browser.lightning.R +import acr.browser.lightning._browser2.theme.ThemeProvider import acr.browser.lightning.constant.FILE import acr.browser.lightning.database.history.HistoryRepository import acr.browser.lightning.html.HtmlPageFactory @@ -21,16 +22,34 @@ import javax.inject.Inject class HistoryPageFactory @Inject constructor( private val listPageReader: ListPageReader, private val application: Application, - private val historyRepository: HistoryRepository + private val historyRepository: HistoryRepository, + themeProvider: ThemeProvider ) : HtmlPageFactory { private val title = application.getString(R.string.action_history) + private fun Int.toColor(): String { + val string = Integer.toHexString(this) + + return string.substring(2) + string.substring(0, 2) + } + + private val backgroundColor = themeProvider.color(R.attr.colorPrimary).toColor() + private val dividerColor = themeProvider.color(R.attr.autoCompleteBackgroundColor).toColor() + private val textColor = themeProvider.color(R.attr.autoCompleteTitleColor).toColor() + private val subtitleColor = themeProvider.color(R.attr.autoCompleteUrlColor).toColor() + override fun buildPage(): Single = historyRepository .lastHundredVisitedHistoryEntries() .map { list -> parse(listPageReader.provideHtml()) andBuild { title { title } + style { content -> + content.replace("--body-bg: {COLOR}", "--body-bg: #$backgroundColor;") + .replace("--divider-color: {COLOR}", "--divider-color: #$dividerColor;") + .replace("--title-color: {COLOR}", "--title-color: #$textColor;") + .replace("--subtitle-color: {COLOR}", "--subtitle-color: #$subtitleColor;") + } body { val repeatedElement = id("repeated").removeElement() id("content") { From d2b9bb07a500c8135b64b92bfa4521ab32167913 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 31 Jul 2022 23:34:25 -0400 Subject: [PATCH 102/161] Implementing theme aware homepage and fixing theme awareness with other pages --- app/src/main/html/homepage.html | 87 +++++++++---------- .../_browser2/theme/LegacyThemeProvider.kt | 5 +- .../html/bookmark/BookmarkPageFactory.kt | 11 ++- .../html/download/DownloadPageFactory.kt | 17 ++-- .../html/history/HistoryPageFactory.kt | 14 +-- .../html/homepage/HomePageFactory.kt | 22 ++++- 6 files changed, 91 insertions(+), 65 deletions(-) diff --git a/app/src/main/html/homepage.html b/app/src/main/html/homepage.html index 312735545..f3c8ae0e2 100644 --- a/app/src/main/html/homepage.html +++ b/app/src/main/html/homepage.html @@ -2,17 +2,28 @@ - - - + + + ${TITLE} -
-
-
- -
-
-